blob: 342fd25d7cd850b59da26bf1245a6df2bacaa2af [file]
import { RuntimeHostProtocolError } from '../protocol/errors.js';
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import {
decodeClientFrame,
decodeExternalSessionCatalogQueryResult,
decodeExternalSessionSourceQueryResult,
EXTERNAL_SESSION_IMPORTED_SESSION_IDS_MAX_ITEMS,
EXTERNAL_SESSION_PAGE_MAX_ITEMS,
HOST_OPERATION_SPECS,
} from '../protocol/index.js';
describe('external Session protocol', () => {
test('identifies external Session queries that expose Host paths', () => {
assert.equal(
HOST_OPERATION_SPECS['external-session.catalog.query'].usesHostPaths?.({
adapterId: 'codex',
workspace: { kind: 'project', projectId: 'project-1' },
}),
false,
);
assert.equal(
HOST_OPERATION_SPECS['external-session.catalog.query'].usesHostPaths?.({
adapterId: 'codex',
workspace: { kind: 'host_path', path: '/workspace' },
}),
true,
);
});
test('round-trips exact source, catalog, and import inputs', () => {
assert.deepEqual(
decodeClientFrame({
requestId: 'request-source',
operation: 'external-session.source.query',
input: {},
}),
{
requestId: 'request-source',
operation: 'external-session.source.query',
input: {},
},
);
assert.deepEqual(
decodeClientFrame({
requestId: 'request-list',
operation: 'external-session.catalog.query',
input: {
adapterId: 'codex',
includeArchived: true,
workspace: { kind: 'project', projectId: 'project-1' },
cursor: '16',
},
}),
{
requestId: 'request-list',
operation: 'external-session.catalog.query',
input: {
adapterId: 'codex',
includeArchived: true,
workspace: { kind: 'project', projectId: 'project-1' },
cursor: '16',
},
},
);
assert.deepEqual(
decodeClientFrame({
requestId: 'request-import',
operation: 'external-session.import',
input: { adapterId: 'codex', sourceSessionId: 'source-session-1' },
}),
{
requestId: 'request-import',
operation: 'external-session.import',
input: { adapterId: 'codex', sourceSessionId: 'source-session-1' },
},
);
});
test('round-trips required external Session import state', () => {
const result = {
sessions: [
{
id: 'source-1',
name: 'Imported source',
hostCwd: '/workspace',
importState: {
importedCount: 10,
importedSessionIds: Array.from(
{ length: EXTERNAL_SESSION_IMPORTED_SESSION_IDS_MAX_ITEMS },
(_, index) => `imported-${EXTERNAL_SESSION_IMPORTED_SESSION_IDS_MAX_ITEMS - index}`,
),
isImporting: true,
},
},
],
nextCursor: null,
};
assert.deepEqual(decodeExternalSessionCatalogQueryResult(result), result);
});
test('rejects missing, open, malformed, and inconsistent external Session import state', () => {
const summary = {
id: 'source-1',
name: 'Imported source',
hostCwd: '/workspace',
};
const decodeState = (importState?: unknown) =>
decodeExternalSessionCatalogQueryResult({
sessions: [{ ...summary, ...(importState === undefined ? {} : { importState }) }],
nextCursor: null,
});
assert.throws(() => decodeState(), isProtocolError);
assert.throws(
() =>
decodeState({
importedCount: 1,
importedSessionIds: ['imported-1'],
isImporting: false,
extra: true,
}),
isProtocolError,
);
assert.throws(
() =>
decodeState({
importedCount: -1,
importedSessionIds: [],
isImporting: false,
}),
isProtocolError,
);
assert.throws(
() =>
decodeState({
importedCount: EXTERNAL_SESSION_IMPORTED_SESSION_IDS_MAX_ITEMS + 1,
importedSessionIds: Array.from(
{ length: EXTERNAL_SESSION_IMPORTED_SESSION_IDS_MAX_ITEMS + 1 },
(_, index) => `imported-${index}`,
),
isImporting: false,
}),
isProtocolError,
);
assert.throws(
() =>
decodeState({
importedCount: 1,
importedSessionIds: ['invalid id'],
isImporting: false,
}),
isProtocolError,
);
assert.throws(
() =>
decodeState({
importedCount: 0,
importedSessionIds: [],
isImporting: 'yes',
}),
isProtocolError,
);
assert.throws(
() =>
decodeState({
importedCount: 1,
importedSessionIds: ['imported-2', 'imported-1'],
isImporting: false,
}),
isProtocolError,
);
});
test('rejects sparse imported Session id arrays', () => {
const importedSessionIds = Array<string>(1);
assert.throws(
() =>
decodeExternalSessionCatalogQueryResult({
sessions: [
{
id: 'source-1',
name: 'Imported source',
hostCwd: '/workspace',
importState: {
importedCount: 1,
importedSessionIds,
isImporting: false,
},
},
],
nextCursor: null,
}),
isProtocolError,
);
});
test('rejects open-ended, malformed, and oversized frames', () => {
assert.throws(
() =>
decodeClientFrame({
requestId: 'request-extra',
operation: 'external-session.source.query',
input: { source: 'codex' },
}),
isProtocolError,
);
assert.throws(
() =>
decodeClientFrame({
requestId: 'request-cursor',
operation: 'external-session.catalog.query',
input: { adapterId: 'codex', cursor: '-1' },
}),
isProtocolError,
);
assert.throws(
() =>
decodeClientFrame({
requestId: 'request-id',
operation: 'external-session.import',
input: { adapterId: 'codex', sourceSessionId: 'bad\nsource' },
}),
isProtocolError,
);
assert.throws(
() =>
decodeExternalSessionCatalogQueryResult({
sessions: Array.from({ length: EXTERNAL_SESSION_PAGE_MAX_ITEMS + 1 }, (_, index) => ({
id: `source-${index}`,
name: `Session ${index}`,
hostCwd: '/workspace',
importState: {
importedCount: 0,
importedSessionIds: [],
isImporting: false,
},
})),
nextCursor: null,
}),
isProtocolError,
);
assert.throws(
() => decodeExternalSessionSourceQueryResult({ adapterIds: ['codex'], extra: true }),
isProtocolError,
);
});
});
function isProtocolError(error: unknown): boolean {
return error instanceof RuntimeHostProtocolError && error.code === 'invalid_frame';
}