import { describe, expect, it } from 'vitest'; import { inferArtifactMime, normalizeArtifactFiles, normalizeArtifactPath, } from './artifact-workspace'; describe('artifact workspace paths', () => { it('normalizes relative paths and infers browser MIME types', () => { expect(normalizeArtifactPath('./src/components/chart.js')).toBe('src/components/chart.js'); expect(inferArtifactMime('styles/app.css')).toBe('text/css; charset=utf-8'); expect(inferArtifactMime('assets/logo.svg')).toBe('image/svg+xml; charset=utf-8'); }); it.each([ '/index.html', '../index.html', 'src/../../index.html', 'src\\main.js', 'src//main.js', '.bonsai-workspace.json', ])('rejects paths that escape or collide with the workspace: %s', (path) => { expect(() => normalizeArtifactPath(path)).toThrow(); }); }); describe('artifact workspace files', () => { it('builds one bounded manifest with a real HTML entry file', () => { const deployment = normalizeArtifactFiles([ { path: 'index.html', content: '' }, { path: 'styles/app.css', content: 'body { color: green; }' }, { path: 'src/main.js', content: 'console.log("ready")' }, ]); expect(deployment.entryPath).toBe('index.html'); expect(deployment.files.map((file) => file.path)).toEqual([ 'index.html', 'styles/app.css', 'src/main.js', ]); expect(deployment.totalBytes).toBeGreaterThan(0); }); it('rejects duplicate, missing-entry, oversized, and non-HTML-entry deployments', () => { expect(() => normalizeArtifactFiles([ { path: 'index.html', content: 'a' }, { path: './index.html', content: 'b' }, ])).toThrow('duplicated'); expect(() => normalizeArtifactFiles([{ path: 'main.js', content: 'x' }])).toThrow('entry file is missing'); expect(() => normalizeArtifactFiles([ { path: 'index.html', content: 'x'.repeat(512 * 1024 + 1) }, ])).toThrow('512 KiB'); expect(() => normalizeArtifactFiles([{ path: 'main.js', content: 'x' }], 'main.js')).toThrow('must be HTML'); }); });