@@ -37,6 +37,9 @@ describe('/api/events/backfill', () => {
3737 await analytics . command ( {
3838 query : `DELETE FROM messages WHERE taskId LIKE 'test-%'` ,
3939 } ) ;
40+ await analytics . command ( {
41+ query : `DELETE FROM events WHERE taskId LIKE 'test-%'` ,
42+ } ) ;
4043 } catch {
4144 // Ignore cleanup errors.
4245 }
@@ -92,6 +95,41 @@ describe('/api/events/backfill', () => {
9295 expect ( response . status ) . toBe ( 200 ) ;
9396 expect ( responseData . success ) . toBe ( true ) ;
9497
98+ const eventsResults = await analytics . query ( {
99+ query : `
100+ SELECT
101+ taskId,
102+ type,
103+ mode,
104+ userId,
105+ orgId,
106+ timestamp
107+ FROM events
108+ WHERE taskId = 'test-task-integration' AND type = 'Task Created'
109+ ORDER BY timestamp ASC
110+ ` ,
111+ format : 'JSONEachRow' ,
112+ } ) ;
113+
114+ const eventData = ( await eventsResults . json ( ) ) as Array < {
115+ taskId : string ;
116+ type : string ;
117+ mode : string ;
118+ userId : string ;
119+ orgId : string ;
120+ timestamp : number ;
121+ } > ;
122+
123+ expect ( eventData ) . toHaveLength ( 1 ) ;
124+ expect ( eventData [ 0 ] ) . toMatchObject ( {
125+ taskId : 'test-task-integration' ,
126+ type : 'Task Created' ,
127+ mode : 'code' ,
128+ userId : 'test-user-id' ,
129+ orgId : 'test-org-id' ,
130+ } ) ;
131+ expect ( typeof eventData [ 0 ] ?. timestamp ) . toBe ( 'number' ) ;
132+
95133 const dbResults = await analytics . query ( {
96134 query : `
97135 SELECT
@@ -175,6 +213,41 @@ describe('/api/events/backfill', () => {
175213 expect ( response . status ) . toBe ( 200 ) ;
176214 expect ( responseData . success ) . toBe ( true ) ;
177215
216+ const eventsResults = await analytics . query ( {
217+ query : `
218+ SELECT
219+ taskId,
220+ type,
221+ mode,
222+ userId,
223+ orgId,
224+ timestamp
225+ FROM events
226+ WHERE taskId = 'test-task-from-file' AND type = 'Task Created'
227+ ORDER BY timestamp ASC
228+ ` ,
229+ format : 'JSONEachRow' ,
230+ } ) ;
231+
232+ const eventData = ( await eventsResults . json ( ) ) as Array < {
233+ taskId : string ;
234+ type : string ;
235+ mode : string ;
236+ userId : string ;
237+ orgId : string ;
238+ timestamp : number ;
239+ } > ;
240+
241+ expect ( eventData ) . toHaveLength ( 1 ) ;
242+ expect ( eventData [ 0 ] ) . toMatchObject ( {
243+ taskId : 'test-task-from-file' ,
244+ type : 'Task Created' ,
245+ mode : 'code' ,
246+ userId : 'test-user-id' ,
247+ orgId : 'test-org-id' ,
248+ } ) ;
249+ expect ( typeof eventData [ 0 ] ?. timestamp ) . toBe ( 'number' ) ;
250+
178251 const dbResults = await analytics . query ( {
179252 query : `
180253 SELECT
@@ -263,6 +336,14 @@ describe('/api/events/backfill', () => {
263336
264337 const dbData = ( await dbResults . json ( ) ) as Array < { count : string } > ;
265338 expect ( dbData [ 0 ] ?. count ) . toBe ( '0' ) ;
339+
340+ const eventsResults = await analytics . query ( {
341+ query : `SELECT COUNT() as count FROM events WHERE taskId = 'test' AND type = 'Task Created'` ,
342+ format : 'JSONEachRow' ,
343+ } ) ;
344+
345+ const eventsData = ( await eventsResults . json ( ) ) as Array < { count : string } > ;
346+ expect ( eventsData [ 0 ] ?. count ) . toBe ( '0' ) ;
266347 } ) ;
267348
268349 it ( 'should return 400 if no file is provided' , async ( ) => {
@@ -440,6 +521,41 @@ describe('/api/events/backfill', () => {
440521 expect ( response . status ) . toBe ( 200 ) ;
441522 expect ( responseData . success ) . toBe ( true ) ;
442523
524+ const eventsResults = await analytics . query ( {
525+ query : `
526+ SELECT
527+ taskId,
528+ type,
529+ mode,
530+ userId,
531+ orgId,
532+ timestamp
533+ FROM events
534+ WHERE taskId = 'test-task-mode-extraction' AND type = 'Task Created'
535+ ORDER BY timestamp ASC
536+ ` ,
537+ format : 'JSONEachRow' ,
538+ } ) ;
539+
540+ const eventData = ( await eventsResults . json ( ) ) as Array < {
541+ taskId : string ;
542+ type : string ;
543+ mode : string ;
544+ userId : string ;
545+ orgId : string ;
546+ timestamp : number ;
547+ } > ;
548+
549+ expect ( eventData ) . toHaveLength ( 1 ) ;
550+ expect ( eventData [ 0 ] ) . toMatchObject ( {
551+ taskId : 'test-task-mode-extraction' ,
552+ type : 'Task Created' ,
553+ mode : 'debug' ,
554+ userId : 'test-user-id' ,
555+ orgId : 'test-org-id' ,
556+ } ) ;
557+ expect ( typeof eventData [ 0 ] ?. timestamp ) . toBe ( 'number' ) ;
558+
443559 const dbResults = await analytics . query ( {
444560 query : `
445561 SELECT
@@ -490,6 +606,107 @@ describe('/api/events/backfill', () => {
490606 } ) ;
491607 } ) ;
492608
609+ it ( 'should emit TASK_CREATED event with correct properties and timestamp' , async ( ) => {
610+ mockAuthorizeApi . mockResolvedValue ( {
611+ success : true ,
612+ userId : 'test-user-id' ,
613+ orgId : 'test-org-id' ,
614+ userType : 'user' ,
615+ orgRole : 'admin' ,
616+ } as unknown as ApiAuthResult ) ;
617+
618+ const messages = [
619+ {
620+ ts : 1750702747687 ,
621+ type : 'say' as const ,
622+ say : 'text' as const ,
623+ text : 'Test task creation' ,
624+ images : [ ] ,
625+ } ,
626+ ] ;
627+
628+ const fileContent = JSON . stringify ( messages ) ;
629+ const file = new File ( [ fileContent ] , 'test-messages.json' , {
630+ type : 'application/json' ,
631+ } ) ;
632+
633+ const customProperties = {
634+ ...testProperties ,
635+ mode : 'architect' ,
636+ apiProvider : 'openai' ,
637+ modelId : 'gpt-4' ,
638+ } ;
639+
640+ const formData = new FormData ( ) ;
641+ formData . append ( 'file' , file ) ;
642+ formData . append ( 'taskId' , 'test-task-created-event' ) ;
643+ formData . append ( 'properties' , JSON . stringify ( customProperties ) ) ;
644+
645+ const request = new NextRequest (
646+ 'http://localhost:3000/api/events/backfill' ,
647+ {
648+ method : 'POST' ,
649+ body : formData ,
650+ } ,
651+ ) ;
652+
653+ const response = await POST ( request ) ;
654+ const responseData = await response . json ( ) ;
655+
656+ expect ( response . status ) . toBe ( 200 ) ;
657+ expect ( responseData . success ) . toBe ( true ) ;
658+
659+ const eventsResults = await analytics . query ( {
660+ query : `
661+ SELECT
662+ taskId,
663+ type,
664+ mode,
665+ userId,
666+ orgId,
667+ timestamp,
668+ apiProvider,
669+ modelId,
670+ appVersion,
671+ platform
672+ FROM events
673+ WHERE taskId = 'test-task-created-event' AND type = 'Task Created'
674+ ` ,
675+ format : 'JSONEachRow' ,
676+ } ) ;
677+
678+ const eventData = ( await eventsResults . json ( ) ) as Array < {
679+ taskId : string ;
680+ type : string ;
681+ mode : string ;
682+ userId : string ;
683+ orgId : string ;
684+ timestamp : number ;
685+ apiProvider : string ;
686+ modelId : string ;
687+ appVersion : string ;
688+ platform : string ;
689+ } > ;
690+
691+ expect ( eventData ) . toHaveLength ( 1 ) ;
692+
693+ const taskCreatedEvent = eventData [ 0 ] ! ;
694+ expect ( taskCreatedEvent ) . toMatchObject ( {
695+ taskId : 'test-task-created-event' ,
696+ type : 'Task Created' ,
697+ mode : 'architect' ,
698+ userId : 'test-user-id' ,
699+ orgId : 'test-org-id' ,
700+ apiProvider : 'openai' ,
701+ modelId : 'gpt-4' ,
702+ appVersion : '1.0.0' ,
703+ platform : 'darwin' ,
704+ } ) ;
705+
706+ const expectedTimestamp = Math . round ( messages [ 0 ] ! . ts / 1000 ) ;
707+ expect ( taskCreatedEvent . timestamp ) . toBe ( expectedTimestamp ) ;
708+ } ) ;
709+
493710 it ( 'should handle invalid ClineMessage schema' , async ( ) => {
494711 mockAuthorizeApi . mockResolvedValue ( {
495712 success : true ,
0 commit comments