mission-control/tests/memory-knowledge.spec.ts

242 lines
7.7 KiB
TypeScript

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')
})
})