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:
@@ -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 () => {
|
||||||
|
|||||||
@@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user