chore: checkpoint Boss app v2.5.11
This commit is contained in:
@@ -260,6 +260,34 @@ public class BossApiClientDispatchPlansTest {
|
||||
assertEquals("GET", connection.requestMethodValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getProjectDetailUsesExtendedReadTimeoutForChatPages() throws Exception {
|
||||
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/thread-1"));
|
||||
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
|
||||
|
||||
BossApiClient.ApiResponse response = apiClient.getProjectDetail("thread-1");
|
||||
|
||||
assertEquals(200, response.statusCode);
|
||||
assertEquals("/api/v1/projects/thread-1", apiClient.lastPath);
|
||||
assertEquals("GET", connection.requestMethodValue);
|
||||
assertEquals(12000, connection.connectTimeoutValue);
|
||||
assertEquals(30000, connection.readTimeoutValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getProjectMessagesUsesExtendedReadTimeoutForRealtimeRefreshes() throws Exception {
|
||||
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/thread-1/messages"));
|
||||
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
|
||||
|
||||
BossApiClient.ApiResponse response = apiClient.getProjectMessages("thread-1");
|
||||
|
||||
assertEquals(200, response.statusCode);
|
||||
assertEquals("/api/v1/projects/thread-1/messages", apiClient.lastPath);
|
||||
assertEquals("GET", connection.requestMethodValue);
|
||||
assertEquals(12000, connection.connectTimeoutValue);
|
||||
assertEquals(30000, connection.readTimeoutValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMasterAgentMemoryWritesStructuredPayload() throws Exception {
|
||||
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/master-agent/memories"));
|
||||
|
||||
@@ -57,6 +57,7 @@ public class ProjectDetailActivityRealtimeTest {
|
||||
);
|
||||
drainRealtimeDebounce(activity);
|
||||
|
||||
waitFor(() -> activity.messageReloadCount == 1);
|
||||
assertEquals(0, activity.reloadCount);
|
||||
assertEquals(1, activity.messageReloadCount);
|
||||
}
|
||||
@@ -144,7 +145,8 @@ public class ProjectDetailActivityRealtimeTest {
|
||||
);
|
||||
drainRealtimeDebounce(activity);
|
||||
|
||||
assertEquals(1, activity.reloadCount);
|
||||
waitFor(() -> activity.loadCallCount == 1);
|
||||
assertEquals(1, activity.loadCallCount);
|
||||
assertEquals(0, activity.messageReloadCount);
|
||||
}
|
||||
|
||||
@@ -172,7 +174,8 @@ public class ProjectDetailActivityRealtimeTest {
|
||||
);
|
||||
drainRealtimeDebounce(activity);
|
||||
|
||||
assertEquals(1, activity.reloadCount);
|
||||
waitFor(() -> activity.loadCallCount == 1);
|
||||
assertEquals(1, activity.loadCallCount);
|
||||
assertEquals(0, activity.messageReloadCount);
|
||||
}
|
||||
|
||||
@@ -211,6 +214,7 @@ public class ProjectDetailActivityRealtimeTest {
|
||||
);
|
||||
drainRealtimeDebounce(activity);
|
||||
|
||||
waitFor(() -> activity.messageReloadCount == 1);
|
||||
assertEquals(0, activity.reloadCount);
|
||||
assertEquals(1, activity.messageReloadCount);
|
||||
}
|
||||
@@ -441,7 +445,8 @@ public class ProjectDetailActivityRealtimeTest {
|
||||
);
|
||||
drainRealtimeDebounce(activity);
|
||||
|
||||
assertEquals(1, activity.reloadCount);
|
||||
waitFor(() -> activity.loadCallCount == 1);
|
||||
assertEquals(1, activity.loadCallCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1532,6 +1532,42 @@ public class ProjectDetailActivityUiTest {
|
||||
assertFalse(viewTreeContainsText(messageView, "Codex"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failedExecutionProgressWithoutBranchDoesNotRenderExecutorWaitingFallback() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "juyuwan")
|
||||
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "juyuwan");
|
||||
TestProjectDetailActivity activity = Robolectric
|
||||
.buildActivity(TestProjectDetailActivity.class, intent)
|
||||
.setup()
|
||||
.get();
|
||||
|
||||
JSONObject message = new JSONObject()
|
||||
.put("id", "failed-progress-no-branch")
|
||||
.put("sender", "master")
|
||||
.put("senderLabel", "主 Agent")
|
||||
.put("body", "执行进度:失败")
|
||||
.put("kind", "execution_progress")
|
||||
.put("sentAt", "2026-06-07T14:24:00+08:00")
|
||||
.put("executionProgress", new JSONObject()
|
||||
.put("title", "进度")
|
||||
.put("controlMode", "codex_thread")
|
||||
.put("status", "failed")
|
||||
.put("phase", "terminal_failed")
|
||||
.put("steps", new JSONArray()
|
||||
.put(new JSONObject().put("text", "等待目标线程回复").put("status", "failed"))));
|
||||
|
||||
View messageView = ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"buildMessageView",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, message)
|
||||
);
|
||||
|
||||
assertTrue(viewTreeContainsText(messageView, "当前状态:执行失败"));
|
||||
assertFalse(viewTreeContainsText(messageView, "等待执行器回写"));
|
||||
assertFalse(viewTreeContainsText(messageView, "分支详情"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completedReplyResponseRendersImmediatelyWithoutReloadingProjectDetail() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
@@ -1603,6 +1639,24 @@ public class ProjectDetailActivityUiTest {
|
||||
assertEquals(0, fakeApiClient.projectDetailCallCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void realtimeMessageReloadDoesNotShowSwipeRefreshSpinner() {
|
||||
Intent intent = new Intent()
|
||||
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "juyuwan")
|
||||
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "juyuwan");
|
||||
TestProjectDetailActivity activity = Robolectric
|
||||
.buildActivity(TestProjectDetailActivity.class, intent)
|
||||
.setup()
|
||||
.get();
|
||||
ReflectionHelpers.setField(activity, "apiClient", new SlowProjectMessagesApiClient());
|
||||
|
||||
activity.triggerRealtimeReload(false);
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
|
||||
SwipeRefreshLayout refreshLayout = activity.findViewById(R.id.screen_refresh_layout);
|
||||
assertFalse(refreshLayout.isRefreshing());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void masterAgentHeaderUsesWechatMoreMenuLabel() {
|
||||
Intent intent = new Intent()
|
||||
@@ -2458,6 +2512,30 @@ public class ProjectDetailActivityUiTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SlowProjectMessagesApiClient extends BossApiClient {
|
||||
SlowProjectMessagesApiClient() {
|
||||
super(new InMemorySharedPreferences(), "https://boss.hyzq.net");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse getProjectMessages(String projectId) throws java.io.IOException, org.json.JSONException {
|
||||
try {
|
||||
Thread.sleep(250L);
|
||||
} catch (InterruptedException error) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return new ApiResponse(
|
||||
200,
|
||||
new JSONObject()
|
||||
.put("ok", true)
|
||||
.put("project", new JSONObject()
|
||||
.put("id", projectId)
|
||||
.put("name", "juyuwan")
|
||||
.put("messages", new JSONArray()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RecordingConversationActionApiClient extends BossApiClient {
|
||||
int markConversationReadCount;
|
||||
String lastMarkedProjectId;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
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.JSONObject;
|
||||
import org.json.JSONArray;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
@@ -68,6 +73,61 @@ public class SkillInventoryActivityTest {
|
||||
assertEquals(0, activity.reloadCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderSkillsShowsManagementDispatchWorkspaceWhenLifecycleRequestsAreAvailable() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
.putExtra(SkillInventoryActivity.EXTRA_DEVICE_ID, "device-1")
|
||||
.putExtra(SkillInventoryActivity.EXTRA_DEVICE_NAME, "Mac Studio");
|
||||
TestSkillInventoryActivity activity = Robolectric
|
||||
.buildActivity(TestSkillInventoryActivity.class, intent)
|
||||
.setup()
|
||||
.get();
|
||||
|
||||
ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"renderSkills",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildSkillPayload()),
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildLifecyclePayload())
|
||||
);
|
||||
|
||||
View content = activity.findViewById(R.id.screen_content);
|
||||
assertTrue(viewTreeContainsText(content, "Skill 管理分发"));
|
||||
assertTrue(viewTreeContainsText(content, "安装远端 Skill"));
|
||||
assertTrue(viewTreeContainsText(content, "分配权限"));
|
||||
assertTrue(viewTreeContainsText(content, "Skill 请求状态"));
|
||||
assertTrue(viewTreeContainsText(content, "更新下发"));
|
||||
assertTrue(viewTreeContainsText(content, "版本锁定"));
|
||||
}
|
||||
|
||||
private static JSONObject buildSkillPayload() throws Exception {
|
||||
return new JSONObject()
|
||||
.put("device", new JSONObject()
|
||||
.put("id", "device-1")
|
||||
.put("name", "Mac Studio"))
|
||||
.put("skills", new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("skillId", "device-1:boss-server-debug")
|
||||
.put("name", "boss-server-debug")
|
||||
.put("description", "服务器排障")
|
||||
.put("category", "ops")
|
||||
.put("path", "/Users/kris/.codex/skills/boss-server-debug/SKILL.md")
|
||||
.put("invocation", "使用 boss-server-debug")
|
||||
.put("updatedAt", "2026-06-08T10:00:00+08:00")));
|
||||
}
|
||||
|
||||
private static JSONObject buildLifecyclePayload() throws Exception {
|
||||
return new JSONObject()
|
||||
.put("ok", true)
|
||||
.put("requests", new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("requestId", "skill-request-1")
|
||||
.put("action", "update")
|
||||
.put("status", "queued")
|
||||
.put("deviceId", "device-1")
|
||||
.put("skillId", "device-1:boss-server-debug")
|
||||
.put("requestedAt", "2026-06-08T10:01:00+08:00")));
|
||||
}
|
||||
|
||||
public static class TestSkillInventoryActivity extends SkillInventoryActivity {
|
||||
private boolean reloadEnabled;
|
||||
private int reloadCount;
|
||||
@@ -81,4 +141,23 @@ public class SkillInventoryActivityTest {
|
||||
setRefreshing(false);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user