Files
outline/server/queues/tasks/base/CronTask.test.ts
T
Tom Moor 57308c46af chore: resolve lint warnings (no-explicit-any, no-redundant-type-constituents, no-base-to-string) (#12209)
* chore: resolve no-redundant-type-constituents and test/mock no-explicit-any warnings

Clears 36 lint warnings: all 5 no-redundant-type-constituents, 6
no-misused-spread (via narrowing getPartitionWhereClause's return type
to WhereAttributeHash), and 25 no-explicit-any in test/mock files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: resolve no-base-to-string warnings in tests

Convert userProvisioner try/catch error assertions to Jest's
.rejects.toThrow() idiom, and cast webhook test body to string.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: resolve no-explicit-any warnings in cancan and tracing

Tighten types in the cancan policy framework and tracing decorators.
Constructor / generic-function upper bounds keep `any` where TypeScript
variance requires it, scoped to single-line oxlint-disable comments.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 22:55:30 -04:00

367 lines
12 KiB
TypeScript

import { Op } from "sequelize";
import { Minute } from "@shared/utils/time";
import type { PartitionInfo } from "./CronTask";
import { CronTask, TaskInterval } from "./CronTask";
type RangeWhere = Record<string, { [Op.gte]: string; [Op.lte]: string }>;
// Create a concrete implementation of CronTask for testing
class TestTask extends CronTask {
public async perform() {
// Not used in these tests
}
public get cron() {
return {
interval: TaskInterval.Day,
};
}
public testPartitionWhereClause(
idField: string,
partition: PartitionInfo | undefined
): RangeWhere {
return this.getPartitionWhereClause(idField, partition) as RangeWhere;
}
}
describe("CronTask", () => {
let task: TestTask;
beforeEach(() => {
task = new TestTask();
});
describe("getStaggerDelay", () => {
it("should return a deterministic delay for the same task name", () => {
const delay1 = CronTask.getStaggerDelay("TaskA", TaskInterval.Hour);
const delay2 = CronTask.getStaggerDelay("TaskA", TaskInterval.Hour);
expect(delay1).toBe(delay2);
});
it("should return different delays for different task names", () => {
const delayA = CronTask.getStaggerDelay(
"CleanupDeletedDocumentsTask",
TaskInterval.Hour
);
const delayB = CronTask.getStaggerDelay(
"CleanupOldEventsTask",
TaskInterval.Hour
);
expect(delayA).not.toBe(delayB);
});
it("should stay within the hourly stagger window (10 minutes)", () => {
const names = [
"CleanupDeletedDocumentsTask",
"CleanupOldEventsTask",
"CleanupOldNotificationsTask",
"CleanupDeletedTeamsTask",
"CleanupExpiredAttachmentsTask",
"CleanupExpiredFileOperationsTask",
];
for (const name of names) {
const delay = CronTask.getStaggerDelay(name, TaskInterval.Hour);
expect(delay).toBeGreaterThanOrEqual(0);
expect(delay).toBeLessThan(10 * Minute.ms);
}
});
it("should stay within the daily stagger window (30 minutes)", () => {
const names = [
"CleanupOAuthAuthorizationCodeTask",
"CleanupDynamicOAuthClientsTask",
"CleanupOldImportsTask",
];
for (const name of names) {
const delay = CronTask.getStaggerDelay(name, TaskInterval.Day);
expect(delay).toBeGreaterThanOrEqual(0);
expect(delay).toBeLessThan(30 * Minute.ms);
}
});
it("should distribute delays across the window for real task names", () => {
const names = [
"CleanupDeletedDocumentsTask",
"CleanupOldEventsTask",
"CleanupOldNotificationsTask",
"CleanupDeletedTeamsTask",
"CleanupExpiredAttachmentsTask",
"CleanupExpiredFileOperationsTask",
];
const delays = names.map((name) =>
CronTask.getStaggerDelay(name, TaskInterval.Hour)
);
const unique = new Set(delays);
expect(unique.size).toBe(delays.length);
});
});
describe("getPartitionWhereClause", () => {
it("should return empty object when partition is undefined", () => {
const where = task.testPartitionWhereClause("id", undefined);
expect(where).toEqual({});
});
it("should generate range WHERE clause for valid partition", () => {
const where = task.testPartitionWhereClause("id", {
partitionIndex: 0,
partitionCount: 3,
});
expect(where).toBeDefined();
expect(where.id).toBeDefined();
expect(where.id[Op.gte]).toBeDefined();
expect(where.id[Op.lte]).toBeDefined();
});
it("should generate correct UUID ranges for 3 partitions", () => {
const where0 = task.testPartitionWhereClause("id", {
partitionIndex: 0,
partitionCount: 3,
});
const where1 = task.testPartitionWhereClause("id", {
partitionIndex: 1,
partitionCount: 3,
});
const where2 = task.testPartitionWhereClause("id", {
partitionIndex: 2,
partitionCount: 3,
});
// Partition 0: Should start from 00000000
expect(where0.id[Op.gte]).toBe("00000000-0000-4000-8000-000000000000");
expect(where0.id[Op.lte]).toBe("55555554-ffff-4fff-bfff-ffffffffffff");
// Partition 1: Should start from 55555555
expect(where1.id[Op.gte]).toBe("55555555-0000-4000-8000-000000000000");
expect(where1.id[Op.lte]).toBe("aaaaaaa9-ffff-4fff-bfff-ffffffffffff");
// Partition 2: Should end at ffffffff
expect(where2.id[Op.gte]).toBe("aaaaaaaa-0000-4000-8000-000000000000");
expect(where2.id[Op.lte]).toBe("ffffffff-ffff-4fff-bfff-ffffffffffff");
});
it("should generate correct UUID ranges for 2 partitions", () => {
const where0 = task.testPartitionWhereClause("id", {
partitionIndex: 0,
partitionCount: 2,
});
const where1 = task.testPartitionWhereClause("id", {
partitionIndex: 1,
partitionCount: 2,
});
// Partition 0: 0x00000000 to 0x7fffffff
expect(where0.id[Op.gte]).toBe("00000000-0000-4000-8000-000000000000");
expect(where0.id[Op.lte]).toBe("7fffffff-ffff-4fff-bfff-ffffffffffff");
// Partition 1: 0x80000000 to 0xffffffff
expect(where1.id[Op.gte]).toBe("80000000-0000-4000-8000-000000000000");
expect(where1.id[Op.lte]).toBe("ffffffff-ffff-4fff-bfff-ffffffffffff");
});
it("should distribute UUID space evenly", () => {
const partitionCount = 4;
const ranges: Array<{ start: string; end: string }> = [];
for (let i = 0; i < partitionCount; i++) {
const where = task.testPartitionWhereClause("id", {
partitionIndex: i,
partitionCount,
});
ranges.push({
start: where.id[Op.gte],
end: where.id[Op.lte],
});
}
// Check that ranges don't overlap and cover the entire space
expect(ranges[0].start).toBe("00000000-0000-4000-8000-000000000000");
expect(ranges[3].end).toBe("ffffffff-ffff-4fff-bfff-ffffffffffff");
// Check that each range ends where the next begins (approximately)
for (let i = 0; i < partitionCount - 1; i++) {
const currentEnd = ranges[i].end.substring(0, 8);
const nextStart = ranges[i + 1].start.substring(0, 8);
// Convert to numbers to check they're consecutive
const currentEndNum = parseInt(currentEnd, 16);
const nextStartNum = parseInt(nextStart, 16);
// Should be consecutive or very close
expect(nextStartNum - currentEndNum).toBeLessThanOrEqual(1);
}
});
it("should handle single partition (no partitioning)", () => {
const where = task.testPartitionWhereClause("id", {
partitionIndex: 0,
partitionCount: 1,
});
// Should cover entire UUID space
expect(where.id[Op.gte]).toBe("00000000-0000-4000-8000-000000000000");
expect(where.id[Op.lte]).toBe("ffffffff-ffff-4fff-bfff-ffffffffffff");
});
it("should throw error for invalid partition info", () => {
expect(() => {
task.testPartitionWhereClause("id", {
partitionIndex: -1,
partitionCount: 3,
});
}).toThrow("Invalid partition info: index -1, count 3");
expect(() => {
task.testPartitionWhereClause("id", {
partitionIndex: 3,
partitionCount: 3,
});
}).toThrow("Invalid partition info: index 3, count 3");
expect(() => {
task.testPartitionWhereClause("id", {
partitionIndex: 0,
partitionCount: 0,
});
}).toThrow("Invalid partition info: index 0, count 0");
});
it("should work with different field names", () => {
const where1 = task.testPartitionWhereClause("id", {
partitionIndex: 0,
partitionCount: 2,
});
const where2 = task.testPartitionWhereClause("documentId", {
partitionIndex: 0,
partitionCount: 2,
});
expect(where1.id).toBeDefined();
expect(where1.documentId).toBeUndefined();
expect(where2.documentId).toBeDefined();
expect(where2.id).toBeUndefined();
});
it("should handle large partition counts efficiently", () => {
const partitionCount = 100;
const ranges: Array<{ start: string; end: string }> = [];
for (let i = 0; i < partitionCount; i++) {
const where = task.testPartitionWhereClause("id", {
partitionIndex: i,
partitionCount,
});
ranges.push({
start: where.id[Op.gte],
end: where.id[Op.lte],
});
}
// First partition should start at 00000000
expect(ranges[0].start).toBe("00000000-0000-4000-8000-000000000000");
// Last partition should end at ffffffff
expect(ranges[99].end).toBe("ffffffff-ffff-4fff-bfff-ffffffffffff");
// Each partition should have a unique range
const startValues = new Set(ranges.map((r) => r.start));
expect(startValues.size).toBe(100);
});
it("should calculate correct hex values for partition boundaries", () => {
// Test specific calculations
const where = task.testPartitionWhereClause("id", {
partitionIndex: 1,
partitionCount: 16, // 16 partitions = 0x10000000 per partition
});
// Partition 1 should be from 0x10000000 to 0x1fffffff
expect(where.id[Op.gte]).toBe("10000000-0000-4000-8000-000000000000");
expect(where.id[Op.lte]).toBe("1fffffff-ffff-4fff-bfff-ffffffffffff");
});
it("should ensure all UUIDs map to exactly one partition", () => {
const testUuids = [
"00000000-0000-4000-8000-000000000000", // Min UUID
"12345678-9abc-4ef0-9234-567890abcdef",
"55555555-5555-4555-9555-555555555555",
"87654321-fedc-4a98-b654-321098765432",
"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa",
"deadbeef-cafe-4abe-aeed-dec0debac1e5",
"ffffffff-ffff-4fff-bfff-ffffffffffff", // Max UUID
];
const partitionCount = 3;
for (const uuid of testUuids) {
let matchCount = 0;
let matchedPartition = -1;
for (let i = 0; i < partitionCount; i++) {
const where = task.testPartitionWhereClause("id", {
partitionIndex: i,
partitionCount,
});
const startUuid = where.id[Op.gte];
const endUuid = where.id[Op.lte];
// Check if UUID falls within this partition's range
if (uuid >= startUuid && uuid <= endUuid) {
matchCount++;
matchedPartition = i;
}
}
// Each UUID should match exactly one partition
expect(matchCount).toBe(1);
expect(matchedPartition).toBeGreaterThanOrEqual(0);
expect(matchedPartition).toBeLessThan(partitionCount);
}
});
it("should generate non-overlapping ranges for any partition count", () => {
const testCounts = [2, 3, 5, 7, 10, 16, 32];
for (const partitionCount of testCounts) {
const ranges: Array<{ start: string; end: string }> = [];
// Get all partition ranges
for (let i = 0; i < partitionCount; i++) {
const where = task.testPartitionWhereClause("id", {
partitionIndex: i,
partitionCount,
});
ranges.push({
start: where.id[Op.gte],
end: where.id[Op.lte],
});
}
// Verify no gaps between consecutive partitions
for (let i = 0; i < partitionCount - 1; i++) {
const currentEnd = ranges[i].end.substring(0, 8);
const nextStart = ranges[i + 1].start.substring(0, 8);
const currentEndNum = parseInt(currentEnd, 16);
const nextStartNum = parseInt(nextStart, 16);
// Next partition should start exactly where current ends + 1
expect(nextStartNum).toBe(currentEndNum + 1);
}
// Verify coverage of entire UUID space
expect(ranges[0].start).toBe("00000000-0000-4000-8000-000000000000");
expect(ranges[partitionCount - 1].end).toBe(
"ffffffff-ffff-4fff-bfff-ffffffffffff"
);
}
});
});
});