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,139 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import type { Job } from 'bullmq';
import { createWorker } from '../src/index.js';
import { JobService } from '../../server/src/services/job-service.js';
import { createDb } from '../../server/src/db/connection.js';
import { users, teams, teamMembers, agentJobs } from '../../server/src/db/schema.js';
import { eq, sql } from 'drizzle-orm';
import type { JobData } from '../src/job-processor.js';
const REDIS_URL = 'redis://localhost:6381';
const DATABASE_URL = 'postgres://waggle:waggle_dev@localhost:5434/waggle';
const QUEUE_NAME = `waggle-jobs-test-${Date.now()}`;
/** Poll for job status until it reaches target or timeout. */
async function waitForJobStatus(
jobService: JobService,
jobId: string,
target: string,
timeoutMs = 10_000,
intervalMs = 200,
): Promise<Awaited<ReturnType<JobService['getJob']>>> {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const job = await jobService.getJob(jobId);
if (job?.status === target || job?.status === 'failed') return job;
await new Promise(r => setTimeout(r, intervalMs));
}
// Return last state even on timeout
return jobService.getJob(jobId);
}
describe('BullMQ Worker', () => {
let db: ReturnType<typeof createDb>;
let jobService: JobService;
let workerInstance: ReturnType<typeof createWorker>;
let testUserId: string;
let testTeamId: string;
beforeAll(async () => {
db = createDb(DATABASE_URL);
jobService = new JobService(db, REDIS_URL, QUEUE_NAME);
workerInstance = createWorker(REDIS_URL, DATABASE_URL, QUEUE_NAME);
// Override handlers with fast mocks (real handlers call LiteLLM which isn't running in tests)
workerInstance.processor.register('chat', async (job) => ({
response: `Mock response for: ${String(job.data.input.message ?? '')}`,
model: 'mock',
}));
workerInstance.processor.register('task', async (job) => ({
result: `Mock task result`,
input: job.data.input,
}));
// Create test data
const [user] = await db.insert(users).values({
clerkId: 'worker_test_user_' + Date.now(),
displayName: 'Worker Test',
email: `worker_${Date.now()}@test.com`,
}).returning();
testUserId = user.id;
const [team] = await db.insert(teams).values({
name: 'Worker Team',
slug: 'worker-test-' + Date.now(),
ownerId: testUserId,
}).returning();
testTeamId = team.id;
await db.insert(teamMembers).values({
teamId: testTeamId,
userId: testUserId,
role: 'owner',
});
}, 15_000);
afterAll(async () => {
try { await workerInstance.worker.close(); } catch { /* ignore */ }
try { await jobService.close(); } catch { /* ignore */ }
await db.execute(sql`DELETE FROM agent_jobs WHERE team_id = ${testTeamId}`);
await db.execute(sql`DELETE FROM team_members WHERE user_id = ${testUserId}`);
await db.execute(sql`DELETE FROM teams WHERE id = ${testTeamId}`);
await db.execute(sql`DELETE FROM users WHERE id = ${testUserId}`);
}, 15_000);
it('queues job and worker picks it up', async () => {
const job = await jobService.createJob(testTeamId, testUserId, 'chat', { message: 'hello' });
expect(job.status).toBe('queued');
const updated = await waitForJobStatus(jobService, job.id, 'completed');
expect(updated?.status).toBe('completed');
expect(updated?.output).toBeDefined();
}, 15_000);
it('tracks job status transitions', async () => {
const job = await jobService.createJob(testTeamId, testUserId, 'chat', { message: 'status test' });
const completed = await waitForJobStatus(jobService, job.id, 'completed');
expect(completed?.startedAt).toBeDefined();
expect(completed?.completedAt).toBeDefined();
expect(completed?.status).toBe('completed');
}, 15_000);
it('marks failed jobs', async () => {
// Register a failing handler
workerInstance.processor.register('fail_test', async () => {
throw new Error('Intentional failure');
});
const job = await jobService.createJob(testTeamId, testUserId, 'fail_test', {});
const failed = await waitForJobStatus(jobService, job.id, 'failed');
expect(failed?.status).toBe('failed');
expect((failed?.output as Record<string, unknown>)?.error).toContain('Intentional failure');
}, 15_000);
it('executes cron wrapper jobs through the registered target handler', async () => {
workerInstance.processor.register('chat', async (job) => ({
response: String(job.data.input.message),
}));
const result = await workerInstance.processor.process({
data: {
jobId: 'cron-wrapper-1',
teamId: testTeamId,
userId: testUserId,
jobType: 'cron',
input: {
jobType: 'chat',
jobConfig: { message: 'scheduled brief' },
},
},
} as unknown as Job<JobData>, db);
expect(result).toEqual({
response: 'scheduled brief',
cron: { delegatedJobType: 'chat' },
});
}, 15_000);
});