Spaces:
Sleeping
Sleeping
| import { test, expect } from '@playwright/test' | |
| import { API_KEY_HEADER } from './helpers' | |
| const stamp = Date.now() | |
| const testFile = `memory/e2e-mk-${stamp}.md` | |
| const testFile2 = `memory/e2e-mk-linked-${stamp}.md` | |
| test.describe('Memory Knowledge Features', () => { | |
| // Setup: create test files with wiki-links and schema | |
| test.beforeAll(async ({ request }) => { | |
| const file1 = await request.post('/api/memory', { | |
| headers: API_KEY_HEADER, | |
| data: { | |
| action: 'create', | |
| path: testFile, | |
| content: [ | |
| '---', | |
| '_schema:', | |
| ' type: note', | |
| ' required: [title, tags]', | |
| 'title: E2E Test Note', | |
| 'tags: [test, e2e]', | |
| '---', | |
| `# E2E Note ${stamp}`, | |
| '', | |
| `This links to [[e2e-mk-linked-${stamp}]] for testing.`, | |
| '', | |
| 'Some searchable content: unique-token-' + stamp, | |
| ].join('\n'), | |
| }, | |
| }) | |
| expect(file1.status()).toBe(200) | |
| const file2 = await request.post('/api/memory', { | |
| headers: API_KEY_HEADER, | |
| data: { | |
| action: 'create', | |
| path: testFile2, | |
| content: [ | |
| '---', | |
| 'title: Linked Note', | |
| '---', | |
| '# Linked Note', | |
| '', | |
| `Back-reference to [[e2e-mk-${stamp}]].`, | |
| ].join('\n'), | |
| }, | |
| }) | |
| expect(file2.status()).toBe(200) | |
| }) | |
| // Cleanup | |
| test.afterAll(async ({ request }) => { | |
| await request.delete('/api/memory', { | |
| headers: API_KEY_HEADER, | |
| data: { action: 'delete', path: testFile }, | |
| }) | |
| await request.delete('/api/memory', { | |
| headers: API_KEY_HEADER, | |
| data: { action: 'delete', path: testFile2 }, | |
| }) | |
| }) | |
| // --- Links API --- | |
| test('GET /api/memory/links returns link graph', async ({ request }) => { | |
| const res = await request.get('/api/memory/links', { | |
| headers: API_KEY_HEADER, | |
| }) | |
| expect(res.status()).toBe(200) | |
| const body = await res.json() | |
| expect(Array.isArray(body.nodes)).toBe(true) | |
| expect(body.nodes.length).toBeGreaterThan(0) | |
| // Each node should have expected shape | |
| const node = body.nodes[0] | |
| expect(node).toHaveProperty('path') | |
| expect(node).toHaveProperty('linkCount') | |
| }) | |
| test('GET /api/memory/links?file= returns per-file links', async ({ request }) => { | |
| const res = await request.get( | |
| `/api/memory/links?file=${encodeURIComponent(testFile)}`, | |
| { headers: API_KEY_HEADER } | |
| ) | |
| expect(res.status()).toBe(200) | |
| const body = await res.json() | |
| expect(body.file).toBe(testFile) | |
| expect(Array.isArray(body.wikiLinks)).toBe(true) | |
| // Should find the wiki-link to the linked file | |
| const link = body.wikiLinks.find((l: any) => l.target.includes('linked')) | |
| expect(link).toBeTruthy() | |
| }) | |
| test('links API requires auth', async ({ request }) => { | |
| const res = await request.get('/api/memory/links') | |
| expect(res.status()).toBe(401) | |
| }) | |
| // --- Health API --- | |
| test('GET /api/memory/health returns health report', async ({ request }) => { | |
| const res = await request.get('/api/memory/health', { | |
| headers: API_KEY_HEADER, | |
| }) | |
| expect(res.status()).toBe(200) | |
| const body = await res.json() | |
| expect(typeof body.overallScore).toBe('number') | |
| expect(body.overallScore).toBeGreaterThanOrEqual(0) | |
| expect(body.overallScore).toBeLessThanOrEqual(100) | |
| expect(Array.isArray(body.categories)).toBe(true) | |
| expect(body.categories.length).toBe(8) | |
| // Each category has expected shape | |
| for (const cat of body.categories) { | |
| expect(cat).toHaveProperty('name') | |
| expect(cat).toHaveProperty('score') | |
| expect(typeof cat.score).toBe('number') | |
| } | |
| }) | |
| test('health API requires auth', async ({ request }) => { | |
| const res = await request.get('/api/memory/health') | |
| expect(res.status()).toBe(401) | |
| }) | |
| // --- Context API --- | |
| test('GET /api/memory/context returns context payload', async ({ request }) => { | |
| const res = await request.get('/api/memory/context', { | |
| headers: API_KEY_HEADER, | |
| }) | |
| expect(res.status()).toBe(200) | |
| const body = await res.json() | |
| expect(body).toHaveProperty('fileTree') | |
| expect(body).toHaveProperty('recentFiles') | |
| expect(body).toHaveProperty('healthSummary') | |
| expect(body).toHaveProperty('maintenanceSignals') | |
| expect(Array.isArray(body.fileTree)).toBe(true) | |
| expect(Array.isArray(body.recentFiles)).toBe(true) | |
| expect(Array.isArray(body.maintenanceSignals)).toBe(true) | |
| expect(typeof body.healthSummary).toBe('object') | |
| }) | |
| test('context API requires auth', async ({ request }) => { | |
| const res = await request.get('/api/memory/context') | |
| expect(res.status()).toBe(401) | |
| }) | |
| // --- Process API --- | |
| test('POST /api/memory/process reflect action', async ({ request }) => { | |
| const res = await request.post('/api/memory/process', { | |
| headers: API_KEY_HEADER, | |
| data: { action: 'reflect' }, | |
| }) | |
| expect(res.status()).toBe(200) | |
| const body = await res.json() | |
| expect(body.action).toBe('reflect') | |
| expect(Array.isArray(body.suggestions)).toBe(true) | |
| }) | |
| test('POST /api/memory/process reweave action', async ({ request }) => { | |
| const res = await request.post('/api/memory/process', { | |
| headers: API_KEY_HEADER, | |
| data: { action: 'reweave' }, | |
| }) | |
| expect(res.status()).toBe(200) | |
| const body = await res.json() | |
| expect(body.action).toBe('reweave') | |
| expect(Array.isArray(body.suggestions)).toBe(true) | |
| }) | |
| test('POST /api/memory/process generate-moc action', async ({ request }) => { | |
| const res = await request.post('/api/memory/process', { | |
| headers: API_KEY_HEADER, | |
| data: { action: 'generate-moc' }, | |
| }) | |
| expect(res.status()).toBe(200) | |
| const body = await res.json() | |
| expect(body.action).toBe('generate-moc') | |
| expect(Array.isArray(body.groups)).toBe(true) | |
| expect(typeof body.totalGroups).toBe('number') | |
| expect(typeof body.totalEntries).toBe('number') | |
| }) | |
| test('process API rejects invalid action', async ({ request }) => { | |
| const res = await request.post('/api/memory/process', { | |
| headers: API_KEY_HEADER, | |
| data: { action: 'invalid' }, | |
| }) | |
| expect(res.status()).toBe(400) | |
| }) | |
| test('process API requires auth', async ({ request }) => { | |
| const res = await request.post('/api/memory/process', { | |
| data: { action: 'reflect' }, | |
| }) | |
| expect(res.status()).toBe(401) | |
| }) | |
| // --- Memory content now returns wiki-links and schema --- | |
| test('GET /api/memory content includes wikiLinks and schema', async ({ request }) => { | |
| const res = await request.get( | |
| `/api/memory?action=content&path=${encodeURIComponent(testFile)}`, | |
| { headers: API_KEY_HEADER } | |
| ) | |
| expect(res.status()).toBe(200) | |
| const body = await res.json() | |
| expect(body.path).toBe(testFile) | |
| // Wiki-links extracted | |
| expect(Array.isArray(body.wikiLinks)).toBe(true) | |
| expect(body.wikiLinks.length).toBeGreaterThan(0) | |
| // Schema validation result | |
| expect(body.schema).toBeTruthy() | |
| expect(body.schema.valid).toBe(true) | |
| expect(body.schema.errors).toEqual([]) | |
| }) | |
| test('save returns schema warnings for missing required fields', async ({ request }) => { | |
| const badContent = [ | |
| '---', | |
| '_schema:', | |
| ' type: note', | |
| ' required: [title, tags, missing_field]', | |
| 'title: Present', | |
| 'tags: [a]', | |
| '---', | |
| '# Body', | |
| ].join('\n') | |
| const res = await request.post('/api/memory', { | |
| headers: API_KEY_HEADER, | |
| data: { action: 'save', path: testFile, content: badContent }, | |
| }) | |
| expect(res.status()).toBe(200) | |
| const body = await res.json() | |
| expect(body.success).toBe(true) | |
| expect(Array.isArray(body.schemaWarnings)).toBe(true) | |
| expect(body.schemaWarnings).toContain('Missing required field: missing_field') | |
| }) | |
| }) | |