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" ;
59import { 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
1115vi . 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
2331async 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+
3255describe ( "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