Skip to content

Commit f415369

Browse files
authored
feat: Add ping vat command (#509)
Closes #490 Changed in this PR: - Added a `kernel.pingVat` command since it was removed from #508 - Used the new command to the control panel - Added tests and covered some more lines
1 parent 9501597 commit f415369

File tree

13 files changed

+632
-77
lines changed

13 files changed

+632
-77
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { describe, it, expect } from 'vitest';
2+
3+
import { clearStateHandler, clearStateSpec } from './clear-state.ts';
4+
import {
5+
collectGarbageHandler,
6+
collectGarbageSpec,
7+
} from './collect-garbage.ts';
8+
import {
9+
executeDBQueryHandler,
10+
executeDBQuerySpec,
11+
} from './execute-db-query.ts';
12+
import { getStatusHandler, getStatusSpec } from './get-status.ts';
13+
import { handlers, methodSpecs } from './index.ts';
14+
import { launchVatHandler, launchVatSpec } from './launch-vat.ts';
15+
import { pingVatHandler, pingVatSpec } from './ping-vat.ts';
16+
import { queueMessageHandler, queueMessageSpec } from './queue-message.ts';
17+
import { reloadConfigHandler, reloadConfigSpec } from './reload-config.ts';
18+
import { restartVatHandler, restartVatSpec } from './restart-vat.ts';
19+
import {
20+
terminateAllVatsHandler,
21+
terminateAllVatsSpec,
22+
} from './terminate-all-vats.ts';
23+
import { terminateVatHandler, terminateVatSpec } from './terminate-vat.ts';
24+
import {
25+
updateClusterConfigHandler,
26+
updateClusterConfigSpec,
27+
} from './update-cluster-config.ts';
28+
29+
describe('handlers/index', () => {
30+
it('should export all handler functions', () => {
31+
expect(handlers).toStrictEqual({
32+
clearState: clearStateHandler,
33+
executeDBQuery: executeDBQueryHandler,
34+
getStatus: getStatusHandler,
35+
launchVat: launchVatHandler,
36+
pingVat: pingVatHandler,
37+
reload: reloadConfigHandler,
38+
restartVat: restartVatHandler,
39+
queueMessage: queueMessageHandler,
40+
terminateAllVats: terminateAllVatsHandler,
41+
collectGarbage: collectGarbageHandler,
42+
terminateVat: terminateVatHandler,
43+
updateClusterConfig: updateClusterConfigHandler,
44+
});
45+
});
46+
47+
it('should have all handlers with the correct method property', () => {
48+
const handlerEntries = Object.entries(handlers);
49+
50+
handlerEntries.forEach(([key, handler]) => {
51+
expect(handler).toHaveProperty('method');
52+
expect(handler.method).toBe(key);
53+
});
54+
});
55+
56+
it('should export all method specs', () => {
57+
expect(methodSpecs).toStrictEqual({
58+
clearState: clearStateSpec,
59+
executeDBQuery: executeDBQuerySpec,
60+
getStatus: getStatusSpec,
61+
launchVat: launchVatSpec,
62+
pingVat: pingVatSpec,
63+
reload: reloadConfigSpec,
64+
restartVat: restartVatSpec,
65+
queueMessage: queueMessageSpec,
66+
terminateAllVats: terminateAllVatsSpec,
67+
collectGarbage: collectGarbageSpec,
68+
terminateVat: terminateVatSpec,
69+
updateClusterConfig: updateClusterConfigSpec,
70+
});
71+
});
72+
73+
it('should have the same keys as handlers', () => {
74+
expect(Object.keys(methodSpecs).sort()).toStrictEqual(
75+
Object.keys(handlers).sort(),
76+
);
77+
});
78+
});

packages/extension/src/kernel-integration/handlers/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from './execute-db-query.ts';
1010
import { getStatusHandler, getStatusSpec } from './get-status.ts';
1111
import { launchVatHandler, launchVatSpec } from './launch-vat.ts';
12+
import { pingVatHandler, pingVatSpec } from './ping-vat.ts';
1213
import { queueMessageHandler, queueMessageSpec } from './queue-message.ts';
1314
import { reloadConfigHandler, reloadConfigSpec } from './reload-config.ts';
1415
import { restartVatHandler, restartVatSpec } from './restart-vat.ts';
@@ -30,6 +31,7 @@ export const handlers = {
3031
executeDBQuery: executeDBQueryHandler,
3132
getStatus: getStatusHandler,
3233
launchVat: launchVatHandler,
34+
pingVat: pingVatHandler,
3335
reload: reloadConfigHandler,
3436
restartVat: restartVatHandler,
3537
queueMessage: queueMessageHandler,
@@ -47,6 +49,7 @@ export const methodSpecs = {
4749
executeDBQuery: executeDBQuerySpec,
4850
getStatus: getStatusSpec,
4951
launchVat: launchVatSpec,
52+
pingVat: pingVatSpec,
5053
reload: reloadConfigSpec,
5154
restartVat: restartVatSpec,
5255
queueMessage: queueMessageSpec,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { Kernel } from '@metamask/ocap-kernel';
2+
import { describe, it, expect, vi, beforeEach } from 'vitest';
3+
4+
import { pingVatHandler } from './ping-vat.ts';
5+
6+
describe('pingVatHandler', () => {
7+
let mockKernel: Kernel;
8+
9+
beforeEach(() => {
10+
mockKernel = {
11+
pingVat: vi.fn().mockResolvedValue('pong'),
12+
} as unknown as Kernel;
13+
});
14+
15+
it('should ping vat and return result', async () => {
16+
const params = { id: 'v0' } as const;
17+
const result = await pingVatHandler.implementation(
18+
{ kernel: mockKernel },
19+
params,
20+
);
21+
22+
expect(mockKernel.pingVat).toHaveBeenCalledWith(params.id);
23+
expect(result).toBe('pong');
24+
});
25+
26+
it('should propagate errors from kernel.pingVat', async () => {
27+
const error = new Error('Ping failed');
28+
vi.mocked(mockKernel.pingVat).mockRejectedValueOnce(error);
29+
30+
const params = { id: 'v0' } as const;
31+
await expect(
32+
pingVatHandler.implementation({ kernel: mockKernel }, params),
33+
).rejects.toThrow(error);
34+
});
35+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { Handler, MethodSpec } from '@metamask/kernel-rpc-methods';
2+
import type { Kernel, VatId } from '@metamask/ocap-kernel';
3+
import { VatIdStruct } from '@metamask/ocap-kernel';
4+
import { vatMethodSpecs } from '@metamask/ocap-kernel/rpc';
5+
import type { PingVatResult } from '@metamask/ocap-kernel/rpc';
6+
import { object } from '@metamask/superstruct';
7+
8+
export type PingVatHooks = {
9+
kernel: Kernel;
10+
};
11+
12+
export const pingVatSpec: MethodSpec<'pingVat', { id: VatId }, string> = {
13+
method: 'pingVat',
14+
params: object({ id: VatIdStruct }),
15+
result: vatMethodSpecs.ping.result,
16+
};
17+
18+
export const pingVatHandler: Handler<
19+
'pingVat',
20+
{ id: VatId },
21+
Promise<PingVatResult>,
22+
PingVatHooks
23+
> = {
24+
...pingVatSpec,
25+
hooks: { kernel: true },
26+
implementation: async ({ kernel }, params): Promise<PingVatResult> => {
27+
return kernel.pingVat(params.id);
28+
},
29+
};

packages/extension/src/kernel-integration/middleware/panel-message.test.ts

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { JsonRpcEngine } from '@metamask/json-rpc-engine';
22
import type { KernelDatabase } from '@metamask/kernel-store';
3-
import type { Kernel } from '@metamask/ocap-kernel';
3+
import type { ClusterConfig, Kernel } from '@metamask/ocap-kernel';
44
import type { JsonRpcRequest } from '@metamask/utils';
55
import { describe, it, expect, vi, beforeEach } from 'vitest';
66

@@ -13,16 +13,51 @@ const { mockAssertHasMethod, mockExecute } = vi.hoisted(() => ({
1313

1414
vi.mock('@metamask/kernel-rpc-methods', () => ({
1515
RpcService: class MockRpcService {
16+
readonly #dependencies: Record<string, unknown>;
17+
18+
constructor(
19+
_handlers: Record<string, unknown>,
20+
dependencies: Record<string, unknown>,
21+
) {
22+
this.#dependencies = dependencies;
23+
}
24+
1625
assertHasMethod = mockAssertHasMethod;
1726

18-
execute = mockExecute;
27+
execute = (method: string, params: unknown) => {
28+
// For updateClusterConfig test, call the actual implementation
29+
if (method === 'updateClusterConfig' && params) {
30+
const updateFn = this.#dependencies.updateClusterConfig as (
31+
config: unknown,
32+
) => void;
33+
updateFn(params);
34+
return Promise.resolve();
35+
}
36+
37+
// For executeDBQuery test, call the actual implementation
38+
if (
39+
method === 'executeDBQuery' &&
40+
typeof params === 'object' &&
41+
params !== null
42+
) {
43+
const { sql } = params as { sql: string };
44+
const executeQueryFn = this.#dependencies.executeDBQuery as (
45+
sql: string,
46+
) => Promise<unknown>;
47+
return executeQueryFn(sql);
48+
}
49+
50+
return mockExecute(method, params);
51+
};
1952
},
2053
}));
2154

2255
vi.mock('../handlers/index.ts', () => ({
2356
handlers: {
2457
testMethod1: { method: 'testMethod1' },
2558
testMethod2: { method: 'testMethod2' },
59+
updateClusterConfig: { method: 'updateClusterConfig' },
60+
executeDBQuery: { method: 'executeDBQuery' },
2661
},
2762
}));
2863

@@ -33,8 +68,12 @@ describe('createPanelMessageMiddleware', () => {
3368

3469
beforeEach(() => {
3570
// Set up mocks
36-
mockKernel = {} as Kernel;
37-
mockKernelDatabase = {} as KernelDatabase;
71+
mockKernel = {
72+
clusterConfig: {} as ClusterConfig,
73+
} as Kernel;
74+
mockKernelDatabase = {
75+
executeQuery: vi.fn(),
76+
} as unknown as KernelDatabase;
3877

3978
// Create a new JSON-RPC engine with our middleware
4079
engine = new JsonRpcEngine();
@@ -209,4 +248,63 @@ describe('createPanelMessageMiddleware', () => {
209248
}),
210249
});
211250
});
251+
252+
it('should update kernel.clusterConfig when updateClusterConfig is called', async () => {
253+
// Create a test cluster config that matches the expected structure
254+
const testConfig = {
255+
bootstrap: 'test-bootstrap',
256+
vats: {
257+
test: {
258+
bundleSpec: 'test-bundle',
259+
},
260+
},
261+
forceReset: true,
262+
} as ClusterConfig;
263+
264+
// Create a request to update cluster config
265+
const request = {
266+
id: 7,
267+
jsonrpc: '2.0',
268+
method: 'updateClusterConfig',
269+
params: testConfig,
270+
} as JsonRpcRequest;
271+
272+
// Process the request
273+
await engine.handle(request);
274+
275+
// Verify that kernel.clusterConfig was updated with the provided config
276+
expect(mockKernel.clusterConfig).toStrictEqual(testConfig);
277+
});
278+
279+
it('should call kernelDatabase.executeQuery when executeDBQuery is called', async () => {
280+
// Set up mock database response
281+
const mockQueryResult = [{ id: '1', name: 'test' }];
282+
vi.mocked(mockKernelDatabase.executeQuery).mockResolvedValueOnce(
283+
mockQueryResult,
284+
);
285+
286+
// Test SQL query
287+
const testSql = 'SELECT * FROM test_table';
288+
289+
// Create a request to execute DB query
290+
const request = {
291+
id: 8,
292+
jsonrpc: '2.0',
293+
method: 'executeDBQuery',
294+
params: { sql: testSql },
295+
} as JsonRpcRequest;
296+
297+
// Process the request
298+
const response = await engine.handle(request);
299+
300+
// Verify that kernelDatabase.executeQuery was called with the correct SQL
301+
expect(mockKernelDatabase.executeQuery).toHaveBeenCalledWith(testSql);
302+
303+
// Verify the response contains the query result
304+
expect(response).toStrictEqual({
305+
id: 8,
306+
jsonrpc: '2.0',
307+
result: mockQueryResult,
308+
});
309+
});
212310
});

0 commit comments

Comments
 (0)