Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
189 lines
6.7 KiB
JavaScript
189 lines
6.7 KiB
JavaScript
/**
|
|
* @fileoverview Tests for AI Agents API endpoints
|
|
* TDD: Test first, then fix
|
|
*/
|
|
|
|
// 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, beforeAll, afterAll } from 'vitest';
|
|
import request from 'supertest';
|
|
import jwt from 'jsonwebtoken';
|
|
import express from 'express';
|
|
import { dbRun, dbGet, dbAll } from '../../../database/connection.js';
|
|
import { setupTestDatabase, cleanupTestDatabase } from '../../../tests/helpers/test-db.js';
|
|
import aiAgentsRoutes from '../ai-agents.js';
|
|
|
|
describe('GET /api/v3/ai/agents/:spaceId', () => {
|
|
let app;
|
|
let authToken;
|
|
let testSpaceId;
|
|
let testProjectId;
|
|
let testTableId;
|
|
let testAgentRowIds = [];
|
|
|
|
beforeAll(async () => {
|
|
await setupTestDatabase();
|
|
|
|
// Create Express app - ai-agents.js has router.use(authenticate) built-in
|
|
app = express();
|
|
app.use(express.json());
|
|
|
|
app.use('/api/v3/ai', aiAgentsRoutes);
|
|
|
|
// Create test user first (needed for FK constraints and auth)
|
|
const ts = Date.now();
|
|
const userResult = await dbRun(`
|
|
INSERT INTO users (email, password_hash, name, role, encryption_key_encrypted, created_at)
|
|
VALUES ($1, 'hash123', 'Test User', 'admin', 'enc123', NOW())
|
|
`, [`ai-agents-test-${ts}@test.com`]);
|
|
const testUserId = userResult.lastInsertRowid;
|
|
|
|
// Generate real JWT token (authenticate middleware verifies it)
|
|
authToken = jwt.sign({ id: testUserId, email: `ai-agents-test-${ts}@test.com`, role: 'admin' }, JWT_SECRET);
|
|
|
|
// Create test space (type must be one of: business, personal, admin, ai)
|
|
const spaceResult = await dbRun(`
|
|
INSERT INTO spaces (name, type, owner_id, created_at, updated_at)
|
|
VALUES ('Test AI Space', 'ai', $1, NOW(), NOW())
|
|
`, [testUserId]);
|
|
testSpaceId = spaceResult.lastInsertRowid;
|
|
|
|
// Create test project (type is NOT NULL)
|
|
const projectResult = await dbRun(`
|
|
INSERT INTO projects (name, description, type, space_id, owner_id, created_at, updated_at)
|
|
VALUES ('Test System Data', 'Test project', 'business', $1, $2, NOW(), NOW())
|
|
`, [testSpaceId, testUserId]);
|
|
testProjectId = projectResult.lastInsertRowid;
|
|
|
|
// Create AI Agents table
|
|
const tableResult = await dbRun(`
|
|
INSERT INTO universal_tables (name, project_id, icon, created_at, updated_at)
|
|
VALUES ('AI Agents', $1, '🤖', NOW(), NOW())
|
|
`, [testProjectId]);
|
|
testTableId = tableResult.lastInsertRowid;
|
|
|
|
// Create test agents with different statuses
|
|
const agents = [
|
|
{ name: 'Active Agent 1', status: 'active', is_active: null },
|
|
{ name: 'Active Agent 2', status: 'active', is_active: true },
|
|
{ name: 'Inactive Agent', status: 'inactive', is_active: false },
|
|
{ name: 'Legacy Active', status: null, is_active: true },
|
|
{ name: 'Testing Agent', status: 'testing', is_active: null }
|
|
];
|
|
|
|
for (const agent of agents) {
|
|
const result = await dbRun(`
|
|
INSERT INTO table_rows (table_id, data, base_id, created_at, updated_at)
|
|
VALUES ($1, $2, $3, NOW(), NOW())
|
|
`, [testTableId, JSON.stringify(agent), `row-${Date.now()}-${Math.random()}`]);
|
|
testAgentRowIds.push(result.lastInsertRowid);
|
|
}
|
|
});
|
|
|
|
afterAll(async () => {
|
|
// Cleanup
|
|
if (testAgentRowIds.length > 0) {
|
|
await dbRun(`DELETE FROM table_rows WHERE id IN (${testAgentRowIds.join(',')})`);
|
|
}
|
|
if (testTableId) {
|
|
await dbRun('DELETE FROM universal_tables WHERE id = $1', [testTableId]);
|
|
}
|
|
if (testProjectId) {
|
|
await dbRun('DELETE FROM projects WHERE id = $1', [testProjectId]);
|
|
}
|
|
if (testSpaceId) {
|
|
await dbRun('DELETE FROM spaces WHERE id = $1', [testSpaceId]);
|
|
}
|
|
await cleanupTestDatabase();
|
|
});
|
|
|
|
it('should return agents for a valid spaceId', async () => {
|
|
const response = await request(app)
|
|
.get(`/api/v3/ai/agents/${testSpaceId}`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(200);
|
|
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data).toBeDefined();
|
|
expect(response.body.data.agents).toBeInstanceOf(Array);
|
|
expect(response.body.data.agents.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should return is_active=true for agents with status=active', async () => {
|
|
const response = await request(app)
|
|
.get(`/api/v3/ai/agents/${testSpaceId}`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(200);
|
|
|
|
const activeAgents = response.body.data.agents.filter(
|
|
(a) => a.name === 'Active Agent 1' || a.name === 'Active Agent 2'
|
|
);
|
|
|
|
expect(activeAgents.length).toBe(2);
|
|
activeAgents.forEach((agent) => {
|
|
expect(agent.is_active).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('should return is_active=false for inactive agents', async () => {
|
|
const response = await request(app)
|
|
.get(`/api/v3/ai/agents/${testSpaceId}`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(200);
|
|
|
|
const inactiveAgent = response.body.data.agents.find(
|
|
(a) => a.name === 'Inactive Agent'
|
|
);
|
|
|
|
expect(inactiveAgent).toBeDefined();
|
|
expect(inactiveAgent.is_active).toBe(false);
|
|
});
|
|
|
|
it('should return is_active=true for legacy agents with is_active=true', async () => {
|
|
const response = await request(app)
|
|
.get(`/api/v3/ai/agents/${testSpaceId}`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(200);
|
|
|
|
const legacyAgent = response.body.data.agents.find(
|
|
(a) => a.name === 'Legacy Active'
|
|
);
|
|
|
|
expect(legacyAgent).toBeDefined();
|
|
expect(legacyAgent.is_active).toBe(true);
|
|
});
|
|
|
|
it('should return empty agents array for space without agents', async () => {
|
|
const response = await request(app)
|
|
.get('/api/v3/ai/agents/99999')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(200);
|
|
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.agents).toEqual([]);
|
|
});
|
|
|
|
it('should prefer table with most rows when multiple agent tables exist', async () => {
|
|
// Create second table with fewer rows
|
|
const smallerTableResult = await dbRun(`
|
|
INSERT INTO universal_tables (name, project_id, icon, created_at, updated_at)
|
|
VALUES ('Empty Agents', $1, '🤖', NOW(), NOW())
|
|
`, [testProjectId]);
|
|
const smallerTableId = smallerTableResult.lastInsertRowid;
|
|
|
|
try {
|
|
const response = await request(app)
|
|
.get(`/api/v3/ai/agents/${testSpaceId}`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.expect(200);
|
|
|
|
// Should still return agents from the table with more rows
|
|
expect(response.body.data.agents.length).toBeGreaterThan(0);
|
|
} finally {
|
|
await dbRun('DELETE FROM universal_tables WHERE id = $1', [smallerTableId]);
|
|
}
|
|
});
|
|
});
|