Files
2026-07-13 13:32:57 +08:00

272 lines
7.9 KiB
TypeScript

import { describe, expect, vi } from "vitest";
vi.mock("~/db.server", () => ({
prisma: {},
$replica: {},
}));
vi.mock("~/services/taskIdentifierCache.server", () => ({
getTaskIdentifiersFromCache: vi.fn().mockResolvedValue(null),
populateTaskIdentifierCache: vi.fn().mockResolvedValue(undefined),
}));
vi.mock("~/services/logger.server", () => ({
logger: { error: vi.fn(), warn: vi.fn(), info: vi.fn() },
}));
vi.mock("~/models/task.server", () => ({
getAllTaskIdentifiers: vi.fn().mockResolvedValue([]),
}));
import { setupAuthenticatedEnvironment } from "@internal/run-engine/tests";
import { postgresTest } from "@internal/testcontainers";
import { generateFriendlyId } from "@trigger.dev/core/v3/isomorphic";
import type { PrismaClient } from "@trigger.dev/database";
import {
syncTaskIdentifiers,
getTaskIdentifiers,
} from "../../app/services/taskIdentifierRegistry.server";
import type { AuthenticatedEnvironment } from "@internal/run-engine/tests";
vi.setConfig({ testTimeout: 30_000 });
async function createWorker(prisma: PrismaClient, env: AuthenticatedEnvironment) {
return prisma.backgroundWorker.create({
data: {
friendlyId: generateFriendlyId("worker"),
contentHash: `hash-${Date.now()}-${Math.random()}`,
projectId: env.project.id,
runtimeEnvironmentId: env.id,
version: `${Date.now()}`,
metadata: {},
engine: "V2",
},
});
}
describe("TaskIdentifierRegistry", () => {
postgresTest("should create task identifiers on first sync", async ({ prisma }) => {
const env = await setupAuthenticatedEnvironment(prisma, "PRODUCTION");
const worker = await createWorker(prisma, env);
await syncTaskIdentifiers(
env.id,
env.project.id,
worker.id,
[
{ id: "task-a", triggerSource: "STANDARD" },
{ id: "task-b", triggerSource: "SCHEDULED" },
],
prisma
);
const rows = await prisma.taskIdentifier.findMany({
where: { runtimeEnvironmentId: env.id },
orderBy: { slug: "asc" },
});
expect(rows).toHaveLength(2);
expect(rows[0].slug).toBe("task-a");
expect(rows[0].currentTriggerSource).toBe("STANDARD");
expect(rows[0].isInLatestDeployment).toBe(true);
expect(rows[1].slug).toBe("task-b");
expect(rows[1].currentTriggerSource).toBe("SCHEDULED");
expect(rows[1].isInLatestDeployment).toBe(true);
});
postgresTest("should update triggerSource on re-deploy", async ({ prisma }) => {
const env = await setupAuthenticatedEnvironment(prisma, "PRODUCTION");
const worker1 = await createWorker(prisma, env);
// First deploy: STANDARD
await syncTaskIdentifiers(
env.id,
env.project.id,
worker1.id,
[{ id: "my-task", triggerSource: "STANDARD" }],
prisma
);
const worker2 = await createWorker(prisma, env);
// Second deploy: change to SCHEDULED
await syncTaskIdentifiers(
env.id,
env.project.id,
worker2.id,
[{ id: "my-task", triggerSource: "SCHEDULED" }],
prisma
);
const rows = await prisma.taskIdentifier.findMany({
where: { runtimeEnvironmentId: env.id },
});
expect(rows).toHaveLength(1);
expect(rows[0].slug).toBe("my-task");
expect(rows[0].currentTriggerSource).toBe("SCHEDULED");
expect(rows[0].currentWorkerId).toBe(worker2.id);
});
postgresTest("should archive tasks removed in a deploy", async ({ prisma }) => {
const env = await setupAuthenticatedEnvironment(prisma, "PRODUCTION");
const worker1 = await createWorker(prisma, env);
// Deploy with both tasks
await syncTaskIdentifiers(
env.id,
env.project.id,
worker1.id,
[
{ id: "task-a", triggerSource: "STANDARD" },
{ id: "task-b", triggerSource: "STANDARD" },
],
prisma
);
const worker2 = await createWorker(prisma, env);
// Deploy with only task-a (task-b removed)
await syncTaskIdentifiers(
env.id,
env.project.id,
worker2.id,
[{ id: "task-a", triggerSource: "STANDARD" }],
prisma
);
const rows = await prisma.taskIdentifier.findMany({
where: { runtimeEnvironmentId: env.id },
orderBy: { slug: "asc" },
});
expect(rows).toHaveLength(2);
expect(rows[0].slug).toBe("task-a");
expect(rows[0].isInLatestDeployment).toBe(true);
expect(rows[1].slug).toBe("task-b");
expect(rows[1].isInLatestDeployment).toBe(false);
});
postgresTest("should resurrect archived tasks on re-deploy", async ({ prisma }) => {
const env = await setupAuthenticatedEnvironment(prisma, "PRODUCTION");
const worker1 = await createWorker(prisma, env);
// Deploy with both
await syncTaskIdentifiers(
env.id,
env.project.id,
worker1.id,
[
{ id: "task-a", triggerSource: "STANDARD" },
{ id: "task-b", triggerSource: "STANDARD" },
],
prisma
);
const worker2 = await createWorker(prisma, env);
// Deploy without task-b
await syncTaskIdentifiers(
env.id,
env.project.id,
worker2.id,
[{ id: "task-a", triggerSource: "STANDARD" }],
prisma
);
const worker3 = await createWorker(prisma, env);
// Deploy with task-b again
await syncTaskIdentifiers(
env.id,
env.project.id,
worker3.id,
[
{ id: "task-a", triggerSource: "STANDARD" },
{ id: "task-b", triggerSource: "STANDARD" },
],
prisma
);
const rows = await prisma.taskIdentifier.findMany({
where: { runtimeEnvironmentId: env.id },
orderBy: { slug: "asc" },
});
expect(rows).toHaveLength(2);
expect(rows[0].isInLatestDeployment).toBe(true);
expect(rows[1].isInLatestDeployment).toBe(true);
});
postgresTest(
"should return identifiers sorted active-first from DB when cache misses",
async ({ prisma }) => {
const env = await setupAuthenticatedEnvironment(prisma, "PRODUCTION");
const worker1 = await createWorker(prisma, env);
await syncTaskIdentifiers(
env.id,
env.project.id,
worker1.id,
[
{ id: "active-task", triggerSource: "STANDARD" },
{ id: "archived-task", triggerSource: "STANDARD" },
],
prisma
);
const worker2 = await createWorker(prisma, env);
// Archive one task
await syncTaskIdentifiers(
env.id,
env.project.id,
worker2.id,
[{ id: "active-task", triggerSource: "STANDARD" }],
prisma
);
// Read with cache miss (mocked to return null)
const result = await getTaskIdentifiers(env.id, prisma);
expect(result).toHaveLength(2);
// Active first
expect(result[0].slug).toBe("active-task");
expect(result[0].isInLatestDeployment).toBe(true);
// Archived second
expect(result[1].slug).toBe("archived-task");
expect(result[1].isInLatestDeployment).toBe(false);
}
);
postgresTest("should handle multiple triggerSource groups in one deploy", async ({ prisma }) => {
const env = await setupAuthenticatedEnvironment(prisma, "PRODUCTION");
const worker = await createWorker(prisma, env);
// Note: AGENT enum value is missing from migrations (no ALTER TYPE migration exists),
// so we test with STANDARD + SCHEDULED only. AGENT works in prod because the enum
// was added via prisma db push or manual ALTER.
await syncTaskIdentifiers(
env.id,
env.project.id,
worker.id,
[
{ id: "standard-task", triggerSource: "STANDARD" },
{ id: "scheduled-task", triggerSource: "SCHEDULED" },
],
prisma
);
const rows = await prisma.taskIdentifier.findMany({
where: { runtimeEnvironmentId: env.id },
orderBy: { slug: "asc" },
});
expect(rows).toHaveLength(2);
expect(rows[0].slug).toBe("scheduled-task");
expect(rows[0].currentTriggerSource).toBe("SCHEDULED");
expect(rows[1].slug).toBe("standard-task");
expect(rows[1].currentTriggerSource).toBe("STANDARD");
});
});