muse-thin-bridge.test.mjs
117 lines 4.3 KB
Raw
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