Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0fdec87
Add basic devtools panel and kernel manager
sirtimid Oct 29, 2024
8dee5ed
connecting devtools offscreen and worker
sirtimid Oct 31, 2024
9eedd14
fix type
sirtimid Oct 31, 2024
d0e4f66
clean
sirtimid Oct 31, 2024
7e6b20d
testing streams
sirtimid Nov 4, 2024
efb1c36
fix chrome duplex
sirtimid Nov 4, 2024
5993900
Move to popup from devtools
sirtimid Nov 5, 2024
7b23f70
fix UI
sirtimid Nov 5, 2024
da10065
cleanup
sirtimid Nov 5, 2024
2550c38
clean
sirtimid Nov 6, 2024
b887966
revert
sirtimid Nov 6, 2024
452b432
refactor and add send message
sirtimid Nov 6, 2024
62526c3
fix import
sirtimid Nov 6, 2024
251a3d7
apply multiplexer
sirtimid Nov 11, 2024
71d8cf8
cleaning
sirtimid Nov 11, 2024
12ca7ae
assert params on send message
sirtimid Nov 11, 2024
93166f5
rmeove logs
sirtimid Nov 11, 2024
584a6d8
clean
sirtimid Nov 11, 2024
22768eb
rewrite response
sirtimid Nov 11, 2024
94c05f1
Cleanup popup stream on disconnect
sirtimid Nov 11, 2024
36fa34e
split setup Popup Stream
sirtimid Nov 11, 2024
2b1ccc9
move setupStatusPolling
sirtimid Nov 11, 2024
4a16901
add tests
sirtimid Nov 14, 2024
7cb9dff
add more tests
sirtimid Nov 14, 2024
b5fa6f5
cleanup
sirtimid Nov 14, 2024
9d7e425
replace dirname
sirtimid Nov 20, 2024
6fd4dd7
fix styles and thresholds
sirtimid Nov 20, 2024
180eedd
better structure and fix tests
sirtimid Nov 20, 2024
c96223b
fix esm dirname
sirtimid Nov 20, 2024
ec5523a
rename dropdown element
sirtimid Nov 20, 2024
63879a9
add stream input validator
sirtimid Nov 20, 2024
19dc687
fix thresholds
sirtimid Nov 20, 2024
64a7e91
gst
sirtimid Nov 20, 2024
02c0fb3
trying to fix test
sirtimid Nov 20, 2024
93cec35
reset text on command
sirtimid Nov 20, 2024
9ac9b80
use drain
sirtimid Nov 20, 2024
ba1753a
update thresholds
sirtimid Nov 20, 2024
8ff4662
fix message an stream validation
sirtimid Nov 20, 2024
9391efe
update thresholds
sirtimid Nov 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@endo/patterns": "^1.4.4",
"@endo/promise-kit": "^1.1.6",
"@metamask/snaps-utils": "^8.3.0",
"@metamask/superstruct": "^3.1.0",
"@metamask/utils": "^9.3.0",
"@ocap/errors": "workspace:^",
"@ocap/kernel": "workspace:^",
Expand Down
370 changes: 370 additions & 0 deletions packages/extension/src/kernel/handle-panel-message.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,370 @@
import '../../../test-utils/src/env/mock-endo.ts';
import { define, literal, object } from '@metamask/superstruct';
import type { Kernel, KernelCommand, VatId } from '@ocap/kernel';
import { describe, it, expect, vi, beforeEach } from 'vitest';

import type { KernelControlCommand } from './messages.js';

// Mock logger
vi.mock('@ocap/utils', () => ({
makeLogger: () => ({
error: vi.fn(),
}),
}));

// Mock kernel validation functions
vi.mock('@ocap/kernel', () => ({
isKernelCommand: () => true,
isVatId: () => true,
VatIdStruct: define<VatId>('VatId', () => true),
KernelSendMessageStruct: object({
id: literal('v0'),
payload: object({
method: literal('ping'),
params: literal(null),
}),
}),
}));

describe('handlePanelMessage', () => {
let mockKernel: Kernel;

beforeEach(() => {
vi.resetModules();

// Create mock kernel
mockKernel = {
launchVat: vi.fn().mockResolvedValue(undefined),
restartVat: vi.fn().mockResolvedValue(undefined),
terminateVat: vi.fn().mockResolvedValue(undefined),
terminateAllVats: vi.fn().mockResolvedValue(undefined),
getVatIds: vi.fn().mockReturnValue(['v0', 'v1']),
sendMessage: vi.fn((id: VatId, _message: KernelCommand) => {
if (id === 'v0') {
return 'success';
}
return { error: 'Unknown vat ID' };
}),
kvGet: vi.fn((key: string) => {
if (key === 'testKey') {
return 'value';
}
return undefined;
}),
kvSet: vi.fn(),
} as unknown as Kernel;
});

describe('vat management commands', () => {
it('should handle launchVat command', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const message: KernelControlCommand = {
method: 'launchVat',
params: { id: 'v0' },
};

const response = await handlePanelMessage(mockKernel, message);

expect(mockKernel.launchVat).toHaveBeenCalledWith({ id: 'v0' });
expect(response).toStrictEqual({
method: 'launchVat',
params: null,
});
});

it('should handle invalid vat ID', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const kernel = await import('@ocap/kernel');
const isVatIdSpy = vi.spyOn(kernel, 'isVatId');
isVatIdSpy.mockReturnValue(false);

const message: KernelControlCommand = {
method: 'launchVat',
params: { id: 'invalid' as VatId },
};

const response = await handlePanelMessage(mockKernel, message);

expect(response).toStrictEqual({
method: 'launchVat',
params: { error: 'Valid vat id required' },
});
});

it('should handle restartVat command', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const message: KernelControlCommand = {
method: 'restartVat',
params: { id: 'v0' },
};

const response = await handlePanelMessage(mockKernel, message);

expect(mockKernel.restartVat).toHaveBeenCalledWith('v0');
expect(response).toStrictEqual({
method: 'restartVat',
params: null,
});
});

it('should handle invalid vat ID for restartVat command', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');

const kernel = await import('@ocap/kernel');
const isVatIdSpy = vi.spyOn(kernel, 'isVatId');
isVatIdSpy.mockReturnValue(false);

const message: KernelControlCommand = {
method: 'restartVat',
params: { id: 'invalid' as VatId },
};

const response = await handlePanelMessage(mockKernel, message);

expect(response).toStrictEqual({
method: 'restartVat',
params: { error: 'Valid vat id required' },
});
});

it('should handle terminateVat command', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const message: KernelControlCommand = {
method: 'terminateVat',
params: { id: 'v0' },
};

const response = await handlePanelMessage(mockKernel, message);

expect(mockKernel.terminateVat).toHaveBeenCalledWith('v0');
expect(response).toStrictEqual({
method: 'terminateVat',
params: null,
});
});

it('should handle invalid vat ID for terminateVat command', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const kernel = await import('@ocap/kernel');
const isVatIdSpy = vi.spyOn(kernel, 'isVatId');
isVatIdSpy.mockReturnValue(false);

const message: KernelControlCommand = {
method: 'terminateVat',
params: { id: 'invalid' as VatId },
};

const response = await handlePanelMessage(mockKernel, message);

expect(response).toStrictEqual({
method: 'terminateVat',
params: { error: 'Valid vat id required' },
});
});

it('should handle terminateAllVats command', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const message: KernelControlCommand = {
method: 'terminateAllVats',
params: null,
};

const response = await handlePanelMessage(mockKernel, message);

expect(mockKernel.terminateAllVats).toHaveBeenCalled();
expect(response).toStrictEqual({
method: 'terminateAllVats',
params: null,
});
});
});

describe('status command', () => {
it('should handle getStatus command', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const message: KernelControlCommand = {
method: 'getStatus',
params: null,
};

const response = await handlePanelMessage(mockKernel, message);

expect(mockKernel.getVatIds).toHaveBeenCalled();
expect(response).toStrictEqual({
method: 'getStatus',
params: {
isRunning: true,
activeVats: ['v0', 'v1'],
},
});
});
});

describe('sendMessage command', () => {
it('should handle kvGet command', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const message: KernelControlCommand = {
method: 'sendMessage',
params: {
payload: { method: 'kvGet', params: 'testKey' },
},
};

const response = await handlePanelMessage(mockKernel, message);

expect(mockKernel.kvGet).toHaveBeenCalledWith('testKey');
expect(response).toStrictEqual({
method: 'sendMessage',
params: { result: 'value' },
});
});

it('should handle kvGet command when key not found', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');

const message: KernelControlCommand = {
method: 'sendMessage',
params: {
payload: { method: 'kvGet', params: 'nonexistentKey' },
},
};

const response = await handlePanelMessage(mockKernel, message);

expect(mockKernel.kvGet).toHaveBeenCalledWith('nonexistentKey');
expect(response).toStrictEqual({
method: 'sendMessage',
params: { error: 'Key not found' },
});
});

it('should handle kvSet command', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const message: KernelControlCommand = {
method: 'sendMessage',
params: {
payload: {
method: 'kvSet',
params: { key: 'testKey', value: 'testValue' },
},
},
};

const response = await handlePanelMessage(mockKernel, message);

expect(mockKernel.kvSet).toHaveBeenCalledWith('testKey', 'testValue');
expect(response).toStrictEqual({
method: 'sendMessage',
params: { key: 'testKey', value: 'testValue' },
});
});

it('should handle vat messages', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const message: KernelControlCommand = {
method: 'sendMessage',
params: {
id: 'v0',
payload: { method: 'ping', params: null },
},
};

const response = await handlePanelMessage(mockKernel, message);

expect(mockKernel.sendMessage).toHaveBeenCalledWith('v0', {
method: 'ping',
params: null,
});
expect(response).toStrictEqual({
method: 'sendMessage',
params: { result: 'success' },
});
});

it('should handle invalid command payload', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const kernel = await import('@ocap/kernel');
const kernelSpy = vi.spyOn(kernel, 'isKernelCommand');
kernelSpy.mockReturnValue(false);

const message: KernelControlCommand = {
method: 'sendMessage',
params: {
payload: { invalid: 'command' },
},
};

const response = await handlePanelMessage(mockKernel, message);

expect(response).toStrictEqual({
method: 'sendMessage',
params: { error: 'Invalid command payload' },
});
});

it('should handle missing vat ID', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const kernel = await import('@ocap/kernel');
const isVatIdSpy = vi.spyOn(kernel, 'isVatId');
isVatIdSpy.mockReturnValue(false);

const message: KernelControlCommand = {
method: 'sendMessage',
params: {
payload: { method: 'ping', params: null },
},
};

const response = await handlePanelMessage(mockKernel, message);

expect(response).toStrictEqual({
method: 'sendMessage',
params: { error: 'Vat ID required for this command' },
});
});
});

describe('error handling', () => {
it('should handle unknown method', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const message: KernelControlCommand = {
method: 'unknownMethod',
params: null,
} as unknown as KernelControlCommand;

const response = await handlePanelMessage(mockKernel, message);

expect(response).toStrictEqual({
method: 'unknownMethod',
params: { error: 'Unknown method' },
});
});

it('should handle kernel errors', async () => {
const { handlePanelMessage } = await import('./handle-panel-message');
const error = new Error('Kernel error');
vi.mocked(mockKernel.launchVat).mockRejectedValue(error);

const message: KernelControlCommand = {
method: 'launchVat',
params: { id: 'v0' },
};

const response = await handlePanelMessage(mockKernel, message);

expect(response).toStrictEqual({
method: 'launchVat',
params: { error: 'Kernel error' },
});

vi.mocked(mockKernel.launchVat).mockRejectedValue('error');

const response2 = await handlePanelMessage(mockKernel, message);

expect(response2).toStrictEqual({
method: 'launchVat',
params: { error: 'error' },
});
});
});
});
Loading