Framework/ROADWebService/ROADWebserviceTest/RFWebServiceCacheTest.m (251 lines of code) (raw):

// // RFWebServiceCacheTest.m // ROADWebService // // Copyright (c) 2014 EPAM Systems, Inc. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or // other materials provided with the distribution. // Neither the name of the EPAM Systems, Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // See the NOTICE file and the LICENSE file distributed with this work // for additional information regarding copyright ownership and licensing #import <XCTest/XCTest.h> #import "RFConcreteWebServiceClient.h" #import "RFDownloader+FakeRequest.h" #import "RFServiceProvider+WebServiceCachingManager.h" #import "RFDownloadFaker.h" #import "RFWebResponse.h" #import "RFWebServiceCacheContext.h" @interface RFWebServiceCacheTest : XCTestCase @end @implementation RFWebServiceCacheTest + (void)setUp { [RFDownloadFaker setUp]; } // Travis bug cause performing +setUp before each test + (void)tearDown { [RFDownloadFaker tearDown]; NSArray *cachingFolderList = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *webServiceCachingPath = [cachingFolderList lastObject]; webServiceCachingPath = [webServiceCachingPath stringByAppendingPathComponent:@"RFCachingDirecory"]; webServiceCachingPath = [webServiceCachingPath stringByAppendingPathComponent:@"RFWebServiceCache.coredata"]; if ([[NSFileManager defaultManager] fileExistsAtPath:webServiceCachingPath]) { [[NSFileManager defaultManager] removeItemAtPath:webServiceCachingPath error:nil]; } } - (void)testPragmaNoCaching { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.cache.pragma"]; NSString *firstDate; NSString *controlDate; [self sendTwoConsequentRequestsOnWebServiceClient:webClient selector:@selector(testCacheNoAttrWithSuccess:failure:) firstResult:&firstDate secondResult:&controlDate]; XCTAssertFalse([controlDate isEqualToString:firstDate], @"Response with Pragma:no-cache was cached!"); } - (void)testCacheControlNoCaching { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.cache.cache-control.no-cache"]; NSString *firstDate; NSString *controlDate; [self sendTwoConsequentRequestsOnWebServiceClient:webClient selector:@selector(testCacheNoAttrWithSuccess:failure:) firstResult:&firstDate secondResult:&controlDate]; XCTAssertFalse([controlDate isEqualToString:firstDate], @"Response with Cache-control:no-cache was cached!"); } - (void)testNoCacheHeaders { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.cache.no.cache.headers"]; NSString *firstDate; NSString *controlDate; [self sendTwoConsequentRequestsOnWebServiceClient:webClient selector:@selector(testCacheNoAttrWithSuccess:failure:) firstResult:&firstDate secondResult:&controlDate]; XCTAssertFalse([controlDate isEqualToString:firstDate], @"Response without any cache specifying was cached!"); } - (void)testExpiresHeader { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.expires.header"]; NSString *firstDate; NSString *controlDate; [self sendTwoConsequentRequestsOnWebServiceClient:webClient selector:@selector(testCacheNoAttrWithSuccess:failure:) firstResult:&firstDate secondResult:&controlDate]; XCTAssertTrue([controlDate isEqualToString:firstDate], @"Response with expires header was not cached!"); } - (void)testMaxAgeHeader { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.max-age.header"]; NSString *firstDate; NSString *controlDate; [self sendTwoConsequentRequestsOnWebServiceClient:webClient selector:@selector(testCacheNoAttrWithSuccess:failure:) firstResult:&firstDate secondResult:&controlDate]; XCTAssertTrue([controlDate isEqualToString:firstDate], @"Response with max-age header was not cached!"); } - (void)testDisableCache { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.max-age.header"]; NSString *firstDate; NSString *controlDate; [self sendTwoConsequentRequestsOnWebServiceClient:webClient selector:@selector(testCacheDisableWithSuccess:failure:) firstResult:&firstDate secondResult:&controlDate]; XCTAssertFalse([controlDate isEqualToString:firstDate], @"Response with disableCache attribute was cached!"); } - (void)testMaxAgeAttribute { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.cache.max-age.attr"]; NSString *firstDate; NSString *controlDate; [self sendTwoConsequentRequestsOnWebServiceClient:webClient selector:@selector(testCacheMaxAgeWithSuccess:failure:) firstResult:&firstDate secondResult:&controlDate]; XCTAssertTrue([controlDate isEqualToString:firstDate], @"Response with maxAge attribute was not cached!"); } - (void)testLastModifiedField { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.cache.last.modified"]; NSString *firstDate; NSString *controlDate; [self sendTwoConsequentRequestsOnWebServiceClient:webClient selector:@selector(testCacheNoAttrWithSuccess:failure:) firstResult:&firstDate secondResult:&controlDate]; XCTAssertFalse([controlDate isEqualToString:firstDate], @"Response with maxAge attribute was not cached!"); } - (void)testEtagField { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.cache.etag"]; NSString *firstDate; NSString *controlDate; [self sendTwoConsequentRequestsOnWebServiceClient:webClient selector:@selector(testCacheNoAttrWithSuccess:failure:) firstResult:&firstDate secondResult:&controlDate]; XCTAssertFalse([controlDate isEqualToString:firstDate], @"Response with maxAge attribute was not cached!"); } - (void)testSameLastModifiedField { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.cache.same.last.modified"]; NSString *firstDate; NSString *controlDate; [self sendTwoConsequentRequestsOnWebServiceClient:webClient selector:@selector(testCacheNoAttrWithSuccess:failure:) firstResult:&firstDate secondResult:&controlDate]; XCTAssertTrue([controlDate isEqualToString:firstDate], @"Response with maxAge attribute was not cached!"); } - (void)testSameEtagField { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.cache.same.etag"]; NSString *firstDate; NSString *controlDate; [self sendTwoConsequentRequestsOnWebServiceClient:webClient selector:@selector(testCacheNoAttrWithSuccess:failure:) firstResult:&firstDate secondResult:&controlDate]; XCTAssertTrue([controlDate isEqualToString:firstDate], @"Response with maxAge attribute was not cached!"); } - (void)testDropCacheMethod { [[RFServiceProvider webServiceCacheManager] dropCache]; RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.max-age.header"]; NSString *firstDate; NSString *controlDate; [self sendTwoConsequentRequestsOnWebServiceClient:webClient selector:@selector(testCacheNoAttrWithSuccess:failure:) firstResult:&firstDate secondResult:&controlDate]; XCTAssertTrue([controlDate isEqualToString:firstDate], @"Response with max-age header was not cached!"); } - (void)testCacheIdentifierParse { // GIVEN NSString * const comparationBaseCacheIdentifier = @"cache.identifier.parsed.1"; NSString * const cacheIdentifier = @"cache.identifier.parsed.%%0%%"; NSDictionary * const parseDictionary = @{@"0": @"1"}; // WHEN NSString * const parsedCacheIdentifier = [[RFServiceProvider webServiceCacheManager] parseCacheIdentifier:cacheIdentifier withParameters:parseDictionary]; // THEN XCTAssertTrue([parsedCacheIdentifier isEqualToString:comparationBaseCacheIdentifier], @"The cache identifiers are not equal"); } - (void)testCacheIdentifierSet { // GIVEN [[RFServiceProvider webServiceCacheManager] dropCache]; NSString * const cacheIdentifier = @"test.cache.identifier"; // WHEN [self performRequestWithIdentifiedCacheWithServiceRoot:@"http://test.cache.identifier/first"]; [self performRequestWithIdentifiedCacheWithServiceRoot:@"http://test.cache.identifier/second"]; // THEN NSArray *response = [[RFServiceProvider webServiceCacheManager] cacheWithIdentifier:cacheIdentifier]; XCTAssertTrue([response count] == 1, @"The cache hasn't contained the value with the identifier which has been set."); } - (void)testCacheIdentifierSetPrefixed { // GIVEN [[RFServiceProvider webServiceCacheManager] dropCache]; NSString * const cacheIdentifierToMatch = @"test.cache.identifier.prefix"; NSString * const cacheIdentifierToFail = @"identifier.prefix"; // WHEN [self performRequestWithParseIdForPrefixedCache:@"1"]; [self performRequestWithParseIdForPrefixedCache:@"2"]; // THEN NSArray *matchedResponses = [[RFServiceProvider webServiceCacheManager] cacheWithIdentifierPrefix:cacheIdentifierToMatch]; NSArray *nonMatchingResponses = [[RFServiceProvider webServiceCacheManager] cacheWithIdentifierPrefix:cacheIdentifierToFail]; XCTAssertTrue([matchedResponses count] == 2, @"The cache did not retrieve all the elements which ahve been set."); XCTAssertTrue([nonMatchingResponses count] == 0, @"The cache matched not only for the prefix but to substring also."); } - (void)testTargetedCacheInvalidation { // GIVEN [[RFServiceProvider webServiceCacheManager] dropCache]; NSString * const cacheIdentifierForPrefixed = @"test.cache.identifier.prefix"; NSString * const cacheIdentifier = @"test.cache.identifier"; [self performRequestWithIdentifiedCacheWithServiceRoot:@"http://test.cache.identifier"]; [self performRequestWithParseIdForPrefixedCache:@"1"]; [self performRequestWithParseIdForPrefixedCache:@"2"]; // pre check NSArray *removedResponses = [[RFServiceProvider webServiceCacheManager] cacheWithIdentifierPrefix:cacheIdentifierForPrefixed]; NSArray *remainingResponse = [[RFServiceProvider webServiceCacheManager] cacheWithIdentifier:cacheIdentifier]; XCTAssertTrue([removedResponses count] == 2, @"The removable cache elemnts for the test have not been created."); XCTAssertNotNil(remainingResponse, @"The cache element which needs to be kept after flush not been created."); // WHEN [[RFServiceProvider webServiceCacheManager] flushElementsWithIdentifierPrefix:cacheIdentifierForPrefixed]; // THEN removedResponses = [[RFServiceProvider webServiceCacheManager] cacheWithIdentifierPrefix:cacheIdentifierForPrefixed]; remainingResponse = [[RFServiceProvider webServiceCacheManager] cacheWithIdentifier:cacheIdentifier]; XCTAssertTrue([removedResponses count] == 0, @"The targeted cache elements were not flushed."); XCTAssertTrue([remainingResponse count] == 1, @"The cached element which should have been kept have been also removed."); } - (void)testMaxAgeAttributeInOffline { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.cache.offline.max-age"]; NSString *firstDate; [self sendRequestOnWebServiceClient:webClient selector:@selector(testCacheMaxAgeWithSuccess:failure:) result:&firstDate]; webClient.sharedHeaders = [@{@"no-connection" : @"YES"} mutableCopy]; NSString *secondDate; [self sendRequestOnWebServiceClient:webClient selector:@selector(testCacheMaxAgeWithSuccess:failure:) result:&secondDate]; XCTAssertTrue([secondDate isEqualToString:firstDate], @"Response with maxAge attribute was not cached!"); } - (void)testOfflineCachingAttributeInOffline { RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.cache.offline.max-age"]; NSString *firstDate; [self sendRequestOnWebServiceClient:webClient selector:@selector(testCacheOfflineCacheWithSuccess:failure:) result:&firstDate]; webClient.sharedHeaders = [@{@"no-connection" : @"YES"} mutableCopy]; RFWebServiceCachingManager *cacheManager = [RFServiceProvider webServiceCacheManager]; RFWebResponse *cachedResponse = [[cacheManager cacheWithIdentifier:@"offlineCache"] lastObject]; cachedResponse.implementation.expirationDate = [NSDate dateWithTimeIntervalSinceNow:-1]; RFWebServiceCacheContext *context = [cacheManager valueForKey:@"_cacheContext"]; NSError* err; [context.context save:&err]; XCTAssertNil(err, @"Couldn't not save context."); NSString *secondDate; [self sendRequestOnWebServiceClient:webClient selector:@selector(testCacheOfflineCacheWithSuccess:failure:) result:&secondDate]; XCTAssertTrue([secondDate isEqualToString:firstDate], @"Response with maxAge attribute was not cached!"); } #pragma mark - Utility methods - (void)performRequestWithIdentifiedCacheWithServiceRoot:(NSString *)serviceRoot { __block BOOL isFinished = NO; RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:serviceRoot]; [webClient testCacheIdentifierWithSuccess:^(id result) { isFinished = YES; } failure:^(NSError *error) { isFinished = YES; }]; while (!isFinished) { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; } } - (void)performRequestWithParseIdForPrefixedCache:(NSString *)parseId { __block BOOL isFinished = NO; RFConcreteWebServiceClient *webClient = [[RFConcreteWebServiceClient alloc] initWithServiceRoot:@"http://test.cache.identifier"]; isFinished = NO; [webClient testCacheIdentifierWithPrefix:parseId success:^(id result) { isFinished = YES; } failure:^(NSError *error) { isFinished = YES; }]; while (!isFinished) { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; } } - (BOOL)sendRequestOnWebServiceClient:(RFConcreteWebServiceClient *)webClient selector:(SEL)selector result:(NSString **)result { __block BOOL isFinished = NO; // Strong variable to store response from web service __block NSString *responseString; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [webClient performSelector:selector withObject:^(NSData *data) { responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; isFinished = YES; } withObject:^(NSError *error) { isFinished = YES; }]; while (!isFinished) { [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate alloc] initWithTimeIntervalSinceNow:0.2]]; } XCTAssertNotNil(responseString, @"Web service request with cache failed!"); *result = responseString; } - (BOOL)sendTwoConsequentRequestsOnWebServiceClient:(RFConcreteWebServiceClient *)webClient selector:(SEL)selector firstResult:(NSString **)firstResult secondResult:(NSString **)secondResult { __block BOOL isFinished = NO; // Strong variable to store response from web service __block NSString *fResult; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [webClient performSelector:selector withObject:^(NSData *result) { fResult = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding]; isFinished = YES; } withObject:^(NSError *error) { isFinished = YES; }]; while (!isFinished) { [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate alloc] initWithTimeIntervalSinceNow:0.2]]; } XCTAssertNotNil(fResult, @"Web service request with cache failed!"); __block NSString *sResult; isFinished = NO; [webClient performSelector:selector withObject:^(NSData *result) { sResult = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding]; isFinished = YES; } withObject:^(NSError *error) { isFinished = YES; }]; #pragma clang diagnostic pop while (!isFinished) { [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate alloc] initWithTimeIntervalSinceNow:0.2]]; } XCTAssertNotNil(sResult, @"Web service request with cache failed at second call!"); *firstResult = fResult; *secondResult = sResult; } @end