muse-thin-bridge.test.mjs
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠ breaking
2 days ago
| 1 | /** |
| 2 | * Muse thin bridge (Option C): config, normalize, resolve, proxy path allowlist. |
| 3 | */ |
| 4 | import { describe, it } from 'node:test'; |
| 5 | import assert from 'node:assert/strict'; |
| 6 | import { |
| 7 | parseMuseConfigFromEnv, |
| 8 | normalizeExternalRef, |
| 9 | resolveExternalRefForApprove, |
| 10 | isAllowedMuseProxyPath, |
| 11 | parseMuseProxyPathPrefixes, |
| 12 | proposalIdFromApprovePath, |
| 13 | MUSE_LINEAGE_REF_PATH, |
| 14 | } from '../lib/muse-thin-bridge.mjs'; |
| 15 | |
| 16 | describe('muse-thin-bridge', () => { |
| 17 | it('parseMuseConfigFromEnv returns null when MUSE_URL unset', () => { |
| 18 | const c = parseMuseConfigFromEnv({}); |
| 19 | assert.equal(c, null); |
| 20 | }); |
| 21 | |
| 22 | it('parseMuseConfigFromEnv parses base URL and optional key', () => { |
| 23 | const c = parseMuseConfigFromEnv({ |
| 24 | MUSE_URL: 'https://muse.example.com/', |
| 25 | MUSE_API_KEY: 'secret', |
| 26 | MUSE_LINEAGE_TIMEOUT_MS: '3000', |
| 27 | }); |
| 28 | assert.ok(c); |
| 29 | assert.strictEqual(c.baseUrl, 'https://muse.example.com'); |
| 30 | assert.strictEqual(c.apiKey, 'secret'); |
| 31 | assert.strictEqual(c.lineageTimeoutMs, 3000); |
| 32 | }); |
| 33 | |
| 34 | it('parseMuseConfigFromEnv rejects non-http(s) URL', () => { |
| 35 | assert.equal(parseMuseConfigFromEnv({ MUSE_URL: 'ftp://x' }), null); |
| 36 | assert.equal(parseMuseConfigFromEnv({ MUSE_URL: 'not-a-url' }), null); |
| 37 | }); |
| 38 | |
| 39 | it('normalizeExternalRef trims and rejects control chars and oversize', () => { |
| 40 | assert.strictEqual(normalizeExternalRef(' abc '), 'abc'); |
| 41 | assert.strictEqual(normalizeExternalRef('a\nb'), ''); |
| 42 | assert.strictEqual(normalizeExternalRef('x'), 'x'); |
| 43 | assert.strictEqual(normalizeExternalRef('a'.repeat(600)), ''); |
| 44 | }); |
| 45 | |
| 46 | it('resolveExternalRefForApprove prefers client ref over fetch', async () => { |
| 47 | let called = false; |
| 48 | const fetchFn = async () => { |
| 49 | called = true; |
| 50 | return /** @type {any} */ ({ ok: true, text: async () => '{"external_ref":"bad"}' }); |
| 51 | }; |
| 52 | const r = await resolveExternalRefForApprove({ |
| 53 | clientRef: 'client-wins', |
| 54 | proposalId: 'p1', |
| 55 | vaultId: 'default', |
| 56 | config: parseMuseConfigFromEnv({ MUSE_URL: 'https://m.example' }), |
| 57 | fetchFn, |
| 58 | }); |
| 59 | assert.strictEqual(r, 'client-wins'); |
| 60 | assert.strictEqual(called, false); |
| 61 | }); |
| 62 | |
| 63 | it('resolveExternalRefForApprove uses JSON external_ref when client empty', async () => { |
| 64 | const fetchFn = async (url) => { |
| 65 | assert.match(String(url), /\/knowtation\/v1\/lineage-ref\?/); |
| 66 | assert.match(String(url), /proposal_id=p1/); |
| 67 | return /** @type {any} */ ({ ok: true, text: async () => '{"external_ref":"muse-commit-9"}' }); |
| 68 | }; |
| 69 | const r = await resolveExternalRefForApprove({ |
| 70 | clientRef: '', |
| 71 | proposalId: 'p1', |
| 72 | vaultId: 'v1', |
| 73 | config: parseMuseConfigFromEnv({ MUSE_URL: 'https://m.example' }), |
| 74 | fetchFn, |
| 75 | }); |
| 76 | assert.strictEqual(r, 'muse-commit-9'); |
| 77 | }); |
| 78 | |
| 79 | it('resolveExternalRefForApprove returns empty on fetch failure without throwing', async () => { |
| 80 | const warnings = []; |
| 81 | const fetchFn = async () => { |
| 82 | throw new Error('network down'); |
| 83 | }; |
| 84 | const r = await resolveExternalRefForApprove({ |
| 85 | clientRef: '', |
| 86 | proposalId: 'p1', |
| 87 | vaultId: 'default', |
| 88 | config: parseMuseConfigFromEnv({ MUSE_URL: 'https://m.example' }), |
| 89 | fetchFn, |
| 90 | logWarn: (msg, extra) => warnings.push({ msg, extra }), |
| 91 | }); |
| 92 | assert.strictEqual(r, ''); |
| 93 | assert.ok(warnings.some((w) => String(w.msg).includes('knowtation:muse-bridge'))); |
| 94 | }); |
| 95 | |
| 96 | it('proposalIdFromApprovePath extracts id', () => { |
| 97 | assert.strictEqual(proposalIdFromApprovePath('/api/v1/proposals/abc/approve'), 'abc'); |
| 98 | assert.strictEqual(proposalIdFromApprovePath('/api/v1/proposals/abc/approve/'), 'abc'); |
| 99 | assert.strictEqual(proposalIdFromApprovePath('/api/v1/notes/x'), null); |
| 100 | }); |
| 101 | |
| 102 | it('isAllowedMuseProxyPath respects prefixes', () => { |
| 103 | const p = ['/knowtation/v1/']; |
| 104 | assert.strictEqual(isAllowedMuseProxyPath('/knowtation/v1/foo', p), true); |
| 105 | assert.strictEqual(isAllowedMuseProxyPath('/other/foo', p), false); |
| 106 | assert.strictEqual(isAllowedMuseProxyPath('/knowtation/v1/../etc', p), false); |
| 107 | }); |
| 108 | |
| 109 | it('MUSE_LINEAGE_REF_PATH is documented path', () => { |
| 110 | assert.strictEqual(MUSE_LINEAGE_REF_PATH, '/knowtation/v1/lineage-ref'); |
| 111 | }); |
| 112 | |
| 113 | it('parseMuseProxyPathPrefixes splits comma list', () => { |
| 114 | const env = { MUSE_PROXY_PATH_PREFIXES: '/a/, /b/' }; |
| 115 | assert.deepStrictEqual(parseMuseProxyPathPrefixes(env), ['/a/', '/b/']); |
| 116 | }); |
| 117 | }); |
File History
2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠
2 days ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6
docs: accept Calendar Events v0 spec with Phase 0 security …
Human
2 days ago