Files
makelore/tests/unit/coding-composition-images.test.ts

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();
}
});
});