Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
249 lines
No EOL
7.8 KiB
JavaScript
249 lines
No EOL
7.8 KiB
JavaScript
/**
|
|
* Labs Routes Tests
|
|
* ADR-043: MindWorkflow Integration
|
|
*/
|
|
|
|
// Set env vars BEFORE imports
|
|
process.env.SKIP_DEV_USER = 'true';
|
|
const JWT_SECRET = 'test-secret-key';
|
|
process.env.JWT_SECRET = JWT_SECRET;
|
|
|
|
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';
|
|
import request from 'supertest';
|
|
import jwt from 'jsonwebtoken';
|
|
import express from 'express';
|
|
import labsRouter from '../labs.js';
|
|
import { dbRun, dbGet, dbAll } from '../../../database/connection.js';
|
|
import { setupTestDatabase, cleanupTestDatabase } from '../../../tests/helpers/test-db.js';
|
|
|
|
// Create test app
|
|
const app = express();
|
|
app.use(express.json());
|
|
|
|
// Mock auth middleware that handles JWT tokens
|
|
app.use((req, res, next) => {
|
|
const token = req.headers.authorization?.split(' ')[1];
|
|
if (token) {
|
|
try {
|
|
req.user = jwt.verify(token, JWT_SECRET);
|
|
} catch (e) {
|
|
// Invalid token
|
|
}
|
|
}
|
|
next();
|
|
});
|
|
|
|
app.use('/api/v3/labs', labsRouter);
|
|
|
|
let authToken;
|
|
let testUserId;
|
|
|
|
describe('Labs Routes', () => {
|
|
beforeAll(async () => {
|
|
// Set up test database with all necessary tables
|
|
await setupTestDatabase();
|
|
|
|
// Create test user
|
|
const ts = Date.now();
|
|
try {
|
|
const userResult = await dbRun(`
|
|
INSERT INTO users (email, password_hash, name, role, encryption_key_encrypted, created_at)
|
|
VALUES (?, ?, ?, ?, ?, NOW())
|
|
`, [`labs-test-${ts}@test.com`, 'hash123', 'Labs Test User', 'admin', 'enc123']);
|
|
testUserId = userResult.lastInsertRowid;
|
|
|
|
authToken = jwt.sign({
|
|
id: testUserId,
|
|
userId: testUserId,
|
|
email: `labs-test-${ts}@test.com`,
|
|
role: 'admin'
|
|
}, JWT_SECRET);
|
|
} catch (err) {
|
|
// User table might not exist, create a simple token
|
|
authToken = jwt.sign({
|
|
id: 1,
|
|
userId: 1,
|
|
email: 'test@test.com',
|
|
role: 'admin'
|
|
}, JWT_SECRET);
|
|
}
|
|
|
|
// Add test AI templates (delete first to avoid unique constraint violations)
|
|
await dbRun(`DELETE FROM labs_ai_templates WHERE mindworkflow_id IN ('test_planner', 'test_assistant')`);
|
|
await dbRun(`
|
|
INSERT INTO labs_ai_templates
|
|
(mindworkflow_id, name, category, description, system_prompt, user_prompt_example, settings, routing_config, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())
|
|
`, [
|
|
'test_planner',
|
|
'Test Strategic Planner',
|
|
'text_to_text',
|
|
'Test template for planning',
|
|
'You are a test planner.',
|
|
'Create a test plan.',
|
|
JSON.stringify({ model: 'gpt-4o-mini', temperature: 0.4 }),
|
|
JSON.stringify({ outputs: [{ id: 'text', type: 'text', enabled: true }] })
|
|
]);
|
|
|
|
await dbRun(`
|
|
INSERT INTO labs_ai_templates
|
|
(mindworkflow_id, name, category, description, system_prompt, user_prompt_example, settings, routing_config, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())
|
|
`, [
|
|
'test_assistant',
|
|
'Test Assistant',
|
|
'text_to_text',
|
|
'Test template for assistance',
|
|
'You are a test assistant.',
|
|
'Help with testing.',
|
|
JSON.stringify({ model: 'gpt-4o-mini', temperature: 0.3 }),
|
|
JSON.stringify({ outputs: [{ id: 'text', type: 'text', enabled: true }] })
|
|
]);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await cleanupTestDatabase();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
// Clean up test data - use correct table and column names
|
|
try {
|
|
await dbRun('DELETE FROM labs_edges WHERE lab_id LIKE ?', ['labs-test-%']);
|
|
await dbRun('DELETE FROM labs_nodes WHERE lab_id LIKE ?', ['labs-test-%']);
|
|
await dbRun('DELETE FROM labs WHERE lab_id LIKE ?', ['labs-test-%']);
|
|
} catch (err) {
|
|
// Tables might not exist yet, ignore errors
|
|
}
|
|
});
|
|
|
|
afterEach(async () => {
|
|
// Clean up test data - use correct table and column names
|
|
try {
|
|
await dbRun('DELETE FROM labs_edges WHERE lab_id LIKE ?', ['labs-test-%']);
|
|
await dbRun('DELETE FROM labs_nodes WHERE lab_id LIKE ?', ['labs-test-%']);
|
|
await dbRun('DELETE FROM labs WHERE lab_id LIKE ?', ['labs-test-%']);
|
|
} catch (err) {
|
|
// Tables might not exist, ignore errors
|
|
}
|
|
});
|
|
|
|
describe('Projects CRUD', () => {
|
|
it.todo('should list all projects');
|
|
it.todo('should create a new project');
|
|
it.todo('should get project with nodes and edges');
|
|
it.todo('should update project');
|
|
it.todo('should delete project and cascade delete nodes/edges');
|
|
it.todo('should return 404 for non-existent project');
|
|
it.todo('should require title when creating project');
|
|
});
|
|
|
|
describe('Nodes CRUD', () => {
|
|
it.todo('should list nodes for a project');
|
|
it.todo('should create a new node');
|
|
it.todo('should update node');
|
|
it.todo('should delete node and related edges');
|
|
it.todo('should return 404 for non-existent node');
|
|
it.todo('should require type and title when creating node');
|
|
});
|
|
|
|
describe('Edges CRUD', () => {
|
|
it.todo('should list edges for a project');
|
|
it.todo('should create a new edge');
|
|
it.todo('should delete edge');
|
|
it.todo('should return 404 for non-existent edge');
|
|
it.todo('should require source_node_id and target_node_id when creating edge');
|
|
});
|
|
|
|
describe('AI Integration', () => {
|
|
it.todo('should get available AI agents');
|
|
it.todo('should get AI providers');
|
|
|
|
it('should get AI templates', async () => {
|
|
const response = await request(app)
|
|
.get('/api/v3/labs/ai/templates')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(200);
|
|
|
|
expect(response.body.success).toBe(true);
|
|
expect(Array.isArray(response.body.data)).toBe(true);
|
|
|
|
// Templates may be empty in test environment (no seeding)
|
|
// If templates exist, verify structure
|
|
if (response.body.data.length > 0) {
|
|
const template = response.body.data[0];
|
|
expect(template).toHaveProperty('id');
|
|
expect(template).toHaveProperty('name');
|
|
expect(template).toHaveProperty('mindworkflow_id');
|
|
expect(template).toHaveProperty('category');
|
|
expect(template).toHaveProperty('description');
|
|
expect(template).toHaveProperty('system_prompt');
|
|
}
|
|
});
|
|
|
|
it.todo('should execute AI node');
|
|
it.todo('should return 404 when executing non-existent node');
|
|
it.todo('should return error when executing non-AI node');
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
it.todo('should handle database errors gracefully');
|
|
it.todo('should validate input parameters');
|
|
it.todo('should return proper error responses');
|
|
});
|
|
|
|
describe('Authentication', () => {
|
|
it.todo('should require authentication for all endpoints');
|
|
it.todo('should return 401 for unauthenticated requests');
|
|
});
|
|
});
|
|
|
|
// Helper functions for tests
|
|
export const createTestProject = async (overrides = {}) => {
|
|
const projectData = {
|
|
space_id: null,
|
|
title: 'Test Project',
|
|
description: 'Test Description',
|
|
settings: {},
|
|
...overrides
|
|
};
|
|
|
|
const response = await request(app)
|
|
.post('/api/v3/labs/projects')
|
|
.send(projectData);
|
|
|
|
return response.body.data;
|
|
};
|
|
|
|
export const createTestNode = async (projectId, overrides = {}) => {
|
|
const nodeData = {
|
|
type: 'text',
|
|
title: 'Test Node',
|
|
content: 'Test content',
|
|
meta: {},
|
|
ai_config: {},
|
|
ui_config: {},
|
|
...overrides
|
|
};
|
|
|
|
const response = await request(app)
|
|
.post(`/api/v3/labs/projects/${projectId}/nodes`)
|
|
.send(nodeData);
|
|
|
|
return response.body.data;
|
|
};
|
|
|
|
export const createTestEdge = async (projectId, sourceNodeId, targetNodeId, overrides = {}) => {
|
|
const edgeData = {
|
|
source_node_id: sourceNodeId,
|
|
target_node_id: targetNodeId,
|
|
source_handle: null,
|
|
target_handle: null,
|
|
...overrides
|
|
};
|
|
|
|
const response = await request(app)
|
|
.post(`/api/v3/labs/projects/${projectId}/edges`)
|
|
.send(edgeData);
|
|
|
|
return response.body.data;
|
|
}; |