|
@@ -694,14 +694,20 @@ describe('ChatSessionService', () => {
|
|
|
* 旧行为「保留本地缓存照常展示」看着更"稳",实际是两端互相覆盖的根源:
|
|
* 旧行为「保留本地缓存照常展示」看着更"稳",实际是两端互相覆盖的根源:
|
|
|
* 用户看到的可能是几小时前的列表,在上面做的删除/归档又会回写云端。
|
|
* 用户看到的可能是几小时前的列表,在上面做的删除/归档又会回写云端。
|
|
|
*/
|
|
*/
|
|
|
- it('🔴 云端不可达 → ready=false,界面据此显示「加载失败」', async () => {
|
|
|
|
|
|
|
+ it('🔴 云端不可达 → hydrate 抛出,由调用方决定严重程度(2026-11-14)', async () => {
|
|
|
const svc2 = withApi({
|
|
const svc2 = withApi({
|
|
|
fetchChatThreads: async () => { throw new Error('network'); },
|
|
fetchChatThreads: async () => { throw new Error('network'); },
|
|
|
upsertChatThreads: async () => { throw new Error('network'); },
|
|
upsertChatThreads: async () => { throw new Error('network'); },
|
|
|
});
|
|
});
|
|
|
- await svc2.hydrateFromCloud(AGENT);
|
|
|
|
|
- expect(svc2.ready()).toBeFalse();
|
|
|
|
|
- expect(svc2.error()).toBe('network');
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * 🔴 行为变更(2026-11-14):hydrate 不再在内部置「全局错误态」。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 原实现在这里 `_ready.set(false)`,导致 `reloadAll` 并发拉十几个容器时
|
|
|
|
|
+ * 任意一个失败就把整个左栏判成不可用(用户实测:只剩一句「加载失败」)。
|
|
|
|
|
+ * 现在单容器失败只抛出,由 `reloadAll` 汇总判定:
|
|
|
|
|
+ * 部分失败仍可用,全部失败才进错误态。
|
|
|
|
|
+ */
|
|
|
|
|
+ await expectAsync(svc2.hydrateFromCloud(AGENT)).toBeRejectedWithError('network');
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
it('云端不可达时 pushAll 不推送(列表本身就不可信)', async () => {
|
|
it('云端不可达时 pushAll 不推送(列表本身就不可信)', async () => {
|
|
@@ -712,7 +718,8 @@ describe('ChatSessionService', () => {
|
|
|
});
|
|
});
|
|
|
svc2.create(ROOM, AGENT);
|
|
svc2.create(ROOM, AGENT);
|
|
|
const before = upsert.calls.count();
|
|
const before = upsert.calls.count();
|
|
|
- await svc2.hydrateFromCloud(AGENT);
|
|
|
|
|
|
|
+ // hydrate 现在会抛出,接住后继续验证 pushAll 的行为
|
|
|
|
|
+ await svc2.hydrateFromCloud(AGENT).catch(() => undefined);
|
|
|
await svc2.pushAll(AGENT);
|
|
await svc2.pushAll(AGENT);
|
|
|
expect(upsert.calls.count()).withContext('云端不可达时不应回写').toBe(before);
|
|
expect(upsert.calls.count()).withContext('云端不可达时不应回写').toBe(before);
|
|
|
});
|
|
});
|
|
@@ -727,6 +734,56 @@ describe('ChatSessionService', () => {
|
|
|
expect(svc2.ready()).toBeTrue();
|
|
expect(svc2.ready()).toBeTrue();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ it('🔴 reloadAll 部分失败 → 仍可用(不再整体报废,2026-11-14)', async () => {
|
|
|
|
|
+ /*
|
|
|
|
|
+ * 用户实测:容器有十几个,只要有一个云函数调用异常,
|
|
|
|
|
+ * 整个左栏就只剩「会话目录加载失败」,其它容器的会话全看不见。
|
|
|
|
|
+ * 现在的口径:至少一个成功 → ready=true 正常展示,失败的记进 partialFailures。
|
|
|
|
|
+ */
|
|
|
|
|
+ const svc2 = withApi({
|
|
|
|
|
+ fetchChatThreads: async (agentId: string, o?: { includeDeleted?: boolean }) => {
|
|
|
|
|
+ if (agentId === 'bad-agent') throw new Error('boom');
|
|
|
|
|
+ return o?.includeDeleted
|
|
|
|
|
+ ? []
|
|
|
|
|
+ : [
|
|
|
|
|
+ {
|
|
|
|
|
+ objectId: 'a',
|
|
|
|
|
+ agentId,
|
|
|
|
|
+ roomId: ROOM,
|
|
|
|
|
+ threadRoot: `$t-${agentId}`,
|
|
|
|
|
+ title: 'A',
|
|
|
|
|
+ titleLocked: false,
|
|
|
|
|
+ messageCount: 1,
|
|
|
|
|
+ lastMessageAt: 1,
|
|
|
|
|
+ archived: false,
|
|
|
|
|
+ lastPreview: '',
|
|
|
|
|
+ },
|
|
|
|
|
+ ];
|
|
|
|
|
+ },
|
|
|
|
|
+ upsertChatThreads: async () => undefined,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ await svc2.reloadAll(['good-agent', 'bad-agent']);
|
|
|
|
|
+
|
|
|
|
|
+ expect(svc2.ready()).withContext('有成功就不能整体报废').toBeTrue();
|
|
|
|
|
+ expect(svc2.error()).withContext('不能因为部分失败就进错误态').toBeNull();
|
|
|
|
|
+ expect(svc2.partialFailures().length).withContext('失败的容器要记下来').toBe(1);
|
|
|
|
|
+ expect(svc2.partialFailures()[0].agentId).toBe('bad-agent');
|
|
|
|
|
+ expect(svc2.all().filter((s) => s.agentId === 'good-agent').length)
|
|
|
|
|
+ .withContext('成功的容器会话要正常展示')
|
|
|
|
|
+ .toBe(1);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('🔴 reloadAll 全部失败 → 才进错误态', async () => {
|
|
|
|
|
+ const svc2 = withApi({
|
|
|
|
|
+ fetchChatThreads: async () => { throw new Error('全挂了'); },
|
|
|
|
|
+ upsertChatThreads: async () => undefined,
|
|
|
|
|
+ });
|
|
|
|
|
+ await svc2.reloadAll(['a1', 'a2']);
|
|
|
|
|
+ expect(svc2.ready()).toBeFalse();
|
|
|
|
|
+ expect(svc2.error()).toBe('全挂了');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
it('🔴 云端删掉的会话,本地下一次拉取即消失', async () => {
|
|
it('🔴 云端删掉的会话,本地下一次拉取即消失', async () => {
|
|
|
let live = [
|
|
let live = [
|
|
|
{ objectId: 'a', agentId: AGENT, roomId: ROOM, threadRoot: '$a', title: 'A', titleLocked: false, messageCount: 1, lastMessageAt: 1, archived: false, lastPreview: '' },
|
|
{ objectId: 'a', agentId: AGENT, roomId: ROOM, threadRoot: '$a', title: 'A', titleLocked: false, messageCount: 1, lastMessageAt: 1, archived: false, lastPreview: '' },
|