moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,307 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { buildServer } from '../../src/index.js';
import { users, agents, agentGroups, agentGroupMembers, agentJobs, teams } from '../../src/db/schema.js';
import { sql, eq } from 'drizzle-orm';
describe('Agent API', () => {
let server: Awaited<ReturnType<typeof buildServer>>;
let user1Id: string;
let user2Id: string;
let teamId: string;
let agent1Id: string;
let agent2Id: string;
beforeAll(async () => {
server = await buildServer();
// Clean up leftover test data
await server.db.execute(sql`DELETE FROM agent_group_members WHERE group_id IN (SELECT id FROM agent_groups WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'agtest_%'))`);
await server.db.execute(sql`DELETE FROM agent_jobs WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'agtest_%')`);
await server.db.execute(sql`DELETE FROM agent_groups WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'agtest_%')`);
await server.db.execute(sql`DELETE FROM agents WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'agtest_%')`);
await server.db.execute(sql`DELETE FROM team_members WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'agtest-%')`);
await server.db.execute(sql`DELETE FROM team_capability_requests WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'agtest-%')`);
await server.db.execute(sql`DELETE FROM team_capability_overrides WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'agtest-%')`);
await server.db.execute(sql`DELETE FROM team_capability_policies WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'agtest-%')`);
await server.db.execute(sql`DELETE FROM teams WHERE slug LIKE 'agtest-%'`);
await server.db.execute(sql`DELETE FROM users WHERE clerk_id LIKE 'agtest_%'`);
// Create test users
const [u1] = await server.db.insert(users).values({
clerkId: 'agtest_user1',
displayName: 'Agent User 1',
email: 'agentuser1@test.com',
}).returning();
user1Id = u1.id;
const [u2] = await server.db.insert(users).values({
clerkId: 'agtest_user2',
displayName: 'Agent User 2',
email: 'agentuser2@test.com',
}).returning();
user2Id = u2.id;
// Create a team for job tests
const [team] = await server.db.insert(teams).values({
name: 'Agent Test Team',
slug: 'agtest-team',
ownerId: user1Id,
}).returning();
teamId = team.id;
// Override auth handler for tests
server._authHandler.fn = async function (request, reply) {
const testUserId = request.headers['x-test-user-id'] as string;
if (!testUserId) {
return reply.code(401).send({ error: 'Missing x-test-user-id header' });
}
request.userId = testUserId;
request.clerkId = 'test';
};
});
afterAll(async () => {
await server.db.execute(sql`DELETE FROM agent_group_members WHERE group_id IN (SELECT id FROM agent_groups WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'agtest_%'))`);
await server.db.execute(sql`DELETE FROM agent_jobs WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'agtest_%')`);
await server.db.execute(sql`DELETE FROM agent_groups WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'agtest_%')`);
await server.db.execute(sql`DELETE FROM agents WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'agtest_%')`);
await server.db.execute(sql`DELETE FROM team_members WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'agtest-%')`);
await server.db.execute(sql`DELETE FROM team_capability_requests WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'agtest-%')`);
await server.db.execute(sql`DELETE FROM team_capability_overrides WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'agtest-%')`);
await server.db.execute(sql`DELETE FROM team_capability_policies WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'agtest-%')`);
await server.db.execute(sql`DELETE FROM teams WHERE slug LIKE 'agtest-%'`);
await server.db.execute(sql`DELETE FROM users WHERE clerk_id LIKE 'agtest_%'`);
await server.close();
});
it('creates an agent with custom model, tools, and system prompt', async () => {
const response = await server.inject({
method: 'POST',
url: '/api/agents',
headers: { 'x-test-user-id': user1Id },
payload: {
name: 'Research Agent',
role: 'researcher',
systemPrompt: 'You are a research agent.',
model: 'claude-sonnet-4-20250514',
tools: ['web_search', 'read_file'],
config: { maxTokens: 4096 },
},
});
expect(response.statusCode).toBe(201);
const body = JSON.parse(response.body);
expect(body.name).toBe('Research Agent');
expect(body.role).toBe('researcher');
expect(body.systemPrompt).toBe('You are a research agent.');
expect(body.model).toBe('claude-sonnet-4-20250514');
expect(body.tools).toEqual(['web_search', 'read_file']);
expect(body.config).toEqual({ maxTokens: 4096 });
expect(body.userId).toBe(user1Id);
agent1Id = body.id;
});
it('creates a second agent for user1', async () => {
const response = await server.inject({
method: 'POST',
url: '/api/agents',
headers: { 'x-test-user-id': user1Id },
payload: {
name: 'Coding Agent',
model: 'claude-haiku-4-5',
tools: ['execute_code'],
},
});
expect(response.statusCode).toBe(201);
agent2Id = JSON.parse(response.body).id;
});
it('lists agents returns only the user\'s agents', async () => {
// Create an agent for user2
await server.inject({
method: 'POST',
url: '/api/agents',
headers: { 'x-test-user-id': user2Id },
payload: { name: 'User2 Agent' },
});
const response = await server.inject({
method: 'GET',
url: '/api/agents',
headers: { 'x-test-user-id': user1Id },
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(Array.isArray(body)).toBe(true);
expect(body.length).toBe(2);
expect(body.every((a: { userId: string }) => a.userId === user1Id)).toBe(true);
});
it('updates agent model and config', async () => {
const response = await server.inject({
method: 'PATCH',
url: `/api/agents/${agent1Id}`,
headers: { 'x-test-user-id': user1Id },
payload: {
model: 'claude-haiku-4-5',
config: { maxTokens: 2048, temperature: 0.7 },
},
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.model).toBe('claude-haiku-4-5');
expect(body.config).toEqual({ maxTokens: 2048, temperature: 0.7 });
});
it('user cannot update another user\'s agent', async () => {
const response = await server.inject({
method: 'PATCH',
url: `/api/agents/${agent1Id}`,
headers: { 'x-test-user-id': user2Id },
payload: { model: 'hacked-model' },
});
expect(response.statusCode).toBe(403);
});
it('user cannot delete another user\'s agent', async () => {
const response = await server.inject({
method: 'DELETE',
url: `/api/agents/${agent1Id}`,
headers: { 'x-test-user-id': user2Id },
});
expect(response.statusCode).toBe(403);
});
it('creates an agent group with parallel strategy and 2 members', async () => {
const response = await server.inject({
method: 'POST',
url: '/api/agent-groups',
headers: { 'x-test-user-id': user1Id },
payload: {
name: 'Research Squad',
description: 'Parallel research team',
strategy: 'parallel',
members: [
{ agentId: agent1Id, roleInGroup: 'worker', executionOrder: 0 },
{ agentId: agent2Id, roleInGroup: 'worker', executionOrder: 0 },
],
},
});
expect(response.statusCode).toBe(201);
const body = JSON.parse(response.body);
expect(body.name).toBe('Research Squad');
expect(body.strategy).toBe('parallel');
expect(body.members).toHaveLength(2);
});
it('creates a group with coordinator strategy and a lead agent', async () => {
const response = await server.inject({
method: 'POST',
url: '/api/agent-groups',
headers: { 'x-test-user-id': user1Id },
payload: {
name: 'Coordinated Team',
strategy: 'coordinator',
members: [
{ agentId: agent1Id, roleInGroup: 'lead', executionOrder: 0 },
{ agentId: agent2Id, roleInGroup: 'worker', executionOrder: 1 },
],
},
});
expect(response.statusCode).toBe(201);
const body = JSON.parse(response.body);
expect(body.strategy).toBe('coordinator');
const lead = body.members.find((m: { roleInGroup: string; agentId: string }) => m.roleInGroup === 'lead');
expect(lead).toBeDefined();
expect(lead.agentId).toBe(agent1Id);
});
it('gets group with members', async () => {
// List groups to get an ID
const listResponse = await server.inject({
method: 'GET',
url: '/api/agent-groups',
headers: { 'x-test-user-id': user1Id },
});
expect(listResponse.statusCode).toBe(200);
const groups = JSON.parse(listResponse.body);
expect(groups.length).toBeGreaterThanOrEqual(2);
const groupId = groups[0].id;
const response = await server.inject({
method: 'GET',
url: `/api/agent-groups/${groupId}`,
headers: { 'x-test-user-id': user1Id },
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.id).toBe(groupId);
expect(body.members).toBeDefined();
expect(Array.isArray(body.members)).toBe(true);
});
it('executes group run — queues a job and returns 202 with jobId', async () => {
const listResponse = await server.inject({
method: 'GET',
url: '/api/agent-groups',
headers: { 'x-test-user-id': user1Id },
});
const groups = JSON.parse(listResponse.body);
const groupId = groups[0].id;
const response = await server.inject({
method: 'POST',
url: `/api/agent-groups/${groupId}/run`,
headers: { 'x-test-user-id': user1Id },
payload: {
task: 'Research the latest AI papers',
teamId,
},
});
expect(response.statusCode).toBe(202);
const body = JSON.parse(response.body);
expect(body.jobId).toBeDefined();
expect(typeof body.jobId).toBe('string');
// Verify the job was created in the database
const [job] = await server.db
.select()
.from(agentJobs)
.where(eq(agentJobs.id, body.jobId))
.limit(1);
expect(job).toBeDefined();
expect(job.status).toBe('queued');
expect(job.jobType).toBe('group');
expect(job.userId).toBe(user1Id);
});
it('deletes an agent', async () => {
const response = await server.inject({
method: 'DELETE',
url: `/api/agents/${agent2Id}`,
headers: { 'x-test-user-id': user1Id },
});
expect(response.statusCode).toBe(204);
// Verify it's gone
const listResponse = await server.inject({
method: 'GET',
url: '/api/agents',
headers: { 'x-test-user-id': user1Id },
});
const remaining = JSON.parse(listResponse.body);
const ids = remaining.map((a: { id: string }) => a.id);
expect(ids).not.toContain(agent2Id);
});
});