// C2-15 (row 267): a hybrid universe end to end. A business-file-shaped mapping (the fixture mapping plus a // `cross` hybrid over harbor and second) on a repo in her layout's shape, read through the C2-05a queries, // once for each exclude form of design 1.4. Bodies are made up; the repos are built under the scratch area. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { excluded } from '../src/scope.mjs'; import { find, get, search, universeInfo, QUERY_REPLY_CAP } from '../src/query.mjs'; import { mapping, MARKER } from './helpers.mjs'; import { scratchRepo, note } from './query-fixture.mjs'; const FILES = { 'Book One/Characters/Mara Vell.md': note('character', 'Mara Vell', `Grey eyes. ${MARKER}`), 'Book One/Research/Ferry Logs.md': note('research', 'Ferry Logs', `Timetable. ${MARKER}`), 'Book One/Playground/What If.md': note('summary', 'What If', `${MARKER} [[Mara Vell]]`), 'Second World/Characters/Ilse.md': note('character', 'Ilse', `${MARKER} Ilse.`), 'Second World/Places/Gate.md': note('place', 'Gate', `${MARKER} Gate.`), 'Crossover/Chapters/01.md': `# Meeting\n${MARKER} [[Mara Vell]] meets [[Ilse]] at [[Gate]].\nShe reads [[Ferry Logs]].\n`, 'Crossover/Places/Bridge.md': note('place', 'Bridge', `${MARKER} Bridge.`), }; // The two forms of one rule: the type form hides second's Characters folder; the path form hides harbor's // Research folder. const FORMS = { type: { exclude: 'second/Characters', hidden: 'Second World/Characters/Ilse.md' }, path: { exclude: 'Book One/Research', hidden: 'Book One/Research/Ferry Logs.md' }, }; const NOTES = 'Ilse crosses over after book one.'; const withCross = exclude => mapping(e => { e.universes.cross = { kind: 'hybrid', name: 'Crossover', sources: ['harbor', 'second'], books: ['Crossover'], constraints: [{ exclude }, { pin: { entity: 'Book One/Characters/Mara Vell.md', field: 'name', value: 'Mara Vell' } }], notes: NOTES }; return e; }); const src = (ref, map) => ({ repo: ref.repo, commit: ref.commit, mapping: map }); // The source universe of a fixture path: the hybrid's own book, harbor's book, or second's. const owner = path => path.startsWith('Crossover/') ? 'cross' : path.startsWith('Book One/') ? 'harbor' : 'second'; const ALL_CANON = ['Book One/Characters/Mara Vell.md', 'Book One/Research/Ferry Logs.md', 'Crossover/Chapters/01.md', 'Crossover/Places/Bridge.md', 'Second World/Characters/Ilse.md', 'Second World/Places/Gate.md']; test('C2-15 excluded(): the type form covers the type folder in each source book, the path form its own subtree', () => { const type = withCross('second/Characters'), path = withCross('Book One/Research'); assert.equal(excluded(type, 'cross', 'Second World/Characters/Ilse.md'), true); assert.equal(excluded(type, 'cross', 'Second World/Characters/Deep/Ilse.md'), true); assert.equal(excluded(type, 'cross', 'Second World/Places/Gate.md'), false); assert.equal(excluded(type, 'cross', 'Book One/Characters/Mara Vell.md'), false); assert.equal(excluded(type, 'cross', 'second/Characters/Ilse.md'), false); assert.equal(excluded(path, 'cross', 'Book One/Research/Ferry Logs.md'), true); assert.equal(excluded(path, 'cross', 'Book One/Research'), true); assert.equal(excluded(path, 'cross', 'Book One/Research Notes/Other.md'), false); assert.equal(excluded(path, 'cross', 'Second World/Characters/Ilse.md'), false); // Not a hybrid, or no such universe: nothing is excluded. assert.equal(excluded(path, 'harbor', 'Book One/Research/Ferry Logs.md'), false); assert.equal(excluded(path, 'nowhere', 'Book One/Research/Ferry Logs.md'), false); }); for (const [form, { exclude, hidden }] of Object.entries(FORMS)) { test(`C2-15 ${form} form: find and search in the hybrid return its own files and the sources' canon, never a source playground or the excluded path`, async t => { const s = src(scratchRepo(t, FILES), withCross(exclude)); const found = (await find(s, { universe: 'cross', query: '' })).results; assert.deepEqual(found.map(r => r.path), ALL_CANON.filter(p => p !== hidden)); for (const r of found) assert.equal(r.universe, owner(r.path)); assert.deepEqual((await find(s, { universe: 'cross', query: '', scope: 'playground' })).results, []); const searched = (await search(s, { universe: 'cross', text: MARKER })).results; const hits = searched.map(r => r.path); assert.ok(!hits.includes(hidden) && !hits.includes('Book One/Playground/What If.md')); assert.ok(hits.includes('Crossover/Places/Bridge.md')); const canonHit = form === 'type' ? 'Book One/Research/Ferry Logs.md' : 'Second World/Characters/Ilse.md'; assert.ok(hits.includes(canonHit)); // Each search hit names its source universe, as find does: the hybrid's own file, a source's canon, every other hit. const universeOf = path => searched.find(r => r.path === path).universe; assert.equal(universeOf('Crossover/Places/Bridge.md'), 'cross'); assert.equal(universeOf(canonHit), form === 'type' ? 'harbor' : 'second'); for (const r of searched) assert.equal(r.universe, owner(r.path), r.path); assert.equal(await get(s, { universe: 'cross', path: hidden }), null); }); test(`C2-15 ${form} form: get keeps the hybrid's links into its sources and drops the excluded target`, async t => { const s = src(scratchRepo(t, FILES), withCross(exclude)); const chapter = await get(s, { universe: 'cross', path: 'Crossover/Chapters/01.md' }); const out = chapter.links_out.map(l => l.path); assert.ok(out.includes('Book One/Characters/Mara Vell.md')); assert.ok(out.includes('Second World/Places/Gate.md')); assert.ok(!out.includes(hidden)); }); } test('C2-15 a search line too long to fit alone, found through the hybrid, still names its source universe', async t => { const huge = 'y'.repeat(QUERY_REPLY_CAP + 1000); const files = { ...FILES, 'Book One/Places/Wall.md': note('place', 'Wall', `${huge} ${MARKER}`) }; const s = src(scratchRepo(t, files), withCross('second/Characters')); const reply = await search(s, { universe: 'cross', text: 'yyyyyyyy' }); assert.deepEqual(reply.results, [{ path: 'Book One/Places/Wall.md', line: 5, excerpt: null, excerpt_too_long: true, scope: 'canon', universe: 'harbor' }]); }); test('C2-15 a source universe never sees a hybrid file', async t => { const s = src(scratchRepo(t, FILES), withCross('Book One/Research')); for (const universe of ['harbor', 'second']) { assert.ok(!(await find(s, { universe, query: '' })).results.some(r => r.path.startsWith('Crossover/')), universe); assert.ok(!(await search(s, { universe, text: MARKER })).results.some(r => r.path.startsWith('Crossover/')), universe); assert.equal(await get(s, { universe, path: 'Crossover/Chapters/01.md' }), null); } const mara = await get(s, { universe: 'harbor', path: 'Book One/Characters/Mara Vell.md' }); assert.ok(!mara.links_in.some(l => l.path.startsWith('Crossover/'))); }); test('C2-15 universeInfo returns the hybrid\'s sources, constraints and notes', async t => { const s = src(scratchRepo(t, FILES), withCross('second/Characters')); const info = await universeInfo(s, { universe: 'cross' }); assert.equal(info.kind, 'hybrid'); assert.deepEqual(info.sources, ['harbor', 'second']); assert.deepEqual(info.constraints, [{ exclude: 'second/Characters' }, { pin: { entity: 'Book One/Characters/Mara Vell.md', field: 'name', value: 'Mara Vell' } }]); assert.equal(info.notes, NOTES); });