Skip to content

Commit ca53728

Browse files
committed
test(webapp): drive the flags route against a real database
The route tests stubbed the writer and the database client and asserted on the arguments the action passed. They now run against a container Postgres and assert on the rows the save leaves behind, so they check what was persisted rather than what was called. Only the auth wrapper is still substituted, so the handler can be invoked without a super-admin session.
1 parent 953281c commit ca53728

1 file changed

Lines changed: 94 additions & 44 deletions

File tree

Lines changed: 94 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
1-
// The page posts only the flags its UI manages, so the action's reading of an absent key is the
2-
// whole bug surface. These drive the real exported action with the auth wrapper unwrapped, and
3-
// assert on what it hands the writer.
4-
import { describe, expect, it, vi } from "vitest";
1+
// The page posts only the flags its UI manages, so how the action reads an absent key is the whole
2+
// bug surface. These drive the real exported action against a real Postgres and assert on the rows
3+
// it leaves behind. The only module substituted is the auth wrapper, so the handler can be called
4+
// without a super-admin session; the database is the genuine article, injected into db.server.
5+
import { boundedIn } from "@trigger.dev/database";
6+
import type { PrismaClient } from "@trigger.dev/database";
7+
import { postgresTest } from "@internal/testcontainers";
8+
import { describe, expect, vi } from "vitest";
59
import { FEATURE_FLAG } from "~/v3/featureFlags";
610

7-
const { replaceGlobalFeatureFlags } = vi.hoisted(() => ({
8-
replaceGlobalFeatureFlags: vi.fn().mockResolvedValue(undefined),
9-
}));
11+
vi.setConfig({ testTimeout: 60_000 });
12+
13+
const db = vi.hoisted(() => ({ client: null as unknown as PrismaClient }));
1014

1115
vi.mock("~/services/routeBuilders/dashboardBuilder", () => ({
1216
dashboardAction: (_options: unknown, handler: unknown) => handler,
1317
dashboardLoader: (_options: unknown, handler: unknown) => handler,
1418
}));
15-
vi.mock("~/v3/featureFlags.server", () => ({
16-
replaceGlobalFeatureFlags,
17-
flags: vi.fn().mockResolvedValue({}),
19+
20+
vi.mock("~/db.server", () => ({
21+
get prisma() {
22+
return db.client;
23+
},
24+
boundedIn,
1825
}));
19-
vi.mock("~/db.server", () => ({ prisma: {}, boundedIn: (v: unknown) => v }));
2026

21-
const { action } = await import("~/routes/admin.feature-flags");
27+
import { action } from "~/routes/admin.feature-flags";
28+
29+
const WORKER_GROUP_ID = "clwg000000000000000000000";
2230

2331
async function post(host: string, body: unknown) {
2432
const request = new Request(`https://${host}/admin/feature-flags`, {
@@ -29,59 +37,101 @@ async function post(host: string, body: unknown) {
2937
return (await (action as any)({ request, params: {}, context: {} })) as Response;
3038
}
3139

40+
async function readFlag(prisma: PrismaClient, key: string) {
41+
const row = await prisma.featureFlag.findFirst({ where: { key }, select: { value: true } });
42+
return row?.value;
43+
}
44+
45+
async function seed(prisma: PrismaClient) {
46+
db.client = prisma;
47+
await prisma.featureFlag.createMany({
48+
data: [
49+
{ id: "ff_locked", key: FEATURE_FLAG.defaultWorkerInstanceGroupId, value: WORKER_GROUP_ID },
50+
{ id: "ff_plain", key: FEATURE_FLAG.mollifierEnabled, value: true },
51+
],
52+
});
53+
}
54+
3255
describe("admin feature flags action", () => {
33-
it("defaults unlockLockedFlags to false when the field is absent", async () => {
34-
replaceGlobalFeatureFlags.mockClear();
56+
postgresTest("keeps the locked flag when the page did not unlock it", async ({ prisma }) => {
57+
await seed(prisma);
58+
3559
const response = await post("localhost:3030", { flags: {} });
3660

3761
expect(response.status).toBe(200);
38-
expect(replaceGlobalFeatureFlags).toHaveBeenCalledTimes(1);
39-
expect(replaceGlobalFeatureFlags.mock.calls[0][1]).toMatchObject({
40-
unlockLockedFlags: false,
41-
isManagedCloud: false,
42-
});
62+
expect(await readFlag(prisma, FEATURE_FLAG.defaultWorkerInstanceGroupId)).toBe(WORKER_GROUP_ID);
63+
expect(await readFlag(prisma, FEATURE_FLAG.mollifierEnabled)).toBeUndefined();
4364
});
4465

45-
it("passes unlockLockedFlags through when the page says it unlocked them", async () => {
46-
replaceGlobalFeatureFlags.mockClear();
47-
await post("localhost:3030", { flags: {}, unlockLockedFlags: true });
66+
postgresTest("keeps the locked flag when the body omits the unlock field", async ({ prisma }) => {
67+
await seed(prisma);
68+
69+
// A tab opened before the field existed posts the old shape.
70+
const response = await post("localhost:3030", { flags: {}, unlockLockedFlags: undefined });
4871

49-
expect(replaceGlobalFeatureFlags.mock.calls[0][1]).toMatchObject({ unlockLockedFlags: true });
72+
expect(response.status).toBe(200);
73+
expect(await readFlag(prisma, FEATURE_FLAG.defaultWorkerInstanceGroupId)).toBe(WORKER_GROUP_ID);
5074
});
5175

52-
it("marks a managed cloud host as such", async () => {
53-
replaceGlobalFeatureFlags.mockClear();
54-
await post("cloud.trigger.dev", { flags: {}, unlockLockedFlags: true });
76+
postgresTest("deletes the locked flag when the page unlocked it", async ({ prisma }) => {
77+
await seed(prisma);
78+
79+
await post("localhost:3030", { flags: {}, unlockLockedFlags: true });
5580

56-
expect(replaceGlobalFeatureFlags.mock.calls[0][1]).toMatchObject({ isManagedCloud: true });
81+
expect(await readFlag(prisma, FEATURE_FLAG.defaultWorkerInstanceGroupId)).toBeUndefined();
5782
});
5883

59-
it("rejects a locked flag submitted to managed cloud without writing", async () => {
60-
replaceGlobalFeatureFlags.mockClear();
61-
const response = await post("cloud.trigger.dev", {
62-
flags: { [FEATURE_FLAG.defaultWorkerInstanceGroupId]: "clwg0001" },
63-
});
84+
postgresTest(
85+
"keeps the locked flag on managed cloud despite the unlock claim",
86+
async ({ prisma }) => {
87+
await seed(prisma);
6488

65-
expect(response.status).toBe(400);
66-
expect(replaceGlobalFeatureFlags).not.toHaveBeenCalled();
67-
});
89+
await post("cloud.trigger.dev", { flags: {}, unlockLockedFlags: true });
90+
91+
expect(await readFlag(prisma, FEATURE_FLAG.defaultWorkerInstanceGroupId)).toBe(
92+
WORKER_GROUP_ID
93+
);
94+
}
95+
);
96+
97+
postgresTest(
98+
"rejects a locked flag submitted to managed cloud, writing nothing",
99+
async ({ prisma }) => {
100+
await seed(prisma);
101+
102+
const response = await post("cloud.trigger.dev", {
103+
flags: { [FEATURE_FLAG.defaultWorkerInstanceGroupId]: "clwg999" },
104+
});
105+
106+
expect(response.status).toBe(400);
107+
expect(await readFlag(prisma, FEATURE_FLAG.defaultWorkerInstanceGroupId)).toBe(
108+
WORKER_GROUP_ID
109+
);
110+
expect(await readFlag(prisma, FEATURE_FLAG.mollifierEnabled)).toBe(true);
111+
}
112+
);
113+
114+
postgresTest("rejects a value the catalog refuses, writing nothing", async ({ prisma }) => {
115+
await seed(prisma);
68116

69-
it("rejects a value that fails the catalog schema without writing", async () => {
70-
replaceGlobalFeatureFlags.mockClear();
71117
const response = await post("localhost:3030", {
72118
flags: { [FEATURE_FLAG.realtimeBackend]: "not-a-backend" },
73119
});
74120

75121
expect(response.status).toBe(400);
76-
expect(replaceGlobalFeatureFlags).not.toHaveBeenCalled();
122+
expect(await readFlag(prisma, FEATURE_FLAG.mollifierEnabled)).toBe(true);
77123
});
78124

79-
it("submits every catalog key so omitted flags are swept", async () => {
80-
replaceGlobalFeatureFlags.mockClear();
81-
await post("localhost:3030", { flags: { [FEATURE_FLAG.mollifierEnabled]: true } });
125+
postgresTest("upserts what was submitted and sweeps what was not", async ({ prisma }) => {
126+
await seed(prisma);
127+
128+
await post("localhost:3030", {
129+
flags: { [FEATURE_FLAG.hasAiAccess]: true },
130+
unlockLockedFlags: false,
131+
});
82132

83-
const { catalogKeys, requestedFlags } = replaceGlobalFeatureFlags.mock.calls[0][1];
84-
expect(catalogKeys).toContain(FEATURE_FLAG.defaultWorkerInstanceGroupId);
85-
expect(requestedFlags).toEqual({ [FEATURE_FLAG.mollifierEnabled]: true });
133+
expect(await readFlag(prisma, FEATURE_FLAG.hasAiAccess)).toBe(true);
134+
expect(await readFlag(prisma, FEATURE_FLAG.mollifierEnabled)).toBeUndefined();
135+
expect(await readFlag(prisma, FEATURE_FLAG.defaultWorkerInstanceGroupId)).toBe(WORKER_GROUP_ID);
86136
});
87137
});

0 commit comments

Comments
 (0)