@@ -434,6 +434,73 @@ describe('rate limit', () => {
434434 new Parse . Error ( Parse . Error . CONNECTION_FAILED , 'Too many requests' )
435435 ) ;
436436 } ) ;
437+
438+ it ( 'should rate limit per user independently with user zone' , async ( ) => {
439+ await reconfigureServer ( {
440+ rateLimit : {
441+ requestPath : '/functions/*path' ,
442+ requestTimeWindow : 10000 ,
443+ requestCount : 1 ,
444+ errorResponseMessage : 'Too many requests' ,
445+ includeInternalRequests : true ,
446+ zone : Parse . Server . RateLimitZone . user ,
447+ } ,
448+ } ) ;
449+ Parse . Cloud . define ( 'test' , ( ) => 'Abc' ) ;
450+ // Sign up two different users using REST API to avoid destroying sessions
451+ const res1 = await request ( {
452+ method : 'POST' ,
453+ headers : headers ,
454+ url : 'http://localhost:8378/1/users' ,
455+ body : JSON . stringify ( { username : 'user1' , password : 'password' } ) ,
456+ } ) ;
457+ const sessionToken1 = res1 . data . sessionToken ;
458+ const res2 = await request ( {
459+ method : 'POST' ,
460+ headers : headers ,
461+ url : 'http://localhost:8378/1/users' ,
462+ body : JSON . stringify ( { username : 'user2' , password : 'password' } ) ,
463+ } ) ;
464+ const sessionToken2 = res2 . data . sessionToken ;
465+ // User 1 makes a request — should succeed
466+ const result1 = await request ( {
467+ method : 'POST' ,
468+ headers : { ...headers , 'X-Parse-Session-Token' : sessionToken1 } ,
469+ url : 'http://localhost:8378/1/functions/test' ,
470+ body : JSON . stringify ( { } ) ,
471+ } ) ;
472+ expect ( result1 . data . result ) . toBe ( 'Abc' ) ;
473+ // User 2 makes a request — should also succeed (independent rate limit per user)
474+ const result2 = await request ( {
475+ method : 'POST' ,
476+ headers : { ...headers , 'X-Parse-Session-Token' : sessionToken2 } ,
477+ url : 'http://localhost:8378/1/functions/test' ,
478+ body : JSON . stringify ( { } ) ,
479+ } ) ;
480+ expect ( result2 . data . result ) . toBe ( 'Abc' ) ;
481+ // User 1 makes another request — should be rate limited
482+ const result3 = await request ( {
483+ method : 'POST' ,
484+ headers : { ...headers , 'X-Parse-Session-Token' : sessionToken1 } ,
485+ url : 'http://localhost:8378/1/functions/test' ,
486+ body : JSON . stringify ( { } ) ,
487+ } ) . catch ( e => e ) ;
488+ expect ( result3 . data ) . toEqual ( {
489+ code : Parse . Error . CONNECTION_FAILED ,
490+ error : 'Too many requests' ,
491+ } ) ;
492+ // User 2 makes another request — should also be rate limited
493+ const result4 = await request ( {
494+ method : 'POST' ,
495+ headers : { ...headers , 'X-Parse-Session-Token' : sessionToken2 } ,
496+ url : 'http://localhost:8378/1/functions/test' ,
497+ body : JSON . stringify ( { } ) ,
498+ } ) . catch ( e => e ) ;
499+ expect ( result4 . data ) . toEqual ( {
500+ code : Parse . Error . CONNECTION_FAILED ,
501+ error : 'Too many requests' ,
502+ } ) ;
503+ } ) ;
437504 } ) ;
438505
439506 it ( 'can validate rateLimit' , async ( ) => {
@@ -679,6 +746,94 @@ describe('rate limit', () => {
679746 } ) ;
680747 } ) ;
681748
749+ it ( 'should enforce rate limit across direct requests and batch sub-requests' , async ( ) => {
750+ await reconfigureServer ( {
751+ rateLimit : [
752+ {
753+ requestPath : '/classes/*path' ,
754+ requestTimeWindow : 10000 ,
755+ requestCount : 2 ,
756+ errorResponseMessage : 'Too many requests' ,
757+ includeInternalRequests : true ,
758+ } ,
759+ ] ,
760+ } ) ;
761+ // First direct request — should succeed (count: 1)
762+ const obj = new Parse . Object ( 'MyObject' ) ;
763+ await obj . save ( ) ;
764+ // Batch with 1 sub-request — should succeed (count: 2)
765+ const response1 = await request ( {
766+ method : 'POST' ,
767+ headers : headers ,
768+ url : 'http://localhost:8378/1/batch' ,
769+ body : JSON . stringify ( {
770+ requests : [
771+ { method : 'POST' , path : '/1/classes/MyObject' , body : { key : 'value1' } } ,
772+ ] ,
773+ } ) ,
774+ } ) ;
775+ expect ( response1 . data . length ) . toBe ( 1 ) ;
776+ expect ( response1 . data [ 0 ] . success ) . toBeDefined ( ) ;
777+ // Another batch with 1 sub-request — should be rate limited (count would be 3)
778+ const response2 = await request ( {
779+ method : 'POST' ,
780+ headers : headers ,
781+ url : 'http://localhost:8378/1/batch' ,
782+ body : JSON . stringify ( {
783+ requests : [
784+ { method : 'POST' , path : '/1/classes/MyObject' , body : { key : 'value2' } } ,
785+ ] ,
786+ } ) ,
787+ } ) . catch ( e => e ) ;
788+ expect ( response2 . data ) . toEqual ( {
789+ code : Parse . Error . CONNECTION_FAILED ,
790+ error : 'Too many requests' ,
791+ } ) ;
792+ } ) ;
793+
794+ it ( 'should enforce rate limit for multiple batch requests in same window' , async ( ) => {
795+ await reconfigureServer ( {
796+ rateLimit : [
797+ {
798+ requestPath : '/classes/*path' ,
799+ requestTimeWindow : 10000 ,
800+ requestCount : 2 ,
801+ errorResponseMessage : 'Too many requests' ,
802+ includeInternalRequests : true ,
803+ } ,
804+ ] ,
805+ } ) ;
806+ // First batch with 2 sub-requests — should succeed (count: 2)
807+ const response1 = await request ( {
808+ method : 'POST' ,
809+ headers : headers ,
810+ url : 'http://localhost:8378/1/batch' ,
811+ body : JSON . stringify ( {
812+ requests : [
813+ { method : 'POST' , path : '/1/classes/MyObject' , body : { key : 'value1' } } ,
814+ { method : 'POST' , path : '/1/classes/MyObject' , body : { key : 'value2' } } ,
815+ ] ,
816+ } ) ,
817+ } ) ;
818+ expect ( response1 . data . length ) . toBe ( 2 ) ;
819+ expect ( response1 . data [ 0 ] . success ) . toBeDefined ( ) ;
820+ // Second batch with 1 sub-request — should be rate limited (count would be 3)
821+ const response2 = await request ( {
822+ method : 'POST' ,
823+ headers : headers ,
824+ url : 'http://localhost:8378/1/batch' ,
825+ body : JSON . stringify ( {
826+ requests : [
827+ { method : 'POST' , path : '/1/classes/MyObject' , body : { key : 'value3' } } ,
828+ ] ,
829+ } ) ,
830+ } ) . catch ( e => e ) ;
831+ expect ( response2 . data ) . toEqual ( {
832+ code : Parse . Error . CONNECTION_FAILED ,
833+ error : 'Too many requests' ,
834+ } ) ;
835+ } ) ;
836+
682837 it ( 'should not reject batch when sub-requests target non-rate-limited paths' , async ( ) => {
683838 await reconfigureServer ( {
684839 rateLimit : [
0 commit comments