242 lines
7.7 KiB
TypeScript
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')
|
|
})
|
|
})
|