Files
waf-platform/EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests/Network/HttpdnsNWHTTPClientTests.m
2026-02-28 18:55:33 +08:00

743 lines
31 KiB
Objective-C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 trustapp.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 è§£æž<C3A6>é€»è¾æµè¯•
#pragma mark - A1. Header è§£æž<C3A6> (9ä¸?
// A1.1 正常å“<C3A5>应
- (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 应该转为å°<C3A5>写
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);
// 验è¯<C3A8>所有头部都被解æž<C3A6>,ä¸?key 转为å°<C3A5>写
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 ä¸<C3A4>完整å“<C3A5>åº?
- (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 无效状æ€<C3A6>行
- (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 头部包å<E280A6>«ç©ºæ ¼
- (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 状æ€<C3A6>ç <C3A7>é<EFBFBD>žæ•°å­?
- (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 状æ€<C3A6>ç <C3A7>为é
- (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 头部没有å†å<E28099>·è¢«è·³è¿?
- (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"); // 有效头部正常解æž<C3A6>
}
#pragma mark - A2. Chunked ç¼ç <C3A7>检æŸ?(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 ä¸<C3A4>完æ•?chunk
- (void)testCheckChunkedBody_IncompleteChunk_ReturnsIncomplete {
NSString *incompleteChunkBody = @"5\r\nhel"; // æ•°æ<C2B0>®ä¸<C3A4>完æ•?
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 指å<E280A1>第一ä¸?\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 无效å<CB86><C3A5>å…­è¿åˆ¶ 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 è§£ç <C3A7> (2ä¸?
// A3.1 多个 chunks è§£ç <C3A7>
- (void)testDecodeChunkedBody_MultipleChunks_DecodesCorrectly {
NSArray *chunks = @[
[@"hello" dataUsingEncoding:NSUTF8StringEncoding],
[@" world" dataUsingEncoding:NSUTF8StringEncoding]
];
NSData *chunkedData = [HttpdnsNWHTTPClientTestHelper createChunkedHTTPResponseWithStatus:200
headers:nil
chunks:chunks];
// æ<><C3A6>å<EFBFBD> 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 无效格å¼<C3A5>è¿”åž 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. 完整å“<C3A5>应解æž<C3A6> (6ä¸?
// A4.1 Content-Length å“<C3A5>应
- (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 å“<C3A5>应
- (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 ä¸<C3A4>匹é…<C3A9>ä»<C3A4>ç„¶æˆ<C3A6>åŠ?
- (void)testParseResponse_ContentLengthMismatch_LogsButSucceeds {
NSData *bodyData = [@"short" dataUsingEncoding:NSUTF8StringEncoding];
NSData *responseData = [HttpdnsNWHTTPClientTestHelper createHTTPResponseWithStatus:200
statusText:@"OK"
headers:@{@"Content-Length": @"100"} // ä¸<C3A4>匹é…?
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); // ä»<C3A4>ç„¶æˆ<C3A6>功,å<C592>ªæ˜¯æ—¥å¿—è­¦å?
XCTAssertEqualObjects(body, bodyData);
}
// A4.5 空数æ<C2B0>®è¿”åžé”™è¯?
- (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 带查询å<C2A2>æ•?
- (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 包å<E280A6>« 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 默认端å<C2AF>£ä¸<C3A4>显ç¤?
- (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 默认端å<C2AF>£ä¸<C3A4>显ç¤?
- (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 é<>žé»˜è®¤ç«¯å<C2AF>£æ˜¾ç¤?
- (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 验è¯<C3A8>æµè¯• (4个å<C2AA> ä½<C3A4>符)
// 注æ„<C3A6>:TLS 验è¯<C3A8>æµè¯•需è¦<C3A8>真实的 SecTrustRef æˆå¤<C3A5>æ<EFBFBD>çš„ mock
// è¿™äºæµè¯•在实际环境中需è¦<C3A8>æ ¹æ<C2B9>®æµè¯•框架调æ•?
// E.1 有效è¯<C3A8>ä¹¦è¿”åž YES
- (void)testEvaluateServerTrust_ValidCertificate_ReturnsYES {
// 需è¦<C3A8>åˆå»ºæœ‰æ•ˆçš„ SecTrustRef 进行测试
// 跳过或标记为手动测试
}
// E.2 Proceed 结果返回 YES
- (void)testEvaluateServerTrust_ProceedResult_ReturnsYES {
// Mock SecTrustEvaluate 返回 kSecTrustResultProceed
}
// E.3 无效è¯<C3A8>ä¹¦è¿”åž NO
- (void)testEvaluateServerTrust_InvalidCertificate_ReturnsNO {
// Mock SecTrustEvaluate 返回 kSecTrustResultDeny
}
// E.4 指定域å<C5B8><C3A5>使用 SSL Policy
- (void)testEvaluateServerTrust_WithDomain_UsesSSLPolicy {
// 验è¯<C3A8>使用äº?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 超大å“<C3A5>应ä½?
- (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 è§£ç <C3A7>失败åžé€€åˆ°åŽŸå§æ•°æ<C2B0>?
- (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 生æˆ<C3A6>æµè¯•
- (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