154 lines
4.8 KiB
TypeScript
154 lines
4.8 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import type { RuntimeScope, RuntimeStreamEvent } from '@mosaicstack/types';
|
|
import {
|
|
type MatrixRuntimeSession,
|
|
type MatrixRuntimeTransport,
|
|
MatrixNativeRuntimeProvider,
|
|
type MatrixReadAuthority,
|
|
type MatrixWriteAuthority,
|
|
} from './matrix-native-runtime-provider.js';
|
|
|
|
const scope: RuntimeScope = {
|
|
actorId: 'operator-1',
|
|
tenantId: 'tenant-a',
|
|
channelId: 'matrix-control',
|
|
correlationId: 'corr-1',
|
|
};
|
|
|
|
const session: MatrixRuntimeSession = {
|
|
id: 'native-1',
|
|
runtimeId: '@worker:example.test',
|
|
state: 'active',
|
|
createdAt: '2026-07-13T00:00:00.000Z',
|
|
updatedAt: '2026-07-13T00:00:00.000Z',
|
|
};
|
|
|
|
function transport(): MatrixRuntimeTransport {
|
|
return {
|
|
health: vi.fn(async () => ({
|
|
status: 'healthy' as const,
|
|
checkedAt: '2026-07-13T00:00:00.000Z',
|
|
})),
|
|
listSessions: vi.fn(async () => [session]),
|
|
verifySession: vi.fn(async (sessionId) => {
|
|
if (sessionId !== session.id) throw new Error('unexpected session');
|
|
return session;
|
|
}),
|
|
stream: vi.fn(async function* (): AsyncIterable<RuntimeStreamEvent> {
|
|
yield {
|
|
type: 'message.delta',
|
|
sessionId: 'native-1',
|
|
cursor: 'cursor-1',
|
|
occurredAt: '2026-07-13T00:00:00.000Z',
|
|
content: 'hello',
|
|
};
|
|
}),
|
|
send: vi.fn(async () => undefined),
|
|
terminate: vi.fn(async () => undefined),
|
|
};
|
|
}
|
|
|
|
function readAuthority(): MatrixReadAuthority {
|
|
return { canRead: vi.fn(async () => true) };
|
|
}
|
|
|
|
function writeAuthority(): MatrixWriteAuthority {
|
|
return {
|
|
canWrite: vi.fn(async () => true),
|
|
assertAuthorized: vi.fn(async () => undefined),
|
|
};
|
|
}
|
|
|
|
describe('MatrixNativeRuntimeProvider contract boundary', (): void => {
|
|
it('advertises the concrete Matrix operations and returns only normalized sessions', async (): Promise<void> => {
|
|
const provider = new MatrixNativeRuntimeProvider({
|
|
transport: transport(),
|
|
readAuthority: readAuthority(),
|
|
});
|
|
|
|
await expect(provider.capabilities(scope)).resolves.toEqual({
|
|
supported: [
|
|
'session.list',
|
|
'session.tree',
|
|
'session.stream',
|
|
'session.send',
|
|
'session.attach',
|
|
'session.terminate',
|
|
],
|
|
});
|
|
await expect(provider.listSessions(scope)).resolves.toEqual([
|
|
expect.objectContaining({ id: 'native-1', providerId: 'runtime.matrix' }),
|
|
]);
|
|
});
|
|
|
|
it('binds read attachments to immutable scope and rejects control mode before transport access', async (): Promise<void> => {
|
|
const matrix = transport();
|
|
const provider = new MatrixNativeRuntimeProvider({
|
|
transport: matrix,
|
|
readAuthority: readAuthority(),
|
|
attachmentIdFactory: () => 'attachment-1',
|
|
now: () => new Date('2026-07-13T00:00:00.000Z'),
|
|
});
|
|
|
|
await expect(provider.attach('native-1', 'control', scope)).rejects.toMatchObject({
|
|
code: 'forbidden',
|
|
});
|
|
expect(matrix.verifySession).not.toHaveBeenCalled();
|
|
|
|
await provider.attach('native-1', 'read', scope);
|
|
await expect(
|
|
provider.detach('attachment-1', { ...scope, tenantId: 'other' }),
|
|
).rejects.toMatchObject({
|
|
code: 'forbidden',
|
|
});
|
|
});
|
|
|
|
it('validates messages and applies exact Matrix authority after bound-session verification', async (): Promise<void> => {
|
|
const matrix = transport();
|
|
const writes = writeAuthority();
|
|
const provider = new MatrixNativeRuntimeProvider({
|
|
transport: matrix,
|
|
readAuthority: readAuthority(),
|
|
writeAuthority: writes,
|
|
});
|
|
|
|
await expect(
|
|
provider.sendMessage('native-1', { content: '', idempotencyKey: 'msg-1' }, scope),
|
|
).rejects.toMatchObject({
|
|
code: 'invalid_request',
|
|
});
|
|
expect(matrix.verifySession).not.toHaveBeenCalled();
|
|
|
|
await provider.sendMessage('native-1', { content: 'hello', idempotencyKey: 'msg-1' }, scope);
|
|
expect(writes.assertAuthorized).toHaveBeenCalledWith({
|
|
operation: 'session.send',
|
|
sessionId: 'native-1',
|
|
scope,
|
|
});
|
|
expect(matrix.send).toHaveBeenCalledWith(
|
|
'native-1',
|
|
{ content: 'hello', idempotencyKey: 'msg-1' },
|
|
scope,
|
|
);
|
|
});
|
|
|
|
it('streams only after exact read authorization', async (): Promise<void> => {
|
|
const matrix = transport();
|
|
const provider = new MatrixNativeRuntimeProvider({
|
|
transport: matrix,
|
|
readAuthority: readAuthority(),
|
|
});
|
|
|
|
await expect(collect(provider.streamSession('native-1', 'cursor-0', scope))).resolves.toEqual([
|
|
expect.objectContaining({ type: 'message.delta', sessionId: 'native-1' }),
|
|
]);
|
|
expect(matrix.stream).toHaveBeenCalledWith('native-1', 'cursor-0', scope);
|
|
});
|
|
});
|
|
|
|
async function collect(stream: AsyncIterable<RuntimeStreamEvent>): Promise<RuntimeStreamEvent[]> {
|
|
const values: RuntimeStreamEvent[] = [];
|
|
for await (const value of stream) values.push(value);
|
|
return values;
|
|
}
|