Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion backend/src/docs/docRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,9 @@ function normalizeAlias(alias: string): string {

function normalizePathAlias(alias: string): string {
let path = alias.trim().replace(/\\/g, '/').replace(/^\/+/, '');
if (!path.endsWith('.md')) throw new Error(alias);
// LookupError (not a plain Error) so callers map an unresolvable path-shaped
// ref to 404 instead of surfacing it as an unexpected failure.
if (!path.endsWith('.md')) throw new LookupError(`Document reference not found: ${alias}`);
if (!path.startsWith('content/')) path = `content/${path}`;
return path;
}
Expand Down
12 changes: 12 additions & 0 deletions backend/tests/docs-content-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ describe('S3 authenticated knowledge content routes',()=>{
const search=await call(ev('GET','/search',{query:{q:'password'}}));assert.ok(search.body.results.length);
assert.equal((await call(ev('GET','/docs/resolve',{query:{ref:'ref.newsletter'}}))).status,200);
});
it('resolves unresolvable path-shaped refs to a clean 404 like unknown dotted refs',async()=>{
const pathShaped=await call(ev('GET','/docs/resolve',{query:{ref:'00-start-here/no-such-doc'}}));
assert.equal(pathShaped.status,404,JSON.stringify(pathShaped.body));
assert.ok(String(pathShaped.body.error).startsWith('Document reference not found'));
const dotted=await call(ev('GET','/docs/resolve',{query:{ref:'sop.finance.does-not-exist-anywhere'}}));
assert.equal(dotted.status,404);
assert.ok(String(dotted.body.error).startsWith('Document reference not found'));
assert.equal((await call(ev('GET','/docs/resolve',{query:{ref:'ref.newsletter'}}))).status,200);
const pathAlias=await call(ev('GET','/docs/resolve',{query:{ref:'accounts/sops/reset-password.md'}}));
assert.equal(pathAlias.status,200,JSON.stringify(pathAlias.body));
assert.equal(pathAlias.body.document.path,'content/accounts/sops/reset-password.md');
});
it('saves with revision, conflicts stale attempts, requires expected revision and records actor',async()=>{
const path='content/accounts/sops/reset-password.md';
assert.equal((await call(ev('PUT','/docs',{query:{path},body:{content:'draft'}}))).status,400);
Expand Down
17 changes: 17 additions & 0 deletions backend/tests/docs-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ describe('docRegistry - build + resolve (port of doc_registry.py)', () => {
assert.throws(() => resolveReference(registry, 'does.not.exist'), LookupError);
});

it('throws LookupError, not a plain Error, for an unknown path-shaped reference', () => {
writeDoc(contentRoot, 'a.md', '---\ntitle: A\n---\n\nbody\n');
const registry = buildRegistry(contentRoot);
try {
resolveReference(registry, '00-start-here/no-such-doc');
assert.fail('expected resolveReference to throw');
} catch (error) {
assert.ok(error instanceof LookupError, `expected LookupError, got ${String(error)}`);
assert.strictEqual((error as Error).message, 'Document reference not found: 00-start-here/no-such-doc');
}
// Same for a path-shaped ref that ends in .md but matches no document.
assert.throws(() => resolveReference(registry, '00-start-here/no-such-doc.md'), LookupError);
// Valid path references still resolve after the throw site.
const record = resolveReference(registry, 'a.md');
assert.strictEqual(record.path, 'content/a.md');
});

it('reports a dangling related_docs reference and still builds', () => {
writeDoc(
contentRoot,
Expand Down