import { access, readFile, rm, stat } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { DEMO_ADAPTER, DEMO_CONNECTION_ID, DEMO_REPLAY_FILE, defaultDemoProjectDir, ensureDemoProject, ensureSeededDemoProject, } from '../src/demo-assets.js'; const packagedDemoSource = 'packaged-orbit-demo'; function packagedDemoAssetPath(relativePath: string): string { return fileURLToPath(new URL(`../assets/demo/orbit/${relativePath}`, import.meta.url)); } async function readPackagedJson(relativePath: string): Promise { return JSON.parse(await readFile(packagedDemoAssetPath(relativePath), 'utf-8')) as T; } function makeIo() { let stderr = ''; return { stdout: { isTTY: true, write() {}, }, stderr: { write(chunk: string) { stderr += chunk; }, }, stderrText: () => stderr, }; } describe('demo assets', () => { const projectDir = join(tmpdir(), `ktx-demo-assets-${process.pid}`); afterEach(async () => { vi.unstubAllEnvs(); await rm(projectDir, { recursive: true, force: true }); }); it('resolves the default demo root under the OS temp directory', () => { const dir = defaultDemoProjectDir(); expect(dir.startsWith(join(tmpdir(), 'ktx-demo-'))).toBe(true); expect(dir).toMatch(/ktx-demo-[a-f0-9]{8}$/); }); it('exports the packaged Orbit demo identity', () => { expect(DEMO_CONNECTION_ID).toBe('orbit_demo'); expect(DEMO_ADAPTER).toBe('live-database'); expect(DEMO_REPLAY_FILE).toBe('replay.memory-flow.v1.json'); }); it('ships the seeded demo bundle required by the May 6 PRD', async () => { const manifest = await readPackagedJson<{ demoAssetSchemaVersion: number; mode: string; source: string; sources: { warehouse: { tables: number; rowCounts: Record }; dbt: { models: number; sourceTables: number }; bi: { explores: number; dashboards: number }; notion: { pages: number }; }; name: string; displayName: string; generated: { semanticLayer: { path: string; sourceCount: number }; knowledge: { pageCount: number }; links: { linkCount: number }; }; }>('manifest.json'); expect(manifest).toMatchObject({ demoAssetSchemaVersion: 2, name: 'orbit', displayName: 'Orbit Demo', mode: 'seeded', source: packagedDemoSource, }); expect(manifest.sources.warehouse.tables).toBeGreaterThanOrEqual(5); expect(manifest.sources.warehouse.tables).toBeLessThanOrEqual(10); expect(Object.keys(manifest.sources.warehouse.rowCounts).sort()).toEqual([ 'accounts', 'arr_movements', 'contracts', 'invoices', 'plans', 'purchase_requests', 'support_tickets', 'users', ]); expect(manifest.sources.dbt.models).toBeGreaterThanOrEqual(3); expect(manifest.sources.dbt.models).toBeLessThanOrEqual(6); expect(manifest.sources.bi.explores).toBeGreaterThanOrEqual(2); expect(manifest.sources.bi.dashboards).toBeGreaterThanOrEqual(2); expect(manifest.sources.notion.pages).toBeGreaterThanOrEqual(5); expect(manifest.generated.semanticLayer.sourceCount).toBeGreaterThanOrEqual(40); expect(manifest.generated.knowledge.pageCount).toBeGreaterThanOrEqual(20); expect(manifest.generated.links.linkCount).toBeGreaterThanOrEqual(10); const dbStat = await stat(packagedDemoAssetPath('demo.db')); expect(dbStat.size).toBeGreaterThan(0); expect(dbStat.size).toBeLessThan(10 * 1024 * 1024); await expect(access(packagedDemoAssetPath('semantic-layer/dbt-main/mart_arr_daily.yaml'))).resolves.toBeUndefined(); await expect(access(packagedDemoAssetPath('semantic-layer/postgres-warehouse/mart_account_activity.yaml'))).resolves.toBeUndefined(); await expect(access(packagedDemoAssetPath('wiki/global/orbit-company-overview.md'))).resolves.toBeUndefined(); await expect(access(packagedDemoAssetPath('links/provenance.json'))).resolves.toBeUndefined(); await expect(access(packagedDemoAssetPath('reports/seeded-demo-report.json'))).resolves.toBeUndefined(); }); it('initializes a flat demo project without writing literal credentials', async () => { const result = await ensureDemoProject({ projectDir, force: false }); expect(result.projectDir).toBe(projectDir); await expect(access(join(projectDir, 'demo.db'))).resolves.toBeUndefined(); await expect(access(join(projectDir, 'state.sqlite'))).resolves.toBeUndefined(); await expect(access(join(projectDir, 'reports'))).resolves.toBeUndefined(); await expect(access(join(projectDir, 'semantic-layer'))).resolves.toBeUndefined(); await expect(access(join(projectDir, 'wiki'))).resolves.toBeUndefined(); await expect(access(join(projectDir, 'replays', 'replay.memory-flow.v1.json'))).resolves.toBeUndefined(); await expect(access(join(projectDir, 'raw-sources'))).resolves.toBeUndefined(); await expect(access(join(projectDir, '_schema'))).rejects.toMatchObject({ code: 'ENOENT' }); const config = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(config).toContain('backend: anthropic'); expect(config).toContain('api_key: env:ANTHROPIC_API_KEY'); expect(config).not.toContain('sk-ant-'); }); it('rejects an existing demo project unless force is set', async () => { await ensureDemoProject({ projectDir, force: false }); await expect(ensureDemoProject({ projectDir, force: false })).rejects.toThrow('Demo project already exists'); await expect(ensureDemoProject({ projectDir, force: true })).resolves.toMatchObject({ projectDir }); }); it('emits debug telemetry when the demo connection is created', async () => { vi.stubEnv('KTX_TELEMETRY_DEBUG', '1'); vi.stubEnv('CI', ''); const io = makeIo(); await ensureDemoProject({ projectDir, force: false, io, cliVersion: '0.2.0' }); expect(io.stderrText()).toContain('"event":"connection_added"'); expect(io.stderrText()).toContain('"driver":"sqlite"'); expect(io.stderrText()).toContain('"isDemoConnection":true'); expect(io.stderrText()).not.toContain(projectDir); }); it('copies the seeded project assets used by the setup wizard tour', async () => { await ensureSeededDemoProject({ projectDir, force: false }); await expect(access(join(projectDir, 'semantic-layer', 'dbt-main', 'mart_arr_daily.yaml'))).resolves.toBeUndefined(); await expect(access(join(projectDir, 'wiki', 'global', 'orbit-company-overview.md'))).resolves.toBeUndefined(); await expect(access(join(projectDir, 'links', 'provenance.json'))).resolves.toBeUndefined(); await expect(access(join(projectDir, 'reports', 'seeded-demo-report.json'))).resolves.toBeUndefined(); }); });