/** * Workspace-dep checker tests. * * These are unit tests — npm metadata is stubbed via the injected * fetcher. The orchestrator's integration test (in index.test.ts) * exercises the same code path against fixture modules. */ import { describe, expect, test } from 'bun:test'; import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import type { NpmMetadata } from './types'; import { checkWorkspaceDeps } from './workspace-deps'; function makeFixture(packageJson: Record): string { const dir = mkdtempSync(join(tmpdir(), 'celilo-workspace-deps-')); writeFileSync(join(dir, 'package.json'), JSON.stringify(packageJson)); return dir; } function stubNpm(versions: Record) { return async (name: string): Promise => { const v = versions[name]; return v ? { name, latestVersion: v } : null; }; } describe('checkWorkspaceDeps', () => { test('ok when no @celilo/* deps are declared', async () => { const dir = makeFixture({ dependencies: { yaml: '^2.0.0' } }); try { const checks = await checkWorkspaceDeps(dir, stubNpm({})); expect(checks).toHaveLength(1); expect(checks[0].status).toBe('ok'); expect(checks[0].message).toContain('no @celilo/* dependencies'); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('ok when no package.json exists', async () => { const dir = mkdtempSync(join(tmpdir(), 'celilo-workspace-deps-')); try { const checks = await checkWorkspaceDeps(dir, stubNpm({})); expect(checks[0].status).toBe('ok'); expect(checks[0].message).toContain('no package.json'); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('ok when installed @celilo dep matches npm latest', async () => { const dir = makeFixture({ dependencies: { '@celilo/capabilities': '^0.2.0' }, }); try { const checks = await checkWorkspaceDeps(dir, stubNpm({ '@celilo/capabilities': '0.2.0' })); expect(checks).toHaveLength(1); expect(checks[0].status).toBe('ok'); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('warn for within-major drift (advisory)', async () => { const dir = makeFixture({ dependencies: { '@celilo/capabilities': '^0.2.0' }, }); try { const checks = await checkWorkspaceDeps(dir, stubNpm({ '@celilo/capabilities': '0.5.1' })); expect(checks[0].status).toBe('warn'); expect(checks[0].suggestedValue).toBe('^0.5.1'); expect(checks[0].migrationUrl).toBeUndefined(); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('fail across a major bump, with migration URL', async () => { const dir = makeFixture({ dependencies: { '@celilo/event-bus': '^0.1.4' }, }); try { const checks = await checkWorkspaceDeps(dir, stubNpm({ '@celilo/event-bus': '1.0.0' })); expect(checks[0].status).toBe('fail'); expect(checks[0].suggestedValue).toBe('^1.0.0'); expect(checks[0].migrationUrl).toBe('https://celilo.computer/docs/migrations/event-bus-1'); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('warn when npm is unreachable for every dep', async () => { const dir = makeFixture({ dependencies: { '@celilo/capabilities': '^0.2.0' }, }); try { // Stub returns null for everything — simulates offline. const checks = await checkWorkspaceDeps(dir, async () => null); expect(checks).toHaveLength(1); expect(checks[0].status).toBe('warn'); expect(checks[0].message).toContain("couldn't reach npm"); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('partial-network: some deps resolved, others skipped', async () => { const dir = makeFixture({ dependencies: { '@celilo/capabilities': '^0.2.0', '@celilo/event-bus': '^0.1.0', }, }); try { const checks = await checkWorkspaceDeps(dir, stubNpm({ '@celilo/capabilities': '0.2.0' })); // One ok for capabilities, one synthetic warn for the missing one. expect(checks.some((c) => c.status === 'ok')).toBe(true); expect(checks.some((c) => c.status === 'warn' && c.message.includes('skipped'))).toBe(true); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('reads devDependencies as well as dependencies', async () => { const dir = makeFixture({ devDependencies: { '@celilo/cli-display': '^0.1.0' }, }); try { const checks = await checkWorkspaceDeps(dir, stubNpm({ '@celilo/cli-display': '0.1.0' })); expect(checks[0].status).toBe('ok'); } finally { rmSync(dir, { recursive: true, force: true }); } }); });