Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01ESFAnh2t9HmLwng8oW95St
94 lines
3.1 KiB
TypeScript
94 lines
3.1 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import type { SlashCommandPayload } from '@mosaicstack/types';
|
|
import { ChatGateway } from './chat.gateway.js';
|
|
|
|
const payload: SlashCommandPayload = {
|
|
command: 'gc',
|
|
conversationId: 'conversation-1',
|
|
approvalId: 'approval-1',
|
|
};
|
|
|
|
/**
|
|
* Task 5 fence (F, existing control): gateway-owned command authorization/approval must
|
|
* cause ZERO chat-runtime dispatch. Placed in the gateway's chat-runtime-router slot (the
|
|
* former direct `AgentService` slot) so any accidental chat-runtime resolution throws
|
|
* loudly instead of silently passing. Because execute/approval run entirely through the
|
|
* command executor dependency and never resolve a chat runtime, this fixture is never
|
|
* triggered and the ingress stays a GREEN control.
|
|
*/
|
|
function failIfUsedChatRuntimeRouter() {
|
|
return {
|
|
onModuleInit: () => {
|
|
throw new Error('chat runtime router must not initialise on the command approval path');
|
|
},
|
|
get active(): never {
|
|
throw new Error('chat runtime must not be resolved on the command approval path');
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildGateway(commandExecutor: {
|
|
execute: ReturnType<typeof vi.fn>;
|
|
createApproval: ReturnType<typeof vi.fn>;
|
|
}): ChatGateway {
|
|
return new ChatGateway(
|
|
failIfUsedChatRuntimeRouter() as never,
|
|
{} as never,
|
|
{} as never,
|
|
{} as never,
|
|
commandExecutor as never,
|
|
{} as never,
|
|
);
|
|
}
|
|
|
|
describe('ChatGateway command approval ingress', () => {
|
|
it('passes the client approval ID through to command execution while deriving the actor server-side', async (): Promise<void> => {
|
|
const commandExecutor = {
|
|
execute: vi.fn().mockResolvedValue({ ...payload, success: true }),
|
|
createApproval: vi.fn(),
|
|
};
|
|
const gateway = buildGateway(commandExecutor);
|
|
const client = { data: { user: { id: 'admin-1' } }, emit: vi.fn() };
|
|
|
|
await gateway.handleCommandExecute(client as never, payload);
|
|
|
|
expect(commandExecutor.execute).toHaveBeenCalledWith(payload, {
|
|
userId: 'admin-1',
|
|
tenantId: 'admin-1',
|
|
});
|
|
expect(client.emit).toHaveBeenCalledWith(
|
|
'command:result',
|
|
expect.objectContaining({ success: true }),
|
|
);
|
|
});
|
|
|
|
it('issues a durable approval only for the authenticated actor', async (): Promise<void> => {
|
|
const commandExecutor = {
|
|
execute: vi.fn(),
|
|
createApproval: vi.fn().mockResolvedValue({
|
|
approvalId: 'approval-1',
|
|
expiresAt: '2026-07-12T00:05:00.000Z',
|
|
}),
|
|
};
|
|
const gateway = buildGateway(commandExecutor);
|
|
const client = { data: { user: { id: 'admin-1' } }, emit: vi.fn() };
|
|
|
|
await gateway.handleCommandApproval(client as never, {
|
|
command: 'gc',
|
|
conversationId: 'conversation-1',
|
|
});
|
|
|
|
expect(commandExecutor.createApproval).toHaveBeenCalledWith(
|
|
{ command: 'gc', conversationId: 'conversation-1' },
|
|
{ userId: 'admin-1', tenantId: 'admin-1' },
|
|
);
|
|
expect(client.emit).toHaveBeenCalledWith('command:approval', {
|
|
command: 'gc',
|
|
conversationId: 'conversation-1',
|
|
success: true,
|
|
approvalId: 'approval-1',
|
|
expiresAt: '2026-07-12T00:05:00.000Z',
|
|
});
|
|
});
|
|
});
|