attachment-store-unit.test.mjs
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf
mirror: GitHub Phase A durable MCP OAuth (#270)
Human
minor
⚠ breaking
10 days ago
| 1 | /** |
| 2 | * Tier 1 — UNIT: Attachment store helpers, id derivation, and projections. |
| 3 | * |
| 4 | * @see docs/ATTACHMENT-STORE-CONTRACT-2F-b.md §9 |
| 5 | */ |
| 6 | import { describe, it, beforeEach, afterEach } from 'node:test'; |
| 7 | import assert from 'node:assert/strict'; |
| 8 | import fs from 'node:fs'; |
| 9 | import path from 'node:path'; |
| 10 | import { fileURLToPath } from 'node:url'; |
| 11 | import { |
| 12 | ATTACHMENT_ID_RE, |
| 13 | NOTE_REF_RE, |
| 14 | deriveAttachmentId, |
| 15 | normalizeUrlForId, |
| 16 | noteRefFromPath, |
| 17 | attachmentSummaryForClient, |
| 18 | attachmentForClient, |
| 19 | urlHostOnly, |
| 20 | MAX_LINKED_NOTES, |
| 21 | } from '../lib/attachments/attachment-store.mjs'; |
| 22 | import { getVaultAttachmentPolicies } from '../lib/attachments/attachment-store-file.mjs'; |
| 23 | import { buildAttachmentFixtureVault, FIXTURE_MIST_ID } from './fixtures/attachment-fixture.mjs'; |
| 24 | import { listAttachments, getAttachment } from '../lib/attachments/attachment-store.mjs'; |
| 25 | |
| 26 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
| 27 | const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-attachment-unit'); |
| 28 | |
| 29 | describe('Attachment store — id regexes', () => { |
| 30 | it('ATTACHMENT_ID_RE accepts canonical ids and rejects malformed', () => { |
| 31 | const id = deriveAttachmentId('file', 'file:media/x.png'); |
| 32 | assert.ok(ATTACHMENT_ID_RE.test(id)); |
| 33 | assert.ok(!ATTACHMENT_ID_RE.test('att_file_SHORT')); |
| 34 | assert.ok(ATTACHMENT_ID_RE.test('att_link_' + 'a'.repeat(32))); |
| 35 | assert.ok(!ATTACHMENT_ID_RE.test('att_LINK_' + 'a'.repeat(32))); |
| 36 | assert.ok(!ATTACHMENT_ID_RE.test('media_file_' + 'a'.repeat(32))); |
| 37 | }); |
| 38 | |
| 39 | it('NOTE_REF_RE accepts canonical refs and rejects traversal', () => { |
| 40 | assert.ok(NOTE_REF_RE.test('note:inbox/foo.md')); |
| 41 | assert.equal(noteRefFromPath('../escape.md'), ''); |
| 42 | assert.equal(noteRefFromPath('/absolute.md'), 'note:absolute.md'); |
| 43 | }); |
| 44 | |
| 45 | it('id derivation is deterministic and source-distinct', () => { |
| 46 | const a = deriveAttachmentId('file', 'file:media/a.png'); |
| 47 | const b = deriveAttachmentId('file', 'file:media/b.png'); |
| 48 | const c = deriveAttachmentId('mist', `mist:${FIXTURE_MIST_ID}`); |
| 49 | assert.equal(a, deriveAttachmentId('file', 'file:media/a.png')); |
| 50 | assert.notEqual(a, b); |
| 51 | assert.notEqual(a, c); |
| 52 | }); |
| 53 | }); |
| 54 | |
| 55 | describe('Attachment store — client projections', () => { |
| 56 | const sample = { |
| 57 | schema: 'knowtation.attachment/v0', |
| 58 | attachment_id: deriveAttachmentId('file', 'file:media/x.png'), |
| 59 | source: 'vault_file', |
| 60 | storage_kind: 'vault_blob', |
| 61 | mime_class: 'image', |
| 62 | mime_type: 'image/png', |
| 63 | scope: 'personal', |
| 64 | display_label: 'x', |
| 65 | byte_size: 100, |
| 66 | linked_note_refs: ['note:inbox/x.md'], |
| 67 | agent_visible: false, |
| 68 | created: '2026-07-02T00:00:00Z', |
| 69 | updated: '2026-07-02T00:00:00Z', |
| 70 | truncated: false, |
| 71 | }; |
| 72 | |
| 73 | it('attachmentSummaryForClient drops sensitive list fields', () => { |
| 74 | const summary = attachmentSummaryForClient(sample); |
| 75 | assert.equal('byte_size' in summary, false); |
| 76 | assert.equal('linked_note_refs' in summary, false); |
| 77 | assert.equal('agent_visible' in summary, false); |
| 78 | assert.equal('mime_type' in summary, false); |
| 79 | assert.equal(summary.attachment_id, sample.attachment_id); |
| 80 | }); |
| 81 | |
| 82 | it('attachmentForClient caps linked_note_refs and sets truncated', () => { |
| 83 | const refs = Array.from({ length: MAX_LINKED_NOTES + 3 }, (_, i) => `note:ref${i}.md`); |
| 84 | const client = attachmentForClient({ ...sample, linked_note_refs: refs }); |
| 85 | assert.equal(client.linked_note_refs.length, MAX_LINKED_NOTES); |
| 86 | assert.equal(client.truncated, true); |
| 87 | }); |
| 88 | |
| 89 | it('display_label for embedded_url uses host only', () => { |
| 90 | assert.equal(urlHostOnly('https://user:[email protected]/x.png?sig=abc'), 'cdn.example.com'); |
| 91 | }); |
| 92 | }); |
| 93 | |
| 94 | describe('Attachment store — overlay defaults', () => { |
| 95 | const dataDir = path.join(tmpRoot, 'overlay'); |
| 96 | |
| 97 | beforeEach(() => { |
| 98 | fs.rmSync(tmpRoot, { recursive: true, force: true }); |
| 99 | fs.mkdirSync(dataDir, { recursive: true }); |
| 100 | }); |
| 101 | |
| 102 | afterEach(() => { |
| 103 | fs.rmSync(tmpRoot, { recursive: true, force: true }); |
| 104 | }); |
| 105 | |
| 106 | it('missing overlay ⇒ agent_visible:false default', () => { |
| 107 | const fx = buildAttachmentFixtureVault(tmpRoot); |
| 108 | const policies = getVaultAttachmentPolicies(fx.dataDir, fx.vaultId); |
| 109 | assert.deepEqual(policies, {}); |
| 110 | |
| 111 | const list = listAttachments(fx.dataDir, fx.vaultPath, fx.vaultId, { |
| 112 | filterScopes: new Set(['personal', 'project', 'org']), |
| 113 | effectiveScope: 'org', |
| 114 | }); |
| 115 | const got = getAttachment(fx.dataDir, fx.vaultPath, fx.vaultId, fx.mistId, { |
| 116 | visibleScopes: new Set(['personal', 'project', 'org']), |
| 117 | }); |
| 118 | assert.ok(got); |
| 119 | assert.equal(got.agent_visible, false); |
| 120 | assert.ok(list.attachments.length >= 1); |
| 121 | }); |
| 122 | }); |
File History
1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf
mirror: GitHub Phase A durable MCP OAuth (#270)
Human
minor
⚠
10 days ago