@@ -989,4 +989,87 @@ public async Task SendEnvelopeAsync_Response413_RecordsSendErrorDiscard()
989989 recorder . DiscardedEvents . Should ( ) . ContainKey ( DiscardReason . SendError . WithCategory ( DataCategory . Error ) ) ;
990990 recorder . DiscardedEvents . Should ( ) . NotContainKey ( DiscardReason . NetworkError . WithCategory ( DataCategory . Error ) ) ;
991991 }
992+
993+ [ Fact ]
994+ public async Task SendEnvelopeAsync_Response429WithJsonMessage_LogsWarning ( )
995+ {
996+ // Arrange
997+ const string expectedDetail = "Sentry dropped data due to a quota or internal rate limit being reached." ;
998+
999+ var httpHandler = Substitute . For < MockableHttpMessageHandler > ( ) ;
1000+
1001+ httpHandler . VerifiableSendAsync ( Arg . Any < HttpRequestMessage > ( ) , Arg . Any < CancellationToken > ( ) )
1002+ . Returns ( _ => SentryResponses . GetJsonErrorResponse ( ( HttpStatusCode ) 429 , expectedDetail ) ) ;
1003+
1004+ var logger = new InMemoryDiagnosticLogger ( ) ;
1005+
1006+ var httpTransport = new HttpTransport (
1007+ new SentryOptions
1008+ {
1009+ Dsn = ValidDsn ,
1010+ Debug = true ,
1011+ DiagnosticLogger = logger
1012+ } ,
1013+ new HttpClient ( httpHandler ) ) ;
1014+
1015+ var envelope = Envelope . FromEvent ( new SentryEvent ( ) ) ;
1016+
1017+ // Act
1018+ await httpTransport . SendEnvelopeAsync ( envelope ) ;
1019+
1020+ // Assert
1021+ var warningEntry = logger . Entries . FirstOrDefault ( e =>
1022+ e . Level == SentryLevel . Warning &&
1023+ e . Message . Contains ( "due to rate limiting" ) ) ;
1024+
1025+ warningEntry . Should ( ) . NotBeNull ( ) ;
1026+ warningEntry ! . Message . Should ( ) . Contain ( "exceeded your quota" ) ;
1027+ warningEntry . Args [ 2 ] . ToString ( ) . Should ( ) . Contain ( expectedDetail ) ;
1028+
1029+ // Should NOT have an error-level log for this
1030+ logger . Entries . Should ( ) . NotContain ( e =>
1031+ e . Level == SentryLevel . Error &&
1032+ e . Message . Contains ( "Sentry rejected the envelope" ) ) ;
1033+ }
1034+
1035+ [ Fact ]
1036+ public async Task SendEnvelopeAsync_Response429WithTextMessage_LogsWarning ( )
1037+ {
1038+ // Arrange
1039+ const string expectedMessage = "Rate limited" ;
1040+
1041+ var httpHandler = Substitute . For < MockableHttpMessageHandler > ( ) ;
1042+
1043+ httpHandler . VerifiableSendAsync ( Arg . Any < HttpRequestMessage > ( ) , Arg . Any < CancellationToken > ( ) )
1044+ . Returns ( _ => SentryResponses . GetTextErrorResponse ( ( HttpStatusCode ) 429 , expectedMessage ) ) ;
1045+
1046+ var logger = new InMemoryDiagnosticLogger ( ) ;
1047+
1048+ var httpTransport = new HttpTransport (
1049+ new SentryOptions
1050+ {
1051+ Dsn = ValidDsn ,
1052+ Debug = true ,
1053+ DiagnosticLogger = logger
1054+ } ,
1055+ new HttpClient ( httpHandler ) ) ;
1056+
1057+ var envelope = Envelope . FromEvent ( new SentryEvent ( ) ) ;
1058+
1059+ // Act
1060+ await httpTransport . SendEnvelopeAsync ( envelope ) ;
1061+
1062+ // Assert
1063+ var warningEntry = logger . Entries . FirstOrDefault ( e =>
1064+ e . Level == SentryLevel . Warning &&
1065+ e . Message . Contains ( "due to rate limiting" ) ) ;
1066+
1067+ warningEntry . Should ( ) . NotBeNull ( ) ;
1068+ warningEntry ! . Args [ 2 ] . ToString ( ) . Should ( ) . Contain ( expectedMessage ) ;
1069+
1070+ // Should NOT have an error-level log for this
1071+ logger . Entries . Should ( ) . NotContain ( e =>
1072+ e . Level == SentryLevel . Error &&
1073+ e . Message . Contains ( "Sentry rejected the envelope" ) ) ;
1074+ }
9921075}
0 commit comments