test(sync): update stale tests to current pipeline contracts

Four sync tests had been failing for a while (CI doesn't run them, so
nobody noticed). All four asserted behavior that was deliberately
changed by earlier refactors — the production code is correct:

- Three event-pipeline tests still expected message.part.updated events
  to coalesce in the queue. That coalescing was removed in #1167 to
  preserve part update ordering (the new contract is covered by
  event-pipeline.test.ts). Updated the delta-ordering and no-coalescing
  expectations, and switched the routes-before-queueing test to
  session.status, which is still a coalescible type, so it keeps
  proving that coalescing happens on the resolved directory.
- One session-ui-store test expected shell sends to run inside an
  opencodeClient.withDirectory scope. Since #1228 the session directory
  travels as an explicit request param on shellSession; the test now
  asserts that contract directly.

All 165 sync tests pass.
This commit is contained in:
Bohdan Triapitsyn
2026-06-10 14:01:10 +03:00
parent 89aa389e6c
commit fac4167499
2 changed files with 33 additions and 35 deletions
@@ -320,7 +320,8 @@ describe('createEventPipeline', () => {
}, },
}, },
}, },
// T2: message.part.updated for part-A — coalesces with T0 // T2: a newer message.part.updated for part-A — delivered as its own
// event (part.updated is never coalesced, ordering is preserved).
{ {
payload: { payload: {
type: 'message.part.updated', type: 'message.part.updated',
@@ -337,7 +338,7 @@ describe('createEventPipeline', () => {
sdk, sdk,
onEvent: (dir, payload) => { onEvent: (dir, payload) => {
received.push({ directory: dir, payload }); received.push({ directory: dir, payload });
if (received.length === 2) { if (received.length === 3) {
cleanup(); cleanup();
releaseStream(); releaseStream();
resolve(); resolve();
@@ -348,10 +349,11 @@ describe('createEventPipeline', () => {
await delivered; await delivered;
expect(received.length).toBe(2); expect(received.length).toBe(3);
expect(received[0].payload.type).toBe('message.part.updated'); expect(received[0].payload.type).toBe('message.part.updated');
expect(received[1].payload.type).toBe('message.part.delta'); expect(received[1].payload.type).toBe('message.part.delta');
expect(received[1].payload.properties.delta).toBe(' world'); expect(received[1].payload.properties.delta).toBe(' world');
expect(received[2].payload.type).toBe('message.part.updated');
}); });
it('keeps delta events for other fields on the same part', async () => { it('keeps delta events for other fields on the same part', async () => {
@@ -416,7 +418,7 @@ describe('createEventPipeline', () => {
expect(received[1].payload.properties.delta).toBe('hello'); expect(received[1].payload.properties.delta).toBe('hello');
}); });
it('coalesces message.part.updated events for the same part', async () => { it('delivers every message.part.updated for the same part (never coalesced)', async () => {
installDomStubs(); installDomStubs();
let releaseStream; let releaseStream;
@@ -427,6 +429,8 @@ describe('createEventPipeline', () => {
const received = []; const received = [];
const directory = '/test/dir'; const directory = '/test/dir';
// part.updated snapshots must all reach the reducer in order — coalescing
// them can drop intermediate states deltas depend on (see PR #1167).
const sdk = createSdkWithEvents([ const sdk = createSdkWithEvents([
{ {
payload: { payload: {
@@ -453,18 +457,20 @@ describe('createEventPipeline', () => {
sdk, sdk,
onEvent: (dir, payload) => { onEvent: (dir, payload) => {
received.push({ directory: dir, payload }); received.push({ directory: dir, payload });
cleanup(); if (received.length === 2) {
releaseStream(); cleanup();
resolve(); releaseStream();
resolve();
}
}, },
}); });
}); });
await delivered; await delivered;
// Only 1 event should be delivered (coalesced) expect(received.length).toBe(2);
expect(received.length).toBe(1);
expect(received[0].payload.type).toBe('message.part.updated'); expect(received[0].payload.type).toBe('message.part.updated');
expect(received[1].payload.type).toBe('message.part.updated');
}); });
it('routes events before queueing so coalescing happens on the resolved directory', async () => { it('routes events before queueing so coalescing happens on the resolved directory', async () => {
@@ -475,23 +481,28 @@ describe('createEventPipeline', () => {
releaseStream = resolve; releaseStream = resolve;
}); });
// Two coalescible session.status events for the same session arrive on
// different transport directories; routing resolves both to the same
// directory, so they must land in one queue and coalesce to the latest.
const received = []; const received = [];
const sdk = createSdkWithEvents([ const sdk = createSdkWithEvents([
{ {
directory: 'global', directory: 'global',
payload: { payload: {
type: 'message.part.updated', type: 'session.status',
properties: { properties: {
part: { id: 'part-A', type: 'text', messageID: 'msg-1' }, sessionID: 'session-1',
status: { type: 'busy' },
}, },
}, },
}, },
{ {
directory: '/real-dir', directory: '/real-dir',
payload: { payload: {
type: 'message.part.updated', type: 'session.status',
properties: { properties: {
part: { id: 'part-A', type: 'text', messageID: 'msg-1', text: 'next' }, sessionID: 'session-1',
status: { type: 'idle' },
}, },
}, },
}, },
@@ -501,7 +512,7 @@ describe('createEventPipeline', () => {
const { cleanup } = createEventPipeline({ const { cleanup } = createEventPipeline({
sdk, sdk,
routeDirectory: (directory, payload) => { routeDirectory: (directory, payload) => {
if (payload.type === 'message.part.updated') { if (payload.type === 'session.status') {
return '/resolved-dir'; return '/resolved-dir';
} }
return directory; return directory;
@@ -519,8 +530,8 @@ describe('createEventPipeline', () => {
expect(received).toHaveLength(1); expect(received).toHaveLength(1);
expect(received[0].directory).toBe('/resolved-dir'); expect(received[0].directory).toBe('/resolved-dir');
expect(received[0].payload.type).toBe('message.part.updated'); expect(received[0].payload.type).toBe('session.status');
expect(received[0].payload.properties.part.text).toBe('next'); expect(received[0].payload.properties.status.type).toBe('idle');
}); });
it('consumes websocket message stream frames when transport is ws', async () => { it('consumes websocket message stream frames when transport is ws', async () => {
+6 -19
View File
@@ -193,25 +193,13 @@ describe('session-worktree-store worktree routing', () => {
describe('routeMessage directory scoping', () => { describe('routeMessage directory scoping', () => {
test('runs sends in the provided session directory', async () => { test('runs sends in the provided session directory', async () => {
// The session directory travels as an explicit request param (not via
// client-wide directory scoping), so concurrent sends can't cross-talk.
const calls = []; const calls = [];
let activeDirectory = '/current/project';
const originalWithDirectory = opencodeClient.withDirectory;
const originalGetDirectory = opencodeClient.getDirectory;
const originalShellSession = opencodeClient.shellSession; const originalShellSession = opencodeClient.shellSession;
opencodeClient.withDirectory = async (directory, fn) => {
calls.push({ method: 'withDirectory', directory });
const previousDirectory = activeDirectory;
activeDirectory = directory ?? undefined;
try {
return await fn();
} finally {
activeDirectory = previousDirectory;
}
};
opencodeClient.getDirectory = () => activeDirectory;
opencodeClient.shellSession = async (params) => { opencodeClient.shellSession = async (params) => {
calls.push({ method: 'session.shell', params }); calls.push(params);
return { info: {}, parts: [] }; return { info: {}, parts: [] };
}; };
@@ -225,12 +213,11 @@ describe('routeMessage directory scoping', () => {
inputMode: 'shell', inputMode: 'shell',
}); });
} finally { } finally {
opencodeClient.withDirectory = originalWithDirectory;
opencodeClient.getDirectory = originalGetDirectory;
opencodeClient.shellSession = originalShellSession; opencodeClient.shellSession = originalShellSession;
} }
expect(calls[0]).toEqual({ method: 'withDirectory', directory: '/session/project' }); expect(calls).toHaveLength(1);
expect(calls[1].params.directory).toBe('/session/project'); expect(calls[0].sessionId).toBe('session-a');
expect(calls[0].directory).toBe('/session/project');
}); });
}); });