@@ -236,7 +236,22 @@ describe('POST /api/v2/workflows/[id]/execute', () => {
236236 rateLimitSubscription : null ,
237237 keyType : 'workspace' ,
238238 } )
239- dbChainMockFns . limit . mockResolvedValue ( [ applicationContext ] )
239+ dbChainMockFns . limit
240+ . mockResolvedValueOnce ( [
241+ {
242+ workflowId : workflowRecord . id ,
243+ workflow : workflowRecord ,
244+ workspaceId : workflowRecord . workspaceId ,
245+ } ,
246+ ] )
247+ . mockResolvedValueOnce ( [
248+ {
249+ id : applicationContext . workspaceId ,
250+ organizationId : applicationContext . workspaceOrganizationId ,
251+ allowPersonalApiKeys : applicationContext . allowPersonalApiKeys ,
252+ billedAccountUserId : applicationContext . billedAccountUserId ,
253+ } ,
254+ ] )
240255 mockAuthorize . mockResolvedValue ( { allowed : true , workflow : workflowRecord } )
241256 mockClaimExecutionId . mockImplementation ( async ( executionId : string ) => ( {
242257 key : `workflow-execution-id:${ executionId } ` ,
@@ -420,9 +435,23 @@ describe('POST /api/v2/workflows/[id]/execute', () => {
420435 rateLimitSubscription : null ,
421436 keyType : 'personal' ,
422437 } )
423- dbChainMockFns . limit . mockResolvedValueOnce ( [
424- { ...applicationContext , allowPersonalApiKeys : false } ,
425- ] )
438+ dbChainMockFns . limit . mockReset ( )
439+ dbChainMockFns . limit
440+ . mockResolvedValueOnce ( [
441+ {
442+ workflowId : workflowRecord . id ,
443+ workflow : workflowRecord ,
444+ workspaceId : workflowRecord . workspaceId ,
445+ } ,
446+ ] )
447+ . mockResolvedValueOnce ( [
448+ {
449+ id : applicationContext . workspaceId ,
450+ organizationId : applicationContext . workspaceOrganizationId ,
451+ allowPersonalApiKeys : false ,
452+ billedAccountUserId : applicationContext . billedAccountUserId ,
453+ } ,
454+ ] )
426455
427456 const res = await callExecute ( { input : { } } )
428457
@@ -487,12 +516,16 @@ describe('POST /api/v2/workflows/[id]/execute', () => {
487516 } )
488517
489518 it ( 'runs the anonymous public path sync but refuses async' , async ( ) => {
519+ dbChainMockFns . limit . mockReset ( )
490520 dbChainMockFns . limit . mockResolvedValueOnce ( [
491521 { isPublicApi : true , isDeployed : true , userId : 'owner-1' , workspaceId : 'workspace-1' } ,
492522 ] )
493523
494524 const okRes = await callPublicExecute ( { input : { } } )
495525 expect ( okRes . status ) . toBe ( 200 )
526+ expect ( mockCheckPreAuthRate . mock . invocationCallOrder [ 0 ] ) . toBeLessThan (
527+ dbChainMockFns . select . mock . invocationCallOrder [ 0 ]
528+ )
496529 expect ( mockAuthenticateV2ApiKey ) . not . toHaveBeenCalled ( )
497530 expect ( mockCheckOperationRate ) . not . toHaveBeenCalled ( )
498531 expect ( mockPreprocessExecution ) . toHaveBeenCalledWith (
@@ -506,7 +539,24 @@ describe('POST /api/v2/workflows/[id]/execute', () => {
506539 expect ( asyncRes . status ) . toBe ( 400 )
507540 } )
508541
542+ it ( 'rejects anonymous abuse before looking up the workflow' , async ( ) => {
543+ mockCheckPreAuthRate . mockResolvedValueOnce ( {
544+ allowed : false ,
545+ remaining : 0 ,
546+ resetAt : new Date ( '2026-08-08T05:00:00Z' ) ,
547+ retryAfterMs : 10_000 ,
548+ } )
549+
550+ const response = await callPublicExecute ( { input : { } } )
551+
552+ expect ( response . status ) . toBe ( 429 )
553+ expect ( dbChainMockFns . select ) . not . toHaveBeenCalled ( )
554+ expect ( mockValidatePublicApiAllowed ) . not . toHaveBeenCalled ( )
555+ expect ( mockAuthenticateV2ApiKey ) . not . toHaveBeenCalled ( )
556+ } )
557+
509558 it ( '401s non-public workflows without a key' , async ( ) => {
559+ dbChainMockFns . limit . mockReset ( )
510560 dbChainMockFns . limit . mockResolvedValueOnce ( [
511561 { isPublicApi : false , isDeployed : true , userId : 'owner-1' , workspaceId : 'workspace-1' } ,
512562 ] )
@@ -532,6 +582,7 @@ describe('POST /api/v2/workflows/[id]/execute', () => {
532582 } )
533583
534584 it ( 'returns a safe error when canonical workflow lookup fails' , async ( ) => {
585+ dbChainMockFns . limit . mockReset ( )
535586 dbChainMockFns . limit . mockRejectedValueOnce ( new Error ( 'database connection details' ) )
536587
537588 const response = await callExecute ( { input : { } } )
0 commit comments