70 lines
3.3 KiB
TypeScript
70 lines
3.3 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { mkdtemp, readdir, rm } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import type { AgentBrowserModule } from '../../electron/agent-browser';
|
|
import { createCodingComposition } from '../../electron/api/coding-composition';
|
|
import { CodingAttachmentStore } from '../../electron/coding-projects/attachment-store';
|
|
import { createMemoryCodingProjectStorage } from '../../electron/coding-projects/project-store';
|
|
import type { PiConversationRuntimeOptions } from '../../electron/coding-runtime/pi/runtime';
|
|
|
|
const captured = vi.hoisted(() => ({ options: undefined as PiConversationRuntimeOptions | undefined }));
|
|
vi.mock('../../electron/coding-runtime/pi/runtime', async (importOriginal) => {
|
|
const original = await importOriginal<typeof import('../../electron/coding-runtime/pi/runtime')>();
|
|
return { ...original, PiConversationRuntime: class extends original.PiConversationRuntime {
|
|
constructor(options: PiConversationRuntimeOptions) {
|
|
super(options);
|
|
captured.options = options;
|
|
}
|
|
} };
|
|
});
|
|
|
|
const roots: string[] = [];
|
|
afterEach(async () => {
|
|
captured.options = undefined;
|
|
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
});
|
|
|
|
describe('coding composition images', () => {
|
|
it('connects live and persisted Pi images to readable attachments and reuses their bytes after restart', async () => {
|
|
const userDataDir = await mkdtemp(path.join(tmpdir(), 'makelore-composition-images-'));
|
|
roots.push(userDataDir);
|
|
const composition = createCodingComposition({
|
|
storage: createMemoryCodingProjectStorage(),
|
|
browser: { close: vi.fn(async () => undefined) } as unknown as AgentBrowserModule,
|
|
paths: {
|
|
executablePath: process.execPath,
|
|
cliPath: path.join(userDataDir, 'unused-cli.js'),
|
|
serverPath: path.join(userDataDir, 'unused-server.mjs'),
|
|
userDataDir,
|
|
bundledSkillsDir: path.resolve('resources/coding-skills'),
|
|
},
|
|
});
|
|
try {
|
|
const bytes = Buffer.from('image bytes');
|
|
const uploaded = await composition.attachments.put(bytes, 'image/png');
|
|
const images = await captured.options!.resolveImages!([uploaded]);
|
|
expect(images).toEqual([{ type: 'image', data: bytes.toString('base64'), mimeType: 'image/png' }]);
|
|
expect(captured.options!.projectImage).toBeTypeOf('function');
|
|
for (const source of ['live', 'session'] as const) {
|
|
const projected = await captured.options!.projectImage!({
|
|
conversationId: 'conversation-image', source, data: images[0].data, mime: images[0].mimeType,
|
|
});
|
|
expect(projected.attachmentId).toBe(uploaded.attachmentId);
|
|
expect((await composition.attachments.read(projected.attachmentId)).data).toEqual(bytes);
|
|
}
|
|
const root = path.join(userDataDir, 'coding-runtime', 'attachments');
|
|
const reopened = new CodingAttachmentStore(root);
|
|
expect(await reopened.put(bytes, 'image/png')).toEqual(uploaded);
|
|
expect((await readdir(root)).sort()).toEqual([
|
|
`${uploaded.attachmentId}.bin`, `${uploaded.attachmentId}.json`,
|
|
]);
|
|
expect((await reopened.put(bytes, 'image/jpeg')).attachmentId).not.toBe(uploaded.attachmentId);
|
|
} finally {
|
|
await composition.shutdown();
|
|
}
|
|
});
|
|
});
|