@@ -8,6 +8,7 @@ const mocks = vi.hoisted(() => ({
88 getPublicIds : vi . fn ( ) ,
99 getSequence : vi . fn ( ) ,
1010 lockBroadcast : vi . fn ( ) ,
11+ markBroadcastSent : vi . fn ( ) ,
1112 captureError : vi . fn ( ) ,
1213 captureEvent : vi . fn ( ) ,
1314} ) ) ;
@@ -20,6 +21,7 @@ vi.mock("./queries", () => ({
2021 getMatchingPublicContactIds : mocks . getPublicIds ,
2122 getSequenceRowById : mocks . getSequence ,
2223 lockBroadcast : mocks . lockBroadcast ,
24+ markBroadcastSent : mocks . markBroadcastSent ,
2325} ) ) ;
2426vi . mock ( "../observability/posthog" , ( ) => ( {
2527 captureError : mocks . captureError ,
@@ -44,9 +46,11 @@ beforeEach(() => {
4446 mocks . getInternalIds . mockResolvedValue ( [ "contact-internal" ] ) ;
4547 mocks . getPublicIds . mockResolvedValue ( [ "cnt_public" ] ) ;
4648 mocks . lockBroadcast . mockResolvedValue ( undefined ) ;
49+ mocks . markBroadcastSent . mockResolvedValue ( undefined ) ;
4750 mocks . getSequence . mockResolvedValue ( {
4851 id : "sequence-internal" ,
4952 sequenceId : "seq_public" ,
53+ type : "broadcast" ,
5054 filter : { aggregator : "and" , filters : [ ] } ,
5155 } ) ;
5256} ) ;
@@ -84,12 +88,23 @@ describe("scheduled broadcast rule processing", () => {
8488 expect ( mocks . lockBroadcast ) . toHaveBeenCalledWith ( "sequence-internal" , [
8589 "cnt_public" ,
8690 ] ) ;
91+ expect ( mocks . markBroadcastSent ) . not . toHaveBeenCalled ( ) ;
8792 expect ( order ) . toEqual ( [ "enroll" , "lock" , "delete" ] ) ;
8893 } ) ;
8994
90- it ( "handles an empty audience and still consumes the due rule" , async ( ) => {
95+ it ( "completes an empty- audience broadcast after lock and still consumes the rule" , async ( ) => {
9196 mocks . getInternalIds . mockResolvedValue ( [ ] ) ;
9297 mocks . getPublicIds . mockResolvedValue ( [ ] ) ;
98+ const order : string [ ] = [ ] ;
99+ mocks . lockBroadcast . mockImplementation ( async ( ) => {
100+ order . push ( "lock" ) ;
101+ } ) ;
102+ mocks . markBroadcastSent . mockImplementation ( async ( ) => {
103+ order . push ( "complete" ) ;
104+ } ) ;
105+ mocks . deleteRule . mockImplementation ( async ( ) => {
106+ order . push ( "delete" ) ;
107+ } ) ;
93108
94109 await processRule ( rule ) ;
95110
@@ -100,6 +115,34 @@ describe("scheduled broadcast rule processing", () => {
100115 "sequence-internal" ,
101116 [ ] ,
102117 ) ;
118+ expect ( mocks . markBroadcastSent ) . toHaveBeenCalledWith ( "seq_public" ) ;
119+ expect ( mocks . captureEvent ) . toHaveBeenCalledWith (
120+ expect . objectContaining ( {
121+ event : "broadcast_sent" ,
122+ source : "automation.process_rules" ,
123+ properties : expect . objectContaining ( {
124+ sequence_id : "seq_public" ,
125+ recipients_count : 0 ,
126+ } ) ,
127+ } ) ,
128+ ) ;
129+ expect ( mocks . deleteRule ) . toHaveBeenCalledWith ( "rule_1" ) ;
130+ expect ( order ) . toEqual ( [ "lock" , "complete" , "delete" ] ) ;
131+ } ) ;
132+
133+ it ( "does not mark non-broadcast sequences completed on empty match" , async ( ) => {
134+ mocks . getInternalIds . mockResolvedValue ( [ ] ) ;
135+ mocks . getPublicIds . mockResolvedValue ( [ ] ) ;
136+ mocks . getSequence . mockResolvedValue ( {
137+ id : "sequence-internal" ,
138+ sequenceId : "seq_public" ,
139+ type : "sequence" ,
140+ filter : { aggregator : "and" , filters : [ ] } ,
141+ } ) ;
142+
143+ await processRule ( rule ) ;
144+
145+ expect ( mocks . markBroadcastSent ) . not . toHaveBeenCalled ( ) ;
103146 expect ( mocks . deleteRule ) . toHaveBeenCalledWith ( "rule_1" ) ;
104147 } ) ;
105148
0 commit comments