Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/kap-server/test/search/searchService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,10 @@ describe('GlobalSearchService', () => {
const service = track(makeService(home!, index));
await service.reindex();
expect((await service.search({ query: '苹果' })).items.length).toBe(1);
// The search above kicked a fire-and-forget background pass whose
// session enumeration already ran with block=false: drain it before the
// append, or a CI-slow pass reads the delta below and publishes it early.
await settleSync(service);

// New bytes arrive, then the next background pass is blocked inside the
// session enumeration. The search must return promptly with the OLD
Expand Down
34 changes: 24 additions & 10 deletions packages/kap-server/test/sessions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1595,24 +1595,38 @@ describe('server-v2 /api/v1/sessions (minidb read model)', () => {
expect(status.body.code).toBe(0);
expect(status.body.data.state).toBe('ready');

// A freshly created session lists, counts, and pages immediately — the
// mutation path never waited for the read model, the read path folds the
// mirror queue back in.
// A freshly created session lists, counts, and pages without waiting for
// the read model — the mutation path never awaited the read model, the
// read path folds the mirror queue back in. That fold is best-effort
// while a mirror flush is in flight: the flush's batch is only per-shard
// atomic and its pending-queue cleanup is not linearized with reads, so a
// read landing exactly inside that window can transiently miss (or
// double-count) the session — poll instead of sampling once.
const created = await postJson<SessionWire>('/api/v1/sessions', {
metadata: { cwd: home as string },
});
const id = created.body.data.id;

const listed = await getJson<PageWire>('/api/v1/sessions');
expect(listed.body.data.items.some((s) => s.id === id)).toBe(true);
await vi.waitFor(
async () => {
const listed = await getJson<PageWire>('/api/v1/sessions');
expect(listed.body.data.items.some((s) => s.id === id)).toBe(true);

const workspaces = await getJson<{ items: { session_count: number }[] }>('/api/v1/workspaces');
expect(workspaces.body.data.items[0]?.session_count).toBe(1);
const workspaces = await getJson<{ items: { session_count: number }[] }>(
'/api/v1/workspaces',
);
expect(workspaces.body.data.items[0]?.session_count).toBe(1);

const paged = await getJson<PageWire>(`/api/v1/sessions?page_size=1&before_id=${id}`);
expect(paged.body.data.items).toEqual([]);
expect(paged.body.data.has_more).toBe(false);
const paged = await getJson<PageWire>(`/api/v1/sessions?page_size=1&before_id=${id}`);
expect(paged.body.data.items).toEqual([]);
expect(paged.body.data.has_more).toBe(false);
},
{ timeout: 10_000 },
);

// Archiving drains the mirror queue before responding, and the restart's
// boot prepare re-projects (or reuses) a fully settled generation — both
// of these reads are deterministic again.
await postJson<{ archived: boolean }>(`/api/v1/sessions/${id}:archive`);
const archivedOnly = await getJson<PageWire>('/api/v1/sessions?archived_only=true');
expect(archivedOnly.body.data.items.map((s) => s.id)).toEqual([id]);
Expand Down
6 changes: 5 additions & 1 deletion packages/kap-server/test/transcript.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,11 @@ describe('server-v2 /api/v1/sessions/{sid}/transcript', () => {
server = undefined;
}
if (home !== undefined) {
await rm(home, { recursive: true, force: true });
// maxRetries: the engine's file log writers flush synchronously on scope
// dispose but their trailing async close can still be creating a file
// under home after server.close() resolves (ENOTEMPTY on a loaded CI
// runner) — same retry pattern as questions.test.ts / fs.test.ts.
await rm(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
home = undefined;
}
});
Expand Down
Loading