EMASCurlTests/EMASCurlDownloadTest.m (166 lines of code) (raw):

// // EMASCurlDownloadTest.m // EMASCurlTests // // Created by xuyecan on 2024/12/16. // #import <Foundation/Foundation.h> #import <XCTest/XCTest.h> #import <EMASCurl/EMASCurl.h> #import "EMASCurlTestConstants.h" static NSURLSession *session; @interface EMASCurlDownloadTestBase : XCTestCase <NSURLSessionDataDelegate> @property (nonatomic, assign) int64_t totalBytesReceived; @property (nonatomic, assign) int64_t expectedTotalBytes; @property (nonatomic, strong) NSMutableArray<NSNumber *> *progressValues; @property (nonatomic, copy) void (^completionBlock)(NSData *data, NSURLResponse *response, NSError *error); @property (nonatomic, copy) void (^progressBlock)(double progress); @property (nonatomic, strong) NSMutableData *receivedData; @end @implementation EMASCurlDownloadTestBase - (void)setUp { [super setUp]; self.progressValues = [NSMutableArray array]; self.totalBytesReceived = 0; self.expectedTotalBytes = 1024 * 1024; } - (void)downloadData:(NSString *)endpoint { NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", endpoint, PATH_DOWNLOAD_1MB_DATA_AT_200KBPS_SPEED]]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { XCTAssertNil(error, @"Download failed with error: %@", error); XCTAssertNotNil(response, @"No response received"); NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; XCTAssertEqual(httpResponse.statusCode, 200, @"Expected 200 status code"); XCTAssertEqual(1024 * 1024, [data length], @"Expected 1MB of data"); dispatch_semaphore_signal(semaphore); }]; [dataTask resume]; XCTAssertEqual(dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC)), 0, @"Download request timed out"); } - (void)downloadDataWithProgress:(NSString *)endpoint { NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", endpoint, PATH_DOWNLOAD_1MB_DATA_AT_200KBPS_SPEED]]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); self.totalBytesReceived = 0; self.expectedTotalBytes = 1024 * 1024; self.progressValues = [NSMutableArray array]; self.receivedData = [NSMutableData data]; __weak typeof(self) weakSelf = self; self.completionBlock = ^(NSData *data, NSURLResponse *response, NSError *error) { XCTAssertNil(error, @"Download failed with error: %@", error); XCTAssertNotNil(response, @"No response received"); typeof(self) strongSelf = weakSelf; NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; XCTAssertEqual(httpResponse.statusCode, 200, @"Expected 200 status code"); XCTAssertEqual(1024 * 1024, [data length], @"Expected 1MB of data"); XCTAssertGreaterThan(strongSelf.progressValues.count, 0, @"Should have received progress updates"); XCTAssertEqualWithAccuracy([[strongSelf.progressValues lastObject] doubleValue], 1.0, 0.01, @"Final progress should be 100%"); double previousProgress = 0; for (NSNumber *progress in strongSelf.progressValues) { XCTAssertGreaterThanOrEqual([progress doubleValue], previousProgress, @"Progress should increase monotonically"); previousProgress = [progress doubleValue]; } dispatch_semaphore_signal(semaphore); }; self.progressBlock = ^(double progress) { typeof(self) strongSelf = weakSelf; [strongSelf.progressValues addObject:@(progress)]; }; NSURLSession *progressSession = [NSURLSession sessionWithConfiguration:[session configuration] delegate:self delegateQueue:nil]; NSURLSessionDataTask *dataTask = [progressSession dataTaskWithRequest:request]; [dataTask resume]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); } - (void)downloadDataAndCancel:(NSString *)endpoint { NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", endpoint, PATH_DOWNLOAD_1MB_DATA_AT_200KBPS_SPEED]]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); self.totalBytesReceived = 0; self.expectedTotalBytes = 1024 * 1024; self.progressValues = [NSMutableArray array]; self.receivedData = [NSMutableData data]; NSURLSession *progressSession = [NSURLSession sessionWithConfiguration:[session configuration] delegate:self delegateQueue:nil]; NSURLSessionDataTask *dataTask = [progressSession dataTaskWithRequest:request]; __weak typeof(self) weakSelf = self; self.completionBlock = ^(NSData *data, NSURLResponse *response, NSError *error) { XCTAssertNotNil(error, @"Expected error due to cancellation"); XCTAssertEqual(error.code, -999, @"Expected cancellation error code"); typeof(self) strongSelf = weakSelf; XCTAssertLessThan([[strongSelf.progressValues lastObject] doubleValue], 0.5, @"Final progress should less than 50%"); dispatch_semaphore_signal(semaphore); }; self.progressBlock = ^(double progress) { if (progress > 0.3) { [dataTask cancel]; } typeof(self) strongSelf = weakSelf; [strongSelf.progressValues addObject:@(progress)]; }; [dataTask resume]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); } #pragma mark - NSURLSessionDataDelegate - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [self.receivedData appendData:data]; self.totalBytesReceived += data.length; double progress = (double)self.totalBytesReceived / self.expectedTotalBytes; if (self.progressBlock) { self.progressBlock(progress); } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { if (self.completionBlock) { NSURLSessionDataTask *dataTask = (NSURLSessionDataTask *)task; self.completionBlock(self.receivedData, dataTask.response, error); } } @end @interface EMASCurlDownloadTestHttp11 : EMASCurlDownloadTestBase @end @implementation EMASCurlDownloadTestHttp11 + (void)setUp { [EMASCurlProtocol setDebugLogEnabled:YES]; NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; [EMASCurlProtocol installIntoSessionConfiguration:config]; session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil]; } - (void)testDownloadBinaryData { [self downloadData:HTTP11_ENDPOINT]; } - (void)testDownloadDataWithProgress { [self downloadDataWithProgress:HTTP11_ENDPOINT]; } - (void)testDownloadDataAndCancel { [self downloadDataAndCancel:HTTP11_ENDPOINT]; } - (void)testCancelDownloadAndDownloadAgain { [self downloadDataAndCancel:HTTP11_ENDPOINT]; [self downloadData:HTTP11_ENDPOINT]; } @end @interface EMASCurlDownloadTestHttp2 : EMASCurlDownloadTestBase @end @implementation EMASCurlDownloadTestHttp2 + (void)setUp { [EMASCurlProtocol setDebugLogEnabled:YES]; [EMASCurlProtocol setHTTPVersion:HTTP2]; NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; NSString *certPath = [testBundle pathForResource:@"ca" ofType:@"crt"]; XCTAssertNotNil(certPath, @"Certificate file not found in test bundle."); [EMASCurlProtocol setSelfSignedCAFilePath:certPath]; NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; [EMASCurlProtocol installIntoSessionConfiguration:config]; session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil]; } - (void)testDownloadBinaryData { [self downloadData:HTTP2_ENDPOINT]; } - (void)testDownloadDataWithProgress { [self downloadDataWithProgress:HTTP2_ENDPOINT]; } - (void)testDownloadDataAndCancel { [self downloadDataAndCancel:HTTP2_ENDPOINT]; } - (void)testCancelDownloadAndDownloadAgain { [self downloadDataAndCancel:HTTP2_ENDPOINT]; [self downloadData:HTTP2_ENDPOINT]; } @end