From 1095fad6202251157f16598b3e7cd92711796bb8 Mon Sep 17 00:00:00 2001 From: thibaultCha Date: Wed, 23 Oct 2013 22:04:17 +0200 Subject: [PATCH] More tests. Some code refactoring. More error handling. Removed TCLogs. Travis badge. --- README.md | 2 +- TCBlobDownload.podspec | 15 +-- .../TCBlobDownload.xcodeproj/project.pbxproj | 46 ++++--- .../TCBlobDownload/TCBlobDownload.h | 4 +- .../TCBlobDownload/TCBlobDownload.m | 77 ++++++----- .../TCBlobDownload/TCBlobDownloadManager.m | 9 +- .../TCBlobDownloadErrorTests.m | 86 ++++++++++++ .../TCBlobDownloadTests/TCBlobDownloadTests.m | 46 ++++++- .../TCBlobDownloadTests/TestValues.h | 21 +++ .../XCTestCase+AsyncTesting.h | 29 ++++ .../XCTestCase+AsyncTesting.m | 127 ++++++++++++++++++ 11 files changed, 395 insertions(+), 67 deletions(-) create mode 100644 TCBlobDownload/TCBlobDownloadTests/TCBlobDownloadErrorTests.m create mode 100644 TCBlobDownload/TCBlobDownloadTests/TestValues.h create mode 100755 TCBlobDownload/TCBlobDownloadTests/XCTestCase+AsyncTesting.h create mode 100755 TCBlobDownload/TCBlobDownloadTests/XCTestCase+AsyncTesting.m diff --git a/README.md b/README.md index a63ad20f..81a68a07 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TCBlobDownload -**Unit tests comming.** +[![Build Status](https://api.travis-ci.org/thibaultCha/TCBlobDownload.png)](https://travis-ci.org/thibaultCha/TCBlobDownload) This library uses **NSOperations** to download big files (typically videos, music... well: BLOBs) using **NSURLConnection** in background threads. This is a static library, very easy to import in your project and it allows you to pull the latest updates. Installation steps are explained in usage section. diff --git a/TCBlobDownload.podspec b/TCBlobDownload.podspec index 62c90aaf..98c691c3 100644 --- a/TCBlobDownload.podspec +++ b/TCBlobDownload.podspec @@ -14,14 +14,13 @@ Pod::Spec.new do |s| s.license = 'MIT (example)' s.author = { "Thibault Charbonnier" => "thibaultcha@me.com" } - # s.platform = :ios - # s.platform = :ios, '5.0' + s.platform = :ios + s.ios.deployment_target = '5.0' - # When using multiple platforms - # s.ios.deployment_target = '5.0' - # s.osx.deployment_target = '10.7' - - s.source = { :git => "https://github.com/thibaultCha/TCBlobDownload", :tag => "1.3.1" } - s.source_files = 'TCBlobDownload/TCBlobDownload/*.{h,m}' + s.source = { + :git => "https://github.com/thibaultCha/TCBlobDownload", + :tag => "1.3.1" + } + s.source_files = 'TCBlobDownload/TCBlobDownload/*.{h,m}' s.requires_arc = true end diff --git a/TCBlobDownload/TCBlobDownload.xcodeproj/project.pbxproj b/TCBlobDownload/TCBlobDownload.xcodeproj/project.pbxproj index b06d0441..71f9266b 100644 --- a/TCBlobDownload/TCBlobDownload.xcodeproj/project.pbxproj +++ b/TCBlobDownload/TCBlobDownload.xcodeproj/project.pbxproj @@ -20,6 +20,8 @@ 21776214172FFC4A001956C7 /* TCBlobDownloadManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 2177620F172FFBD9001956C7 /* TCBlobDownloadManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 21776215172FFC56001956C7 /* TCBlobDownload.h in Headers */ = {isa = PBXBuildFile; fileRef = 2177620D172FFBD9001956C7 /* TCBlobDownload.h */; settings = {ATTRIBUTES = (Public, ); }; }; 21776220172FFE64001956C7 /* TCBlobDownload-Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 2177621F172FFE64001956C7 /* TCBlobDownload-Prefix.pch */; }; + 21D06E26181852B600CAADCD /* XCTestCase+AsyncTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = 21D06E25181852B600CAADCD /* XCTestCase+AsyncTesting.m */; }; + 21D06E281818539500CAADCD /* TCBlobDownloadErrorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 21D06E271818539500CAADCD /* TCBlobDownloadErrorTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -54,12 +56,16 @@ 21203AE2178C593900C19335 /* TCBlobDownloadTests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TCBlobDownloadTests-Prefix.pch"; sourceTree = ""; }; 217761FC172FFBBB001956C7 /* libTCBlobDownload.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libTCBlobDownload.a; sourceTree = BUILT_PRODUCTS_DIR; }; 217761FF172FFBBB001956C7 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 2177620D172FFBD9001956C7 /* TCBlobDownload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCBlobDownload.h; sourceTree = ""; }; - 2177620E172FFBD9001956C7 /* TCBlobDownload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCBlobDownload.m; sourceTree = ""; }; - 2177620F172FFBD9001956C7 /* TCBlobDownloadManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCBlobDownloadManager.h; sourceTree = ""; }; - 21776210172FFBD9001956C7 /* TCBlobDownloadManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCBlobDownloadManager.m; sourceTree = ""; }; + 2177620D172FFBD9001956C7 /* TCBlobDownload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TCBlobDownload.h; path = TCBlobDownload/TCBlobDownload.h; sourceTree = ""; }; + 2177620E172FFBD9001956C7 /* TCBlobDownload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TCBlobDownload.m; path = TCBlobDownload/TCBlobDownload.m; sourceTree = ""; }; + 2177620F172FFBD9001956C7 /* TCBlobDownloadManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TCBlobDownloadManager.h; path = TCBlobDownload/TCBlobDownloadManager.h; sourceTree = ""; }; + 21776210172FFBD9001956C7 /* TCBlobDownloadManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TCBlobDownloadManager.m; path = TCBlobDownload/TCBlobDownloadManager.m; sourceTree = ""; }; 2177621F172FFE64001956C7 /* TCBlobDownload-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "TCBlobDownload-Prefix.pch"; path = "TCBlobDownload/TCBlobDownload-Prefix.pch"; sourceTree = ""; }; 217AB36D17395D0700A174C5 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = TCBlobDownload/fr.lproj/Localizable.strings; sourceTree = ""; }; + 21D06E24181852B600CAADCD /* XCTestCase+AsyncTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCTestCase+AsyncTesting.h"; sourceTree = ""; }; + 21D06E25181852B600CAADCD /* XCTestCase+AsyncTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCTestCase+AsyncTesting.m"; sourceTree = ""; }; + 21D06E271818539500CAADCD /* TCBlobDownloadErrorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCBlobDownloadErrorTests.m; sourceTree = ""; }; + 21D06E29181853DA00CAADCD /* TestValues.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestValues.h; sourceTree = ""; }; 21EE597D17395AA10084CF48 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = TCBlobDownload/en.lproj/Localizable.strings; sourceTree = ""; }; /* End PBXFileReference section */ @@ -88,7 +94,10 @@ 21203ADA178C593900C19335 /* TCBlobDownloadTests */ = { isa = PBXGroup; children = ( + 21D06E29181853DA00CAADCD /* TestValues.h */, 21203AE0178C593900C19335 /* TCBlobDownloadTests.m */, + 21D06E271818539500CAADCD /* TCBlobDownloadErrorTests.m */, + 21D06E231818529000CAADCD /* libs */, 21203ADB178C593900C19335 /* Supporting Files */, ); path = TCBlobDownloadTests; @@ -107,7 +116,10 @@ 217761F3172FFBBB001956C7 = { isa = PBXGroup; children = ( - 21776201172FFBBB001956C7 /* TCBlobDownload */, + 2177620D172FFBD9001956C7 /* TCBlobDownload.h */, + 2177620E172FFBD9001956C7 /* TCBlobDownload.m */, + 2177620F172FFBD9001956C7 /* TCBlobDownloadManager.h */, + 21776210172FFBD9001956C7 /* TCBlobDownloadManager.m */, 21776221172FFE8F001956C7 /* Supporting Files */, 21203ADA178C593900C19335 /* TCBlobDownloadTests */, 217761FE172FFBBB001956C7 /* Frameworks */, @@ -134,24 +146,22 @@ name = Frameworks; sourceTree = ""; }; - 21776201172FFBBB001956C7 /* TCBlobDownload */ = { + 21776221172FFE8F001956C7 /* Supporting Files */ = { isa = PBXGroup; children = ( - 2177620D172FFBD9001956C7 /* TCBlobDownload.h */, - 2177620E172FFBD9001956C7 /* TCBlobDownload.m */, - 2177620F172FFBD9001956C7 /* TCBlobDownloadManager.h */, - 21776210172FFBD9001956C7 /* TCBlobDownloadManager.m */, + 2177621F172FFE64001956C7 /* TCBlobDownload-Prefix.pch */, + 21EE597C17395AA10084CF48 /* Localizable.strings */, ); - path = TCBlobDownload; + name = "Supporting Files"; sourceTree = ""; }; - 21776221172FFE8F001956C7 /* Supporting Files */ = { + 21D06E231818529000CAADCD /* libs */ = { isa = PBXGroup; children = ( - 2177621F172FFE64001956C7 /* TCBlobDownload-Prefix.pch */, - 21EE597C17395AA10084CF48 /* Localizable.strings */, + 21D06E24181852B600CAADCD /* XCTestCase+AsyncTesting.h */, + 21D06E25181852B600CAADCD /* XCTestCase+AsyncTesting.m */, ); - name = "Supporting Files"; + name = libs; sourceTree = ""; }; /* End PBXGroup section */ @@ -256,7 +266,9 @@ buildActionMask = 2147483647; files = ( 21203AED178C63AE00C19335 /* TCBlobDownload.m in Sources */, + 21D06E281818539500CAADCD /* TCBlobDownloadErrorTests.m in Sources */, 21203AE1178C593900C19335 /* TCBlobDownloadTests.m in Sources */, + 21D06E26181852B600CAADCD /* XCTestCase+AsyncTesting.m in Sources */, 21203AEC178C63AB00C19335 /* TCBlobDownloadManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -323,7 +335,7 @@ GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = "TCBlobDownloadTests/TCBlobDownloadTests-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = xctest; }; @@ -348,7 +360,7 @@ GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = "TCBlobDownloadTests/TCBlobDownloadTests-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = xctest; }; diff --git a/TCBlobDownload/TCBlobDownload/TCBlobDownload.h b/TCBlobDownload/TCBlobDownload/TCBlobDownload.h index ef175e70..4797f362 100644 --- a/TCBlobDownload/TCBlobDownload/TCBlobDownload.h +++ b/TCBlobDownload/TCBlobDownload/TCBlobDownload.h @@ -5,7 +5,7 @@ // Copyright (c) 2013 Thibault Charbonnier. All rights reserved. // - +//#define NO_LOG #if defined(DEBUG) && !defined(NO_LOG) #define TCLog(format, ...) NSLog(format, ## __VA_ARGS__) #else @@ -14,6 +14,8 @@ #import +extern NSString * const HTTPErrorCode; + typedef void (^FirstResponseBlock)(NSURLResponse *response); typedef void (^ProgressBlock)(float receivedLength, float totalLength); typedef void (^ErrorBlock)(NSError *error); diff --git a/TCBlobDownload/TCBlobDownload/TCBlobDownload.m b/TCBlobDownload/TCBlobDownload/TCBlobDownload.m index 5f2a6377..e7a1ce40 100644 --- a/TCBlobDownload/TCBlobDownload/TCBlobDownload.m +++ b/TCBlobDownload/TCBlobDownload/TCBlobDownload.m @@ -8,6 +8,7 @@ const double kBufferSize = 1024*1024; // 1 MB const NSTimeInterval kDefaultTimeout = 30; NSString * const kErrorDomain = @"com.thibaultcha.tcblobdownload"; +NSString * const HTTPErrorCode = @"httpStatus"; #import "TCBlobDownload.h" @@ -76,15 +77,30 @@ - (void)start cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:kDefaultTimeout]; - NSAssert([NSURLConnection canHandleRequest:fileRequest], @"NSURLConnection can't handle provided request"); + if (![NSURLConnection canHandleRequest:fileRequest]) { + __autoreleasing NSError *error = [NSError errorWithDomain:kErrorDomain + code:1 + userInfo:@{ NSLocalizedDescriptionKey: + [NSString stringWithFormat:@"Invalid URL provided to TCBlobDownload: %@", + fileRequest.URL] }]; + if (self.errorBlock) { + self.errorBlock(error); + } + if ([self.delegate respondsToSelector:@selector(download:didStopWithError:)]) { + [self.delegate download:self didStopWithError:error]; + } + + [self finishOperation]; + + return; + } NSFileManager *fm = [NSFileManager defaultManager]; - // File already exists or not + if (![fm fileExistsAtPath:self.pathToFile]) { [fm createFileAtPath:self.pathToFile contents:nil attributes:nil]; - TCLog(@"Created file at path: %@", self.pathToFile); } else { uint64_t fileSize = [[fm attributesOfItemAtPath:self.pathToFile error:nil] fileSize]; @@ -99,7 +115,6 @@ - (void)start delegate:self startImmediately:NO]; if (self.connection) { - TCLog(@"Operation started for file:\n%@", self.pathToFile); [self.connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; [self willChangeValueForKey:@"isExecuting"]; @@ -124,14 +139,16 @@ - (BOOL)isFinished - (void)connection:(NSURLConnection*)connection didFailWithError:(NSError *)error { - TCLog(@"Download failed. Error - %@ %@", - [error localizedDescription], - [error userInfo][NSURLErrorFailingURLStringErrorKey]); + __autoreleasing NSError *downloadError = [NSError errorWithDomain:kErrorDomain + code:4 + userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Download failed for file: %@. Reason: %@", + self.fileName, + error.localizedDescription] }]; if (self.errorBlock) { - self.errorBlock(error); + self.errorBlock(downloadError); } if ([self.delegate respondsToSelector:@selector(download:didStopWithError:)]) { - [self.delegate download:self didStopWithError:error]; + [self.delegate download:self didStopWithError:downloadError]; } [self cancelDownloadAndRemoveFile:NO]; @@ -147,23 +164,30 @@ - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLRespons if (httpUrlResponse.statusCode >= 400) { error = [NSError errorWithDomain:kErrorDomain code:2 - userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:NSLocalizedString(@"HTTP error code %d (%@) ", @"HTTP error code {satus code} ({status code description})"), - httpUrlResponse.statusCode, - [NSHTTPURLResponse localizedStringForStatusCode:httpUrlResponse.statusCode]]}]; + userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat: + NSLocalizedString(@"HTTP error code %d (%@) ", @"HTTP error code {satus code} ({status code description})"), + httpUrlResponse.statusCode, + [NSHTTPURLResponse localizedStringForStatusCode:httpUrlResponse.statusCode]], + HTTPErrorCode: @(httpUrlResponse.statusCode) }]; } if ([TCBlobDownload freeDiskSpace] < _expectedDataLength && _expectedDataLength != -1) { error = [NSError errorWithDomain:kErrorDomain - code:1 - userInfo:@{NSLocalizedDescriptionKey: - NSLocalizedString(@"Not enough free disk space", @"")}]; + code:3 + userInfo:@{ NSLocalizedDescriptionKey:NSLocalizedString(@"Not enough free disk space", @"") }]; } - if (error) { - TCLog(@"Download failed. Error - %@ %@", - [error localizedDescription], - [error userInfo][NSURLErrorFailingURLStringErrorKey]); + if (!error) { + [self.receivedDataBuffer setData:nil]; + if (self.firstResponseBlock) { + self.firstResponseBlock(response); + } + if ([self.delegate respondsToSelector:@selector(download:didReceiveFirstResponse:)]) { + [self.delegate download:self didReceiveFirstResponse:response]; + } + } + else { if (self.errorBlock) { self.errorBlock(error); } @@ -173,16 +197,6 @@ - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLRespons [self cancelDownloadAndRemoveFile:NO]; } - else { - [self.receivedDataBuffer setData:nil]; - - if (self.firstResponseBlock) { - self.firstResponseBlock(response); - } - if ([self.delegate respondsToSelector:@selector(download:didReceiveFirstResponse:)]) { - [self.delegate download:self didReceiveFirstResponse:response]; - } - } } - (void)connection:(NSURLConnection*)connection didReceiveData:(NSData *)data @@ -210,7 +224,6 @@ - (void)connection:(NSURLConnection*)connection didReceiveData:(NSData *)data - (void)connectionDidFinishLoading:(NSURLConnection*)connection { - TCLog(@"Download succeeded. Bytes received: %lld", _receivedDataLength); [self.file writeData:self.receivedDataBuffer]; [self.receivedDataBuffer setData:nil]; @@ -237,13 +250,12 @@ - (void)finishOperation [self.file closeFile]; [self didChangeValueForKey:@"isFinished"]; [self didChangeValueForKey:@"isExecuting"]; - TCLog(@"Operation ended for file %@", self.fileName); } - (void)cancelDownloadAndRemoveFile:(BOOL)remove { [self finishOperation]; - TCLog(@"Cancel download received for file %@", self.pathToFile); + NSFileManager *fm = [NSFileManager defaultManager]; if (remove && [fm fileExistsAtPath:self.pathToFile]) { @@ -288,6 +300,7 @@ + (uint64_t)freeDiskSpace TCLog(@"Error obtaining system memory infos: Domain = %@, Code = %d", [error domain], [error code]); + // TODO handle error } return totalFreeSpace; } diff --git a/TCBlobDownload/TCBlobDownload/TCBlobDownloadManager.m b/TCBlobDownload/TCBlobDownload/TCBlobDownloadManager.m index 555ca337..fde8c98f 100644 --- a/TCBlobDownload/TCBlobDownload/TCBlobDownloadManager.m +++ b/TCBlobDownload/TCBlobDownload/TCBlobDownloadManager.m @@ -121,7 +121,6 @@ - (void)cancelAllDownloadsAndRemoveFiles:(BOOL)remove for (TCBlobDownload *blob in [self.operationQueue operations]) { [blob cancelDownloadAndRemoveFile:remove]; } - TCLog(@"Cancelled all downloads."); } @@ -133,6 +132,11 @@ + (BOOL)createPathFromPath:(NSString *)path { NSFileManager *fm = [NSFileManager defaultManager]; + if (path == nil || [path isEqualToString:@""]) { + // handle error + return false; + } + if ([fm fileExistsAtPath:path]) { return true; } @@ -143,7 +147,8 @@ + (BOOL)createPathFromPath:(NSString *)path attributes:nil error:&error]; if (error) { - TCLog(@"Error creating download directory - %@", error); + TCLog(@"Error creating download directory %@ - %@", path, error); + // TODO handle error } return created; } diff --git a/TCBlobDownload/TCBlobDownloadTests/TCBlobDownloadErrorTests.m b/TCBlobDownload/TCBlobDownloadTests/TCBlobDownloadErrorTests.m new file mode 100644 index 00000000..380e1e9d --- /dev/null +++ b/TCBlobDownload/TCBlobDownloadTests/TCBlobDownloadErrorTests.m @@ -0,0 +1,86 @@ +// +// TCBlobDownloadErrorTests.m +// TCBlobDownload +// +// Created by Thibault Charbonnier on 23/10/2013. +// Copyright (c) 2013 thibaultCha. All rights reserved. +// + +#import +#import "XCTestCase+AsyncTesting.h" + +#import "TestValues.h" +#import "TCBlobDownloadManager.h" + +@interface TCBlobDownloadErrorTests : XCTestCase +@property (nonatomic, strong) TCBlobDownloadManager *manager; +@end + +@implementation TCBlobDownloadErrorTests + +- (void)setUp +{ + [super setUp]; + + _manager = [[TCBlobDownloadManager alloc] init]; + + __autoreleasing NSError *error; + [[NSFileManager defaultManager] createDirectoryAtPath:[NSString pathWithComponents:@[NSTemporaryDirectory(), pathToDownloadTests]] + withIntermediateDirectories:YES + attributes:nil + error:&error]; + if (error) { + XCTFail(@"Error while creating tests directory"); + NSLog(@"Error : %d - %@", error.code, error.localizedDescription); + } + + [self.manager setDefaultDownloadPath:[NSString pathWithComponents:@[NSTemporaryDirectory(), pathToDownloadTests]]]; +} + +- (void)tearDown +{ + self.manager = nil; + + __autoreleasing NSError *error; + [[NSFileManager defaultManager]removeItemAtPath:[NSString pathWithComponents:@[NSTemporaryDirectory(), pathToDownloadTests]] + error:&error]; + if (error) { + XCTFail(@"Error while removing tests directory"); + NSLog(@"Error : %d - %@", error.code, error.localizedDescription); + } + + [super tearDown]; +} + +- (void)testInvalidURL +{ + [self.manager startDownloadWithURL:[NSURL URLWithString:kInvalidURLToDownload] + customPath:nil + firstResponse:NULL + progress:NULL + error:^(NSError *error) { + XCTAssertNotNil(error, @"No error passed for invalid URL"); + [self notify:XCTAsyncTestCaseStatusSucceeded]; + } + complete:NULL]; + + [self waitForStatus:XCTAsyncTestCaseStatusSucceeded timeout:5]; +} + +/*- (void)testHTTPErrorStatusCode +{ + [self.manager startDownloadWithURL:[NSURL URLWithString:k404URLToDownload] + customPath:nil + firstResponse:NULL + progress:NULL + error:^(NSError *error) { + XCTAssertNotNil(error, @"No error passed for 404 URL"); + XCTAssertEqual(error.userInfo[HTTPErrorCode], @(404), @"HTTP status code is not equal to 404"); + [self notify:XCTAsyncTestCaseStatusSucceeded]; + } + complete:NULL]; + + [self waitForStatus:XCTAsyncTestCaseStatusSucceeded timeout:5]; +}*/ + +@end diff --git a/TCBlobDownload/TCBlobDownloadTests/TCBlobDownloadTests.m b/TCBlobDownload/TCBlobDownloadTests/TCBlobDownloadTests.m index 70b08012..4af7f0d7 100644 --- a/TCBlobDownload/TCBlobDownloadTests/TCBlobDownloadTests.m +++ b/TCBlobDownload/TCBlobDownloadTests/TCBlobDownloadTests.m @@ -7,10 +7,10 @@ // #import -#import "TCBlobDownloadManager.h" +#import "XCTestCase+AsyncTesting.h" -static NSString * const pathToDownloadTests = @"com.thibaultcha.tcblobdltests"; -static NSString * const kValidURLToDownload = @"https://github.com/thibaultCha/TCBlobDownload/archive/master.zip"; +#import "TestValues.h" +#import "TCBlobDownloadManager.h" @interface TCBlobDownloadTests : XCTestCase @property (nonatomic, strong) TCBlobDownloadManager *manager; @@ -42,7 +42,8 @@ - (void)setUp - (void)tearDown { self.manager = nil; - + self.validURL = nil; + __autoreleasing NSError *error; [[NSFileManager defaultManager]removeItemAtPath:[NSString pathWithComponents:@[NSTemporaryDirectory(), pathToDownloadTests]] error:&error]; @@ -80,7 +81,13 @@ - (void)testSetDefaultDownloadPath { [self.manager setDefaultDownloadPath:NSHomeDirectory()]; XCTAssertEqualObjects(self.manager.defaultDownloadPath, NSHomeDirectory(), - @"Default download path is not setting correctly"); + @"Default download path is not set correctly"); +} + +- (void)testCreatePathFromPath +{ + // test if null + // test if exists } - (void)testAllOperationsCorrectlyCancelled @@ -90,6 +97,9 @@ - (void)testAllOperationsCorrectlyCancelled customPath:nil delegate:nil]; } + + [self waitForTimeout:kDefaultAsyncTimeout]; + [self.manager cancelAllDownloadsAndRemoveFiles:YES]; XCTAssert(self.manager.downloadCount == 0, @"TCBlobDownloadManager cancelAllDownload did not properly finished all operations."); @@ -99,7 +109,7 @@ - (void)testAllOperationsCorrectlyCancelled #pragma mark - TCBlobDownload -- (void)testShouldCreateDownloadDirectory +- (void)testShouldHandleNilDownloadPath { TCBlobDownload *download1 = [[TCBlobDownload alloc] initWithURL:self.validURL downloadPath:nil @@ -125,11 +135,32 @@ - (void)testShouldCreateDownloadDirectory } +- (void)testCreateDownloadDirectory +{ + NSString *testDirectory = [NSString pathWithComponents:@[self.manager.defaultDownloadPath, @"createme"]]; + + [self.manager startDownloadWithURL:self.validURL + customPath:testDirectory + firstResponse:^(NSURLResponse *response) { + BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:testDirectory]; + XCTAssert(exists, @"Custom download directory not created"); + + [self notify:XCTAsyncTestCaseStatusSucceeded]; + } + progress:NULL + error:NULL + complete:NULL]; + + [self waitForStatus:XCTAsyncTestCaseStatusSucceeded timeout:5]; +} + - (void)testOperationCorrectlyCancelled { TCBlobDownload *download = [self.manager startDownloadWithURL:self.validURL customPath:nil delegate:nil]; + [self waitForTimeout:kDefaultAsyncTimeout]; + [download cancelDownloadAndRemoveFile:YES]; XCTAssert(self.manager.downloadCount == 0, @"Operation TCBlobDownload did not finish properly."); } @@ -139,6 +170,9 @@ - (void)testFileIsRemovedOnCancel TCBlobDownload *download = [self.manager startDownloadWithURL:self.validURL customPath:nil delegate:nil]; + + [self waitForTimeout:kDefaultAsyncTimeout]; + [download cancelDownloadAndRemoveFile:YES]; __autoreleasing NSError *fileError; diff --git a/TCBlobDownload/TCBlobDownloadTests/TestValues.h b/TCBlobDownload/TCBlobDownloadTests/TestValues.h new file mode 100644 index 00000000..bcfff348 --- /dev/null +++ b/TCBlobDownload/TCBlobDownloadTests/TestValues.h @@ -0,0 +1,21 @@ +// +// TestValues.h +// TCBlobDownload +// +// Created by Thibault Charbonnier on 23/10/2013. +// Copyright (c) 2013 thibaultCha. All rights reserved. +// + +#ifndef TCBlobDownload_TestValues_h +#define TCBlobDownload_TestValues_h + +static NSString * const pathToDownloadTests = @"com.thibaultcha.tcblobdltests"; + +static NSString * const kValidURLToDownload = @"https://github.com/thibaultCha/TCBlobDownload/archive/master.zip"; + +static NSString * const kInvalidURLToDownload = @"wait, where?"; +static NSString * const k404URLToDownload = @"https://github.com/thibaultCha/TCBlobDownload/archive/totoro"; + +static const NSTimeInterval kDefaultAsyncTimeout = 2; + +#endif diff --git a/TCBlobDownload/TCBlobDownloadTests/XCTestCase+AsyncTesting.h b/TCBlobDownload/TCBlobDownloadTests/XCTestCase+AsyncTesting.h new file mode 100755 index 00000000..e7b3ed7f --- /dev/null +++ b/TCBlobDownload/TCBlobDownloadTests/XCTestCase+AsyncTesting.h @@ -0,0 +1,29 @@ +// +// XCTestCase+AsyncTesting.h +// AsyncXCTestingKit +// +// Created by 小野 将司 on 12/03/17. +// Modified for XCTest by Vincil Bishop +// Copyright (c) 2012年 AppBankGames Inc. All rights reserved. +// + +#import + + +enum { + XCTAsyncTestCaseStatusUnknown = 0, + XCTAsyncTestCaseStatusWaiting, + XCTAsyncTestCaseStatusSucceeded, + XCTAsyncTestCaseStatusFailed, + XCTAsyncTestCaseStatusCancelled, +}; +typedef NSUInteger XCTAsyncTestCaseStatus; + + +@interface XCTestCase (AsyncTesting) + +- (void)waitForStatus:(XCTAsyncTestCaseStatus)status timeout:(NSTimeInterval)timeout; +- (void)waitForTimeout:(NSTimeInterval)timeout; +- (void)notify:(XCTAsyncTestCaseStatus)status; + +@end diff --git a/TCBlobDownload/TCBlobDownloadTests/XCTestCase+AsyncTesting.m b/TCBlobDownload/TCBlobDownloadTests/XCTestCase+AsyncTesting.m new file mode 100755 index 00000000..fbd5baf5 --- /dev/null +++ b/TCBlobDownload/TCBlobDownloadTests/XCTestCase+AsyncTesting.m @@ -0,0 +1,127 @@ +// +// XCTestCase+AsyncTesting.m +// AsyncXCTestingKit +// +// Created by 小野 将司 on 12/03/17. +// Modified for XCTest by Vincil Bishop +// Copyright (c) 2012年 AppBankGames Inc. All rights reserved. +// + +#import "XCTestCase+AsyncTesting.h" +#import "objc/runtime.h" + +static void *kLoopUntil_Key = "LoopUntil_Key"; +static void *kNotified_Key = "kNotified_Key"; +static void *kNotifiedStatus_Key = "kNotifiedStatus_Key"; +static void *kExpectedStatus_Key = "kExpectedStatus_Key"; + +@implementation XCTestCase (AsyncTesting) + +#pragma mark - Public + + +- (void)waitForStatus:(XCTAsyncTestCaseStatus)status timeout:(NSTimeInterval)timeout +{ + self.notified = NO; + self.expectedStatus = status; + self.loopUntil = [NSDate dateWithTimeIntervalSinceNow:timeout]; + + NSDate *dt = [NSDate dateWithTimeIntervalSinceNow:0.1]; + while (!self.notified && [self.loopUntil timeIntervalSinceNow] > 0) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:dt]; + dt = [NSDate dateWithTimeIntervalSinceNow:0.1]; + } + + // Only assert when notified. Do not assert when timed out + // Fail if not notified + if (self.notified) { + XCTAssertEqual(self.notifiedStatus, self.expectedStatus, @"Notified status does not match the expected status."); + } else { + XCTFail(@"Async test timed out."); + } +} + +- (void)waitForTimeout:(NSTimeInterval)timeout +{ + self.notified = NO; + self.expectedStatus = XCTAsyncTestCaseStatusUnknown; + self.loopUntil = [NSDate dateWithTimeIntervalSinceNow:timeout]; + + NSDate *dt = [NSDate dateWithTimeIntervalSinceNow:0.1]; + while (!self.notified && [self.loopUntil timeIntervalSinceNow] > 0) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:dt]; + dt = [NSDate dateWithTimeIntervalSinceNow:0.1]; + } +} + +- (void)notify:(XCTAsyncTestCaseStatus)status +{ + self.notifiedStatus = status; + // self.notified must be set at the last of this method + self.notified = YES; +} + +#pragma nark - Object Association Helpers - + +- (void) setAssociatedObject:(id)anObject key:(void*)key +{ + objc_setAssociatedObject(self, key, anObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (id) getAssociatedObject:(void*)key +{ + id anObject = objc_getAssociatedObject(self, key); + return anObject; +} + +#pragma mark - Property Implementations - + +- (NSDate*) loopUntil +{ + return [self getAssociatedObject:kLoopUntil_Key]; +} + +- (void) setLoopUntil:(NSDate*)value +{ + [self setAssociatedObject:value key:kLoopUntil_Key]; +} + +- (BOOL) notified +{ + NSNumber *valueNumber = [self getAssociatedObject:kNotified_Key]; + return [valueNumber boolValue]; +} + +- (void) setNotified:(BOOL)value +{ + NSNumber *valueNumber = [NSNumber numberWithBool:value]; + [self setAssociatedObject:valueNumber key:kNotified_Key]; +} + +- (XCTAsyncTestCaseStatus) notifiedStatus +{ + NSNumber *valueNumber = [self getAssociatedObject:kNotifiedStatus_Key]; + return [valueNumber integerValue]; +} + +- (void) setNotifiedStatus:(XCTAsyncTestCaseStatus)value +{ + NSNumber *valueNumber = [NSNumber numberWithInt:value]; + [self setAssociatedObject:valueNumber key:kNotifiedStatus_Key]; +} + +- (XCTAsyncTestCaseStatus) expectedStatus +{ + NSNumber *valueNumber = [self getAssociatedObject:kExpectedStatus_Key]; + return [valueNumber integerValue]; +} + +- (void) setExpectedStatus:(XCTAsyncTestCaseStatus)value +{ + NSNumber *valueNumber = [NSNumber numberWithInt:value]; + [self setAssociatedObject:valueNumber key:kExpectedStatus_Key]; +} + +@end