Files
waf-platform/EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests/Network/HttpdnsNWHTTPClientTests.m
2026-02-20 17:56:24 +08:00

743 lines
31 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// HttpdnsNWHTTPClientTests.m
// AlicloudHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
//
#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
#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