/** * Unit tests for the 8-path whitelist and tier utilities. * * These tests pin the vocabulary in place — if someone renames a * canonical path without updating routes/UI/prompts, the build breaks * here first instead of at runtime with cryptic whitelist rejections. */ import { MEMORY_AGENT_PATHS, MEMORY_USER_PATHS, MEMORY_ALL_PATHS, MEMORY_WRITABLE_PATHS, assertWritablePath, getTierForPath, getPathDescriptor, getWritablePathsForScope, renderPathsRubric, } from '../paths'; describe('memory/paths', () => { describe('whitelist shape', () => { it('has exactly 4 agent-tier + 4 user-tier paths = 8 total', () => { expect(MEMORY_AGENT_PATHS).toHaveLength(4); expect(MEMORY_USER_PATHS).toHaveLength(4); expect(MEMORY_ALL_PATHS).toHaveLength(8); expect(MEMORY_WRITABLE_PATHS.size).toBe(8); }); it('every agent-tier path starts with memory/agent/', () => { for (const p of MEMORY_AGENT_PATHS) { expect(p.path).toMatch(/^memory\/agent\//); expect(p.tier).toBe('agent'); } }); it('every user-tier path starts with memory/user/', () => { for (const p of MEMORY_USER_PATHS) { expect(p.path).toMatch(/^memory\/user\//); expect(p.tier).toBe('user'); } }); it('every descriptor has a non-empty label and description', () => { for (const p of MEMORY_ALL_PATHS) { expect(p.label.length).toBeGreaterThan(0); expect(p.description.length).toBeGreaterThan(20); } }); it('paths are unique', () => { const paths = MEMORY_ALL_PATHS.map((p) => p.path); expect(new Set(paths).size).toBe(paths.length); }); }); describe('getTierForPath', () => { it('returns agent for memory/agent/* paths', () => { expect(getTierForPath('memory/agent/playbook.md')).toBe('agent'); expect(getTierForPath('memory/agent/pitfalls.md')).toBe('agent'); }); it('returns user for memory/user/* paths', () => { expect(getTierForPath('memory/user/profile.md')).toBe('user'); expect(getTierForPath('memory/user/preferences.md')).toBe('user'); }); it('falls back to prefix inspection for unknown paths', () => { expect(getTierForPath('memory/user/legacy.md')).toBe('user'); expect(getTierForPath('memory/2026-04-14.md')).toBe('agent'); }); }); describe('getPathDescriptor', () => { it('returns the descriptor for a canonical path', () => { const d = getPathDescriptor('memory/agent/playbook.md'); expect(d).toBeDefined(); expect(d!.tier).toBe('agent'); expect(d!.label).toBe('Playbook'); }); it('returns undefined for non-whitelisted paths', () => { expect(getPathDescriptor('memory/random.md')).toBeUndefined(); }); }); describe('getWritablePathsForScope', () => { it('returns all 8 when scope has a userId', () => { const paths = getWritablePathsForScope({ userId: 'alice' }); expect(paths).toHaveLength(8); }); it('returns only 4 agent-tier paths when userId is missing', () => { const paths = getWritablePathsForScope({ userId: null }); expect(paths).toHaveLength(4); expect(paths.every((p) => p.tier === 'agent')).toBe(true); }); it('treats empty-string userId as isolated', () => { const paths = getWritablePathsForScope({ userId: '' }); expect(paths).toHaveLength(4); }); }); describe('assertWritablePath', () => { const scoped = { userId: 'alice' }; const isolated = { userId: null }; it('accepts a valid agent-tier write from any scope', () => { expect(() => assertWritablePath('memory/agent/playbook.md', scoped) ).not.toThrow(); expect(() => assertWritablePath('memory/agent/playbook.md', isolated) ).not.toThrow(); }); it('accepts a valid user-tier write from a scoped caller', () => { expect(() => assertWritablePath('memory/user/preferences.md', scoped) ).not.toThrow(); }); it('rejects a user-tier write from an isolated caller', () => { expect(() => assertWritablePath('memory/user/preferences.md', isolated) ).toThrow(/requires a caller userId/); }); it('rejects paths without the memory/ prefix', () => { expect(() => assertWritablePath('foo.md', scoped)).toThrow( /must start with "memory\// ); }); it('rejects non-whitelisted paths inside memory/', () => { expect(() => assertWritablePath('memory/random-notes.md', scoped) ).toThrow(/not on the canonical whitelist/); }); it('rejects legacy date-keyed paths', () => { expect(() => assertWritablePath('memory/2026-04-14.md', scoped)).toThrow( /not on the canonical whitelist/ ); }); }); describe('renderPathsRubric', () => { it('renders every writable path for a scoped caller', () => { const text = renderPathsRubric({ userId: 'alice' }); for (const p of MEMORY_ALL_PATHS) { expect(text).toContain(p.path); } expect(text).toContain('[Playbook, shared]'); expect(text).toContain('[Profile, private]'); }); it('omits user-tier paths for an isolated caller', () => { const text = renderPathsRubric({ userId: null }); for (const p of MEMORY_AGENT_PATHS) { expect(text).toContain(p.path); } for (const p of MEMORY_USER_PATHS) { expect(text).not.toContain(p.path); } }); }); });