feat(coding): add model tools and device packages
This commit is contained in:
1 parent
841273b433
commit
664b8823a0
60 files changed
+3847
-2068
No files matched your search
@@ -0,0 +1,87 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { EventEmitter } from 'node:events';
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import type { HostApiContext } from '../../electron/api/context';
|
||||
import { handleDevicePackageRoutes } from '../../electron/api/routes/device-packages';
|
||||
import { DevicePackageError } from '../../electron/coding-packages/device-package-manager';
|
||||
|
||||
function request(method: string, body?: unknown): IncomingMessage {
|
||||
const req = new EventEmitter();
|
||||
const raw = body === undefined ? undefined : JSON.stringify(body);
|
||||
Object.assign(req, {
|
||||
method,
|
||||
headers: raw === undefined ? {} : { 'content-length': String(Buffer.byteLength(raw)) },
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
if (raw !== undefined) yield Buffer.from(raw);
|
||||
},
|
||||
});
|
||||
return req as IncomingMessage;
|
||||
}
|
||||
|
||||
function response() {
|
||||
const chunks: string[] = [];
|
||||
const res = new EventEmitter();
|
||||
Object.assign(res, {
|
||||
statusCode: 0,
|
||||
setHeader: vi.fn(),
|
||||
end: vi.fn((chunk?: string) => { if (chunk) chunks.push(chunk); }),
|
||||
});
|
||||
return {
|
||||
res: res as unknown as ServerResponse,
|
||||
get status() { return (res as { statusCode: number }).statusCode; },
|
||||
json: () => JSON.parse(chunks.join('')) as Record<string, unknown>,
|
||||
};
|
||||
}
|
||||
|
||||
async function invoke(ctx: HostApiContext, method: string, target: string, body?: unknown) {
|
||||
const output = response();
|
||||
const handled = await handleDevicePackageRoutes(
|
||||
request(method, body), output.res, new URL(`http://localhost${target}`), ctx,
|
||||
);
|
||||
return { handled, status: output.status, payload: output.json() };
|
||||
}
|
||||
|
||||
describe('device package routes', () => {
|
||||
it('lists, enables, and removes device packages without exposing an install route', async () => {
|
||||
const empty = { schemaVersion: 1, generation: 0, packages: [] };
|
||||
const list = vi.fn().mockResolvedValue(empty);
|
||||
const setEnabled = vi.fn().mockResolvedValue({ ...empty, generation: 1 });
|
||||
const uninstall = vi.fn().mockResolvedValue({ ...empty, generation: 2 });
|
||||
const ctx = { codingProducts: { devicePackages: { list, setEnabled, uninstall } } } as unknown as HostApiContext;
|
||||
|
||||
expect(await invoke(ctx, 'GET', '/api/coding/device-packages')).toMatchObject({
|
||||
handled: true, status: 200, payload: empty,
|
||||
});
|
||||
expect(await invoke(ctx, 'PATCH', '/api/coding/device-packages/pi-tools', { enabled: false }))
|
||||
.toMatchObject({ handled: true, status: 200, payload: { generation: 1 } });
|
||||
expect(await invoke(ctx, 'DELETE', '/api/coding/device-packages/pi-tools', {}))
|
||||
.toMatchObject({ handled: true, status: 200, payload: { generation: 2 } });
|
||||
expect(setEnabled).toHaveBeenCalledWith('pi-tools', false);
|
||||
expect(uninstall).toHaveBeenCalledWith('pi-tools');
|
||||
|
||||
const unsupported = response();
|
||||
expect(await handleDevicePackageRoutes(
|
||||
request('POST', { source: 'npm:pi-web-search' }), unsupported.res,
|
||||
new URL('http://localhost/api/coding/device-packages'), ctx,
|
||||
)).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects forged fields and returns only bounded manager errors', async () => {
|
||||
const setEnabled = vi.fn();
|
||||
const uninstall = vi.fn().mockRejectedValue(new DevicePackageError(
|
||||
'local_package_in_use', 'Package is still active',
|
||||
));
|
||||
const ctx = { codingProducts: { devicePackages: {
|
||||
list: vi.fn(), setEnabled, uninstall,
|
||||
} } } as unknown as HostApiContext;
|
||||
|
||||
expect(await invoke(ctx, 'PATCH', '/api/coding/device-packages/pi-tools', {
|
||||
enabled: false, source: 'C:\\forged',
|
||||
})).toMatchObject({ status: 400, payload: { code: 'local_package_request_invalid' } });
|
||||
expect(setEnabled).not.toHaveBeenCalled();
|
||||
expect(await invoke(ctx, 'DELETE', '/api/coding/device-packages/pi-tools', {}))
|
||||
.toMatchObject({ status: 409, payload: { code: 'local_package_in_use' } });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user