feat: add master-agent prompts and memory management

This commit is contained in:
kris
2026-04-01 04:10:11 +08:00
parent 9000a9f185
commit d316f0490e
31 changed files with 4398 additions and 32 deletions

View File

@@ -109,6 +109,85 @@ public class BossApiClientDispatchPlansTest {
assertEquals("{\"modelOverride\":\"gpt-5.4\",\"reasoningEffortOverride\":\"high\"}", connection.requestBody());
}
@Test
public void updateProjectAgentControlsWritesPromptOverrideWhenProvided() throws Exception {
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/master-agent/agent-controls"));
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
BossApiClient.ApiResponse response = apiClient.updateProjectAgentControls("master-agent", "gpt-5.4", "high", "当前对话提示词");
assertEquals(200, response.statusCode);
assertEquals("/api/v1/projects/master-agent/agent-controls", apiClient.lastPath);
assertEquals("POST", connection.requestMethodValue);
assertEquals(
"{\"modelOverride\":\"gpt-5.4\",\"reasoningEffortOverride\":\"high\",\"promptOverride\":\"当前对话提示词\"}",
connection.requestBody()
);
}
@Test
public void getMasterAgentPromptProfileUsesScopedEndpoint() throws Exception {
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/master-agent/prompt-profile"));
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
BossApiClient.ApiResponse response = apiClient.getMasterAgentPromptProfile("master-agent");
assertEquals(200, response.statusCode);
assertEquals("/api/v1/projects/master-agent/prompt-profile", apiClient.lastPath);
assertEquals("GET", connection.requestMethodValue);
}
@Test
public void updateMasterAgentPromptProfileWritesUserPromptAndOverride() throws Exception {
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/master-agent/prompt-profile"));
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
JSONObject payload = new JSONObject()
.put("userPromptContent", "用户私有主提示词")
.put("promptOverride", "当前对话提示词");
BossApiClient.ApiResponse response = apiClient.updateMasterAgentPromptProfile("master-agent", payload);
assertEquals(200, response.statusCode);
assertEquals("/api/v1/projects/master-agent/prompt-profile", apiClient.lastPath);
assertEquals("POST", connection.requestMethodValue);
assertEquals("{\"userPromptContent\":\"用户私有主提示词\",\"promptOverride\":\"当前对话提示词\"}", connection.requestBody());
}
@Test
public void getMasterAgentMemoriesUsesScopedEndpoint() throws Exception {
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/master-agent/memories"));
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
BossApiClient.ApiResponse response = apiClient.getMasterAgentMemories("master-agent");
assertEquals(200, response.statusCode);
assertEquals("/api/v1/projects/master-agent/memories", apiClient.lastPath);
assertEquals("GET", connection.requestMethodValue);
}
@Test
public void createMasterAgentMemoryWritesStructuredPayload() throws Exception {
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/master-agent/memories"));
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
JSONObject payload = new JSONObject()
.put("scope", "project")
.put("projectId", "boss-console")
.put("title", "项目目标")
.put("content", "把会话页收成微信式列表")
.put("memoryType", "project_progress")
.put("tags", new JSONArray().put("ui").put("progress"));
BossApiClient.ApiResponse response = apiClient.createMasterAgentMemory("master-agent", payload);
assertEquals(200, response.statusCode);
assertEquals("/api/v1/projects/master-agent/memories", apiClient.lastPath);
assertEquals("POST", connection.requestMethodValue);
assertEquals(
"{\"scope\":\"project\",\"projectId\":\"boss-console\",\"title\":\"项目目标\",\"content\":\"把会话页收成微信式列表\",\"memoryType\":\"project_progress\",\"tags\":[\"ui\",\"progress\"]}",
connection.requestBody()
);
}
@Test
public void sendProjectMessageUsesQueueFriendlyReadTimeoutForMasterAgent() throws Exception {
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/master-agent/messages"));

View File

@@ -0,0 +1,419 @@
package com.hyzq.boss;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Intent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.TimeUnit;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 34)
public class MasterAgentMemoryActivityTest {
@Test
public void renderMemoriesShowsGlobalAndProjectSections() throws Exception {
TestMasterAgentMemoryActivity activity = Robolectric
.buildActivity(
TestMasterAgentMemoryActivity.class,
new Intent()
.putExtra(MasterAgentMemoryActivity.EXTRA_PROJECT_ID, "master-agent")
.putExtra(MasterAgentMemoryActivity.EXTRA_PROJECT_NAME, "主 Agent")
)
.setup()
.get();
JSONObject globalMemory = new JSONObject()
.put("memoryId", "mem-global")
.put("scope", "global")
.put("title", "偏好")
.put("content", "优先中文回复")
.put("memoryType", "user_preference")
.put("tags", new JSONArray().put("ui"));
JSONObject projectMemory = new JSONObject()
.put("memoryId", "mem-project")
.put("scope", "project")
.put("projectId", "master-agent")
.put("title", "项目进度")
.put("content", "主 Agent 对话链已接通")
.put("memoryType", "project_progress")
.put("tags", new JSONArray().put("progress"));
JSONObject payload = new JSONObject()
.put("memories", new JSONObject()
.put("global", new JSONObject().put("items", new JSONArray().put(globalMemory)))
.put("project", new JSONObject().put("items", new JSONArray().put(projectMemory))));
ReflectionHelpers.callInstanceMethod(
activity,
"renderMemories",
ReflectionHelpers.ClassParameter.from(JSONObject.class, payload)
);
View content = activity.findViewById(R.id.screen_content);
assertTrue(viewTreeContainsText(content, "我的通用记忆"));
assertTrue(viewTreeContainsText(content, "当前项目记忆"));
assertTrue(viewTreeContainsText(content, "优先中文回复"));
JSONObject memories = payload.getJSONObject("memories");
JSONArray globalMemoryItems = (JSONArray) ReflectionHelpers.callInstanceMethod(
activity,
"extractMemoryItems",
ReflectionHelpers.ClassParameter.from(JSONObject.class, memories),
ReflectionHelpers.ClassParameter.from(String.class, "global")
);
JSONArray projectMemoryItems = (JSONArray) ReflectionHelpers.callInstanceMethod(
activity,
"extractMemoryItems",
ReflectionHelpers.ClassParameter.from(JSONObject.class, memories),
ReflectionHelpers.ClassParameter.from(String.class, "project")
);
assertEquals(1, globalMemoryItems.length());
assertEquals(1, projectMemoryItems.length());
assertEquals("偏好", globalMemoryItems.getJSONObject(0).getString("title"));
assertEquals("项目进度", projectMemoryItems.getJSONObject(0).getString("title"));
}
@Test
public void saveMemoryWritesStructuredCreatePayload() throws Exception {
TestMasterAgentMemoryActivity activity = Robolectric
.buildActivity(
TestMasterAgentMemoryActivity.class,
new Intent()
.putExtra(MasterAgentMemoryActivity.EXTRA_PROJECT_ID, "master-agent")
.putExtra(MasterAgentMemoryActivity.EXTRA_PROJECT_NAME, "主 Agent")
)
.setup()
.get();
ReflectionHelpers.setField(activity, "contentLoaded", true);
ReflectionHelpers.setField(activity, "apiClient", new ScriptedBossApiClient(
new RecordingConnection(
new URL("https://boss.hyzq.net/api/v1/projects/master-agent/memories"),
200,
"{\"ok\":true}",
"{\"ok\":false,\"message\":\"MEMORY_SAVE_FAILED\"}"
)
));
ReflectionHelpers.setField(activity, "executor", new DirectExecutorService());
ReflectionHelpers.setField(activity, "projectId", "master-agent");
ReflectionHelpers.callInstanceMethod(
activity,
"saveMemory",
ReflectionHelpers.ClassParameter.from(JSONObject.class, null),
ReflectionHelpers.ClassParameter.from(String.class, "project"),
ReflectionHelpers.ClassParameter.from(String.class, "项目目标"),
ReflectionHelpers.ClassParameter.from(String.class, "把会话页收成微信式列表"),
ReflectionHelpers.ClassParameter.from(String.class, "project_progress"),
ReflectionHelpers.ClassParameter.from(String.class, "ui,progress")
);
org.robolectric.Shadows.shadowOf(android.os.Looper.getMainLooper()).idle();
assertEquals(
"{\"scope\":\"project\",\"projectId\":\"master-agent\",\"title\":\"项目目标\",\"content\":\"把会话页收成微信式列表\",\"memoryType\":\"project_progress\",\"tags\":[\"ui\",\"progress\"]}",
((ScriptedBossApiClient) ReflectionHelpers.getField(activity, "apiClient")).connection.requestBody()
);
}
private static boolean viewTreeContainsText(View root, String expectedText) {
if (root instanceof TextView) {
CharSequence text = ((TextView) root).getText();
if (text != null && text.toString().contains(expectedText)) {
return true;
}
}
if (!(root instanceof ViewGroup)) {
return false;
}
ViewGroup group = (ViewGroup) root;
for (int index = 0; index < group.getChildCount(); index += 1) {
if (viewTreeContainsText(group.getChildAt(index), expectedText)) {
return true;
}
}
return false;
}
private static final class TestMasterAgentMemoryActivity extends MasterAgentMemoryActivity {
@Override
protected void reload() {
// Tests render synthetic payloads directly.
}
}
private static final class DirectExecutorService extends AbstractExecutorService {
private boolean shutdown;
@Override
public void shutdown() {
shutdown = true;
}
@Override
public List<Runnable> shutdownNow() {
shutdown = true;
return Collections.emptyList();
}
@Override
public boolean isShutdown() {
return shutdown;
}
@Override
public boolean isTerminated() {
return shutdown;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) {
return true;
}
@Override
public void execute(Runnable command) {
command.run();
}
}
private static final class ScriptedBossApiClient extends BossApiClient {
private final Map<String, RecordingConnection> connections;
private final RecordingConnection connection;
private static final class InMemorySharedPreferences implements android.content.SharedPreferences {
private final Map<String, Object> values = new HashMap<>();
@Override
public Map<String, ?> getAll() {
return new HashMap<>(values);
}
@Override
public String getString(String key, String defValue) {
Object value = values.get(key);
return value instanceof String ? (String) value : defValue;
}
@Override
public java.util.Set<String> getStringSet(String key, java.util.Set<String> defValues) {
Object value = values.get(key);
if (!(value instanceof java.util.Set)) {
return defValues;
}
// noinspection unchecked
return (java.util.Set<String>) value;
}
@Override
public int getInt(String key, int defValue) {
Object value = values.get(key);
return value instanceof Integer ? (Integer) value : defValue;
}
@Override
public long getLong(String key, long defValue) {
Object value = values.get(key);
return value instanceof Long ? (Long) value : defValue;
}
@Override
public float getFloat(String key, float defValue) {
Object value = values.get(key);
return value instanceof Float ? (Float) value : defValue;
}
@Override
public boolean getBoolean(String key, boolean defValue) {
Object value = values.get(key);
return value instanceof Boolean ? (Boolean) value : defValue;
}
@Override
public boolean contains(String key) {
return values.containsKey(key);
}
@Override
public Editor edit() {
return new Editor() {
@Override public Editor putString(String key, String value) { values.put(key, value); return this; }
@Override public Editor putStringSet(String key, java.util.Set<String> value) { values.put(key, value); return this; }
@Override public Editor putInt(String key, int value) { values.put(key, value); return this; }
@Override public Editor putLong(String key, long value) { values.put(key, value); return this; }
@Override public Editor putFloat(String key, float value) { values.put(key, value); return this; }
@Override public Editor putBoolean(String key, boolean value) { values.put(key, value); return this; }
@Override public Editor remove(String key) { values.remove(key); return this; }
@Override public Editor clear() { values.clear(); return this; }
@Override public boolean commit() { return true; }
@Override public void apply() {}
};
}
@Override public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {}
@Override public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {}
}
ScriptedBossApiClient(RecordingConnection connection) {
super(new InMemorySharedPreferences(), "https://boss.hyzq.net");
this.connection = connection;
this.connections = new HashMap<>();
this.connections.put(connection.getURL().getPath(), connection);
}
@Override
HttpURLConnection openConnection(String path) {
RecordingConnection scripted = connections.get(path);
if (scripted == null) {
throw new IllegalStateException("Missing scripted connection for " + path);
}
return scripted;
}
@Override
String encode(String value) {
return value;
}
@Override
void rememberIdentity(JSONObject json) {
// JVM 单测不需要落 Android 侧身份缓存。
}
}
private static final class InMemorySharedPreferences implements android.content.SharedPreferences {
private final Map<String, Object> values = new HashMap<>();
@Override
public Map<String, ?> getAll() {
return new HashMap<>(values);
}
@Override
public String getString(String key, String defValue) {
Object value = values.get(key);
return value instanceof String ? (String) value : defValue;
}
@Override
public java.util.Set<String> getStringSet(String key, java.util.Set<String> defValues) {
Object value = values.get(key);
return value instanceof java.util.Set ? (java.util.Set<String>) value : defValues;
}
@Override
public int getInt(String key, int defValue) {
Object value = values.get(key);
return value instanceof Integer ? (Integer) value : defValue;
}
@Override
public long getLong(String key, long defValue) {
Object value = values.get(key);
return value instanceof Long ? (Long) value : defValue;
}
@Override
public float getFloat(String key, float defValue) {
Object value = values.get(key);
return value instanceof Float ? (Float) value : defValue;
}
@Override
public boolean getBoolean(String key, boolean defValue) {
Object value = values.get(key);
return value instanceof Boolean ? (Boolean) value : defValue;
}
@Override
public boolean contains(String key) {
return values.containsKey(key);
}
@Override
public Editor edit() {
return new Editor() {
@Override
public Editor putString(String key, String value) { values.put(key, value); return this; }
@Override
public Editor putStringSet(String key, java.util.Set<String> values) { InMemorySharedPreferences.this.values.put(key, values); return this; }
@Override
public Editor putInt(String key, int value) { InMemorySharedPreferences.this.values.put(key, value); return this; }
@Override
public Editor putLong(String key, long value) { InMemorySharedPreferences.this.values.put(key, value); return this; }
@Override
public Editor putFloat(String key, float value) { InMemorySharedPreferences.this.values.put(key, value); return this; }
@Override
public Editor putBoolean(String key, boolean value) { InMemorySharedPreferences.this.values.put(key, value); return this; }
@Override
public Editor remove(String key) { InMemorySharedPreferences.this.values.remove(key); return this; }
@Override
public Editor clear() { InMemorySharedPreferences.this.values.clear(); return this; }
@Override
public boolean commit() { return true; }
@Override
public void apply() {}
};
}
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {}
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {}
}
private static final class RecordingConnection extends HttpURLConnection {
private final ByteArrayOutputStream requestBody = new ByteArrayOutputStream();
private final Map<String, String> requestHeaders = new HashMap<>();
private final int responseCodeValue;
private final String responseBody;
private final String errorBody;
private String requestMethodValue = "GET";
RecordingConnection(URL url, int responseCodeValue, String responseBody, String errorBody) {
super(url);
this.responseCodeValue = responseCodeValue;
this.responseBody = responseBody;
this.errorBody = errorBody;
}
@Override public void disconnect() {}
@Override public boolean usingProxy() { return false; }
@Override public void connect() {}
@Override public void setRequestMethod(String method) throws ProtocolException { requestMethodValue = method; }
@Override public void setRequestProperty(String key, String value) { requestHeaders.put(key, value); }
@Override public OutputStream getOutputStream() { return requestBody; }
@Override public int getResponseCode() { return responseCodeValue; }
@Override public InputStream getInputStream() { return new ByteArrayInputStream(responseBody.getBytes(StandardCharsets.UTF_8)); }
@Override public InputStream getErrorStream() {
if (responseCodeValue < 400) {
return null;
}
return new ByteArrayInputStream(errorBody.getBytes(StandardCharsets.UTF_8));
}
String requestBody() { return requestBody.toString(StandardCharsets.UTF_8); }
}
}

View File

@@ -0,0 +1,325 @@
package com.hyzq.boss;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Intent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import org.json.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.TimeUnit;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 34)
public class MasterAgentPromptActivityTest {
@Test
public void renderPromptProfileShowsAdminUserAndConversationLayers() throws Exception {
TestMasterAgentPromptActivity activity = Robolectric
.buildActivity(
TestMasterAgentPromptActivity.class,
new Intent()
.putExtra(MasterAgentPromptActivity.EXTRA_PROJECT_ID, "master-agent")
.putExtra(MasterAgentPromptActivity.EXTRA_PROJECT_NAME, "主 Agent")
)
.setup()
.get();
JSONObject payload = new JSONObject()
.put("promptPolicy", new JSONObject().put("globalPrompt", "全局主提示词"))
.put("userPrompt", new JSONObject().put("content", "用户私有主提示词"))
.put("projectControls", new JSONObject().put("promptOverride", "当前对话提示词"));
ReflectionHelpers.callInstanceMethod(
activity,
"renderPromptProfile",
ReflectionHelpers.ClassParameter.from(JSONObject.class, payload)
);
View content = activity.findViewById(R.id.screen_content);
assertTrue(viewTreeContainsText(content, "管理员全局主提示词"));
assertTrue(viewTreeContainsText(content, "全局主提示词"));
assertTrue(viewTreeContainsText(content, "用户私有主提示词"));
assertTrue(viewTreeContainsText(content, "当前对话提示词"));
assertTrue(viewTreeContainsText(content, "合成预览"));
}
@Test
public void savePromptProfileWritesBothEditableLayers() throws Exception {
TestMasterAgentPromptActivity activity = Robolectric
.buildActivity(
TestMasterAgentPromptActivity.class,
new Intent()
.putExtra(MasterAgentPromptActivity.EXTRA_PROJECT_ID, "master-agent")
.putExtra(MasterAgentPromptActivity.EXTRA_PROJECT_NAME, "主 Agent")
)
.setup()
.get();
ReflectionHelpers.setField(activity, "contentLoaded", true);
ReflectionHelpers.setField(activity, "apiClient", new ScriptedBossApiClient(
new RecordingConnection(
new URL("https://boss.hyzq.net/api/v1/projects/master-agent/prompt-profile"),
200,
"{\"ok\":true,\"promptPolicy\":{\"globalPrompt\":\"全局主提示词\"},\"userPrompt\":{\"content\":\"用户私有主提示词\"},\"projectControls\":{\"promptOverride\":\"当前对话提示词\"}}",
"{\"ok\":false,\"message\":\"PROMPT_SAVE_FAILED\"}"
)
));
ReflectionHelpers.setField(activity, "executor", new DirectExecutorService());
JSONObject payload = new JSONObject()
.put("promptPolicy", new JSONObject().put("globalPrompt", "全局主提示词"))
.put("userPrompt", new JSONObject().put("content", "用户私有主提示词"))
.put("projectControls", new JSONObject().put("promptOverride", "当前对话提示词"));
ReflectionHelpers.callInstanceMethod(
activity,
"renderPromptProfile",
ReflectionHelpers.ClassParameter.from(JSONObject.class, payload)
);
EditText userInput = ReflectionHelpers.getField(activity, "userPromptInput");
EditText conversationInput = ReflectionHelpers.getField(activity, "projectPromptInput");
userInput.setText("更新后的用户提示词");
conversationInput.setText("更新后的对话提示词");
ReflectionHelpers.callInstanceMethod(activity, "savePromptProfile");
org.robolectric.Shadows.shadowOf(android.os.Looper.getMainLooper()).idle();
assertEquals(
"{\"userPromptContent\":\"更新后的用户提示词\",\"promptOverride\":\"更新后的对话提示词\"}",
((ScriptedBossApiClient) ReflectionHelpers.getField(activity, "apiClient")).connection.requestBody()
);
}
private static boolean viewTreeContainsText(View root, String expectedText) {
if (root instanceof TextView) {
CharSequence text = ((TextView) root).getText();
if (text != null && text.toString().contains(expectedText)) {
return true;
}
}
if (!(root instanceof ViewGroup)) {
return false;
}
ViewGroup group = (ViewGroup) root;
for (int index = 0; index < group.getChildCount(); index += 1) {
if (viewTreeContainsText(group.getChildAt(index), expectedText)) {
return true;
}
}
return false;
}
private static final class TestMasterAgentPromptActivity extends MasterAgentPromptActivity {
@Override
protected void reload() {
// Tests render synthetic payloads directly.
}
}
private static final class DirectExecutorService extends AbstractExecutorService {
private boolean shutdown;
@Override
public void shutdown() {
shutdown = true;
}
@Override
public List<Runnable> shutdownNow() {
shutdown = true;
return Collections.emptyList();
}
@Override
public boolean isShutdown() {
return shutdown;
}
@Override
public boolean isTerminated() {
return shutdown;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) {
return true;
}
@Override
public void execute(Runnable command) {
command.run();
}
}
private static final class ScriptedBossApiClient extends BossApiClient {
private final Map<String, RecordingConnection> connections;
private final RecordingConnection connection;
ScriptedBossApiClient(RecordingConnection connection) {
super(new InMemorySharedPreferences(), "https://boss.hyzq.net");
this.connection = connection;
this.connections = new HashMap<>();
this.connections.put(connection.getURL().getPath(), connection);
}
@Override
HttpURLConnection openConnection(String path) {
RecordingConnection scripted = connections.get(path);
if (scripted == null) {
throw new IllegalStateException("Missing scripted connection for " + path);
}
return scripted;
}
@Override
String encode(String value) {
return value;
}
@Override
void rememberIdentity(JSONObject json) {
// JVM 单测不需要落 Android 侧身份缓存。
}
}
private static final class InMemorySharedPreferences implements android.content.SharedPreferences {
private final Map<String, Object> values = new HashMap<>();
@Override
public Map<String, ?> getAll() {
return new HashMap<>(values);
}
@Override
public String getString(String key, String defValue) {
Object value = values.get(key);
return value instanceof String ? (String) value : defValue;
}
@Override
public Set<String> getStringSet(String key, Set<String> defValues) {
Object value = values.get(key);
if (!(value instanceof Set)) {
return defValues;
}
// noinspection unchecked
return (Set<String>) value;
}
@Override
public int getInt(String key, int defValue) {
Object value = values.get(key);
return value instanceof Integer ? (Integer) value : defValue;
}
@Override
public long getLong(String key, long defValue) {
Object value = values.get(key);
return value instanceof Long ? (Long) value : defValue;
}
@Override
public float getFloat(String key, float defValue) {
Object value = values.get(key);
return value instanceof Float ? (Float) value : defValue;
}
@Override
public boolean getBoolean(String key, boolean defValue) {
Object value = values.get(key);
return value instanceof Boolean ? (Boolean) value : defValue;
}
@Override
public boolean contains(String key) {
return values.containsKey(key);
}
@Override
public Editor edit() {
return new Editor() {
@Override
public Editor putString(String key, String value) { values.put(key, value); return this; }
@Override
public Editor putStringSet(String key, java.util.Set<String> values) { InMemorySharedPreferences.this.values.put(key, values); return this; }
@Override
public Editor putInt(String key, int value) { InMemorySharedPreferences.this.values.put(key, value); return this; }
@Override
public Editor putLong(String key, long value) { InMemorySharedPreferences.this.values.put(key, value); return this; }
@Override
public Editor putFloat(String key, float value) { InMemorySharedPreferences.this.values.put(key, value); return this; }
@Override
public Editor putBoolean(String key, boolean value) { InMemorySharedPreferences.this.values.put(key, value); return this; }
@Override
public Editor remove(String key) { InMemorySharedPreferences.this.values.remove(key); return this; }
@Override
public Editor clear() { InMemorySharedPreferences.this.values.clear(); return this; }
@Override
public boolean commit() { return true; }
@Override
public void apply() {}
};
}
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {}
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {}
}
private static final class RecordingConnection extends HttpURLConnection {
private final ByteArrayOutputStream requestBody = new ByteArrayOutputStream();
private final Map<String, String> requestHeaders = new HashMap<>();
private final int responseCodeValue;
private final String responseBody;
private final String errorBody;
private String requestMethodValue = "GET";
RecordingConnection(URL url, int responseCodeValue, String responseBody, String errorBody) {
super(url);
this.responseCodeValue = responseCodeValue;
this.responseBody = responseBody;
this.errorBody = errorBody;
}
@Override public void disconnect() {}
@Override public boolean usingProxy() { return false; }
@Override public void connect() {}
@Override public void setRequestMethod(String method) throws ProtocolException { requestMethodValue = method; }
@Override public void setRequestProperty(String key, String value) { requestHeaders.put(key, value); }
@Override public OutputStream getOutputStream() { return requestBody; }
@Override public int getResponseCode() { return responseCodeValue; }
@Override public InputStream getInputStream() { return new ByteArrayInputStream(responseBody.getBytes(StandardCharsets.UTF_8)); }
@Override public InputStream getErrorStream() {
if (responseCodeValue < 400) {
return null;
}
return new ByteArrayInputStream(errorBody.getBytes(StandardCharsets.UTF_8));
}
String requestBody() { return requestBody.toString(StandardCharsets.UTF_8); }
}
}

View File

@@ -44,8 +44,10 @@ public class ProjectDetailActivityMasterAgentMenuTest {
assertMenuItem(listView, 0, "模型");
assertMenuItem(listView, 1, "推理强度");
assertMenuItem(listView, 2, "会话信息");
assertMenuItem(listView, 3, "刷新");
assertMenuItem(listView, 2, "提示词");
assertMenuItem(listView, 3, "记忆");
assertMenuItem(listView, 4, "会话信息");
assertMenuItem(listView, 5, "刷新");
}
@Test