@@ -230,10 +230,12 @@ public static IEnumerable<TestCase> GetTestCases()
230230 Description = "400/close/timeout" ,
231231 CustomValidator = ( response , state ) =>
232232 {
233+ // If server sent a response, only 400 is acceptable
234+ if ( response is not null )
235+ return response . StatusCode == 400 ? TestVerdict . Pass : TestVerdict . Fail ;
236+ // No response: close or timeout means server correctly rejected
233237 if ( state is ConnectionState . TimedOut or ConnectionState . ClosedByServer )
234238 return TestVerdict . Pass ;
235- if ( response is not null && response . StatusCode == 400 )
236- return TestVerdict . Pass ;
237239 return TestVerdict . Fail ;
238240 }
239241 }
@@ -545,11 +547,13 @@ public static IEnumerable<TestCase> GetTestCases()
545547 Description = "400/close/timeout" ,
546548 CustomValidator = ( response , state ) =>
547549 {
548- // Server should wait for remaining bytes then timeout, or reject
550+ // If server sent a response, only 400 is acceptable
551+ // (a 2xx means it processed an incomplete body — always wrong)
552+ if ( response is not null )
553+ return response . StatusCode == 400 ? TestVerdict . Pass : TestVerdict . Fail ;
554+ // No response: close or timeout means server correctly waited for remaining bytes
549555 if ( state is ConnectionState . TimedOut or ConnectionState . ClosedByServer )
550556 return TestVerdict . Pass ;
551- if ( response is not null && response . StatusCode == 400 )
552- return TestVerdict . Pass ;
553557 return TestVerdict . Fail ;
554558 }
555559 }
@@ -615,11 +619,13 @@ public static IEnumerable<TestCase> GetTestCases()
615619 Description = "400/close/timeout" ,
616620 CustomValidator = ( response , state ) =>
617621 {
618- // Server should wait for more chunks then timeout, or reject
622+ // If server sent a response, only 400 is acceptable
623+ // (a 2xx means it processed an incomplete body — always wrong)
624+ if ( response is not null )
625+ return response . StatusCode == 400 ? TestVerdict . Pass : TestVerdict . Fail ;
626+ // No response: close or timeout means server correctly waited
619627 if ( state is ConnectionState . TimedOut or ConnectionState . ClosedByServer )
620628 return TestVerdict . Pass ;
621- if ( response is not null && response . StatusCode == 400 )
622- return TestVerdict . Pass ;
623629 return TestVerdict . Fail ;
624630 }
625631 }
@@ -787,10 +793,10 @@ public static IEnumerable<TestCase> GetTestCases()
787793 return TestVerdict . Warn ;
788794 if ( response . StatusCode is >= 200 and < 300 )
789795 {
790- var body = ( response . Body ?? "" ) . TrimEnd ( ' \r ' , ' \n ' ) ;
796+ var body = GetEffectiveBody ( response ) ;
791797 if ( body == "hello" ) return TestVerdict . Pass ;
792- if ( IsStaticResponse ( body ) || body . Length == 0 ) return TestVerdict . Pass ;
793- return TestVerdict . Warn ;
798+ if ( IsStaticResponse ( body ) ) return TestVerdict . Pass ;
799+ return TestVerdict . Fail ;
794800 }
795801 return TestVerdict . Fail ;
796802 }
@@ -1277,15 +1283,71 @@ public static IEnumerable<TestCase> GetTestCases()
12771283
12781284 private static bool IsStaticResponse ( string body ) => body == "OK" ;
12791285
1286+ /// <summary>
1287+ /// Extracts the effective body from a response, decoding chunked TE if present.
1288+ /// </summary>
1289+ private static string GetEffectiveBody ( HttpResponse response )
1290+ {
1291+ var raw = ( response . Body ?? "" ) . TrimEnd ( '\r ' , '\n ' ) ;
1292+
1293+ if ( response . Headers . TryGetValue ( "Transfer-Encoding" , out var te ) &&
1294+ te . Contains ( "chunked" , StringComparison . OrdinalIgnoreCase ) )
1295+ {
1296+ var decoded = TryDecodeChunked ( raw ) ;
1297+ if ( decoded is not null )
1298+ return decoded ;
1299+ }
1300+
1301+ return raw ;
1302+ }
1303+
1304+ private static string ? TryDecodeChunked ( string raw )
1305+ {
1306+ var sb = new StringBuilder ( ) ;
1307+ var pos = 0 ;
1308+
1309+ while ( pos < raw . Length )
1310+ {
1311+ var lineEnd = raw . IndexOf ( "\r \n " , pos , StringComparison . Ordinal ) ;
1312+ if ( lineEnd < 0 ) return null ;
1313+
1314+ var sizeLine = raw [ pos ..lineEnd ] ;
1315+ var semiIdx = sizeLine . IndexOf ( ';' ) ;
1316+ if ( semiIdx >= 0 ) sizeLine = sizeLine [ ..semiIdx ] ;
1317+ sizeLine = sizeLine . Trim ( ) ;
1318+
1319+ if ( ! int . TryParse ( sizeLine , System . Globalization . NumberStyles . HexNumber , null , out var chunkSize ) )
1320+ return null ;
1321+
1322+ if ( chunkSize == 0 )
1323+ break ;
1324+
1325+ var dataStart = lineEnd + 2 ;
1326+ var dataEnd = dataStart + chunkSize ;
1327+ if ( dataEnd > raw . Length ) return null ;
1328+
1329+ sb . Append ( raw [ dataStart ..dataEnd ] ) ;
1330+
1331+ // Skip past chunk data + CRLF
1332+ pos = dataEnd ;
1333+ if ( pos + 2 <= raw . Length && raw [ pos ] == '\r ' && raw [ pos + 1 ] == '\n ' )
1334+ pos += 2 ;
1335+ else if ( pos < raw . Length )
1336+ pos ++ ;
1337+ }
1338+
1339+ return sb . ToString ( ) ;
1340+ }
1341+
12801342 private static Func < HttpResponse ? , string ? > EchoAnalyzer ( string expectedBody )
12811343 {
12821344 return response =>
12831345 {
12841346 if ( response is null || response . StatusCode is < 200 or >= 300 ) return null ;
1285- var body = ( response . Body ?? "" ) . TrimEnd ( ' \r ' , ' \n ' ) ;
1347+ var body = GetEffectiveBody ( response ) ;
12861348 if ( IsStaticResponse ( body ) ) return "Static response — server does not echo POST body" ;
12871349 if ( body == expectedBody ) return "Echoed correctly" ;
1288- if ( body . Length == 0 ) return "Empty body" ;
1350+ if ( body . Length == 0 ) return "Empty body — server did not echo " ;
12891351 return $ "Echo mismatch: expected \" { expectedBody } \" , got \" { ( body . Length > 40 ? body [ ..40 ] + "..." : body ) } \" ";
12901352 } ;
12911353 }
@@ -1298,11 +1360,10 @@ public static IEnumerable<TestCase> GetTestCases()
12981360 return TestVerdict . Fail ;
12991361 if ( response . StatusCode is < 200 or >= 300 )
13001362 return TestVerdict . Fail ;
1301- var body = ( response . Body ?? "" ) . TrimEnd ( ' \r ' , ' \n ' ) ;
1363+ var body = GetEffectiveBody ( response ) ;
13021364 if ( body == expectedBody ) return TestVerdict . Pass ;
13031365 if ( IsStaticResponse ( body ) ) return TestVerdict . Pass ;
1304- if ( body . Length == 0 ) return TestVerdict . Pass ;
1305- return TestVerdict . Warn ;
1366+ return TestVerdict . Fail ;
13061367 } ;
13071368 }
13081369
0 commit comments