import { readFile } from 'node:fs/promises' import path from 'node:path' import { describe, expect, it } from 'vitest' import { applyRepositoryProfileServerMetadata, canonicalizeRepositoryProfile, digestRepositoryProfile, exportRepositoryProfileJson, exportRepositoryProfileYaml, importRepositoryProfile, parseRepositoryProfile, RepositoryProfileImportError, type RepositoryProfile, validateRepositoryProfile, } from './index' const repositoryRoot = path.resolve(import.meta.dirname, '../../..') const examplePath = path.join( repositoryRoot, 'examples/repository-profiles/example-profile.yaml', ) const exampleDigest = '041e20f67e299665e85e5f14800a4bbcfa5e6c42ccdd7b22d29206e2c3f6727e' async function example(): Promise { return importRepositoryProfile(await readFile(examplePath), 'yaml') } function mutableCopy(profile: RepositoryProfile): Record { return structuredClone(profile) as unknown as Record } describe('RepositoryProfile parsing and canonical digest', () => { it('validates the existing fixture with exact Python reference digest parity', async () => { const profile = await example() const result = validateRepositoryProfile(profile) expect(result).toMatchObject({ valid: true, contentDigest: exampleDigest }) expect(digestRepositoryProfile(profile)).toBe(exampleDigest) expect(canonicalizeRepositoryProfile(profile)).not.toContain( 'contentDigest', ) }) it('preserves array order in the canonical digest', async () => { const profile = await example() const reversed = structuredClone(profile) as RepositoryProfile ;( reversed.spec.stack as unknown as { languages: string[] } ).languages.reverse() ;( reversed.spec.stack as unknown as { testFrameworks: string[] } ).testFrameworks.reverse() expect(digestRepositoryProfile(reversed)).not.toBe(exampleDigest) }) it('round-trips deterministic LF JSON and YAML without data loss', async () => { const profile = await example() const json = exportRepositoryProfileJson(profile) const yaml = exportRepositoryProfileYaml(profile) expect(json.endsWith('\n')).toBe(true) expect(yaml.endsWith('\n')).toBe(true) expect(json).not.toContain('\r') expect(yaml).not.toContain('\r') expect(exportRepositoryProfileJson(profile)).toBe(json) expect(exportRepositoryProfileYaml(profile)).toBe(yaml) expect(importRepositoryProfile(json, 'json')).toEqual(profile) expect(importRepositoryProfile(yaml, 'yaml')).toEqual(profile) }) it('accepts BOM, CRLF, comments, and reordered YAML object keys', async () => { const source = await readFile(examplePath, 'utf8') const parsed = importRepositoryProfile( `\uFEFF# portable profile\r\n${source.replaceAll('\n', '\r\n')}`, 'yaml', ) expect(digestRepositoryProfile(parsed)).toBe(exampleDigest) const jsonObject = JSON.parse( exportRepositoryProfileJson(parsed), ) as Record const reordered = JSON.stringify({ spec: jsonObject.spec, metadata: jsonObject.metadata, kind: jsonObject.kind, apiVersion: jsonObject.apiVersion, }) expect(digestRepositoryProfile(importRepositoryProfile(reordered))).toBe( exampleDigest, ) }) it.each([ ['duplicate YAML keys', 'name: first\nname: second\n', 'yaml'], ['custom YAML tags', 'value: !unsafe payload\n', 'yaml'], ['YAML aliases', 'value: &shared payload\ncopy: *shared\n', 'yaml'], ['duplicate JSON keys', '{"name":"first","name":"second"}', 'json'], ] as const)('rejects %s', (_label, source, format) => { expect(() => parseRepositoryProfile(source, format)).toThrow( RepositoryProfileImportError, ) }) it('rejects invalid UTF-8 and oversized input before validation', () => { for (const [source, code] of [ [new Uint8Array([0xc3, 0x28]), 'profile_utf8_invalid'], ['x'.repeat(1_048_577), 'profile_too_large'], ] as const) { try { parseRepositoryProfile(source, 'yaml') throw new Error('Expected parsing to fail') } catch (error) { expect(error).toBeInstanceOf(RepositoryProfileImportError) expect((error as RepositoryProfileImportError).issues).toEqual([ expect.objectContaining({ code }), ]) } } }) }) describe('RepositoryProfile structural and semantic validation', () => { it('returns JSON-pointer structural issues with remediation', () => { const result = validateRepositoryProfile({ apiVersion: 'devrunbook.io/v1alpha1', kind: 'RepositoryProfile', metadata: { name: 'Incomplete' }, spec: {}, unexpected: true, }) expect(result.valid).toBe(false) if (result.valid) return expect(result.issues).toEqual( expect.arrayContaining([ expect.objectContaining({ path: '/metadata/revision', code: 'schema_required', remediation: expect.any(String), }), expect.objectContaining({ path: '/unexpected', code: 'schema_additionalProperties', }), ]), ) }) it('detects duplicate command ids and requires confirmation for safe suggestions', async () => { const candidate = mutableCopy(await example()) const spec = candidate.spec as Record const commands = spec.commands as Record[] commands.push({ ...commands[0], command: 'printf "$(still inert)"', confirmed: false, safeForAgentSuggestion: true, }) delete (candidate.metadata as Record).contentDigest const result = validateRepositoryProfile(candidate) expect(result.valid).toBe(false) if (result.valid) return expect(result.issues).toEqual( expect.arrayContaining([ expect.objectContaining({ code: 'command_id_duplicate' }), expect.objectContaining({ code: 'unsafe_unconfirmed_command' }), ]), ) expect(commands.at(-1)?.command).toBe('printf "$(still inert)"') }) it.each([' ', 'pnpm test\nrm -rf ignored', 'pnpm\u0000test'])( 'rejects ambiguous command text %j without interpreting it', async (command) => { const candidate = mutableCopy(await example()) const spec = candidate.spec as Record const commands = spec.commands as Record[] commands[0]!.command = command delete (candidate.metadata as Record).contentDigest const result = validateRepositoryProfile(candidate) expect(result.valid).toBe(false) if (result.valid) return expect(result.issues).toEqual( expect.arrayContaining([ expect.objectContaining({ path: '/spec/commands/0/command', code: 'command_text_invalid', }), ]), ) expect(commands[0]!.command).toBe(command) }, ) it.each(['../outside', '/absolute', 'C:/windows', 'nested\\windows', 'a//b'])( 'rejects non-normalized repository path %s', async (invalidPath) => { const candidate = mutableCopy(await example()) const spec = candidate.spec as Record const paths = spec.paths as Record paths.protected = [invalidPath] delete (candidate.metadata as Record).contentDigest const result = validateRepositoryProfile(candidate) expect(result.valid).toBe(false) if (result.valid) return expect(result.issues).toEqual( expect.arrayContaining([ expect.objectContaining({ path: '/spec/paths/protected/0', code: 'repository_path_invalid', }), ]), ) }, ) it('detects exact and ancestor protected/generated/excluded overlap', async () => { const candidate = mutableCopy(await example()) const spec = candidate.spec as Record const paths = spec.paths as Record paths.protected = ['data', 'application'] paths.generated = ['data/cache'] paths.excluded = ['data', 'app'] delete (candidate.metadata as Record).contentDigest const result = validateRepositoryProfile(candidate) expect(result.valid).toBe(false) if (result.valid) return expect( result.issues.filter(({ code }) => code === 'path_class_overlap'), ).toHaveLength(3) expect(result.issues.map(({ message }) => message).join('\n')).not.toMatch( /application.*app/u, ) }) it('verifies a supplied digest and reports mismatch at its exact path', async () => { const candidate = mutableCopy(await example()) ;(candidate.spec as Record).notes = 'Meaningful change' const result = validateRepositoryProfile(candidate) expect(result.valid).toBe(false) if (result.valid) return expect(result.issues).toEqual([ expect.objectContaining({ path: '/metadata/contentDigest', code: 'content_digest_mismatch', remediation: expect.any(String), }), ]) }) it('supports conservative intelligence and override extensions', async () => { const candidate = mutableCopy(await example()) const metadata = candidate.metadata as Record delete metadata.contentDigest const spec = candidate.spec as Record Object.assign(spec.stack as Record, { services: ['web'], runtimes: ['Node.js 24'], queues: ['PostgreSQL jobs'], ciSystems: ['GitHub Actions'], }) Object.assign(spec.paths as Record, { packageRoots: ['packages'], serviceRoots: ['apps/web'], dataRuntime: ['runtime'], ignored: ['tmp'], }) Object.assign(spec.policies as Record, { requiredValidationRoles: ['lint', 'typecheck'], branchConventions: ['feature/*'], environmentConstraints: ['Node.js 24'], }) spec.sourceFacts = [ { path: '/spec/commands/0/command', value: 'npm install', source: 'manifest', evidence: ['package.json'], confidence: 'high', }, ] spec.manualOverrides = [ { path: '/spec/commands/0/command', value: 'pnpm install --frozen-lockfile', observedValue: 'npm install', evidence: ['pnpm-lock.yaml'], confirmedAt: '2026-07-27T00:00:00Z', }, ] expect(validateRepositoryProfile(candidate)).toMatchObject({ valid: true }) }) it('applies server revision and digest without mutating client input', async () => { const profile = await example() const original = structuredClone(profile) const revision = applyRepositoryProfileServerMetadata(profile, 2) expect(profile).toEqual(original) expect(revision.metadata.revision).toBe(2) expect(revision.metadata.contentDigest).toBe( digestRepositoryProfile(revision), ) expect(revision.metadata.contentDigest).not.toBe(exampleDigest) }) })