|
| 1 | +import test from 'node:test'; |
| 2 | +import assert from 'node:assert/strict'; |
| 3 | +import fs from 'node:fs'; |
| 4 | +import os from 'node:os'; |
| 5 | +import path from 'node:path'; |
| 6 | +import { buildInventory } from '../lib/inventory.mjs'; |
| 7 | +import { compileContext } from '../lib/context.mjs'; |
| 8 | +import { databaseStats, indexRepository } from '../lib/indexer.mjs'; |
| 9 | +import { generate } from '../lib/pipeline.mjs'; |
| 10 | +import { projectPaths, readJson, writeJson } from '../lib/core.mjs'; |
| 11 | + |
| 12 | +function fixture() { |
| 13 | + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'docgen-v2-')); const p = projectPaths(root); |
| 14 | + fs.mkdirSync(path.join(root, 'src'), { recursive: true }); |
| 15 | + fs.mkdirSync(path.dirname(p.config), { recursive: true }); |
| 16 | + writeJson(p.project, { schemaVersion: '2.0', kitVersion: '2.0.0' }); |
| 17 | + writeJson(p.config, { schemaVersion: '2.0', ignore: { binary: { enabled: true, maxTextFileBytes: 1024 * 1024 } }, context: { maxTokens: { default: 4000, generate: 4000 } }, execution: { generationBatchSize: 2 }, audit: { llmEnabled: false } }); |
| 18 | + writeJson(p.state, { schemaVersion: '2.0', kitVersion: '2.0.0', stages: {}, pages: {} }); |
| 19 | + return root; |
| 20 | +} |
| 21 | + |
| 22 | +test('inventory excludes binary and docgenignore paths', () => { |
| 23 | + const root = fixture(); |
| 24 | + fs.writeFileSync(path.join(root, 'src', 'Resource.java'), '@Path("/quotes")\nclass Resource { @GET void get() {} }\n'); |
| 25 | + fs.writeFileSync(path.join(root, 'secret.txt'), 'ignore me'); |
| 26 | + fs.writeFileSync(path.join(root, 'image.png'), Buffer.from([0x89,0x50,0x4e,0x47,0,1])); |
| 27 | + fs.writeFileSync(path.join(root, '.docgenignore'), 'secret.txt\n'); |
| 28 | + const inv = buildInventory(root, { force: true }); |
| 29 | + assert(inv.files.some((x) => x.path === 'src/Resource.java')); |
| 30 | + assert(!inv.files.some((x) => x.path === 'secret.txt')); |
| 31 | + assert(inv.excluded.some((x) => x.path === 'image.png')); |
| 32 | +}); |
| 33 | + |
| 34 | +test('index is incremental and extracts repository facts', () => { |
| 35 | + const root = fixture(); |
| 36 | + fs.writeFileSync(path.join(root, 'src', 'Resource.java'), '@Path("/quotes")\nclass Resource { @POST void create() {} }\n'); |
| 37 | + const first = indexRepository(root, { force: true }); |
| 38 | + const second = indexRepository(root); |
| 39 | + const stats = databaseStats(root); |
| 40 | + assert.equal(first.changedFiles > 0, true); |
| 41 | + assert.equal(second.changedFiles, 0); |
| 42 | + assert.equal(second.unchangedFiles > 0, true); |
| 43 | + assert.equal(stats.facts > 0, true); |
| 44 | +}); |
| 45 | + |
| 46 | +test('context compiler stays within configured budget', () => { |
| 47 | + const root = fixture(); |
| 48 | + for (let i = 0; i < 60; i++) fs.writeFileSync(path.join(root, 'src', `Resource${i}.java`), `@Path("/items/${i}")\nclass Resource${i} { @GET void get${i}() {} }\n`); |
| 49 | + indexRepository(root, { force: true }); |
| 50 | + const { payload } = compileContext(root, { stage: 'generate', target: 'api', query: 'endpoint path resource', maxTokens: 1200 }); |
| 51 | + assert(payload.estimatedTokens <= 1200); |
| 52 | + assert(payload.facts.length > 0); |
| 53 | + assert(payload.omissions.facts > 0); |
| 54 | +}); |
| 55 | + |
| 56 | +test('reference catalog page is rendered without a provider call', async () => { |
| 57 | + const root = fixture(); const p = projectPaths(root); |
| 58 | + fs.writeFileSync(path.join(root, 'src', 'Resource.java'), '@Path("/quotes")\nclass Resource { @POST void create() {} }\n'); |
| 59 | + indexRepository(root, { force: true }); |
| 60 | + fs.mkdirSync(p.model, { recursive: true }); |
| 61 | + writeJson(path.join(p.model, 'catalogs.json'), { schemaVersion: '2.0', endpoints: [{ id: 'create-quote', name: 'Create quote', method: 'POST', path: '/quotes', evidence: [{ path: 'src/Resource.java', startLine: 1 }] }], messageHandlers: [], externalDependencies: [], dataStores: [], scheduledJobs: [] }); |
| 62 | + writeJson(p.plan, { schemaVersion: '2.0', pages: [{ id: 'endpoint-catalog', title: 'Endpoint Catalog', summary: 'HTTP API reference.', category: 'api', mode: 'reference', type: 'reference', order: 1, audience: ['engineer'], coverageTags: ['endpoint-catalog'], query: 'endpoints', requiredSections: [], relatedPages: [] }] }); |
| 63 | + const result = await generate(root); |
| 64 | + assert.equal(result.providerPages, 0); |
| 65 | + const output = fs.readFileSync(path.join(root, 'docs', 'api', 'endpoint-catalog.md'), 'utf8'); |
| 66 | + assert.match(output, /POST/); |
| 67 | + assert.match(output, /\/quotes/); |
| 68 | + assert.equal(fs.existsSync(path.join(p.telemetry, 'provider-runs.jsonl')), false); |
| 69 | +}); |
0 commit comments