407 lines
19 KiB
Objective-C
407 lines
19 KiB
Objective-C
//
|
||
// HttpdnsNWHTTPClient_BasicIntegrationTests.m
|
||
// TrustHttpDNSTests
|
||
//
|
||
// @author Created by Claude Code on 2025-11-01
|
||
// Copyright © 2025 trustapp.com. All rights reserved.
|
||
//
|
||
// 基础集æˆ<C3A6>测试 - 包å<E280A6>«åŸºç¡€åŠŸèƒ½ (G) 和连接å¤<C3A5>ç”?(J) 测试ç»?
|
||
// 测试总数�2 个(G:7 + J:5�
|
||
//
|
||
|
||
#import "HttpdnsNWHTTPClientTestBase.h"
|
||
|
||
@interface HttpdnsNWHTTPClient_BasicIntegrationTests : HttpdnsNWHTTPClientTestBase
|
||
|
||
@end
|
||
|
||
@implementation HttpdnsNWHTTPClient_BasicIntegrationTests
|
||
|
||
#pragma mark - G. 集æˆ<C3A6>测试(真实网络)
|
||
|
||
// G.1 HTTP GET 请求
|
||
- (void)testIntegration_HTTPGetRequest_RealNetwork {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:@"HTTP GET request"];
|
||
|
||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
NSError *error = nil;
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"HttpdnsNWHTTPClient/1.0"
|
||
timeout:15.0
|
||
error:&error];
|
||
|
||
XCTAssertNotNil(response, @"Response should not be nil");
|
||
XCTAssertNil(error, @"Error should be nil, got: %@", error);
|
||
XCTAssertEqual(response.statusCode, 200, @"Status code should be 200");
|
||
XCTAssertNotNil(response.body, @"Body should not be nil");
|
||
XCTAssertGreaterThan(response.body.length, 0, @"Body should not be empty");
|
||
|
||
// 验è¯<C3A8>å“<C3A5>应包å<E280A6>« JSON
|
||
NSError *jsonError = nil;
|
||
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:response.body
|
||
options:0
|
||
error:&jsonError];
|
||
XCTAssertNotNil(jsonDict, @"Response should be valid JSON");
|
||
XCTAssertNil(jsonError, @"JSON parsing should succeed");
|
||
|
||
[expectation fulfill];
|
||
});
|
||
|
||
[self waitForExpectations:@[expectation] timeout:20.0];
|
||
}
|
||
|
||
// G.2 HTTPS GET 请求
|
||
- (void)testIntegration_HTTPSGetRequest_RealNetwork {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:@"HTTPS GET request"];
|
||
|
||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
NSError *error = nil;
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
|
||
userAgent:@"HttpdnsNWHTTPClient/1.0"
|
||
timeout:15.0
|
||
error:&error];
|
||
|
||
XCTAssertNotNil(response, @"Response should not be nil");
|
||
XCTAssertNil(error, @"Error should be nil, got: %@", error);
|
||
XCTAssertEqual(response.statusCode, 200, @"Status code should be 200");
|
||
XCTAssertNotNil(response.body, @"Body should not be nil");
|
||
|
||
// 验è¯<C3A8> TLS æˆ<C3A6>功建立
|
||
XCTAssertGreaterThan(response.body.length, 0, @"HTTPS body should not be empty");
|
||
|
||
[expectation fulfill];
|
||
});
|
||
|
||
[self waitForExpectations:@[expectation] timeout:20.0];
|
||
}
|
||
|
||
// G.3 HTTP 404 å“<C3A5>应
|
||
- (void)testIntegration_NotFound_Returns404 {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:@"404 response"];
|
||
|
||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
NSError *error = nil;
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/status/404"
|
||
userAgent:@"HttpdnsNWHTTPClient/1.0"
|
||
timeout:15.0
|
||
error:&error];
|
||
|
||
XCTAssertNotNil(response, @"Response should not be nil even for 404");
|
||
XCTAssertNil(error, @"Error should be nil for valid HTTP response");
|
||
XCTAssertEqual(response.statusCode, 404, @"Status code should be 404");
|
||
|
||
[expectation fulfill];
|
||
});
|
||
|
||
[self waitForExpectations:@[expectation] timeout:20.0];
|
||
}
|
||
|
||
// G.4 连接å¤<C3A5>用测试
|
||
- (void)testIntegration_ConnectionReuse_MultipleRequests {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:@"Connection reuse"];
|
||
|
||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
NSError *error1 = nil;
|
||
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"HttpdnsNWHTTPClient/1.0"
|
||
timeout:15.0
|
||
error:&error1];
|
||
|
||
XCTAssertNotNil(response1, @"First response should not be nil");
|
||
XCTAssertNil(error1, @"First request should succeed");
|
||
XCTAssertEqual(response1.statusCode, 200);
|
||
|
||
// ç«‹å<E280B9>³å<C2B3>‘起第二个请求,应该å¤<C3A5>用连接
|
||
NSError *error2 = nil;
|
||
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"HttpdnsNWHTTPClient/1.0"
|
||
timeout:15.0
|
||
error:&error2];
|
||
|
||
XCTAssertNotNil(response2, @"Second response should not be nil");
|
||
XCTAssertNil(error2, @"Second request should succeed");
|
||
XCTAssertEqual(response2.statusCode, 200);
|
||
|
||
[expectation fulfill];
|
||
});
|
||
|
||
[self waitForExpectations:@[expectation] timeout:30.0];
|
||
}
|
||
|
||
// G.5 Chunked å“<C3A5>应处ç<E2809E>†
|
||
- (void)testIntegration_ChunkedResponse_RealNetwork {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:@"Chunked response"];
|
||
|
||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
NSError *error = nil;
|
||
// httpbin.org/stream-bytes 返回 chunked ç¼–ç <C3A7>çš„å“<C3A5>åº?
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/stream-bytes/1024"
|
||
userAgent:@"HttpdnsNWHTTPClient/1.0"
|
||
timeout:15.0
|
||
error:&error];
|
||
|
||
XCTAssertNotNil(response, @"Response should not be nil");
|
||
XCTAssertNil(error, @"Error should be nil, got: %@", error);
|
||
XCTAssertEqual(response.statusCode, 200);
|
||
XCTAssertEqual(response.body.length, 1024, @"Should receive exactly 1024 bytes");
|
||
|
||
// 验è¯<C3A8> Transfer-Encoding å¤?
|
||
NSString *transferEncoding = response.headers[@"transfer-encoding"];
|
||
if (transferEncoding) {
|
||
XCTAssertTrue([transferEncoding containsString:@"chunked"], @"Should use chunked encoding");
|
||
}
|
||
|
||
[expectation fulfill];
|
||
});
|
||
|
||
[self waitForExpectations:@[expectation] timeout:20.0];
|
||
}
|
||
|
||
#pragma mark - é¢<C3A9>外的集æˆ<C3A6>测è¯?
|
||
|
||
// G.6 超时测试(å<CB86>¯é€‰ï¼‰
|
||
- (void)testIntegration_RequestTimeout_ReturnsError {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:@"Request timeout"];
|
||
|
||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
NSError *error = nil;
|
||
// httpbin.org/delay/10 会延è¿?10 ç§’å“<C3A5>应,我们设置 2 ç§’è¶…æ—?
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
|
||
userAgent:@"HttpdnsNWHTTPClient/1.0"
|
||
timeout:2.0
|
||
error:&error];
|
||
|
||
XCTAssertNil(response, @"Response should be nil on timeout");
|
||
XCTAssertNotNil(error, @"Error should be set on timeout");
|
||
|
||
[expectation fulfill];
|
||
});
|
||
|
||
[self waitForExpectations:@[expectation] timeout:5.0];
|
||
}
|
||
|
||
// G.7 多个ä¸<C3A4>å<EFBFBD>Œå¤´éƒ¨çš„请æ±?
|
||
- (void)testIntegration_CustomHeaders_Reflected {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:@"Custom headers"];
|
||
|
||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
NSError *error = nil;
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/headers"
|
||
userAgent:@"TestUserAgent/1.0"
|
||
timeout:15.0
|
||
error:&error];
|
||
|
||
XCTAssertNotNil(response);
|
||
XCTAssertEqual(response.statusCode, 200);
|
||
|
||
// è§£æž<C3A6> JSON å“<C3A5>应,验è¯<C3A8>我们的 User-Agent 被å<C2AB>‘é€?
|
||
NSError *jsonError = nil;
|
||
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:response.body
|
||
options:0
|
||
error:&jsonError];
|
||
XCTAssertNotNil(jsonDict);
|
||
|
||
NSDictionary *headers = jsonDict[@"headers"];
|
||
XCTAssertTrue([headers[@"User-Agent"] containsString:@"TestUserAgent"], @"User-Agent should be sent");
|
||
|
||
[expectation fulfill];
|
||
});
|
||
|
||
[self waitForExpectations:@[expectation] timeout:20.0];
|
||
}
|
||
|
||
#pragma mark - J. 连接å¤<C3A5>用详细测试
|
||
|
||
// J.1 连接过期测试ï¼?1ç§’å<E28099>Žåˆ›å»ºæ–°è¿žæŽ¥ï¼‰
|
||
- (void)testConnectionReuse_Expiry31Seconds_NewConnectionCreated {
|
||
if (getenv("SKIP_SLOW_TESTS")) {
|
||
return;
|
||
}
|
||
|
||
XCTestExpectation *expectation = [self expectationWithDescription:@"Connection expiry"];
|
||
|
||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
CFAbsoluteTime time1 = CFAbsoluteTimeGetCurrent();
|
||
NSError *error1 = nil;
|
||
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"First"
|
||
timeout:15.0
|
||
error:&error1];
|
||
CFAbsoluteTime elapsed1 = CFAbsoluteTimeGetCurrent() - time1;
|
||
XCTAssertTrue(response1 != nil || error1 != nil);
|
||
|
||
// ç‰å¾…31秒让连接过期
|
||
[NSThread sleepForTimeInterval:31.0];
|
||
|
||
// 第二个请求应该创建新连接(å<CB86>¯èƒ½ç¨<C3A7>æ…¢ï¼Œå› ä¸ºéœ€è¦<C3A8>建立连接)
|
||
CFAbsoluteTime time2 = CFAbsoluteTimeGetCurrent();
|
||
NSError *error2 = nil;
|
||
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"Second"
|
||
timeout:15.0
|
||
error:&error2];
|
||
CFAbsoluteTime elapsed2 = CFAbsoluteTimeGetCurrent() - time2;
|
||
XCTAssertTrue(response2 != nil || error2 != nil);
|
||
|
||
// 注æ„<C3A6>:由于网络波动,ä¸<C3A4>èƒ½ä¸¥æ ¼æ¯”è¾ƒæ—¶é—´
|
||
// å<>ªéªŒè¯<C3A8>请求都æˆ<C3A6>功å<C5B8>³å<C2B3>¯
|
||
|
||
[expectation fulfill];
|
||
});
|
||
|
||
[self waitForExpectations:@[expectation] timeout:70.0];
|
||
}
|
||
|
||
// J.2 è¿žæŽ¥æ± å®¹é‡<C3A9>é™<C3A9>制验è¯?
|
||
- (void)testConnectionReuse_TenRequests_OnlyFourConnectionsKept {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:@"Pool size limit"];
|
||
|
||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
// 连ç»10个请æ±?
|
||
for (NSInteger i = 0; i < 10; i++) {
|
||
NSError *error = nil;
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"PoolSizeTest"
|
||
timeout:15.0
|
||
error:&error];
|
||
XCTAssertTrue(response != nil || error != nil);
|
||
}
|
||
|
||
// ç‰å¾…所有连接归è¿?
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
// æ— æ³•ç›´æŽ¥éªŒè¯<C3A8>æ± å¤§å°<C3A5>,但如果实现æ£ç¡®ï¼Œæ± 应自动é™<C3A9>制
|
||
// å<>Žç»è¯·æ±‚应该ä»<C3A4>能æ£å¸¸å·¥ä½œ
|
||
NSError *error = nil;
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"Verification"
|
||
timeout:15.0
|
||
error:&error];
|
||
XCTAssertTrue(response != nil || error != nil);
|
||
|
||
[expectation fulfill];
|
||
});
|
||
|
||
[self waitForExpectations:@[expectation] timeout:120.0];
|
||
}
|
||
|
||
// J.3 ä¸<C3A4>å<EFBFBD>Œè·¯å¾„å¤<C3A5>用连接
|
||
- (void)testConnectionReuse_DifferentPaths_SameConnection {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:@"Different paths"];
|
||
|
||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
NSArray<NSString *> *paths = @[@"/get", @"/headers", @"/user-agent", @"/uuid"];
|
||
NSMutableArray<NSNumber *> *times = [NSMutableArray array];
|
||
|
||
for (NSString *path in paths) {
|
||
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
|
||
NSString *urlString = [NSString stringWithFormat:@"http://127.0.0.1:11080%@", path];
|
||
NSError *error = nil;
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
|
||
userAgent:@"PathTest"
|
||
timeout:15.0
|
||
error:&error];
|
||
CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - start;
|
||
|
||
XCTAssertTrue(response != nil || error != nil);
|
||
[times addObject:@(elapsed)];
|
||
}
|
||
|
||
// 如果连接å¤<C3A5>用工作æ£å¸¸ï¼Œå<C592>Žç»è¯·æ±‚应该更快(但网络波动å<C2A8>¯èƒ½å½±å“<C3A5>)
|
||
// 至少验è¯<C3A8>所有请求都æˆ<C3A6>功
|
||
XCTAssertEqual(times.count, paths.count);
|
||
|
||
[expectation fulfill];
|
||
});
|
||
|
||
[self waitForExpectations:@[expectation] timeout:60.0];
|
||
}
|
||
|
||
// J.4 HTTP vs HTTPS 使用ä¸<C3A4>å<EFBFBD>Œè¿žæŽ¥
|
||
- (void)testConnectionReuse_HTTPvsHTTPS_DifferentPoolKeys {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:@"HTTP vs HTTPS"];
|
||
|
||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
// HTTP 请求
|
||
NSError *httpError = nil;
|
||
HttpdnsNWHTTPClientResponse *httpResponse = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"HTTP"
|
||
timeout:15.0
|
||
error:&httpError];
|
||
XCTAssertTrue(httpResponse != nil || httpError != nil);
|
||
|
||
// HTTPS 请求(应该使用ä¸<C3A4>å<EFBFBD>Œçš„连接æ±?keyï¼?
|
||
NSError *httpsError = nil;
|
||
HttpdnsNWHTTPClientResponse *httpsResponse = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
|
||
userAgent:@"HTTPS"
|
||
timeout:15.0
|
||
error:&httpsError];
|
||
XCTAssertTrue(httpsResponse != nil || httpsError != nil);
|
||
|
||
// 两者都应该æˆ<C3A6>功,且ä¸<C3A4>会相互干扰
|
||
[expectation fulfill];
|
||
});
|
||
|
||
[self waitForExpectations:@[expectation] timeout:35.0];
|
||
}
|
||
|
||
// J.5 长连接ä¿<C3A4>æŒ<C3A6>测è¯?
|
||
- (void)testConnectionReuse_TwentyRequestsOneSecondApart_ConnectionKeptAlive {
|
||
if (getenv("SKIP_SLOW_TESTS")) {
|
||
return;
|
||
}
|
||
|
||
XCTestExpectation *expectation = [self expectationWithDescription:@"Keep-alive"];
|
||
|
||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
NSInteger successCount = 0;
|
||
NSMutableArray<NSNumber *> *requestTimes = [NSMutableArray array];
|
||
|
||
// 20个请求,间隔1秒(第一个请求立å<E280B9>³æ‰§è¡Œï¼‰
|
||
for (NSInteger i = 0; i < 20; i++) {
|
||
// 除第一个请求外,æ¯<C3A6>次请求å‰<C3A5>ç‰å¾…1ç§?
|
||
if (i > 0) {
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
}
|
||
|
||
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
|
||
NSError *error = nil;
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"KeepAlive"
|
||
timeout:10.0
|
||
error:&error];
|
||
CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - startTime;
|
||
[requestTimes addObject:@(elapsed)];
|
||
|
||
if (response && (response.statusCode == 200 || response.statusCode == 503)) {
|
||
successCount++;
|
||
} else {
|
||
// 如果请求失败,æ<C592><C3A6>å‰<C3A5>退出以é<C2A5>¿å…<C3A5>è¶…æ—¶
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 至少大部分请求应该æˆ<C3A6>åŠ?
|
||
XCTAssertGreaterThan(successCount, 15, @"Most requests should succeed with connection reuse");
|
||
|
||
// 验è¯<C3A8>连接å¤<C3A5>用:å<C5A1>Žç»è¯·æ±‚应该更快(如果使用了keep-aliveï¼?
|
||
if (requestTimes.count >= 10) {
|
||
double firstRequestTime = [requestTimes[0] doubleValue];
|
||
double laterAvgTime = 0;
|
||
for (NSInteger i = 5; i < MIN(10, requestTimes.count); i++) {
|
||
laterAvgTime += [requestTimes[i] doubleValue];
|
||
}
|
||
laterAvgTime /= MIN(5, requestTimes.count - 5);
|
||
// å<>Žç»è¯·æ±‚应该ä¸<C3A4>会明显更慢(说明连接å¤<C3A5>用工作æ£å¸¸ï¼‰
|
||
XCTAssertLessThanOrEqual(laterAvgTime, firstRequestTime * 2.0, @"Connection reuse should keep latency reasonable");
|
||
}
|
||
|
||
[expectation fulfill];
|
||
});
|
||
|
||
// 超时计算: 19ç§’sleep + 20个请求×~2ç§?= 59秒,设置50秒(æ<CB86><C3A6>å‰<C3A5>退出机制ä¿<C3A4>è¯<C3A8>效率)
|
||
[self waitForExpectations:@[expectation] timeout:50.0];
|
||
}
|
||
|
||
@end
|