272 lines
7.9 KiB
TypeScript
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");
|
|
});
|
|
});
|