feat: integrate pinned teacher discussion workspace
This commit is contained in:
@@ -1156,3 +1156,44 @@ it.each(['teacher', 'friend'] as const)('routes project-level %s config and topi
|
||||
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
describe('structured teacher service integration', () => {
|
||||
it('buffers model JSON, offers once, locks updates, persists state, and never applies a cancelled update', async () => {
|
||||
const f = await fixture();
|
||||
const scope = { ...f.scope, sourceId: 'project', role: 'teacher' as const };
|
||||
const initial = await f.service.create(scope);
|
||||
const tool = { kind: 'ideas', title: '宠物游戏', items: [{ id: 'dog', text: '养只小狗', state: 'kept' }] };
|
||||
f.replyWith(JSON.stringify({ reply: '我们可以先把想法放在一起。', quickReplies: [], tool }));
|
||||
const send = (text: string, discussion?: { toolId: string; revision: number }) => f.service.send(scope, initial.id, {
|
||||
requestId: crypto.randomUUID(), text, presentation: 'discussion-v1', sourceConversationId: f.scope.sourceId, discussion,
|
||||
});
|
||||
const pending = await send('想养只小狗');
|
||||
expect(pending.requests[0].response).toBe('');
|
||||
expect(pending.discussion).toBeUndefined();
|
||||
f.finish();
|
||||
await vi.waitFor(async () => expect((await f.service.read(scope, initial.id)).requests[0].status).toBe('completed'));
|
||||
const offered = await f.service.read(scope, initial.id);
|
||||
expect(offered.discussion?.status).toBe('offered');
|
||||
expect(offered.requests[0].response).toBe('我们可以先把想法放在一起。');
|
||||
const active = await f.service.updateDiscussion(scope, initial.id, { toolId: offered.discussion!.id, revision: 1, action: 'enter' });
|
||||
const context = { toolId: active.discussion!.id, revision: 2 };
|
||||
f.replyWith(JSON.stringify({ reply: '把花园也放进来了。', tool: { ...tool, title: '小狗和花园' } }));
|
||||
const running = await send('还想种花', context);
|
||||
expect(running.discussion?.content.title).toBe('宠物游戏');
|
||||
await expect(f.service.updateDiscussion(scope, initial.id, { ...context, action: 'finish' })).rejects.toMatchObject({ code: 'teacher_topic_busy' });
|
||||
await f.service.cancel(scope, initial.id, running.requests.at(-1)!.id);
|
||||
await vi.waitFor(async () => expect((await f.service.read(scope, initial.id)).requests.at(-1)?.status).toBe('cancelled'));
|
||||
expect((await f.service.read(scope, initial.id)).discussion?.content.title).toBe('宠物游戏');
|
||||
await send('再把花园加进来', context);
|
||||
f.finish();
|
||||
await vi.waitFor(async () => expect((await f.service.read(scope, initial.id)).requests.at(-1)?.status).toBe('completed'));
|
||||
const completed = await f.service.read(scope, initial.id);
|
||||
expect(completed.discussion?.content.title).toBe('小狗和花园');
|
||||
expect(completed.discussion?.revision).toBe(3);
|
||||
expect(completed.requests[0].discussionSnapshot?.title).toBe('宠物游戏');
|
||||
await expect(send('旧版本', context)).rejects.toMatchObject({ code: 'teacher_discussion_changed' });
|
||||
const restarted = await f.restart();
|
||||
expect((await restarted.read(scope, initial.id)).discussion).toEqual(completed.discussion);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user