// // HttpdnsNWHTTPClientTests.m // TrustHttpDNSTests // // @author Created by Claude Code on 2025-11-01 // Copyright © 2025 trustapp.com. All rights reserved. // #import #import #import "HttpdnsNWHTTPClient.h" #import "HttpdnsNWHTTPClient_Internal.h" #import "HttpdnsNWReusableConnection.h" #import "HttpdnsNWHTTPClientTestHelper.h" @interface HttpdnsNWHTTPClientTests : XCTestCase @property (nonatomic, strong) HttpdnsNWHTTPClient *client; @end @implementation HttpdnsNWHTTPClientTests - (void)setUp { [super setUp]; self.client = [[HttpdnsNWHTTPClient alloc] init]; } - (void)tearDown { self.client = nil; [super tearDown]; } #pragma mark - A. HTTP 解析逻辑测试 #pragma mark - A1. Header 解析 (9? // A1.1 正常响应 - (void)testParseHTTPHeaders_ValidResponse_Success { NSData *data = [HttpdnsNWHTTPClientTestHelper createHTTPResponseWithStatus:200 statusText:@"OK" headers:@{@"Content-Type": @"application/json"} body:nil]; NSUInteger headerEndIndex; NSInteger statusCode; NSDictionary *headers; NSError *error; HttpdnsHTTPHeaderParseResult result = [self.client tryParseHTTPHeadersInData:data headerEndIndex:&headerEndIndex statusCode:&statusCode headers:&headers error:&error]; XCTAssertEqual(result, HttpdnsHTTPHeaderParseResultSuccess); XCTAssertEqual(statusCode, 200); XCTAssertNotNil(headers); XCTAssertEqualObjects(headers[@"content-type"], @"application/json"); // key 应该转为小写 XCTAssertNil(error); } // A1.2 多个头部 - (void)testParseHTTPHeaders_MultipleHeaders_AllParsed { NSDictionary *testHeaders = @{ @"Content-Type": @"application/json", @"Content-Length": @"123", @"Connection": @"keep-alive", @"X-Custom-Header": @"custom-value", @"Cache-Control": @"no-cache" }; NSData *data = [HttpdnsNWHTTPClientTestHelper createHTTPResponseWithStatus:200 statusText:@"OK" headers:testHeaders body:nil]; NSUInteger headerEndIndex; NSInteger statusCode; NSDictionary *headers; NSError *error; HttpdnsHTTPHeaderParseResult result = [self.client tryParseHTTPHeadersInData:data headerEndIndex:&headerEndIndex statusCode:&statusCode headers:&headers error:&error]; XCTAssertEqual(result, HttpdnsHTTPHeaderParseResultSuccess); XCTAssertEqual(headers.count, testHeaders.count); // 验证所有头部都被解析,?key 转为小写 XCTAssertEqualObjects(headers[@"content-type"], @"application/json"); XCTAssertEqualObjects(headers[@"content-length"], @"123"); XCTAssertEqualObjects(headers[@"connection"], @"keep-alive"); XCTAssertEqualObjects(headers[@"x-custom-header"], @"custom-value"); XCTAssertEqualObjects(headers[@"cache-control"], @"no-cache"); } // A1.3 不完整响? - (void)testParseHTTPHeaders_IncompleteData_ReturnsIncomplete { NSString *incompleteResponse = @"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n"; NSData *data = [incompleteResponse dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex; NSInteger statusCode; NSDictionary *headers; NSError *error; HttpdnsHTTPHeaderParseResult result = [self.client tryParseHTTPHeadersInData:data headerEndIndex:&headerEndIndex statusCode:&statusCode headers:&headers error:&error]; XCTAssertEqual(result, HttpdnsHTTPHeaderParseResultIncomplete); } // A1.4 无效状态行 - (void)testParseHTTPHeaders_InvalidStatusLine_ReturnsError { NSString *invalidResponse = @"INVALID\r\n\r\n"; NSData *data = [invalidResponse dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex; NSInteger statusCode; NSDictionary *headers; NSError *error; HttpdnsHTTPHeaderParseResult result = [self.client tryParseHTTPHeadersInData:data headerEndIndex:&headerEndIndex statusCode:&statusCode headers:&headers error:&error]; XCTAssertEqual(result, HttpdnsHTTPHeaderParseResultError); XCTAssertNotNil(error); } // A1.5 头部包含空格 - (void)testParseHTTPHeaders_HeadersWithWhitespace_Trimmed { NSString *responseWithSpaces = @"HTTP/1.1 200 OK\r\nContent-Type: application/json \r\n\r\n"; NSData *data = [responseWithSpaces dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex; NSInteger statusCode; NSDictionary *headers; NSError *error; HttpdnsHTTPHeaderParseResult result = [self.client tryParseHTTPHeadersInData:data headerEndIndex:&headerEndIndex statusCode:&statusCode headers:&headers error:&error]; XCTAssertEqual(result, HttpdnsHTTPHeaderParseResultSuccess); XCTAssertEqualObjects(headers[@"content-type"], @"application/json"); // 应该?trim } // A1.6 头部没有? - (void)testParseHTTPHeaders_EmptyHeaderValue_HandledGracefully { NSString *responseWithEmptyValue = @"HTTP/1.1 200 OK\r\nX-Empty-Header:\r\n\r\n"; NSData *data = [responseWithEmptyValue dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex; NSInteger statusCode; NSDictionary *headers; NSError *error; HttpdnsHTTPHeaderParseResult result = [self.client tryParseHTTPHeadersInData:data headerEndIndex:&headerEndIndex statusCode:&statusCode headers:&headers error:&error]; XCTAssertEqual(result, HttpdnsHTTPHeaderParseResultSuccess); XCTAssertEqualObjects(headers[@"x-empty-header"], @""); } // A1.7 状态码非数? - (void)testParseHTTPHeaders_NonNumericStatusCode_ReturnsError { NSString *invalidStatusCode = @"HTTP/1.1 ABC OK\r\n\r\n"; NSData *data = [invalidStatusCode dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex; NSInteger statusCode; NSDictionary *headers; NSError *error; HttpdnsHTTPHeaderParseResult result = [self.client tryParseHTTPHeadersInData:data headerEndIndex:&headerEndIndex statusCode:&statusCode headers:&headers error:&error]; XCTAssertEqual(result, HttpdnsHTTPHeaderParseResultError); } // A1.8 状态码为零 - (void)testParseHTTPHeaders_StatusCodeZero_ReturnsError { NSString *zeroStatusCode = @"HTTP/1.1 0 OK\r\n\r\n"; NSData *data = [zeroStatusCode dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex; NSInteger statusCode; NSDictionary *headers; NSError *error; HttpdnsHTTPHeaderParseResult result = [self.client tryParseHTTPHeadersInData:data headerEndIndex:&headerEndIndex statusCode:&statusCode headers:&headers error:&error]; XCTAssertEqual(result, HttpdnsHTTPHeaderParseResultError); } // A1.9 头部没有冒号被跳? - (void)testParseHTTPHeaders_HeaderWithoutColon_Skipped { NSString *responseWithInvalidHeader = @"HTTP/1.1 200 OK\r\nInvalidHeader\r\nContent-Type: application/json\r\n\r\n"; NSData *data = [responseWithInvalidHeader dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex; NSInteger statusCode; NSDictionary *headers; NSError *error; HttpdnsHTTPHeaderParseResult result = [self.client tryParseHTTPHeadersInData:data headerEndIndex:&headerEndIndex statusCode:&statusCode headers:&headers error:&error]; XCTAssertEqual(result, HttpdnsHTTPHeaderParseResultSuccess); XCTAssertEqualObjects(headers[@"content-type"], @"application/json"); // 有效头部正常解析 } #pragma mark - A2. Chunked 编码检?(8? // A2.1 单个 chunk - (void)testCheckChunkedBody_SingleChunk_DetectsComplete { NSString *singleChunkBody = @"5\r\nhello\r\n0\r\n\r\n"; NSString *response = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\n\r\n%@", singleChunkBody]; NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex = [@"HTTP/1.1 200 OK" length]; NSError *error; HttpdnsHTTPChunkParseResult result = [self.client checkChunkedBodyCompletionInData:data headerEndIndex:headerEndIndex error:&error]; XCTAssertEqual(result, HttpdnsHTTPChunkParseResultSuccess); XCTAssertNil(error); } // A2.2 多个 chunks - (void)testCheckChunkedBody_MultipleChunks_DetectsComplete { NSString *multiChunkBody = @"5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n"; NSString *response = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\n\r\n%@", multiChunkBody]; NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex = [@"HTTP/1.1 200 OK" length]; NSError *error; HttpdnsHTTPChunkParseResult result = [self.client checkChunkedBodyCompletionInData:data headerEndIndex:headerEndIndex error:&error]; XCTAssertEqual(result, HttpdnsHTTPChunkParseResultSuccess); XCTAssertNil(error); } // A2.3 不完?chunk - (void)testCheckChunkedBody_IncompleteChunk_ReturnsIncomplete { NSString *incompleteChunkBody = @"5\r\nhel"; // 数据不完? NSString *response = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\n\r\n%@", incompleteChunkBody]; NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex = [@"HTTP/1.1 200 OK" length]; NSError *error; HttpdnsHTTPChunkParseResult result = [self.client checkChunkedBodyCompletionInData:data headerEndIndex:headerEndIndex error:&error]; XCTAssertEqual(result, HttpdnsHTTPChunkParseResultIncomplete); } // A2.4 ?chunk extension - (void)testCheckChunkedBody_WithChunkExtension_Ignored { NSString *chunkWithExtension = @"5;name=value\r\nhello\r\n0\r\n\r\n"; NSString *response = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\n\r\n%@", chunkWithExtension]; NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding]; // headerEndIndex 指向第一?\r\n\r\n 中的第一?\r NSUInteger headerEndIndex = [@"HTTP/1.1 200 OK" length]; NSError *error; HttpdnsHTTPChunkParseResult result = [self.client checkChunkedBodyCompletionInData:data headerEndIndex:headerEndIndex error:&error]; XCTAssertEqual(result, HttpdnsHTTPChunkParseResultSuccess); XCTAssertNil(error); } // A2.5 无效十六进制 size - (void)testCheckChunkedBody_InvalidHexSize_ReturnsError { NSString *invalidChunkSize = @"ZZZ\r\nhello\r\n0\r\n\r\n"; NSString *response = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\n\r\n%@", invalidChunkSize]; NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex = [@"HTTP/1.1 200 OK" length]; NSError *error; HttpdnsHTTPChunkParseResult result = [self.client checkChunkedBodyCompletionInData:data headerEndIndex:headerEndIndex error:&error]; XCTAssertEqual(result, HttpdnsHTTPChunkParseResultError); XCTAssertNotNil(error); } // A2.6 Chunk size 溢出 - (void)testCheckChunkedBody_ChunkSizeOverflow_ReturnsError { NSString *overflowChunkSize = @"FFFFFFFFFFFFFFFF\r\n"; NSString *response = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\n\r\n%@", overflowChunkSize]; NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex = [@"HTTP/1.1 200 OK" length]; NSError *error; HttpdnsHTTPChunkParseResult result = [self.client checkChunkedBodyCompletionInData:data headerEndIndex:headerEndIndex error:&error]; XCTAssertEqual(result, HttpdnsHTTPChunkParseResultError); XCTAssertNotNil(error); } // A2.7 缺少 CRLF 终止? - (void)testCheckChunkedBody_MissingCRLFTerminator_ReturnsError { NSString *missingTerminator = @"5\r\nhelloXX0\r\n\r\n"; // 应该?hello\r\n NSString *response = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\n\r\n%@", missingTerminator]; NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex = [@"HTTP/1.1 200 OK" length]; NSError *error; HttpdnsHTTPChunkParseResult result = [self.client checkChunkedBodyCompletionInData:data headerEndIndex:headerEndIndex error:&error]; XCTAssertEqual(result, HttpdnsHTTPChunkParseResultError); XCTAssertNotNil(error); } // A2.8 ?trailers - (void)testCheckChunkedBody_WithTrailers_DetectsComplete { NSString *chunkWithTrailers = @"5\r\nhello\r\n0\r\nX-Trailer: value\r\nX-Custom: test\r\n\r\n"; NSString *response = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\n\r\n%@", chunkWithTrailers]; NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger headerEndIndex = [@"HTTP/1.1 200 OK" length]; NSError *error; HttpdnsHTTPChunkParseResult result = [self.client checkChunkedBodyCompletionInData:data headerEndIndex:headerEndIndex error:&error]; XCTAssertEqual(result, HttpdnsHTTPChunkParseResultSuccess); XCTAssertNil(error); } #pragma mark - A3. Chunked 解码 (2? // A3.1 多个 chunks 解码 - (void)testDecodeChunkedBody_MultipleChunks_DecodesCorrectly { NSArray *chunks = @[ [@"hello" dataUsingEncoding:NSUTF8StringEncoding], [@" world" dataUsingEncoding:NSUTF8StringEncoding] ]; NSData *chunkedData = [HttpdnsNWHTTPClientTestHelper createChunkedHTTPResponseWithStatus:200 headers:nil chunks:chunks]; // 提取 chunked body 部分(跳?headers? NSData *headerData = [@"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]; NSData *bodyData = [chunkedData subdataWithRange:NSMakeRange(headerData.length, chunkedData.length - headerData.length)]; NSError *error; NSData *decoded = [self.client decodeChunkedBody:bodyData error:&error]; XCTAssertNotNil(decoded); XCTAssertNil(error); NSString *decodedString = [[NSString alloc] initWithData:decoded encoding:NSUTF8StringEncoding]; XCTAssertEqualObjects(decodedString, @"hello world"); } // A3.2 无效格式返回 nil - (void)testDecodeChunkedBody_InvalidFormat_ReturnsNil { NSString *invalidChunked = @"ZZZ\r\nbad data\r\n"; NSData *bodyData = [invalidChunked dataUsingEncoding:NSUTF8StringEncoding]; NSError *error; NSData *decoded = [self.client decodeChunkedBody:bodyData error:&error]; XCTAssertNil(decoded); XCTAssertNotNil(error); } #pragma mark - A4. 完整响应解析 (6? // A4.1 Content-Length 响应 - (void)testParseResponse_WithContentLength_ParsesCorrectly { NSString *bodyString = @"{\"ips\":[]}"; NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding]; NSData *responseData = [HttpdnsNWHTTPClientTestHelper createHTTPResponseWithStatus:200 statusText:@"OK" headers:@{@"Content-Type": @"application/json"} body:bodyData]; NSInteger statusCode; NSDictionary *headers; NSData *body; NSError *error; BOOL success = [self.client parseHTTPResponseData:responseData statusCode:&statusCode headers:&headers body:&body error:&error]; XCTAssertTrue(success); XCTAssertEqual(statusCode, 200); XCTAssertNotNil(headers); XCTAssertEqualObjects(headers[@"content-type"], @"application/json"); XCTAssertEqualObjects(body, bodyData); XCTAssertNil(error); } // A4.2 Chunked 响应 - (void)testParseResponse_WithChunkedEncoding_DecodesBody { NSArray *chunks = @[ [@"{\"ips\"" dataUsingEncoding:NSUTF8StringEncoding], [@":[]}" dataUsingEncoding:NSUTF8StringEncoding] ]; NSData *responseData = [HttpdnsNWHTTPClientTestHelper createChunkedHTTPResponseWithStatus:200 headers:@{@"Content-Type": @"application/json"} chunks:chunks]; NSInteger statusCode; NSDictionary *headers; NSData *body; NSError *error; BOOL success = [self.client parseHTTPResponseData:responseData statusCode:&statusCode headers:&headers body:&body error:&error]; XCTAssertTrue(success); XCTAssertEqual(statusCode, 200); NSString *bodyString = [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]; XCTAssertEqualObjects(bodyString, @"{\"ips\":[]}"); } // A4.3 ?body - (void)testParseResponse_EmptyBody_Success { NSData *responseData = [HttpdnsNWHTTPClientTestHelper createHTTPResponseWithStatus:204 statusText:@"No Content" headers:nil body:nil]; NSInteger statusCode; NSDictionary *headers; NSData *body; NSError *error; BOOL success = [self.client parseHTTPResponseData:responseData statusCode:&statusCode headers:&headers body:&body error:&error]; XCTAssertTrue(success); XCTAssertEqual(statusCode, 204); XCTAssertEqual(body.length, 0); } // A4.4 Content-Length 不匹配仍然成? - (void)testParseResponse_ContentLengthMismatch_LogsButSucceeds { NSData *bodyData = [@"short" dataUsingEncoding:NSUTF8StringEncoding]; NSData *responseData = [HttpdnsNWHTTPClientTestHelper createHTTPResponseWithStatus:200 statusText:@"OK" headers:@{@"Content-Length": @"100"} // 不匹? body:bodyData]; NSInteger statusCode; NSDictionary *headers; NSData *body; NSError *error; BOOL success = [self.client parseHTTPResponseData:responseData statusCode:&statusCode headers:&headers body:&body error:&error]; XCTAssertTrue(success); // 仍然成功,只是日志警? XCTAssertEqualObjects(body, bodyData); } // A4.5 空数据返回错? - (void)testParseResponse_EmptyData_ReturnsError { NSData *emptyData = [NSData data]; NSInteger statusCode; NSDictionary *headers; NSData *body; NSError *error; BOOL success = [self.client parseHTTPResponseData:emptyData statusCode:&statusCode headers:&headers body:&body error:&error]; XCTAssertFalse(success); XCTAssertNotNil(error); } // A4.6 只有 headers ?body - (void)testParseResponse_OnlyHeaders_EmptyBody { NSData *responseData = [HttpdnsNWHTTPClientTestHelper createHTTPResponseWithStatus:200 statusText:@"OK" headers:@{@"Content-Type": @"text/plain"} body:nil]; NSInteger statusCode; NSDictionary *headers; NSData *body; NSError *error; BOOL success = [self.client parseHTTPResponseData:responseData statusCode:&statusCode headers:&headers body:&body error:&error]; XCTAssertTrue(success); XCTAssertEqual(statusCode, 200); XCTAssertNotNil(headers); XCTAssertEqual(body.length, 0); } #pragma mark - C. 请求构建测试 (7? // C.1 基本 GET 请求 - (void)testBuildHTTPRequest_BasicGET_CorrectFormat { NSURL *url = [NSURL URLWithString:@"http://example.com/"]; NSString *request = [self.client buildHTTPRequestStringWithURL:url userAgent:@"TestAgent"]; XCTAssertTrue([request containsString:@"GET / HTTP/1.1\r\n"]); XCTAssertTrue([request containsString:@"Host: example.com\r\n"]); XCTAssertTrue([request containsString:@"User-Agent: TestAgent\r\n"]); XCTAssertTrue([request containsString:@"Accept: application/json\r\n"]); XCTAssertTrue([request containsString:@"Accept-Encoding: identity\r\n"]); XCTAssertTrue([request containsString:@"Connection: keep-alive\r\n"]); XCTAssertTrue([request hasSuffix:@"\r\n\r\n"]); } // C.2 带查询参? - (void)testBuildHTTPRequest_WithQueryString_Included { NSURL *url = [NSURL URLWithString:@"http://example.com/path?foo=bar&baz=qux"]; NSString *request = [self.client buildHTTPRequestStringWithURL:url userAgent:nil]; XCTAssertTrue([request containsString:@"GET /path?foo=bar&baz=qux HTTP/1.1\r\n"]); } // C.3 包含 User-Agent - (void)testBuildHTTPRequest_WithUserAgent_Included { NSURL *url = [NSURL URLWithString:@"http://example.com/"]; NSString *request = [self.client buildHTTPRequestStringWithURL:url userAgent:@"CustomAgent/1.0"]; XCTAssertTrue([request containsString:@"User-Agent: CustomAgent/1.0\r\n"]); } // C.4 HTTP 默认端口不显? - (void)testBuildHTTPRequest_HTTPDefaultPort_NotInHost { NSURL *url = [NSURL URLWithString:@"http://example.com:80/"]; NSString *request = [self.client buildHTTPRequestStringWithURL:url userAgent:nil]; XCTAssertTrue([request containsString:@"Host: example.com\r\n"]); XCTAssertFalse([request containsString:@"Host: example.com:80\r\n"]); } // C.5 HTTPS 默认端口不显? - (void)testBuildHTTPRequest_HTTPSDefaultPort_NotInHost { NSURL *url = [NSURL URLWithString:@"https://example.com:443/"]; NSString *request = [self.client buildHTTPRequestStringWithURL:url userAgent:nil]; XCTAssertTrue([request containsString:@"Host: example.com\r\n"]); XCTAssertFalse([request containsString:@"Host: example.com:443\r\n"]); } // C.6 非默认端口显? - (void)testBuildHTTPRequest_NonDefaultPort_InHost { NSURL *url = [NSURL URLWithString:@"http://example.com:8080/"]; NSString *request = [self.client buildHTTPRequestStringWithURL:url userAgent:nil]; XCTAssertTrue([request containsString:@"Host: example.com:8080\r\n"]); } // C.7 固定头部存在 - (void)testBuildHTTPRequest_FixedHeaders_Present { NSURL *url = [NSURL URLWithString:@"http://example.com/"]; NSString *request = [self.client buildHTTPRequestStringWithURL:url userAgent:nil]; XCTAssertTrue([request containsString:@"Accept: application/json\r\n"]); XCTAssertTrue([request containsString:@"Accept-Encoding: identity\r\n"]); XCTAssertTrue([request containsString:@"Connection: keep-alive\r\n"]); } #pragma mark - E. TLS 验证测试 (4个占位符) // 注意:TLS 验证测试需要真实的 SecTrustRef 或复杂的 mock // 这些测试在实际环境中需要根据测试框架调? // E.1 有效证书返回 YES - (void)testEvaluateServerTrust_ValidCertificate_ReturnsYES { // 需要创建有效的 SecTrustRef 进行测试 // 跳过或标记为手动测试 } // E.2 Proceed 结果返回 YES - (void)testEvaluateServerTrust_ProceedResult_ReturnsYES { // Mock SecTrustEvaluate 返回 kSecTrustResultProceed } // E.3 无效证书返回 NO - (void)testEvaluateServerTrust_InvalidCertificate_ReturnsNO { // Mock SecTrustEvaluate 返回 kSecTrustResultDeny } // E.4 指定域名使用 SSL Policy - (void)testEvaluateServerTrust_WithDomain_UsesSSLPolicy { // 验证使用?SecPolicyCreateSSL(true, domain) } #pragma mark - F. 边缘情况测试 (5? // F.1 超长 URL - (void)testPerformRequest_VeryLongURL_HandlesCorrectly { NSMutableString *longPath = [NSMutableString stringWithString:@"http://example.com/"]; for (int i = 0; i < 1000; i++) { [longPath appendString:@"long/"]; } NSURL *url = [NSURL URLWithString:longPath]; XCTAssertNotNil(url); NSString *request = [self.client buildHTTPRequestStringWithURL:url userAgent:nil]; XCTAssertTrue(request.length > 5000); } // F.2 ?User-Agent - (void)testBuildRequest_EmptyUserAgent_NoUserAgentHeader { NSURL *url = [NSURL URLWithString:@"http://example.com/"]; NSString *requestWithNil = [self.client buildHTTPRequestStringWithURL:url userAgent:nil]; XCTAssertFalse([requestWithNil containsString:@"User-Agent:"]); } // F.3 超大响应? - (void)testParseResponse_VeryLargeBody_HandlesCorrectly { NSData *largeBody = [HttpdnsNWHTTPClientTestHelper randomDataWithSize:5 * 1024 * 1024]; NSData *responseData = [HttpdnsNWHTTPClientTestHelper createHTTPResponseWithStatus:200 statusText:@"OK" headers:nil body:largeBody]; NSInteger statusCode; NSDictionary *headers; NSData *body; NSError *error; BOOL success = [self.client parseHTTPResponseData:responseData statusCode:&statusCode headers:&headers body:&body error:&error]; XCTAssertTrue(success); XCTAssertEqual(body.length, largeBody.length); } // F.4 Chunked 解码失败回退到原始数? - (void)testParseResponse_ChunkedDecodeFails_FallsBackToRaw { NSString *badChunked = @"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nBAD_CHUNK_DATA"; NSData *responseData = [badChunked dataUsingEncoding:NSUTF8StringEncoding]; NSInteger statusCode; NSDictionary *headers; NSData *body; NSError *error; BOOL success = [self.client parseHTTPResponseData:responseData statusCode:&statusCode headers:&headers body:&body error:&error]; XCTAssertTrue(success); XCTAssertNotNil(body); } // F.5 连接?key 生成测试 - (void)testConnectionPoolKey_DifferentHosts_SeparateKeys { NSString *key1 = [self.client connectionPoolKeyForHost:@"host1.com" port:@"80" useTLS:NO]; NSString *key2 = [self.client connectionPoolKeyForHost:@"host2.com" port:@"80" useTLS:NO]; XCTAssertNotEqualObjects(key1, key2); } - (void)testConnectionPoolKey_DifferentPorts_SeparateKeys { NSString *key1 = [self.client connectionPoolKeyForHost:@"example.com" port:@"80" useTLS:NO]; NSString *key2 = [self.client connectionPoolKeyForHost:@"example.com" port:@"8080" useTLS:NO]; XCTAssertNotEqualObjects(key1, key2); } - (void)testConnectionPoolKey_HTTPvsHTTPS_SeparateKeys { NSString *keyHTTP = [self.client connectionPoolKeyForHost:@"example.com" port:@"80" useTLS:NO]; NSString *keyHTTPS = [self.client connectionPoolKeyForHost:@"example.com" port:@"443" useTLS:YES]; XCTAssertNotEqualObjects(keyHTTP, keyHTTPS); } @end