Files
nexu-io-open-design/apps/daemon/tests/artifacts/create.test.ts
Marc Chan d5d9a761ca refactor(daemon): organize source modules (#4454)
* docs(daemon): add agent guidance

* refactor(daemon): organize source modules

* chore(guard): update daemon design system import

* refactor(daemon): shorten domain module filenames

* fix(daemon): correct pitch-deck manifest path after test move

The pitch-deck manifest test moved one directory deeper into
tests/design-systems/ but kept the old '../../..' repoRoot, resolving
to apps/ instead of the repo root and failing to find
plugins/_official/examples/html-ppt-pitch-deck/open-design.json.

* fix(daemon): retarget media model verifier

Generated-By: looper 0.9.10 (runner=fixer, agent=codex)

* docs(daemon): refresh AGENTS legacy paths after module reorg

* test(e2e): allow manual edit P0 CI scaling

Generated-By: looper 0.9.10 (runner=fixer, agent=codex)

* test(daemon): fix frontmatter import path

Generated-By: looper 0.9.10 (runner=fixer, agent=opencode)

* fix(web): avoid replaying legacy terminal runs

Generated-By: looper 0.9.10 (runner=fixer, agent=opencode)

* fix(web): preserve legacy terminal replay recovery

Generated-By: looper 0.9.10 (runner=fixer, agent=opencode)

* fix(daemon): correct live-artifacts test data root

* fix(daemon): cover moved route registrars in context contract

Generated-By: looper 0.9.10 (runner=fixer, agent=opencode)

* fix(daemon): restore moved runtime artifact imports

Generated-By: looper 0.9.10 (runner=fixer, agent=opencode)
2026-06-20 16:08:20 +00:00

164 lines
5.1 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { buildCreateArtifactRequestBody, createProjectArtifactFile } from '../../src/artifacts/create.js';
import { listFiles, writeProjectFile } from '../../src/projects.js';
describe('normal artifact create helper', () => {
it('builds the non-overwrite HTTP request body used by MCP and CLI', () => {
expect(buildCreateArtifactRequestBody({
name: 'index.html',
content: '<!doctype html>',
})).toEqual({
name: 'index.html',
content: '<!doctype html>',
encoding: 'utf8',
artifact: true,
overwrite: false,
});
});
it('infers an artifact manifest and writes with overwrite disabled', async () => {
const writeProjectFile = vi.fn(async () => ({ name: 'deck.html' }));
await createProjectArtifactFile({
projectsRoot: '/tmp/projects',
projectId: 'project-1',
input: {
name: 'deck.html',
content: '<!doctype html><h1>Deck</h1>',
},
writeProjectFile,
});
expect(writeProjectFile).toHaveBeenCalledWith(
'/tmp/projects',
'project-1',
'deck.html',
Buffer.from('<!doctype html><h1>Deck</h1>', 'utf8'),
{
overwrite: false,
artifactManifest: expect.objectContaining({
kind: 'deck',
renderer: 'deck-html',
entry: 'deck.html',
}),
},
undefined,
);
});
it('passes existing target errors through to callers', async () => {
const err = new Error('file already exists') as Error & { code?: string };
err.code = 'EEXIST';
const writeProjectFile = vi.fn(async () => {
throw err;
});
await expect(createProjectArtifactFile({
projectsRoot: '/tmp/projects',
projectId: 'project-1',
input: {
name: 'index.html',
content: '<!doctype html>',
},
writeProjectFile,
})).rejects.toMatchObject({ code: 'EEXIST' });
});
it('rejects artifact creation when no manifest can be inferred', async () => {
const writeProjectFile = vi.fn(async () => ({ name: 'component.jsx' }));
await expect(createProjectArtifactFile({
projectsRoot: '/tmp/projects',
projectId: 'project-1',
input: {
name: 'component.jsx',
content: 'export function Component() { return <div />; }',
},
writeProjectFile,
})).rejects.toMatchObject({
code: 'ARTIFACT_MANIFEST_REQUIRED',
message: expect.stringContaining('artifactManifest is required'),
});
expect(writeProjectFile).not.toHaveBeenCalled();
});
it('treats null artifactManifest as missing when inference is unavailable', async () => {
const writeProjectFile = vi.fn(async () => ({ name: 'component.jsx' }));
await expect(createProjectArtifactFile({
projectsRoot: '/tmp/projects',
projectId: 'project-1',
input: {
name: 'component.jsx',
content: 'export function Component() { return <div />; }',
artifactManifest: null,
},
writeProjectFile,
})).rejects.toMatchObject({ code: 'ARTIFACT_MANIFEST_REQUIRED' });
expect(writeProjectFile).not.toHaveBeenCalled();
});
it('rejects invalid explicit manifests before writing the entry file', async () => {
const writeProjectFile = vi.fn(async () => ({ name: 'component.jsx' }));
await expect(createProjectArtifactFile({
projectsRoot: '/tmp/projects',
projectId: 'project-1',
input: {
name: 'component.jsx',
content: 'export function Component() { return <div />; }',
artifactManifest: {
kind: 'react-component',
exports: ['jsx'],
},
},
writeProjectFile,
})).rejects.toMatchObject({
code: 'ARTIFACT_MANIFEST_INVALID',
message: expect.stringContaining('artifactManifest.renderer must be a string'),
});
expect(writeProjectFile).not.toHaveBeenCalled();
});
it('lists explicit manifests for nested artifact entry files', async () => {
const projectsRoot = await mkdtemp(path.join(tmpdir(), 'od-artifact-create-'));
try {
await createProjectArtifactFile({
projectsRoot,
projectId: 'project-1',
input: {
name: 'dry-run/deck.html',
content: '<!doctype html><h1>Deck</h1>',
artifactManifest: {
kind: 'deck',
renderer: 'deck-html',
exports: ['html', 'pdf'],
title: 'Nested Deck',
},
},
writeProjectFile: writeProjectFile as any,
});
const files = await listFiles(projectsRoot, 'project-1');
expect(files).toEqual([
expect.objectContaining({
name: 'dry-run/deck.html',
artifactKind: 'deck',
artifactManifest: expect.objectContaining({
kind: 'deck',
renderer: 'deck-html',
title: 'Nested Deck',
entry: 'dry-run/deck.html',
}),
}),
]);
} finally {
await rm(projectsRoot, { recursive: true, force: true });
}
});
});