@@ -4,10 +4,7 @@ import type { PrismaClient } from "@trigger.dev/database";
44import { postgresTest } from "@internal/testcontainers" ;
55import { describe , expect , vi } from "vitest" ;
66import { FEATURE_FLAG , type FeatureFlagKey } from "~/v3/featureFlags" ;
7- import {
8- makeSetMultipleFlags ,
9- replaceGlobalFeatureFlags ,
10- } from "~/v3/featureFlags.server" ;
7+ import { makeSetMultipleFlags , replaceGlobalFeatureFlags } from "~/v3/featureFlags.server" ;
118
129vi . setConfig ( { testTimeout : 60_000 } ) ;
1310
@@ -40,124 +37,129 @@ async function readFlags(
4037}
4138
4239describe ( "replaceGlobalFeatureFlags — graces runOpsMintKind, preserves replace semantics" , ( ) => {
43- postgresTest ( "a runOpsMintKind flip stamps prev + flippedAt (graced, not a bare upsert)" , async ( {
44- prisma,
45- } ) => {
46- await makeSetMultipleFlags ( prisma ) ( { [ FEATURE_FLAG . runOpsMintKind ] : "cuid" } ) ;
47-
48- await replaceGlobalFeatureFlags ( prisma , {
49- requestedFlags : { [ FEATURE_FLAG . runOpsMintKind ] : "runOpsId" } ,
50- catalogKeys : CATALOG_KEYS ,
51- isProtected : NEVER_PROTECTED ,
52- graceMs : 60_000 ,
53- } ) ;
54-
55- const m = await readFlags ( prisma , MINT_KEYS ) ;
56- expect ( m [ FEATURE_FLAG . runOpsMintKind ] ) . toBe ( "runOpsId" ) ;
57- expect ( m [ FEATURE_FLAG . runOpsMintKindPrev ] ) . toBe ( "cuid" ) ;
58- expect ( typeof m [ FEATURE_FLAG . runOpsMintKindFlippedAt ] ) . toBe ( "string" ) ;
59- } ) ;
60-
61- postgresTest ( "derived stamp fields supplied in the body are ignored (computed server-side)" , async ( {
62- prisma,
63- } ) => {
64- await makeSetMultipleFlags ( prisma ) ( { [ FEATURE_FLAG . runOpsMintKind ] : "cuid" } ) ;
65-
66- await replaceGlobalFeatureFlags ( prisma , {
67- // Caller forges the stamp: prev="runOpsId", flippedAt=epoch. Both must be ignored.
68- requestedFlags : {
69- [ FEATURE_FLAG . runOpsMintKind ] : "runOpsId" ,
70- [ FEATURE_FLAG . runOpsMintKindPrev ] : "runOpsId" ,
71- [ FEATURE_FLAG . runOpsMintKindFlippedAt ] : "1970-01-01T00:00:00.000Z" ,
72- } ,
73- catalogKeys : CATALOG_KEYS ,
74- isProtected : NEVER_PROTECTED ,
75- graceMs : 60_000 ,
76- } ) ;
77-
78- const m = await readFlags ( prisma , MINT_KEYS ) ;
79- expect ( m [ FEATURE_FLAG . runOpsMintKindPrev ] ) . toBe ( "cuid" ) ;
80- expect ( m [ FEATURE_FLAG . runOpsMintKindFlippedAt ] ) . not . toBe ( "1970-01-01T00:00:00.000Z" ) ;
81- } ) ;
82-
83- postgresTest ( "re-applying the same runOpsMintKind does not reset the grace clock" , async ( {
84- prisma,
85- } ) => {
86- await makeSetMultipleFlags ( prisma ) ( { [ FEATURE_FLAG . runOpsMintKind ] : "cuid" } ) ;
87-
88- await replaceGlobalFeatureFlags ( prisma , {
89- requestedFlags : { [ FEATURE_FLAG . runOpsMintKind ] : "runOpsId" } ,
90- catalogKeys : CATALOG_KEYS ,
91- isProtected : NEVER_PROTECTED ,
92- graceMs : 60_000 ,
93- } ) ;
94- const first = await readFlags ( prisma , MINT_KEYS ) ;
95-
96- await replaceGlobalFeatureFlags ( prisma , {
97- requestedFlags : { [ FEATURE_FLAG . runOpsMintKind ] : "runOpsId" } ,
98- catalogKeys : CATALOG_KEYS ,
99- isProtected : NEVER_PROTECTED ,
100- graceMs : 60_000 ,
101- } ) ;
102- const second = await readFlags ( prisma , MINT_KEYS ) ;
103-
104- expect ( second [ FEATURE_FLAG . runOpsMintKind ] ) . toBe ( "runOpsId" ) ;
105- expect ( second [ FEATURE_FLAG . runOpsMintKindPrev ] ) . toBe ( first [ FEATURE_FLAG . runOpsMintKindPrev ] ) ;
106- expect ( second [ FEATURE_FLAG . runOpsMintKindFlippedAt ] ) . toBe (
107- first [ FEATURE_FLAG . runOpsMintKindFlippedAt ]
108- ) ;
109- } ) ;
110-
111- postgresTest ( "the mint trio survives a replace that omits runOpsMintKind (not swept)" , async ( {
112- prisma,
113- } ) => {
114- await replaceGlobalFeatureFlags ( prisma , {
115- requestedFlags : { [ FEATURE_FLAG . runOpsMintKind ] : "runOpsId" } ,
116- catalogKeys : CATALOG_KEYS ,
117- isProtected : NEVER_PROTECTED ,
118- graceMs : 60_000 ,
119- } ) ;
120- const before = await readFlags ( prisma , MINT_KEYS ) ;
121-
122- // A later save that edits only an unrelated flag and omits the mint keys entirely.
123- await replaceGlobalFeatureFlags ( prisma , {
124- requestedFlags : { [ FEATURE_FLAG . mollifierEnabled ] : true } ,
125- catalogKeys : CATALOG_KEYS ,
126- isProtected : NEVER_PROTECTED ,
127- graceMs : 60_000 ,
128- } ) ;
129-
130- const after = await readFlags ( prisma , MINT_KEYS ) ;
131- expect ( after [ FEATURE_FLAG . runOpsMintKind ] ) . toBe ( "runOpsId" ) ;
132- expect ( after [ FEATURE_FLAG . runOpsMintKindPrev ] ) . toBe ( before [ FEATURE_FLAG . runOpsMintKindPrev ] ) ;
133- expect ( after [ FEATURE_FLAG . runOpsMintKindFlippedAt ] ) . toBe (
134- before [ FEATURE_FLAG . runOpsMintKindFlippedAt ]
135- ) ;
136- const mollifier = await readFlags ( prisma , [ FEATURE_FLAG . mollifierEnabled ] ) ;
137- expect ( mollifier [ FEATURE_FLAG . mollifierEnabled ] ) . toBe ( true ) ;
138- } ) ;
139-
140- postgresTest ( "non-mint flags keep replace semantics: submitted upserts, omitted deletes" , async ( {
141- prisma,
142- } ) => {
143- await replaceGlobalFeatureFlags ( prisma , {
144- requestedFlags : { [ FEATURE_FLAG . mollifierEnabled ] : true } ,
145- catalogKeys : CATALOG_KEYS ,
146- isProtected : NEVER_PROTECTED ,
147- graceMs : 60_000 ,
148- } ) ;
149-
150- await replaceGlobalFeatureFlags ( prisma , {
151- requestedFlags : { [ FEATURE_FLAG . hasAiAccess ] : true } ,
152- catalogKeys : CATALOG_KEYS ,
153- isProtected : NEVER_PROTECTED ,
154- graceMs : 60_000 ,
155- } ) ;
156-
157- const m = await readFlags ( prisma , [ FEATURE_FLAG . mollifierEnabled , FEATURE_FLAG . hasAiAccess ] ) ;
158- expect ( m [ FEATURE_FLAG . hasAiAccess ] ) . toBe ( true ) ;
159- expect ( m [ FEATURE_FLAG . mollifierEnabled ] ) . toBeUndefined ( ) ;
160- } ) ;
40+ postgresTest (
41+ "a runOpsMintKind flip stamps prev + flippedAt (graced, not a bare upsert)" ,
42+ async ( { prisma } ) => {
43+ await makeSetMultipleFlags ( prisma ) ( { [ FEATURE_FLAG . runOpsMintKind ] : "cuid" } ) ;
44+
45+ await replaceGlobalFeatureFlags ( prisma , {
46+ requestedFlags : { [ FEATURE_FLAG . runOpsMintKind ] : "runOpsId" } ,
47+ catalogKeys : CATALOG_KEYS ,
48+ isProtected : NEVER_PROTECTED ,
49+ graceMs : 60_000 ,
50+ } ) ;
51+
52+ const m = await readFlags ( prisma , MINT_KEYS ) ;
53+ expect ( m [ FEATURE_FLAG . runOpsMintKind ] ) . toBe ( "runOpsId" ) ;
54+ expect ( m [ FEATURE_FLAG . runOpsMintKindPrev ] ) . toBe ( "cuid" ) ;
55+ expect ( typeof m [ FEATURE_FLAG . runOpsMintKindFlippedAt ] ) . toBe ( "string" ) ;
56+ }
57+ ) ;
58+
59+ postgresTest (
60+ "derived stamp fields supplied in the body are ignored (computed server-side)" ,
61+ async ( { prisma } ) => {
62+ await makeSetMultipleFlags ( prisma ) ( { [ FEATURE_FLAG . runOpsMintKind ] : "cuid" } ) ;
63+
64+ await replaceGlobalFeatureFlags ( prisma , {
65+ // Caller forges the stamp: prev="runOpsId", flippedAt=epoch. Both must be ignored.
66+ requestedFlags : {
67+ [ FEATURE_FLAG . runOpsMintKind ] : "runOpsId" ,
68+ [ FEATURE_FLAG . runOpsMintKindPrev ] : "runOpsId" ,
69+ [ FEATURE_FLAG . runOpsMintKindFlippedAt ] : "1970-01-01T00:00:00.000Z" ,
70+ } ,
71+ catalogKeys : CATALOG_KEYS ,
72+ isProtected : NEVER_PROTECTED ,
73+ graceMs : 60_000 ,
74+ } ) ;
75+
76+ const m = await readFlags ( prisma , MINT_KEYS ) ;
77+ expect ( m [ FEATURE_FLAG . runOpsMintKindPrev ] ) . toBe ( "cuid" ) ;
78+ expect ( m [ FEATURE_FLAG . runOpsMintKindFlippedAt ] ) . not . toBe ( "1970-01-01T00:00:00.000Z" ) ;
79+ }
80+ ) ;
81+
82+ postgresTest (
83+ "re-applying the same runOpsMintKind does not reset the grace clock" ,
84+ async ( { prisma } ) => {
85+ await makeSetMultipleFlags ( prisma ) ( { [ FEATURE_FLAG . runOpsMintKind ] : "cuid" } ) ;
86+
87+ await replaceGlobalFeatureFlags ( prisma , {
88+ requestedFlags : { [ FEATURE_FLAG . runOpsMintKind ] : "runOpsId" } ,
89+ catalogKeys : CATALOG_KEYS ,
90+ isProtected : NEVER_PROTECTED ,
91+ graceMs : 60_000 ,
92+ } ) ;
93+ const first = await readFlags ( prisma , MINT_KEYS ) ;
94+
95+ await replaceGlobalFeatureFlags ( prisma , {
96+ requestedFlags : { [ FEATURE_FLAG . runOpsMintKind ] : "runOpsId" } ,
97+ catalogKeys : CATALOG_KEYS ,
98+ isProtected : NEVER_PROTECTED ,
99+ graceMs : 60_000 ,
100+ } ) ;
101+ const second = await readFlags ( prisma , MINT_KEYS ) ;
102+
103+ expect ( second [ FEATURE_FLAG . runOpsMintKind ] ) . toBe ( "runOpsId" ) ;
104+ expect ( second [ FEATURE_FLAG . runOpsMintKindPrev ] ) . toBe ( first [ FEATURE_FLAG . runOpsMintKindPrev ] ) ;
105+ expect ( second [ FEATURE_FLAG . runOpsMintKindFlippedAt ] ) . toBe (
106+ first [ FEATURE_FLAG . runOpsMintKindFlippedAt ]
107+ ) ;
108+ }
109+ ) ;
110+
111+ postgresTest (
112+ "the mint trio survives a replace that omits runOpsMintKind (not swept)" ,
113+ async ( { prisma } ) => {
114+ await replaceGlobalFeatureFlags ( prisma , {
115+ requestedFlags : { [ FEATURE_FLAG . runOpsMintKind ] : "runOpsId" } ,
116+ catalogKeys : CATALOG_KEYS ,
117+ isProtected : NEVER_PROTECTED ,
118+ graceMs : 60_000 ,
119+ } ) ;
120+ const before = await readFlags ( prisma , MINT_KEYS ) ;
121+
122+ // A later save that edits only an unrelated flag and omits the mint keys entirely.
123+ await replaceGlobalFeatureFlags ( prisma , {
124+ requestedFlags : { [ FEATURE_FLAG . mollifierEnabled ] : true } ,
125+ catalogKeys : CATALOG_KEYS ,
126+ isProtected : NEVER_PROTECTED ,
127+ graceMs : 60_000 ,
128+ } ) ;
129+
130+ const after = await readFlags ( prisma , MINT_KEYS ) ;
131+ expect ( after [ FEATURE_FLAG . runOpsMintKind ] ) . toBe ( "runOpsId" ) ;
132+ expect ( after [ FEATURE_FLAG . runOpsMintKindPrev ] ) . toBe ( before [ FEATURE_FLAG . runOpsMintKindPrev ] ) ;
133+ expect ( after [ FEATURE_FLAG . runOpsMintKindFlippedAt ] ) . toBe (
134+ before [ FEATURE_FLAG . runOpsMintKindFlippedAt ]
135+ ) ;
136+ const mollifier = await readFlags ( prisma , [ FEATURE_FLAG . mollifierEnabled ] ) ;
137+ expect ( mollifier [ FEATURE_FLAG . mollifierEnabled ] ) . toBe ( true ) ;
138+ }
139+ ) ;
140+
141+ postgresTest (
142+ "non-mint flags keep replace semantics: submitted upserts, omitted deletes" ,
143+ async ( { prisma } ) => {
144+ await replaceGlobalFeatureFlags ( prisma , {
145+ requestedFlags : { [ FEATURE_FLAG . mollifierEnabled ] : true } ,
146+ catalogKeys : CATALOG_KEYS ,
147+ isProtected : NEVER_PROTECTED ,
148+ graceMs : 60_000 ,
149+ } ) ;
150+
151+ await replaceGlobalFeatureFlags ( prisma , {
152+ requestedFlags : { [ FEATURE_FLAG . hasAiAccess ] : true } ,
153+ catalogKeys : CATALOG_KEYS ,
154+ isProtected : NEVER_PROTECTED ,
155+ graceMs : 60_000 ,
156+ } ) ;
157+
158+ const m = await readFlags ( prisma , [ FEATURE_FLAG . mollifierEnabled , FEATURE_FLAG . hasAiAccess ] ) ;
159+ expect ( m [ FEATURE_FLAG . hasAiAccess ] ) . toBe ( true ) ;
160+ expect ( m [ FEATURE_FLAG . mollifierEnabled ] ) . toBeUndefined ( ) ;
161+ }
162+ ) ;
161163
162164 postgresTest ( "protected omitted flags are not deleted" , async ( { prisma } ) => {
163165 await replaceGlobalFeatureFlags ( prisma , {
0 commit comments