@@ -148,6 +148,7 @@ beforeEach(() => {
148148 ( findPullRequestBySha as ReturnType < typeof vi . fn > ) . mockResolvedValue ( null ) ;
149149 ( getLatestCheckRun as ReturnType < typeof vi . fn > ) . mockResolvedValue ( { conclusion : 'failure' } ) ;
150150 ( getPullRequest as ReturnType < typeof vi . fn > ) . mockResolvedValue ( {
151+ state : 'open' ,
151152 head : { sha : 'abc123' , ref : 'feature/login' } ,
152153 base : { sha : 'def456' , ref : 'main' } ,
153154 labels : [ ] ,
@@ -610,6 +611,41 @@ describe('workflow_run trigger — workflow_run event', () => {
610611 expect ( res ) . toEqual ( { status : 200 , body : 'Accepted' } ) ;
611612 expect ( scanQueue . add ) . toHaveBeenCalledOnce ( ) ;
612613 } ) ;
614+
615+ it ( 'does not enqueue a scan when cache hits but PR is already merged' , async ( ) => {
616+ ( getPullRequest as ReturnType < typeof vi . fn > ) . mockResolvedValueOnce ( { state : 'closed' } ) ;
617+
618+ const res = await processWebhookRequest ( webhookRequest ( workflowRunPayload ( ) , { event : 'workflow_run' } ) ) ;
619+
620+ expect ( res ) . toEqual ( { status : 200 , body : 'PR not found' } ) ;
621+ expect ( findPullRequestBySha ) . not . toHaveBeenCalled ( ) ;
622+ expect ( scanQueue . add ) . not . toHaveBeenCalled ( ) ;
623+ } ) ;
624+
625+ it ( 'does not enqueue when PR is merged by the time getPullRequest state check runs' , async ( ) => {
626+ ( redis . get as ReturnType < typeof vi . fn > ) . mockResolvedValueOnce ( null ) ;
627+ ( findPullRequestBySha as ReturnType < typeof vi . fn > ) . mockResolvedValueOnce ( {
628+ number : 42 ,
629+ head : { ref : 'feature/login' , sha : 'abc123' } ,
630+ base : { ref : 'main' , sha : 'def456' } ,
631+ labels : [ ] ,
632+ } ) ;
633+ ( getPullRequest as ReturnType < typeof vi . fn > ) . mockResolvedValueOnce ( { state : 'closed' } ) ;
634+
635+ const res = await processWebhookRequest ( webhookRequest ( workflowRunPayload ( ) , { event : 'workflow_run' } ) ) ;
636+
637+ expect ( res ) . toEqual ( { status : 200 , body : 'PR not found' } ) ;
638+ expect ( scanQueue . add ) . not . toHaveBeenCalled ( ) ;
639+ } ) ;
640+
641+ it ( 'returns PR not found when getPullRequest throws during state validation' , async ( ) => {
642+ ( getPullRequest as ReturnType < typeof vi . fn > ) . mockRejectedValueOnce ( new Error ( 'GitHub API error' ) ) ;
643+
644+ const res = await processWebhookRequest ( webhookRequest ( workflowRunPayload ( ) , { event : 'workflow_run' } ) ) ;
645+
646+ expect ( res ) . toEqual ( { status : 200 , body : 'PR not found' } ) ;
647+ expect ( scanQueue . add ) . not . toHaveBeenCalled ( ) ;
648+ } ) ;
613649} ) ;
614650
615651// ---------------------------------------------------------------------------
@@ -837,6 +873,16 @@ describe('workflow_job trigger — workflow_job event', () => {
837873 expect ( res ) . toEqual ( { status : 200 , body : 'Accepted' } ) ;
838874 expect ( scanQueue . add ) . toHaveBeenCalledOnce ( ) ;
839875 } ) ;
876+
877+ it ( 'does not enqueue a scan when cache hits but PR is already merged' , async ( ) => {
878+ ( getPullRequest as ReturnType < typeof vi . fn > ) . mockResolvedValueOnce ( { state : 'closed' } ) ;
879+
880+ const res = await processWebhookRequest ( webhookRequest ( workflowJobPayload ( ) , { event : 'workflow_job' } ) ) ;
881+
882+ expect ( res ) . toEqual ( { status : 200 , body : 'PR not found' } ) ;
883+ expect ( findPullRequestBySha ) . not . toHaveBeenCalled ( ) ;
884+ expect ( scanQueue . add ) . not . toHaveBeenCalled ( ) ;
885+ } ) ;
840886} ) ;
841887
842888// ---------------------------------------------------------------------------
@@ -1095,4 +1141,21 @@ describe('issue_comment handler', () => {
10951141 expect ( createPrComment ) . toHaveBeenCalled ( ) ;
10961142 expect ( scanQueue . add ) . not . toHaveBeenCalled ( ) ;
10971143 } ) ;
1144+
1145+ it ( 'does not store exceptions or enqueue scan when PR is already merged' , async ( ) => {
1146+ ( getPullRequest as ReturnType < typeof vi . fn > ) . mockResolvedValueOnce ( {
1147+ state : 'closed' ,
1148+ head : { sha : 'abc123' , ref : 'feature/login' } ,
1149+ base : { sha : 'def456' , ref : 'main' } ,
1150+ labels : [ ] ,
1151+ } ) ;
1152+
1153+ const res = await processWebhookRequest ( webhookRequest (
1154+ commentPayload ( ) , { event : 'issue_comment' }
1155+ ) ) ;
1156+
1157+ expect ( res ) . toEqual ( { status : 200 , body : 'PR not open' } ) ;
1158+ expect ( storeExceptions ) . not . toHaveBeenCalled ( ) ;
1159+ expect ( scanQueue . add ) . not . toHaveBeenCalled ( ) ;
1160+ } ) ;
10981161} ) ;
0 commit comments