@@ -169,4 +169,58 @@ describe("with admin access", () => {
169169 expect ( lastHourlyCount . hour ) . toEqual ( new Date ( ) . toISOString ( ) . slice ( 0 , 13 ) + ':00:00.000Z' ) ;
170170 expect ( lastHourlyCount . count ) . toBe ( 1 ) ;
171171 } ) ;
172+
173+ it ( "should read rule_id from ClickHouse events with COALESCE for both camelCase and snake_case data" , async ( { expect } ) => {
174+ // Regression test: a ClickHouse migration converts ruleId -> rule_id (snake_case).
175+ // The stats query must handle both field name formats via COALESCE.
176+ // This test verifies the COALESCE query reads the correct rule_id from the event.
177+ await Project . createAndSwitch ( ) ;
178+ await Project . updateConfig ( {
179+ 'auth.signUpRules.coalesce-rule' : {
180+ enabled : true ,
181+ displayName : 'COALESCE Test Rule' ,
182+ priority : 1 ,
183+ condition : 'true' ,
184+ action : { type : 'log' } ,
185+ } ,
186+ } ) ;
187+
188+ await Auth . Password . signUpWithEmail ( ) ;
189+
190+ // Wait for the ClickHouse event to appear and verify via a raw COALESCE query
191+ let chResult : any ;
192+ for ( let attempt = 0 ; attempt < 15 ; attempt ++ ) {
193+ await wait ( 500 ) ;
194+ chResult = await niceBackendFetch ( "/api/v1/internal/analytics/query" , {
195+ method : "POST" ,
196+ accessType : "admin" ,
197+ body : {
198+ query : `
199+ SELECT
200+ COALESCE(
201+ NULLIF(CAST(data.rule_id, 'Nullable(String)'), ''),
202+ NULLIF(CAST(data.ruleId, 'Nullable(String)'), '')
203+ ) as rule_id
204+ FROM events
205+ WHERE event_type = '$sign-up-rule-trigger'
206+ LIMIT 1
207+ ` ,
208+ params : { } ,
209+ } ,
210+ } ) ;
211+ if ( chResult . status === 200 && chResult . body ?. result ?. length > 0 ) break ;
212+ }
213+
214+ expect ( chResult . status ) . toBe ( 200 ) ;
215+ expect ( chResult . body . result . length ) . toBeGreaterThan ( 0 ) ;
216+ expect ( chResult . body . result [ 0 ] . rule_id ) . toBe ( 'coalesce-rule' ) ;
217+
218+ // Verify the stats endpoint returns correct data with the COALESCE-based query
219+ const response = await niceBackendFetch ( "/api/v1/internal/sign-up-rules-stats" , { accessType : "admin" } ) ;
220+ expect ( response . status ) . toBe ( 200 ) ;
221+ expect ( response . body . rule_triggers . length ) . toBeGreaterThan ( 0 ) ;
222+ const trigger = response . body . rule_triggers . find ( ( t : any ) => t . rule_id === 'coalesce-rule' ) ;
223+ expect ( trigger ) . toBeTruthy ( ) ;
224+ expect ( trigger . total_count ) . toBeGreaterThanOrEqual ( 1 ) ;
225+ } ) ;
172226} ) ;
0 commit comments