@@ -308,4 +308,114 @@ mod tests {
308308 let resp = test:: call_service ( & app, req) . await ;
309309 assert_eq ! ( resp. status( ) , 401 ) ;
310310 }
311+
312+ #[ actix_web:: test]
313+ async fn test_send_chargelog_rate_limit ( ) {
314+ let ( mut user, _mail) = TestUser :: random ( ) . await ;
315+ user. login ( ) . await ;
316+ let charger = user. add_random_charger ( ) . await ;
317+
318+ let app = App :: new ( ) . configure ( configure) . service ( send_chargelog) ;
319+ let app = test:: init_service ( app) . await ;
320+
321+ let user_uuid = crate :: routes:: user:: tests:: get_test_uuid ( & user. mail )
322+ . unwrap ( )
323+ . to_string ( ) ;
324+
325+ let metadata = json ! ( {
326+ "charger_uuid" : charger. uuid,
327+ "password" : charger. password,
328+ "user_uuid" : user_uuid,
329+ "display_name" : "Test Device" ,
330+ "filename" : "chargelog.pdf" ,
331+ "monthly_send" : true
332+ } ) ;
333+
334+ let boundary = "----testboundary3" ;
335+ let ip = "123.123.123.100" ;
336+
337+ // Send requests up to the burst limit (5 in test mode)
338+ for i in 0 ..5 {
339+ let body = build_multipart_body ( boundary, & metadata, & [ 1 , 2 , 3 , 4 , 5 ] ) ;
340+ let req = test:: TestRequest :: post ( )
341+ . uri ( "/send_chargelog_to_user" )
342+ . append_header ( ( "X-Forwarded-For" , ip) )
343+ . append_header ( (
344+ "Content-Type" ,
345+ format ! ( "multipart/form-data; boundary={boundary}" ) ,
346+ ) )
347+ . set_payload ( body)
348+ . to_request ( ) ;
349+ let resp = test:: call_service ( & app, req) . await ;
350+ assert_eq ! (
351+ resp. status( ) ,
352+ 200 ,
353+ "Request {} should succeed (within burst limit)" ,
354+ i + 1
355+ ) ;
356+ }
357+
358+ // The 6th request should be rate limited
359+ let body = build_multipart_body ( boundary, & metadata, & [ 1 , 2 , 3 , 4 , 5 ] ) ;
360+ let req = test:: TestRequest :: post ( )
361+ . uri ( "/send_chargelog_to_user" )
362+ . append_header ( ( "X-Forwarded-For" , ip) )
363+ . append_header ( (
364+ "Content-Type" ,
365+ format ! ( "multipart/form-data; boundary={boundary}" ) ,
366+ ) )
367+ . set_payload ( body)
368+ . to_request ( ) ;
369+ let resp = test:: call_service ( & app, req) . await ;
370+ assert_eq ! (
371+ resp. status( ) ,
372+ 429 ,
373+ "Request 6 should be rate limited (429 Too Many Requests)"
374+ ) ;
375+
376+ // Verify that a different IP can still make requests
377+ let body = build_multipart_body ( boundary, & metadata, & [ 1 , 2 , 3 , 4 , 5 ] ) ;
378+ let req = test:: TestRequest :: post ( )
379+ . uri ( "/send_chargelog_to_user" )
380+ . append_header ( ( "X-Forwarded-For" , "123.123.123.101" ) )
381+ . append_header ( (
382+ "Content-Type" ,
383+ format ! ( "multipart/form-data; boundary={boundary}" ) ,
384+ ) )
385+ . set_payload ( body)
386+ . to_request ( ) ;
387+ let resp = test:: call_service ( & app, req) . await ;
388+ assert_eq ! (
389+ resp. status( ) ,
390+ 200 ,
391+ "Request from different IP should succeed"
392+ ) ;
393+
394+ // Verify that a different charger from the same IP can make requests
395+ let charger2 = user. add_random_charger ( ) . await ;
396+ let metadata2 = json ! ( {
397+ "charger_uuid" : charger2. uuid,
398+ "password" : charger2. password,
399+ "user_uuid" : user_uuid,
400+ "display_name" : "Test Device 2" ,
401+ "filename" : "chargelog2.pdf" ,
402+ "monthly_send" : false
403+ } ) ;
404+ let body = build_multipart_body ( boundary, & metadata2, & [ 1 , 2 , 3 , 4 , 5 ] ) ;
405+ let req = test:: TestRequest :: post ( )
406+ . uri ( "/send_chargelog_to_user" )
407+ . append_header ( ( "X-Forwarded-For" , ip) )
408+ . append_header ( (
409+ "Content-Type" ,
410+ format ! ( "multipart/form-data; boundary={boundary}" ) ,
411+ ) )
412+ . set_payload ( body)
413+ . to_request ( ) ;
414+ let resp = test:: call_service ( & app, req) . await ;
415+ assert_eq ! (
416+ resp. status( ) ,
417+ 200 ,
418+ "Request from different charger (same IP) should succeed"
419+ ) ;
420+ }
311421}
0 commit comments