177 lines
5.2 KiB
TypeScript
177 lines
5.2 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import type { AgentRuntimeProvider, RuntimeScope } from '@mosaicstack/types';
|
|
import {
|
|
MatrixNativeRuntimeProvider,
|
|
type MatrixRuntimeTransport,
|
|
} from './matrix-native-runtime-provider.js';
|
|
import {
|
|
TmuxFleetRuntimeProvider,
|
|
type FleetRuntimeTransport,
|
|
} from './tmux-fleet-runtime-provider.js';
|
|
|
|
const scope: RuntimeScope = {
|
|
actorId: 'operator-1',
|
|
tenantId: 'tenant-a',
|
|
channelId: 'cli',
|
|
correlationId: 'corr-1',
|
|
};
|
|
|
|
interface ProviderFixture {
|
|
name: string;
|
|
provider: AgentRuntimeProvider;
|
|
providerId: string;
|
|
verifySession: unknown;
|
|
send: unknown;
|
|
}
|
|
|
|
function fixtures(): ProviderFixture[] {
|
|
const fleetTransport: FleetRuntimeTransport = {
|
|
verifySession: vi.fn(async () => ({
|
|
id: 'session-1',
|
|
runtimeId: 'native-1',
|
|
socketName: 'fleet',
|
|
})),
|
|
listSessions: vi.fn(async () => [
|
|
{ id: 'session-1', runtimeId: 'native-1', socketName: 'fleet' },
|
|
]),
|
|
sendMessage: vi.fn(async () => undefined),
|
|
terminate: vi.fn(async () => undefined),
|
|
};
|
|
const fleet = new TmuxFleetRuntimeProvider({
|
|
transport: fleetTransport,
|
|
readAuthority: { canRead: vi.fn(async () => true) },
|
|
writeAuthority: {
|
|
canWrite: vi.fn(async () => true),
|
|
assertAuthorized: vi.fn(async () => undefined),
|
|
},
|
|
attachmentIdFactory: () => 'attachment-1',
|
|
now: () => new Date('2026-07-13T00:00:00.000Z'),
|
|
});
|
|
|
|
const matrixTransport: MatrixRuntimeTransport = {
|
|
health: vi.fn(async () => ({
|
|
status: 'healthy' as const,
|
|
checkedAt: '2026-07-13T00:00:00.000Z',
|
|
})),
|
|
verifySession: vi.fn(async () => ({
|
|
id: 'session-1',
|
|
runtimeId: 'native-1',
|
|
state: 'active' as const,
|
|
createdAt: '2026-07-13T00:00:00.000Z',
|
|
updatedAt: '2026-07-13T00:00:00.000Z',
|
|
})),
|
|
listSessions: vi.fn(async () => [
|
|
{
|
|
id: 'session-1',
|
|
runtimeId: 'native-1',
|
|
state: 'active' as const,
|
|
createdAt: '2026-07-13T00:00:00.000Z',
|
|
updatedAt: '2026-07-13T00:00:00.000Z',
|
|
},
|
|
]),
|
|
stream: async function* () {},
|
|
send: vi.fn(async () => undefined),
|
|
terminate: vi.fn(async () => undefined),
|
|
};
|
|
const matrix = new MatrixNativeRuntimeProvider({
|
|
transport: matrixTransport,
|
|
readAuthority: { canRead: vi.fn(async () => true) },
|
|
writeAuthority: {
|
|
canWrite: vi.fn(async () => true),
|
|
assertAuthorized: vi.fn(async () => undefined),
|
|
},
|
|
attachmentIdFactory: () => 'attachment-1',
|
|
now: () => new Date('2026-07-13T00:00:00.000Z'),
|
|
});
|
|
|
|
return [
|
|
{
|
|
name: 'tmux/fleet',
|
|
provider: fleet,
|
|
providerId: 'fleet.tmux',
|
|
verifySession: fleetTransport.verifySession,
|
|
send: fleetTransport.sendMessage,
|
|
},
|
|
{
|
|
name: 'Matrix/native',
|
|
provider: matrix,
|
|
providerId: 'runtime.matrix',
|
|
verifySession: matrixTransport.verifySession,
|
|
send: matrixTransport.send,
|
|
},
|
|
];
|
|
}
|
|
|
|
/** Shared contract tests for the migration-safe provider intersection. */
|
|
describe('tmux/fleet and Matrix/native provider parity', (): void => {
|
|
it.each(fixtures())(
|
|
'%s exposes the shared runtime operations',
|
|
async (fixture): Promise<void> => {
|
|
await expect(fixture.provider.capabilities(scope)).resolves.toEqual(
|
|
expect.objectContaining({
|
|
supported: expect.arrayContaining([
|
|
'session.list',
|
|
'session.tree',
|
|
'session.send',
|
|
'session.attach',
|
|
'session.terminate',
|
|
]),
|
|
}),
|
|
);
|
|
await expect(fixture.provider.listSessions(scope)).resolves.toEqual([
|
|
expect.objectContaining({
|
|
id: 'session-1',
|
|
providerId: fixture.providerId,
|
|
runtimeId: 'native-1',
|
|
}),
|
|
]);
|
|
},
|
|
);
|
|
|
|
it.each(fixtures())(
|
|
'%s rejects empty messages before touching its transport',
|
|
async (fixture): Promise<void> => {
|
|
await expect(
|
|
fixture.provider.sendMessage(
|
|
'session-1',
|
|
{ content: '', idempotencyKey: 'message-1' },
|
|
scope,
|
|
),
|
|
).rejects.toMatchObject({ code: 'invalid_request' });
|
|
expect(fixture.verifySession).not.toHaveBeenCalled();
|
|
expect(fixture.send).not.toHaveBeenCalled();
|
|
},
|
|
);
|
|
|
|
it.each(fixtures())(
|
|
'%s rejects an empty termination approval before touching its transport',
|
|
async (fixture): Promise<void> => {
|
|
await expect(fixture.provider.terminate('session-1', '', scope)).rejects.toMatchObject({
|
|
code: 'invalid_request',
|
|
});
|
|
expect(fixture.verifySession).not.toHaveBeenCalled();
|
|
},
|
|
);
|
|
|
|
it.each(fixtures())(
|
|
'%s creates a read-only handle bound to immutable scope',
|
|
async (fixture): Promise<void> => {
|
|
await expect(fixture.provider.attach('session-1', 'control', scope)).rejects.toMatchObject({
|
|
code: 'forbidden',
|
|
});
|
|
expect(fixture.verifySession).not.toHaveBeenCalled();
|
|
|
|
await expect(fixture.provider.attach('session-1', 'read', scope)).resolves.toEqual(
|
|
expect.objectContaining({
|
|
attachmentId: 'attachment-1',
|
|
sessionId: 'session-1',
|
|
mode: 'read',
|
|
}),
|
|
);
|
|
await expect(
|
|
fixture.provider.detach('attachment-1', { ...scope, actorId: 'operator-2' }),
|
|
).rejects.toMatchObject({ code: 'forbidden' });
|
|
},
|
|
);
|
|
});
|