feat: add realtime sync and import project understanding

This commit is contained in:
kris
2026-04-04 07:53:27 +08:00
parent 9c53e583ba
commit 908ad8858b
12 changed files with 809 additions and 12 deletions

View File

@@ -0,0 +1,30 @@
package com.hyzq.boss;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 34)
public class BossRealtimeClientTest {
@Test
public void parseEventBlockExtractsEventNameAndJsonPayload() {
BossRealtimeEvent event = BossRealtimeClient.parseEventBlock(
"event: project.messages.updated\n" +
"data: {\"projectId\":\"project-1\",\"status\":\"completed\"}\n\n"
);
assertEquals("project.messages.updated", event.eventName);
assertEquals("project-1", event.payload.optString("projectId"));
assertEquals("completed", event.payload.optString("status"));
}
@Test
public void parseEventBlockReturnsNullForKeepaliveComment() {
assertNull(BossRealtimeClient.parseEventBlock(": keepalive\n\n"));
}
}

View File

@@ -37,7 +37,9 @@ public class DeviceImportDraftActivityTest {
"applyPayload",
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildPendingDraft()),
ReflectionHelpers.ClassParameter.from(JSONObject.class, null),
ReflectionHelpers.ClassParameter.from(JSONObject.class, null)
ReflectionHelpers.ClassParameter.from(JSONObject.class, null),
ReflectionHelpers.ClassParameter.from(JSONArray.class, null),
ReflectionHelpers.ClassParameter.from(JSONArray.class, null)
);
View content = activity.findViewById(R.id.screen_content);
@@ -64,7 +66,9 @@ public class DeviceImportDraftActivityTest {
"applyPayload",
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildAppliedDraft()),
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildAppliedResolution()),
ReflectionHelpers.ClassParameter.from(JSONObject.class, null)
ReflectionHelpers.ClassParameter.from(JSONObject.class, null),
ReflectionHelpers.ClassParameter.from(JSONArray.class, null),
ReflectionHelpers.ClassParameter.from(JSONArray.class, null)
);
View content = activity.findViewById(R.id.screen_content);
@@ -92,13 +96,18 @@ public class DeviceImportDraftActivityTest {
"applyPayload",
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildPendingResolutionDraft()),
ReflectionHelpers.ClassParameter.from(JSONObject.class, null),
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildQueuedReviewTask())
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildQueuedReviewTask()),
ReflectionHelpers.ClassParameter.from(JSONArray.class, buildUnderstandingTasks()),
ReflectionHelpers.ClassParameter.from(JSONArray.class, buildProjectUnderstandings())
);
View content = activity.findViewById(R.id.screen_content);
assertTrue(viewTreeContainsText(content, "主 Agent 审核中"));
assertTrue(viewTreeContainsText(content, "审核任务"));
assertTrue(viewTreeContainsText(content, "状态queued"));
assertTrue(viewTreeContainsText(content, "项目理解"));
assertTrue(viewTreeContainsText(content, "北区试产线回归"));
assertTrue(viewTreeContainsText(content, "树莓派二代接入与联调"));
}
private static JSONObject buildPendingDraft() throws Exception {
@@ -200,6 +209,27 @@ public class DeviceImportDraftActivityTest {
.put("status", "queued");
}
private static JSONArray buildUnderstandingTasks() throws Exception {
return new JSONArray()
.put(new JSONObject()
.put("taskId", "mastertask-understanding-1")
.put("candidateId", "candidate-1")
.put("threadDisplayName", "北区试产线回归")
.put("status", "completed"));
}
private static JSONArray buildProjectUnderstandings() throws Exception {
return new JSONArray()
.put(new JSONObject()
.put("candidateId", "candidate-1")
.put("threadDisplayName", "北区试产线回归")
.put("projectGoal", "完成树莓派二代接入与联调")
.put("currentProgress", "正在核对接线和控制链路")
.put("technicalArchitecture", "Next.js 控制台 + local-agent + Codex 线程")
.put("currentBlockers", "串口稳定性待验证")
.put("recommendedNextStep", "先确认串口日志"));
}
private static boolean viewTreeContainsText(View root, String expectedText) {
if (root instanceof TextView) {
CharSequence text = ((TextView) root).getText();

View File

@@ -0,0 +1,56 @@
package com.hyzq.boss;
import static org.junit.Assert.assertEquals;
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;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 34)
public class MainActivityRealtimeTest {
@Test
public void conversationRealtimeEventRefreshesVisibleConversationTab() throws Exception {
TestMainActivity activity = Robolectric.buildActivity(TestMainActivity.class).setup().resume().get();
ReflectionHelpers.callInstanceMethod(activity, "showContent");
ReflectionHelpers.callInstanceMethod(
activity,
"handleRealtimeEvent",
ReflectionHelpers.ClassParameter.from(
BossRealtimeEvent.class,
new BossRealtimeEvent("conversation.updated", new JSONObject().put("projectId", "project-1"))
)
);
assertEquals(1, activity.refreshCount);
}
@Test
public void devicesRealtimeEventDoesNotRefreshConversationTab() throws Exception {
TestMainActivity activity = Robolectric.buildActivity(TestMainActivity.class).setup().resume().get();
ReflectionHelpers.callInstanceMethod(activity, "showContent");
ReflectionHelpers.callInstanceMethod(
activity,
"handleRealtimeEvent",
ReflectionHelpers.ClassParameter.from(
BossRealtimeEvent.class,
new BossRealtimeEvent("devices.updated", new JSONObject().put("deviceId", "mac-studio"))
)
);
assertEquals(0, activity.refreshCount);
}
public static class TestMainActivity extends MainActivity {
int refreshCount;
@Override
void refreshCurrentTab() {
refreshCount += 1;
}
}
}

View File

@@ -0,0 +1,77 @@
package com.hyzq.boss;
import static org.junit.Assert.assertEquals;
import android.content.Intent;
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;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 34)
public class ProjectDetailActivityRealtimeTest {
@Test
public void matchingProjectMessageEventTriggersReload() throws Exception {
Intent intent = new Intent()
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线");
TestRealtimeProjectDetailActivity activity = Robolectric
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
.setup()
.resume()
.get();
ReflectionHelpers.callInstanceMethod(
activity,
"handleRealtimeEvent",
ReflectionHelpers.ClassParameter.from(
BossRealtimeEvent.class,
new BossRealtimeEvent("project.messages.updated", new JSONObject().put("projectId", "project-1"))
)
);
assertEquals(1, activity.reloadCount);
}
@Test
public void unrelatedProjectMessageEventDoesNotTriggerReload() throws Exception {
Intent intent = new Intent()
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线");
TestRealtimeProjectDetailActivity activity = Robolectric
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
.setup()
.resume()
.get();
ReflectionHelpers.callInstanceMethod(
activity,
"handleRealtimeEvent",
ReflectionHelpers.ClassParameter.from(
BossRealtimeEvent.class,
new BossRealtimeEvent("project.messages.updated", new JSONObject().put("projectId", "project-2"))
)
);
assertEquals(0, activity.reloadCount);
}
public static class TestRealtimeProjectDetailActivity extends ProjectDetailActivity {
int reloadCount;
@Override
boolean shouldLoadOnCreate() {
return false;
}
@Override
protected void reload() {
reloadCount += 1;
}
}
}