File size: 4,440 Bytes
f0634fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | import { beforeEach, describe, expect, it } from 'vitest';
import { SyncDescriptor } from '#/_base/di/descriptors';
import { createDecorator, ScopeActivation } from '#/_base/di/instantiation';
import {
_clearScopedRegistryForTests,
registerScopedService,
} from '#/_base/di/scope';
import { Service } from '#/_base/di/service';
import { createScopedTestHost, stubPair } from '#/_base/di/test';
import { LifecycleScope } from '#/app/scopes';
import {
IAgentCommandService,
} from '#/agent/command/agentCommand';
import { AgentCommandService } from '#/agent/command/agentCommandService';
import {
CommandContribution,
type CommandRunContext,
} from '#/agent/command/commandContribution';
import { ErrorCodes } from '#/errors';
interface IEcho {
readonly _serviceBrand: undefined;
readonly value: string;
}
const IEcho = createDecorator<IEcho>('test-command-echo');
const ICommandProvider = createDecorator<Service>('test-command-provider');
describe('AgentCommandService — CommandContribution fold', () => {
beforeEach(() => {
_clearScopedRegistryForTests();
registerScopedService(
LifecycleScope.Agent,
IAgentCommandService,
AgentCommandService,
ScopeActivation.OnDemand,
'command',
);
});
function hostWithProvider(
contributions: ReadonlyArray<{
readonly name: string;
readonly description?: string;
readonly run: (ctx: CommandRunContext) => void | Promise<void>;
}>,
) {
class CommandProvider extends Service {
constructor() {
super();
for (const contribution of contributions) {
this.provide(CommandContribution, contribution);
}
}
}
const host = createScopedTestHost([stubPair(IEcho, { _serviceBrand: undefined, value: 'echo!' })]);
const handle = host.app.instantiation.provide(ICommandProvider, new SyncDescriptor(CommandProvider));
host.app.accessor.get(ICommandProvider);
const agent = host.child(LifecycleScope.Agent, 'agent-1');
return { host, agent, handle, commands: agent.accessor.get(IAgentCommandService) };
}
it('lists contributed commands with source and runs them with container access', async () => {
const calls: string[] = [];
const { host, commands } = hostWithProvider([
{
name: 'alpha',
description: 'the alpha command',
run: (ctx) => {
calls.push(`alpha:${ctx.args}`);
},
},
{
name: 'beta',
run: (ctx) => {
calls.push(`beta:${ctx.get(IEcho).value}`);
},
},
{
name: 'gamma',
run: async (ctx) => {
await new Promise((resolve) => setTimeout(resolve, 0));
calls.push(`gamma:${ctx.args}`);
},
},
]);
expect(commands.list().map((command) => command.name)).toEqual(['alpha', 'beta', 'gamma']);
expect(commands.list()[0]).toMatchObject({
name: 'alpha',
description: 'the alpha command',
source: 'CommandProvider',
});
await commands.run('alpha', 'x y');
await commands.run('beta');
await commands.run('gamma', 'z');
expect(calls).toEqual(['alpha:x y', 'beta:echo!', 'gamma:z']);
host.dispose();
});
it('shadows an earlier record with a later one of the same name', async () => {
const calls: string[] = [];
const { host, commands } = hostWithProvider([
{
name: 'dup',
run: () => {
calls.push('first');
},
},
{
name: 'dup',
run: () => {
calls.push('second');
},
},
]);
expect(commands.list()).toHaveLength(1);
await commands.run('dup');
expect(calls).toEqual(['second']);
host.dispose();
});
it('fails unknown commands with a coded REQUEST_INVALID error', async () => {
const { host, commands } = hostWithProvider([]);
await expect(commands.run('nope')).rejects.toMatchObject({
code: ErrorCodes.REQUEST_INVALID,
});
host.dispose();
});
it('withdraws the commands when the provider unit dies', async () => {
const { host, handle, commands } = hostWithProvider([
{ name: 'alpha', run: () => {} },
]);
expect(commands.list()).toHaveLength(1);
handle.dispose();
await host.app.instantiation.cascade.whenIdle();
await new Promise((resolve) => setTimeout(resolve, 0));
expect(commands.list()).toHaveLength(0);
host.dispose();
});
});
|