UNPKG

@sidequest/backend-test

Version:

@sidequest/backend-test is a test suite for backend implementations of Sidequest, a Node.js library for managing background jobs and distributed queues.

246 lines (243 loc) 12.2 kB
import { describe, it, expect } from 'vitest'; import { backend } from './backend.js'; function defineCountJobsOverTimeTestSuite() { describe("countJobsOverTime", () => { it("should throw error for invalid time range format", async () => { await expect(backend.countJobsOverTime("invalid")).rejects.toThrow(); await expect(backend.countJobsOverTime("12x")).rejects.toThrow(); await expect(backend.countJobsOverTime("")).rejects.toThrow(); }); it("should return correct number of time buckets with zero counts when no jobs exist", async () => { const result = await backend.countJobsOverTime("5m"); expect(result).toHaveLength(5); result.forEach((bucket) => { expect(bucket).toHaveProperty("timestamp"); expect(bucket.timestamp).toBeInstanceOf(Date); expect(bucket.total).toBe(0); expect(bucket.waiting).toBe(0); expect(bucket.claimed).toBe(0); expect(bucket.running).toBe(0); expect(bucket.completed).toBe(0); expect(bucket.failed).toBe(0); expect(bucket.canceled).toBe(0); }); // Check that timestamps are in chronological order and properly spaced for (let i = 1; i < result.length; i++) { const timeDiff = result[i].timestamp.getTime() - result[i - 1].timestamp.getTime(); expect(timeDiff).toBe(60 * 1000); // 1 minute in milliseconds } }); it("should return correct number of time buckets for different time ranges", async () => { const resultMinutes = await backend.countJobsOverTime("3m"); expect(resultMinutes).toHaveLength(3); const resultHours = await backend.countJobsOverTime("2h"); expect(resultHours).toHaveLength(2); const resultDays = await backend.countJobsOverTime("4d"); expect(resultDays).toHaveLength(4); }); it("should count jobs correctly by state and time bucket", async () => { const now = new Date(); const twoMinutesAgo = new Date(now.getTime() - 2 * 60 * 1000); const oneMinuteAgo = new Date(now.getTime() - 1 * 60 * 1000); // Create jobs with different timestamps await backend.createNewJob({ queue: "test-queue", script: "test-script", class: "TestJob", args: [], constructor_args: [], state: "waiting", attempt: 0, }); const job2 = await backend.createNewJob({ queue: "test-queue", script: "test-script", class: "TestJob", args: [], constructor_args: [], state: "waiting", attempt: 0, }); const job3 = await backend.createNewJob({ queue: "test-queue", script: "test-script", class: "TestJob", args: [], constructor_args: [], state: "waiting", attempt: 0, }); // Update jobs to different states and times await backend.updateJob({ id: job2.id, state: "completed", completed_at: twoMinutesAgo, }); await backend.updateJob({ id: job3.id, state: "failed", failed_at: oneMinuteAgo, }); const result = await backend.countJobsOverTime("5m"); expect(result).toHaveLength(5); // Find the most recent bucket (should have the waiting job) const mostRecentBucket = result[result.length - 1]; expect(mostRecentBucket.waiting).toBeGreaterThanOrEqual(1); expect(mostRecentBucket.total).toBeGreaterThanOrEqual(1); // Check that we have some completed and failed jobs in the time range const totalCompleted = result.reduce((sum, bucket) => sum + bucket.completed, 0); const totalFailed = result.reduce((sum, bucket) => sum + bucket.failed, 0); const totalWaiting = result.reduce((sum, bucket) => sum + bucket.waiting, 0); expect(totalCompleted).toBeGreaterThanOrEqual(1); expect(totalFailed).toBeGreaterThanOrEqual(1); expect(totalWaiting).toBeGreaterThanOrEqual(1); // Verify total counts add up correctly for each bucket result.forEach((bucket) => { const calculatedTotal = bucket.waiting + bucket.claimed + bucket.running + bucket.completed + bucket.failed + bucket.canceled; expect(bucket.total).toBe(calculatedTotal); }); }); it("should handle hour-based time ranges correctly", async () => { const result = await backend.countJobsOverTime("24h"); expect(result).toHaveLength(24); // Check that timestamps are spaced 1 hour apart for (let i = 1; i < result.length; i++) { const timeDiff = result[i].timestamp.getTime() - result[i - 1].timestamp.getTime(); expect(timeDiff).toBe(60 * 60 * 1000); // 1 hour in milliseconds } // Verify each bucket has proper structure result.forEach((bucket) => { expect(bucket).toHaveProperty("timestamp"); expect(bucket).toHaveProperty("total"); expect(bucket).toHaveProperty("waiting"); expect(bucket).toHaveProperty("claimed"); expect(bucket).toHaveProperty("running"); expect(bucket).toHaveProperty("completed"); expect(bucket).toHaveProperty("failed"); expect(bucket).toHaveProperty("canceled"); expect(typeof bucket.total).toBe("number"); expect(typeof bucket.waiting).toBe("number"); expect(typeof bucket.claimed).toBe("number"); expect(typeof bucket.running).toBe("number"); expect(typeof bucket.completed).toBe("number"); expect(typeof bucket.failed).toBe("number"); expect(typeof bucket.canceled).toBe("number"); }); }); it("should handle day-based time ranges correctly", async () => { const result = await backend.countJobsOverTime("7d"); expect(result).toHaveLength(7); // Check that timestamps are spaced 1 day apart for (let i = 1; i < result.length; i++) { const timeDiff = result[i].timestamp.getTime() - result[i - 1].timestamp.getTime(); expect(timeDiff).toBe(24 * 60 * 60 * 1000); // 1 day in milliseconds } }); it("should use appropriate timestamp fields based on job state", async () => { const now = new Date(); const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000); // Create a job and update it through different states const job = await backend.createNewJob({ queue: "test-queue", script: "test-script", class: "TestJob", args: [], constructor_args: [], state: "waiting", attempt: 0, }); // Update to completed state with specific completed_at timestamp await backend.updateJob({ id: job.id, state: "completed", completed_at: oneHourAgo, }); const result = await backend.countJobsOverTime("2h"); expect(result).toHaveLength(2); // The job should appear in the time bucket corresponding to when it was completed const totalCompleted = result.reduce((sum, bucket) => sum + bucket.completed, 0); expect(totalCompleted).toBe(1); }); it("should return timestamps in chronological order", async () => { const result = await backend.countJobsOverTime("10m"); expect(result).toHaveLength(10); for (let i = 1; i < result.length; i++) { expect(result[i].timestamp.getTime()).toBeGreaterThan(result[i - 1].timestamp.getTime()); } // The last timestamp should be the most recent const lastTimestamp = result[result.length - 1].timestamp; const now = new Date(); // Allow for some tolerance (within the last minute) const timeDifference = now.getTime() - lastTimestamp.getTime(); expect(timeDifference).toBeLessThan(60 * 1000); // Less than 1 minute difference }); it("should include all job states in each time bucket", async () => { const result = await backend.countJobsOverTime("3m"); result.forEach((bucket) => { // Verify structure and types expect(bucket.timestamp).toBeInstanceOf(Date); expect(typeof bucket.total).toBe("number"); expect(typeof bucket.waiting).toBe("number"); expect(typeof bucket.claimed).toBe("number"); expect(typeof bucket.running).toBe("number"); expect(typeof bucket.completed).toBe("number"); expect(typeof bucket.failed).toBe("number"); expect(typeof bucket.canceled).toBe("number"); // Verify all counts are non-negative expect(bucket.total).toBeGreaterThanOrEqual(0); expect(bucket.waiting).toBeGreaterThanOrEqual(0); expect(bucket.claimed).toBeGreaterThanOrEqual(0); expect(bucket.running).toBeGreaterThanOrEqual(0); expect(bucket.completed).toBeGreaterThanOrEqual(0); expect(bucket.failed).toBeGreaterThanOrEqual(0); expect(bucket.canceled).toBeGreaterThanOrEqual(0); }); }); it("should handle edge case with single time unit", async () => { const result = await backend.countJobsOverTime("1m"); expect(result).toHaveLength(1); const resultHour = await backend.countJobsOverTime("1h"); expect(resultHour).toHaveLength(1); const resultDay = await backend.countJobsOverTime("1d"); expect(resultDay).toHaveLength(1); }); it("should filter jobs correctly within the time range", async () => { const now = new Date(); const veryOldTime = new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000); // 10 days ago // Create a very old job const oldJob = await backend.createNewJob({ queue: "test-queue", script: "test-script", class: "TestJob", args: [], constructor_args: [], state: "waiting", attempt: 0, }); await backend.updateJob({ id: oldJob.id, state: "completed", completed_at: veryOldTime, }); // Create a recent job await backend.createNewJob({ queue: "test-queue", script: "test-script", class: "TestJob", args: [], constructor_args: [], state: "waiting", attempt: 0, }); const result = await backend.countJobsOverTime("5m"); // The old job should not appear in the 5-minute window const totalCompleted = result.reduce((sum, bucket) => sum + bucket.completed, 0); expect(totalCompleted).toBe(0); // But the recent waiting job should appear const totalWaiting = result.reduce((sum, bucket) => sum + bucket.waiting, 0); expect(totalWaiting).toBeGreaterThanOrEqual(1); }); }); } export { defineCountJobsOverTimeTestSuite as default }; //# sourceMappingURL=countJobsOverTime.js.map