Files
DevRunbook-Public/packages/repository-intel/src/index.test.ts
T
DevRunbook release export cfd2804e27
Managed validation / full (push) Successful in 3m18s
Publish DevRunbook source
2026-09-03 04:09:17 +02:00

320 lines
11 KiB
TypeScript

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<RepositoryProfile> {
return importRepositoryProfile(await readFile(examplePath), 'yaml')
}
function mutableCopy(profile: RepositoryProfile): Record<string, unknown> {
return structuredClone(profile) as unknown as Record<string, unknown>
}
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<string, unknown>
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<string, unknown>
const commands = spec.commands as Record<string, unknown>[]
commands.push({
...commands[0],
command: 'printf "$(still inert)"',
confirmed: false,
safeForAgentSuggestion: true,
})
delete (candidate.metadata as Record<string, unknown>).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<string, unknown>
const commands = spec.commands as Record<string, unknown>[]
commands[0]!.command = command
delete (candidate.metadata as Record<string, unknown>).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<string, unknown>
const paths = spec.paths as Record<string, unknown>
paths.protected = [invalidPath]
delete (candidate.metadata as Record<string, unknown>).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<string, unknown>
const paths = spec.paths as Record<string, unknown>
paths.protected = ['data', 'application']
paths.generated = ['data/cache']
paths.excluded = ['data', 'app']
delete (candidate.metadata as Record<string, unknown>).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<string, unknown>).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<string, unknown>
delete metadata.contentDigest
const spec = candidate.spec as Record<string, unknown>
Object.assign(spec.stack as Record<string, unknown>, {
services: ['web'],
runtimes: ['Node.js 24'],
queues: ['PostgreSQL jobs'],
ciSystems: ['GitHub Actions'],
})
Object.assign(spec.paths as Record<string, unknown>, {
packageRoots: ['packages'],
serviceRoots: ['apps/web'],
dataRuntime: ['runtime'],
ignored: ['tmp'],
})
Object.assign(spec.policies as Record<string, unknown>, {
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)
})
})