From 4664a897864128b43c9fd6e140071ad830d75e2d Mon Sep 17 00:00:00 2001 From: Brian Baumhover Date: Tue, 22 Aug 2017 13:01:28 -0500 Subject: [PATCH 1/6] Add milliseconds to SDK timestamp on events --- KeenClient/KIOUtil.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KeenClient/KIOUtil.m b/KeenClient/KIOUtil.m index 53df038..6e21730 100644 --- a/KeenClient/KIOUtil.m +++ b/KeenClient/KIOUtil.m @@ -96,7 +96,7 @@ + (id)convertDate:(id)date { NSDateFormatter *dateFormatter = [NSDateFormatter new]; NSLocale *enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; [dateFormatter setLocale:enUSPOSIXLocale]; - [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"]; + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; NSString *iso8601String = [dateFormatter stringFromDate:date]; return iso8601String; From dae104b08e029bbd0c374ea96e4a97b0c942939f Mon Sep 17 00:00:00 2001 From: Brian Baumhover Date: Wed, 23 Aug 2017 15:13:15 -0500 Subject: [PATCH 2/6] Add uploadWithCompletionHandler:(void(^)(NSError *), which reports upload errors to clients. Also add a keen.prior_attempts property to events so it's obvious if an event was upload because of a retry. This change also removes a bunch of redundant tests, adds a few new constants where appropriate, provides better names for some constants, uses the more concise indexing operator [] for dictionaries and arrays where code has been modified, and adds new error codes for upload related errors. --- KeenClient/KIODBStore.m | 23 +- KeenClient/KIOUploader.h | 2 +- KeenClient/KIOUploader.m | 201 ++++++-- KeenClient/KIOUtil.m | 2 +- KeenClient/KeenClient.h | 34 +- KeenClient/KeenClient.m | 12 +- KeenClient/KeenConstants.h | 72 ++- KeenClient/KeenConstants.m | 28 +- KeenClientTests/GlobalPropertiesTests.m | 29 +- KeenClientTests/KeenClientTests.m | 20 +- KeenClientTests/KeenEventTests.m | 575 ++++++++--------------- KeenClientTests/KeenGeoLocationTests.m | 13 +- KeenClientTests/MockNSURLSession.h | 11 +- KeenClientTests/SdkTrackingHeaderTests.m | 3 +- 14 files changed, 505 insertions(+), 520 deletions(-) diff --git a/KeenClient/KIODBStore.m b/KeenClient/KIODBStore.m index 3cb0249..ca63d74 100644 --- a/KeenClient/KIODBStore.m +++ b/KeenClient/KIODBStore.m @@ -10,6 +10,7 @@ #import "KIODBStore.h" #import "KIODBStorePrivate.h" #import "keen_io_sqlite3.h" +#import "KeenConstants.h" @interface KIODBStore () @@ -555,13 +556,17 @@ - (NSMutableDictionary *)getEventsWithMaxAttempts:(int)maxAttempts andProjectID: // Fetch data out the statement long long eventId = keen_io_sqlite3_column_int64(find_event_stmt, 0); - NSString *coll = [NSString stringWithUTF8String:(char *)keen_io_sqlite3_column_text(find_event_stmt, 1)]; + NSString *collection = + [NSString stringWithUTF8String:(char *)keen_io_sqlite3_column_text(find_event_stmt, 1)]; const void *dataPtr = keen_io_sqlite3_column_blob(find_event_stmt, 2); int dataSize = keen_io_sqlite3_column_bytes(find_event_stmt, 2); NSData *data = [[NSData alloc] initWithBytes:dataPtr length:dataSize]; + // Get number of upload attempts + NSNumber *attempts = [NSNumber numberWithUnsignedLong:keen_io_sqlite3_column_int(find_event_stmt, 3)]; + // Bind and mark the event pending. if (keen_io_sqlite3_bind_int64(make_pending_event_stmt, 1, eventId) != SQLITE_OK) { [self handleSQLiteFailure:@"bind int for make pending"]; @@ -575,13 +580,14 @@ - (NSMutableDictionary *)getEventsWithMaxAttempts:(int)maxAttempts andProjectID: // Reset the pendifier [self resetSQLiteStatement:make_pending_event_stmt]; - if ([events objectForKey:coll] == nil) { + if ([events objectForKey:collection] == nil) { // We don't have an entry in the dictionary yet for this collection // so create one. - [events setObject:[NSMutableDictionary dictionary] forKey:coll]; + [events setObject:[NSMutableDictionary dictionary] forKey:collection]; } + NSMutableDictionary *collectionData = events[collection]; - [[events objectForKey:coll] setObject:data forKey:[NSNumber numberWithUnsignedLongLong:eventId]]; + collectionData[@(eventId)] = @{ @"data": data, kKeenEventKeenDataAttemptsKey: attempts }; } [self resetSQLiteStatement:find_event_stmt]; @@ -1106,11 +1112,10 @@ - (BOOL)prepareAllSQLiteStatements { return NO; // This statement finds non-pending events in the table. - if (![self - prepareSQLStatement:&find_event_stmt - sqlQuery: - "SELECT id, collection, eventData FROM events WHERE pending=0 AND projectID=? AND attempts 0) { + KCLogError(@"%@ errors while trying to upload events", @(errors.count)); + *ppError = [NSError errorWithDomain:kKeenErrorDomain + code:KeenErrorCodeEventUploadError + userInfo:@{kKeenErrorInnerErrorArrayKey: errors}]; + } } @end diff --git a/KeenClient/KIOUtil.m b/KeenClient/KIOUtil.m index 6e21730..f174c9c 100644 --- a/KeenClient/KIOUtil.m +++ b/KeenClient/KIOUtil.m @@ -83,7 +83,7 @@ + (BOOL)handleError:(NSError **)error const id objects[] = {errorMessage, underlyingError}; NSUInteger count = underlyingError ? 2 : 1; NSDictionary *userInfo = [NSDictionary dictionaryWithObjects:objects forKeys:keys count:count]; - *error = [NSError errorWithDomain:kKeenErrorDomain code:1 userInfo:userInfo]; + *error = [NSError errorWithDomain:kKeenErrorDomain code:KeenErrorCodeGeneral userInfo:userInfo]; KCLogError(@"%@", *error); } diff --git a/KeenClient/KeenClient.h b/KeenClient/KeenClient.h index 5464aa1..5e93a40 100644 --- a/KeenClient/KeenClient.h +++ b/KeenClient/KeenClient.h @@ -19,6 +19,9 @@ typedef NSDictionary * (^KeenGlobalPropertiesBlock)(NSString *eventCollection); // Block type for analysis/query completion typedef void (^AnalysisCompletionBlock)(NSData *responseData, NSURLResponse *response, NSError *error); +// Block type for upload completion +typedef void (^UploadCompletionBlock)(NSError *error); + /** KeenClient has class methods to return managed instances of itself and instance methods to collect new events and upload them through the Keen IO API. @@ -30,7 +33,11 @@ typedef void (^AnalysisCompletionBlock)(NSData *responseData, NSURLResponse *res andReadKey:@"my_read_key"]; NSDictionary *myEvent = [NSDictionary dictionary]; [[KeenClient sharedClient] addEvent:myEvent toEventCollection:@"purchases"]; - [[KeenClient sharedClient] uploadWithFinishedBlock:nil]; + [[KeenClient sharedClient] uploadWithCompletionHandler:^(NSError *error) { + if (error) { + // Upload failed, check error for details + } + }]; */ @interface KeenClient : NSObject @@ -327,7 +334,30 @@ typedef void (^AnalysisCompletionBlock)(NSData *responseData, NSURLResponse *res @param block The block to be executed once uploading is finished, regardless of whether or not the upload succeeded. The block is also called when no upload was necessary because no events were captured. */ -- (void)uploadWithFinishedBlock:(void (^)())block; +- (void)uploadWithFinishedBlock:(void (^)())block DEPRECATED_MSG_ATTRIBUTE("use uploadWithCompletionHandler:"); + +/** + Upload all the events captured so far by addEvent. This will asynchronously upload all events that have been cached. + + If an upload fails, the events will be saved for a later attempt. It is possible that connectivity loss will cause + events to be successfully recorded, but the client will fail to read the server response. In this case, events will be + uploaded again as a duplicate event. + + If a particular event is invalid, the event will be dropped from the queue and the failure message + will be logged. + + @param completionHandler The block to be executed once uploading is finished, regardless of whether or not the upload + succeeded. The block is also called when no upload was necessary because no events were captured. If an error occured, + the error parameter passed to the block will be non-null with a keen-specific error domain and error code. In the case + where an error is returned to the SDK from a system API, the underlying NSError can be found in + error.userInfo[kKeenErrorInnerErrorKey]. If specific events fail to upload, error.code will be + KeenErrorCodeEventUploadError and the corresponding failures can be accessed as an NSArray of NSError through + error.userInfo[kKeenErrorInnerErrorArrayKey]. If a HTTP status other than 2XX is read, error.code will be + KeenErrorCodeResponseError and the status code will be available as error.userInfo[kKeenErrorHttpStatus]. + KeenErrorCodeResponseError is also reported for other errors when reading a response. An error.code of + KeenErrorCodeNetworkDisconnected indicates that no network connection was available, and so nothing was uploaded. + */ +- (void)uploadWithCompletionHandler:(UploadCompletionBlock)completionHandler; /** Refresh the current geo location. The Keen Client only gets geo at the beginning of each session (i.e. when the client diff --git a/KeenClient/KeenClient.m b/KeenClient/KeenClient.m index 774c411..361a72e 100644 --- a/KeenClient/KeenClient.m +++ b/KeenClient/KeenClient.m @@ -428,7 +428,7 @@ - (BOOL)validateEvent:(NSDictionary *)event withDepth:(NSUInteger)depth error:(N errorMessage = @"You must specify a non-null, non-empty event."; return [KIOUtil handleError:anError withErrorMessage:errorMessage]; } - id keenObject = [event objectForKey:@"keen"]; + id keenObject = event[kKeenEventKeenDataKey]; if (keenObject && ![keenObject isKindOfClass:[NSDictionary class]]) { errorMessage = @"An event's root-level property named 'keen' must be a dictionary."; return [KIOUtil handleError:anError withErrorMessage:errorMessage]; @@ -530,15 +530,15 @@ - (BOOL)addEvent:(NSDictionary *)event NSMutableDictionary *eventToWrite = [NSMutableDictionary dictionaryWithDictionary:event]; // either set "keen" only from keen properties or merge in - NSDictionary *originalKeenDict = [eventToWrite objectForKey:@"keen"]; + NSDictionary *originalKeenDict = eventToWrite[kKeenEventKeenDataKey]; if (originalKeenDict) { // have to merge NSMutableDictionary *keenDict = [KIOUtil handleInvalidJSONInObject:keenProperties]; [keenDict addEntriesFromDictionary:originalKeenDict]; - [eventToWrite setObject:keenDict forKey:@"keen"]; + eventToWrite[kKeenEventKeenDataKey] = keenDict; } else { // just set it directly - [eventToWrite setObject:keenProperties forKey:@"keen"]; + eventToWrite[kKeenEventKeenDataKey] = keenProperties; } NSError *serializationError; @@ -567,6 +567,10 @@ - (void)uploadWithFinishedBlock:(void (^)())block { [self.uploader uploadEventsForConfig:self.config completionHandler:block]; } +- (void)uploadWithCompletionHandler:(UploadCompletionBlock)completionHandler { + [self.uploader uploadEventsForConfig:self.config completionHandler:completionHandler]; +} + #pragma mark - Querying #pragma mark Async methods diff --git a/KeenClient/KeenConstants.h b/KeenClient/KeenConstants.h index d670a7f..6f8539d 100644 --- a/KeenClient/KeenConstants.h +++ b/KeenClient/KeenConstants.h @@ -10,25 +10,65 @@ #define kKeenSdkVersion @"3.7.0" -extern NSString * const kKeenApiUrlScheme; -extern NSString * const kKeenDefaultApiUrlAuthority; -extern NSString * const kKeenApiVersion; - -extern NSString * const kKeenNameParam; -extern NSString * const kKeenDescriptionParam; -extern NSString * const kKeenSuccessParam; -extern NSString * const kKeenErrorParam; -extern NSString * const kKeenErrorCodeParam; -extern NSString * const kKeenInvalidCollectionNameError; -extern NSString * const kKeenInvalidPropertyNameError; -extern NSString * const kKeenInvalidPropertyValueError; +// Keen API constants +extern NSString *const kKeenApiUrlScheme; +extern NSString *const kKeenDefaultApiUrlAuthority; +extern NSString *const kKeenApiVersion; + +extern NSString *const kKeenResponseErrorNameKey; +extern NSString *const kKeenResponseErrorDescriptionKey; +extern NSString *const kKeenSuccessParam; +extern NSString *const kKeenResponseErrorDictionaryKey; +extern NSString *const kKeenErrorCodeParam; +extern NSString *const kKeenInvalidCollectionNameError; +extern NSString *const kKeenInvalidPropertyNameError; +extern NSString *const kKeenInvalidPropertyValueError; + +// The key on an event dictionary for keen-provided data +extern NSString *const kKeenEventKeenDataKey; + +// A key containing the number of upload attempts for a given event +extern NSString *const kKeenEventKeenDataAttemptsKey; + +// how many events can be stored for a single collection before aging them out extern NSUInteger const kKeenMaxEventsPerCollection; + +// how many events to drop when aging out extern NSUInteger const kKeenNumberEventsToForget; -extern NSString * const kKeenErrorDomain; +// custom domain for NSErrors +extern NSString *const kKeenErrorDomain; + +// Error codes for NSError +// clang-format off +NS_ENUM(NSInteger, KeenErrorCode) { + KeenErrorCodeGeneral = 1, + KeenErrorCodeNetworkDisconnected = 2, + KeenErrorCodeSerialization = 3, + KeenErrorCodeResponseError = 4, + KeenErrorCodeEventUploadError = 6 +}; +// clang-format on + +// Key in NSError userInfo used for storing an instance of an NSError returned +// from an underlying API call +extern NSString *const kKeenErrorInnerErrorKey; + +// Key in NSError userInfo used for storing an NSArray of underlying NSErrors +extern NSString *const kKeenErrorInnerErrorArrayKey; + +// Key in NSError userInfo used for error descriptions +extern NSString *const kKeenErrorDescriptionKey; + +// Key in NSError userInfo used for http status codes +extern NSString *const kKeenErrorHttpStatus; + +// Name of header that provides SDK version info +extern NSString *const kKeenSdkVersionHeader; -extern NSString * const kKeenSdkVersionHeader; -extern NSString * const kKeenSdkVersionWithPlatform; +// The SDK version info header content. +extern NSString *const kKeenSdkVersionWithPlatform; -extern NSString * const kKeenFileStoreImportedKey; +// User settings key indicating that a file store import has already been done. +extern NSString *const kKeenFileStoreImportedKey; diff --git a/KeenClient/KeenConstants.m b/KeenClient/KeenConstants.m index d1ae378..6d7291a 100644 --- a/KeenClient/KeenConstants.m +++ b/KeenClient/KeenConstants.m @@ -12,31 +12,35 @@ NSString *const kKeenDefaultApiUrlAuthority = @"api.keen.io"; NSString *const kKeenApiVersion = @"3.0"; -// Keen API constants - -NSString *const kKeenNameParam = @"name"; -NSString *const kKeenDescriptionParam = @"description"; +NSString *const kKeenResponseErrorNameKey = @"name"; +NSString *const kKeenResponseErrorDescriptionKey = @"description"; NSString *const kKeenSuccessParam = @"success"; -NSString *const kKeenErrorParam = @"error"; +NSString *const kKeenResponseErrorDictionaryKey = @"error"; NSString *const kKeenErrorCodeParam = @"error_code"; NSString *const kKeenInvalidCollectionNameError = @"InvalidCollectionNameError"; NSString *const kKeenInvalidPropertyNameError = @"InvalidPropertyNameError"; NSString *const kKeenInvalidPropertyValueError = @"InvalidPropertyValueError"; -// Keen constants related to how much data we'll cache on the device before aging it out +NSString *const kKeenEventKeenDataKey = @"keen"; + +NSString *const kKeenEventKeenDataAttemptsKey = @"prior_attempts"; -// how many events can be stored for a single collection before aging them out NSUInteger const kKeenMaxEventsPerCollection = 10000; -// how many events to drop when aging out + NSUInteger const kKeenNumberEventsToForget = 100; -// custom domain for NSErrors NSString *const kKeenErrorDomain = @"io.keen"; -// Name of header that provides SDK version info +NSString *const kKeenErrorInnerErrorKey = @"InnerError"; + +NSString *const kKeenErrorInnerErrorArrayKey = @"InnerErrorArray"; + +NSString *const kKeenErrorDescriptionKey = @"Description"; + +NSString *const kKeenErrorHttpStatus = @"HTTPStatus"; + NSString *const kKeenSdkVersionHeader = @"Keen-Sdk"; -// The SDK version info header content. + NSString *const kKeenSdkVersionWithPlatform = @"ios-" kKeenSdkVersion; -// User settings key indicating that a file store import has already been done. NSString *const kKeenFileStoreImportedKey = @"didFSImport"; diff --git a/KeenClientTests/GlobalPropertiesTests.m b/KeenClientTests/GlobalPropertiesTests.m index dcde5bb..66a219b 100644 --- a/KeenClientTests/GlobalPropertiesTests.m +++ b/KeenClientTests/GlobalPropertiesTests.m @@ -9,6 +9,7 @@ #import "KeenClient.h" #import "KeenTestConstants.h" +#import "KeenConstants.h" #import "KeenClientTestable.h" #import "GlobalPropertiesTests.h" @@ -31,7 +32,7 @@ - (void)testGlobalPropertiesDictionary { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:eventCollectionName]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSError *error = nil; NSDictionary *storedEvent = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; @@ -56,7 +57,7 @@ - (void)testGlobalPropertiesDictionary { // a dictionary that contains an addon should be okay NSDictionary *theEvent = @{ - @"keen": @{ + kKeenEventKeenDataKey: @{ @"addons": @[@{ @"name": @"addon:name", @"input": @{@"param_name": @"property_that_contains_param"}, @@ -66,7 +67,7 @@ - (void)testGlobalPropertiesDictionary { @"a": @"b" }; storedEvent = RunTest(theEvent, 2); - NSDictionary *deserializedAddon = storedEvent[@"keen"][@"addons"][0]; + NSDictionary *deserializedAddon = storedEvent[kKeenEventKeenDataKey][@"addons"][0]; XCTAssertEqualObjects(@"addon:name", deserializedAddon[@"name"], @"Addon name should be right"); } @@ -86,7 +87,7 @@ - (void)testGlobalPropertiesDictionaryInstanceClient { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:eventCollectionName]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSError *error = nil; NSDictionary *storedEvent = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; @@ -111,7 +112,7 @@ - (void)testGlobalPropertiesDictionaryInstanceClient { // a dictionary that contains an addon should be okay NSDictionary *theEvent = @{ - @"keen": @{ + kKeenEventKeenDataKey: @{ @"addons": @[@{ @"name": @"addon:name", @"input": @{@"param_name": @"property_that_contains_param"}, @@ -121,7 +122,7 @@ - (void)testGlobalPropertiesDictionaryInstanceClient { @"a": @"b" }; storedEvent = RunTest(theEvent, 2); - NSDictionary *deserializedAddon = storedEvent[@"keen"][@"addons"][0]; + NSDictionary *deserializedAddon = storedEvent[kKeenEventKeenDataKey][@"addons"][0]; XCTAssertEqualObjects(@"addon:name", deserializedAddon[@"name"], @"Addon name should be right"); } @@ -142,7 +143,7 @@ - (void)testGlobalPropertiesBlock { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:eventCollectionName]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSError *error = nil; NSDictionary *storedEvent = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; @@ -178,7 +179,7 @@ - (void)testGlobalPropertiesBlock { // a dictionary that contains an addon should be okay NSDictionary *theEvent = @{ - @"keen": @{ + kKeenEventKeenDataKey: @{ @"addons": @[@{ @"name": @"addon:name", @"input": @{@"param_name": @"property_that_contains_param"}, @@ -192,7 +193,7 @@ - (void)testGlobalPropertiesBlock { return theEvent; }, 2); - NSDictionary *deserializedAddon = storedEvent[@"keen"][@"addons"][0]; + NSDictionary *deserializedAddon = storedEvent[kKeenEventKeenDataKey][@"addons"][0]; XCTAssertEqualObjects(@"addon:name", deserializedAddon[@"name"], @"Addon name should be right"); } @@ -213,7 +214,7 @@ - (void)testGlobalPropertiesBlockInstanceClient { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:eventCollectionName]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSError *error = nil; NSDictionary *storedEvent = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; @@ -249,7 +250,7 @@ - (void)testGlobalPropertiesBlockInstanceClient { // a dictionary that contains an addon should be okay NSDictionary *theEvent = @{ - @"keen": @{ + kKeenEventKeenDataKey: @{ @"addons": @[@{ @"name": @"addon:name", @"input": @{@"param_name": @"property_that_contains_param"}, @@ -263,7 +264,7 @@ - (void)testGlobalPropertiesBlockInstanceClient { return theEvent; }, 2); - NSDictionary *deserializedAddon = storedEvent[@"keen"][@"addons"][0]; + NSDictionary *deserializedAddon = storedEvent[kKeenEventKeenDataKey][@"addons"][0]; XCTAssertEqualObjects(@"addon:name", deserializedAddon[@"name"], @"Addon name should be right"); } @@ -285,7 +286,7 @@ - (void)testGlobalPropertiesTogether { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:@"apples"]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSError *error = nil; NSDictionary *storedEvent = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; @@ -312,7 +313,7 @@ - (void)testGlobalPropertiesTogetherInstanceClient { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:@"apples"]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSError *error = nil; NSDictionary *storedEvent = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; diff --git a/KeenClientTests/KeenClientTests.m b/KeenClientTests/KeenClientTests.m index b278f2e..5661a6f 100644 --- a/KeenClientTests/KeenClientTests.m +++ b/KeenClientTests/KeenClientTests.m @@ -88,8 +88,8 @@ - (void)testInitWithApiUrlAuthority { andWriteKey:kDefaultWriteKey andReadKey:kDefaultReadKey]; XCTAssertEqualObjects( - kKeenDefaultApiUrlAuthority, client.config.apiUrlAuthority, @"Should set default API URL authority"); - + kKeenDefaultApiUrlAuthority, client.config.apiUrlAuthority, @"Should set default API URL authority"); + NSString *customAuthority = @"some.url.com"; KeenClient *client2 = [[KeenClient alloc] initWithProjectID:kDefaultProjectID andWriteKey:kDefaultWriteKey @@ -103,8 +103,8 @@ - (void)testSharedWithApiUrlAuthority { andWriteKey:kDefaultWriteKey andReadKey:kDefaultReadKey]; XCTAssertEqualObjects( - kKeenDefaultApiUrlAuthority, client.config.apiUrlAuthority, @"Should set default API URL authority"); - + kKeenDefaultApiUrlAuthority, client.config.apiUrlAuthority, @"Should set default API URL authority"); + NSString *customAuthority = @"some.url.com"; KeenClient *client2 = [KeenClient sharedClientWithProjectID:kDefaultProjectID andWriteKey:kDefaultWriteKey @@ -122,7 +122,7 @@ - (void)testBasicAddon { andReadKey:kDefaultReadKey]; NSDictionary *theEvent = @{ - @"keen": @{ + kKeenEventKeenDataKey: @{ @"addons": @[@{ @"name": @"addon:name", @"input": @{@"param_name": @"property_that_contains_param"}, @@ -143,10 +143,10 @@ - (void)testBasicAddon { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:@"foo"]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSDictionary *deserializedDict = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; - NSDictionary *deserializedAddon = deserializedDict[@"keen"][@"addons"][0]; + NSDictionary *deserializedAddon = deserializedDict[kKeenEventKeenDataKey][@"addons"][0]; XCTAssertEqualObjects(@"addon:name", deserializedAddon[@"name"], @"Addon name should be right"); } @@ -235,7 +235,8 @@ - (void)testProxy { XCTAssertNil(error); XCTestExpectation *uploadFinished = [self expectationWithDescription:@"upload finished"]; - [client uploadWithFinishedBlock:^{ + [client uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); [uploadFinished fulfill]; }]; @@ -254,7 +255,8 @@ - (void)testProxy { XCTAssertNil(error); XCTestExpectation *secondUploadFinished = [self expectationWithDescription:@"second upload finished"]; - [client uploadWithFinishedBlock:^{ + [client uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); [secondUploadFinished fulfill]; }]; diff --git a/KeenClientTests/KeenEventTests.m b/KeenClientTests/KeenEventTests.m index e04f62d..0c2ee29 100644 --- a/KeenClientTests/KeenEventTests.m +++ b/KeenClientTests/KeenEventTests.m @@ -14,6 +14,7 @@ #import "KeenTestConstants.h" #import "KeenTestCaseBase.h" #import "KeenEventTests.h" +#import "KeenConstants.h" @implementation KeenEventTests @@ -81,7 +82,7 @@ - (void)testAddEvent { // dict with root keen prop should do nothing badValue = [[NSError alloc] init]; - event = @{ @"a": @"apple", @"keen": @"bapple" }; + event = @{ @"a": @"apple", kKeenEventKeenDataKey: @"bapple" }; XCTAssertFalse([client addEvent:event toEventCollection:@"foo" error:&error], @"addEvent should fail"); XCTAssertNotNil(error, @""); error = nil; @@ -92,7 +93,7 @@ - (void)testAddEvent { // dict with non-root keen prop should work error = nil; - event = @{ @"nested": @{@"keen": @"whatever"} }; + event = @{ @"nested": @{kKeenEventKeenDataKey: @"whatever"} }; XCTAssertTrue([client addEvent:event toEventCollection:@"foo" error:nil], @"addEvent should succeed"); XCTAssertNil(error, @"no error should be returned"); XCTAssertTrue([clientI addEvent:event toEventCollection:@"foo" error:nil], @"addEvent should succeed"); @@ -128,11 +129,11 @@ - (void)testEventWithTimestamp { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:@"foo"]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSError *error = nil; NSDictionary *deserializedDict = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; - NSString *deserializedDate = deserializedDict[@"keen"][@"timestamp"]; + NSString *deserializedDate = deserializedDict[kKeenEventKeenDataKey][@"timestamp"]; NSString *originalDate = [KIOUtil convertDate:date]; XCTAssertEqualObjects(originalDate, deserializedDate, @"If a timestamp is specified it should be used."); originalDate = [KIOUtil convertDate:date]; @@ -157,11 +158,11 @@ - (void)testEventWithLocation { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:@"foo"]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSError *error = nil; NSDictionary *deserializedDict = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; - NSDictionary *deserializedLocation = deserializedDict[@"keen"][@"location"]; + NSDictionary *deserializedLocation = deserializedDict[kKeenEventKeenDataKey][@"location"]; NSArray *deserializedCoords = deserializedLocation[@"coordinates"]; XCTAssertEqualObjects(@-122.47, deserializedCoords[0], @"Longitude was incorrect."); XCTAssertEqualObjects(@37.73, deserializedCoords[1], @"Latitude was incorrect."); @@ -185,7 +186,7 @@ - (void)testEventWithDictionary { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:@"foo"]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSError *error = nil; NSDictionary *deserializedDict = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; @@ -202,19 +203,19 @@ - (void)testEventWithNonDictionaryKeen { andWriteKey:kDefaultWriteKey andReadKey:kDefaultReadKey]; - NSDictionary *theEvent = @{ @"keen": @"abc" }; + NSDictionary *theEvent = @{ kKeenEventKeenDataKey: @"abc" }; NSError *error = nil; [client addEvent:theEvent toEventCollection:@"foo" error:&error]; [clientI addEvent:theEvent toEventCollection:@"foo" error:&error]; XCTAssertNotNil(error, @"an event with a non-dict value for 'keen' should error"); } -- (void)addSimpleEventAndUploadWithMock:(id)mock andFinishedBlock:(void (^)())finishedBlock { ++ (void)addSimpleEventAndUploadWithMock:(id)mock andCompletionHandler:(UploadCompletionBlock)completionHandler { // add an event [mock addEvent:[NSDictionary dictionaryWithObject:@"apple" forKey:@"a"] toEventCollection:@"foo" error:nil]; // and "upload" it - [mock uploadWithFinishedBlock:finishedBlock]; + [mock uploadWithCompletionHandler:completionHandler]; } #pragma mark - test upload @@ -224,7 +225,8 @@ - (void)testUploadWithNoEvents { id mock = [self createClientWithResponseData:nil andStatusCode:HTTPCode200OK]; - [mock uploadWithFinishedBlock:^{ + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); [uploadFinishedBlockCalled fulfill]; }]; @@ -241,28 +243,11 @@ - (void)testUploadSuccess { id mock = [self createClientWithResponseData:nil andStatusCode:HTTPCode2XXSuccess]; XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [responseArrived fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval - handler:^(NSError *_Nullable error) { - XCTAssertTrue([KIODBStore.sharedInstance - getTotalEventCountWithProjectID:[mock config].projectID] == 0, - @"There should be no files after a successful upload."); - }]; -} - -- (void)testUploadSuccessInstanceClient { - id mock = [self createClientWithResponseData:nil andStatusCode:HTTPCode2XXSuccess]; - - // make sure the event was deleted from the store - XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [responseArrived fulfill]; - }]; + [KeenEventTests addSimpleEventAndUploadWithMock:mock + andCompletionHandler:^(NSError *error) { + XCTAssertNil(error); + [responseArrived fulfill]; + }]; [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval handler:^(NSError *_Nullable error) { @@ -276,28 +261,15 @@ - (void)testUploadFailedServerDown { id mock = [self createClientWithResponseData:nil andStatusCode:HTTPCode500InternalServerError]; XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [responseArrived fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval - handler:^(NSError *_Nullable error) { - // make sure the file wasn't deleted from the store - XCTAssertTrue([KIODBStore.sharedInstance - getTotalEventCountWithProjectID:[mock config].projectID] == 1, - @"There should be one file after a failed upload."); - }]; -} - -- (void)testUploadFailedServerDownInstanceClient { - id mock = [self createClientWithResponseData:nil andStatusCode:HTTPCode500InternalServerError]; - - XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [responseArrived fulfill]; - }]; + [KeenEventTests + addSimpleEventAndUploadWithMock:mock + andCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual(error.domain, kKeenErrorDomain); + XCTAssertEqual(error.code, KeenErrorCodeResponseError); + XCTAssertEqualObjects(error.userInfo[kKeenErrorHttpStatus], @(HTTPCode500InternalServerError)); + [responseArrived fulfill]; + }]; [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval handler:^(NSError *_Nullable error) { @@ -312,28 +284,15 @@ - (void)testUploadFailedServerDownNonJsonResponse { id mock = [self createClientWithResponseData:@{} andStatusCode:HTTPCode500InternalServerError]; XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [responseArrived fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval - handler:^(NSError *_Nullable error) { - // make sure the file wasn't deleted locally - XCTAssertTrue([KIODBStore.sharedInstance - getTotalEventCountWithProjectID:[mock config].projectID] == 1, - @"There should be one file after a failed upload."); - }]; -} - -- (void)testUploadFailedServerDownNonJsonResponseInstanceClient { - id mock = [self createClientWithResponseData:@{} andStatusCode:HTTPCode500InternalServerError]; - - XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [responseArrived fulfill]; - }]; + [KeenEventTests + addSimpleEventAndUploadWithMock:mock + andCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual(error.domain, kKeenErrorDomain); + XCTAssertEqual(error.code, KeenErrorCodeResponseError); + XCTAssertEqualObjects(error.userInfo[kKeenErrorHttpStatus], @(HTTPCode500InternalServerError)); + [responseArrived fulfill]; + }]; [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval handler:^(NSError *_Nullable error) { @@ -345,33 +304,85 @@ - (void)testUploadFailedServerDownNonJsonResponseInstanceClient { } - (void)testDeleteAfterMaxAttempts { - id mock = [self createClientWithResponseData:nil andStatusCode:HTTPCode500InternalServerError]; - + NSString *firstEventCollection = @"foo"; + NSString *secondEventCollection = @"bar"; + NSInteger maxAttempts = 3; + __block NSNumber *firstEventPriorAttempts = @(0); + id mock = [self + createClientWithResponseData:nil + andStatusCode:HTTPCode500InternalServerError + andNetworkConnected:@YES + andRequestValidator:^BOOL(id requestObject) { + XCTAssertTrue([requestObject isKindOfClass:[NSMutableURLRequest class]]); + NSMutableURLRequest *request = requestObject; + NSError *serializationError; + NSDictionary *requestDictionary = [NSJSONSerialization JSONObjectWithData:request.HTTPBody + options:nil + error:&serializationError]; + XCTAssertNil(serializationError); + // If the event has fewer than the max attempts we allow, ensure it contains + // the correct number of prior attempts under keen.prior_attempts + if (firstEventPriorAttempts.integerValue < maxAttempts) { + NSArray *collectionEvents = requestDictionary[firstEventCollection]; + NSDictionary *eventDictionary = (NSDictionary *)collectionEvents.firstObject; + XCTAssertEqualObjects( + (NSNumber *)eventDictionary[kKeenEventKeenDataKey][kKeenEventKeenDataAttemptsKey], + firstEventPriorAttempts); + } + return @YES; + }]; // add an event - [mock addEvent:[NSDictionary dictionaryWithObject:@"apple" forKey:@"a"] toEventCollection:@"foo" error:nil]; + [mock addEvent:[NSDictionary dictionaryWithObject:@"apple" forKey:@"a"] + toEventCollection:firstEventCollection + error:nil]; XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; // and "upload" it - [mock uploadWithFinishedBlock:^{ + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual(error.domain, kKeenErrorDomain); + XCTAssertEqual(error.code, KeenErrorCodeResponseError); + XCTAssertEqualObjects(error.userInfo[kKeenErrorHttpStatus], @(HTTPCode500InternalServerError)); // make sure the file wasn't deleted from the store XCTAssertTrue([KIODBStore.sharedInstance getTotalEventCountWithProjectID:[mock config].projectID] == 1, @"There should be one file after an unsuccessful attempts."); + firstEventPriorAttempts = @([firstEventPriorAttempts integerValue] + 1); // add another event - [mock addEvent:[NSDictionary dictionaryWithObject:@"apple" forKey:@"a"] toEventCollection:@"foo" error:nil]; - [mock uploadWithFinishedBlock:^{ + [mock addEvent:[NSDictionary dictionaryWithObject:@"apple" forKey:@"a"] + toEventCollection:secondEventCollection + error:nil]; + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual(error.domain, kKeenErrorDomain); + XCTAssertEqual(error.code, KeenErrorCodeResponseError); + XCTAssertEqualObjects(error.userInfo[kKeenErrorHttpStatus], @(HTTPCode500InternalServerError)); + // make sure both files weren't deleted from the store XCTAssertTrue([KIODBStore.sharedInstance getTotalEventCountWithProjectID:[mock config].projectID] == 2, @"There should be two files after 2 unsuccessful attempts."); - [mock uploadWithFinishedBlock:^{ + firstEventPriorAttempts = @([firstEventPriorAttempts integerValue] + 1); + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual(error.domain, kKeenErrorDomain); + XCTAssertEqual(error.code, KeenErrorCodeResponseError); + XCTAssertEqualObjects(error.userInfo[kKeenErrorHttpStatus], @(HTTPCode500InternalServerError)); + // make sure the first file was deleted from the store, but the second one remains - XCTAssertTrue([[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 + XCTAssertTrue([[KIODBStore.sharedInstance getEventsWithMaxAttempts:maxAttempts andProjectID:[mock config].projectID] allKeys] .count == 1, @"There should be one file after 3 unsuccessful attempts."); - [mock uploadWithFinishedBlock:^{ + firstEventPriorAttempts = @([firstEventPriorAttempts integerValue] + 1); + + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual(error.domain, kKeenErrorDomain); + XCTAssertEqual(error.code, KeenErrorCodeResponseError); + XCTAssertEqualObjects(error.userInfo[kKeenErrorHttpStatus], @(HTTPCode500InternalServerError)); + [responseArrived fulfill]; }]; }]; @@ -398,18 +409,21 @@ - (void)testIncrementEvenOnNoResponse { XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; // and "upload" it - [mock uploadWithFinishedBlock:^{ + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); // make sure the file wasn't deleted from the store XCTAssertTrue([KIODBStore.sharedInstance getTotalEventCountWithProjectID:[mock config].projectID] == 1, @"There should be one event after an unsuccessful attempt."); // add another event - [mock uploadWithFinishedBlock:^{ + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); // make sure both files weren't deleted from the store XCTAssertTrue([KIODBStore.sharedInstance getTotalEventCountWithProjectID:[mock config].projectID] == 1, @"There should be one event after 2 unsuccessful attempts."); - [mock uploadWithFinishedBlock:^{ + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); [responseArrived fulfill]; }]; }]; @@ -439,31 +453,13 @@ - (void)testUploadFailedBadRequest { andStatusCode:HTTPCode200OK]; XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [responseArrived fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval - handler:^(NSError *_Nullable error) { - // make sure the file was deleted locally - // make sure the event was deleted from the store - XCTAssertTrue([KIODBStore.sharedInstance getTotalEventCountWithProjectID:nil] == 0, - @"An invalid event should be deleted after an upload attempt."); - }]; -} - -- (void)testUploadFailedBadRequestInstanceClient { - id mock = [self createClientWithResponseData:[self buildResponseJsonWithSuccess:NO - AndErrorCode:@"InvalidCollectionNameError" - AndDescription:@"anything"] - andStatusCode:HTTPCode200OK]; - - XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [responseArrived fulfill]; - }]; + [KeenEventTests addSimpleEventAndUploadWithMock:mock + andCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual(error.domain, kKeenErrorDomain); + XCTAssertEqual(error.code, KeenErrorCodeEventUploadError); + [responseArrived fulfill]; + }]; [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval handler:^(NSError *_Nullable error) { @@ -478,29 +474,15 @@ - (void)testUploadFailedBadRequestUnknownError { id mock = [self createClientWithResponseData:@{} andStatusCode:HTTPCode400BadRequest]; XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [responseArrived fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval - handler:^(NSError *_Nullable error) { - // make sure the file wasn't deleted locally - XCTAssertTrue( - [KIODBStore.sharedInstance - getTotalEventCountWithProjectID:[mock config].projectID] == 1, - @"An upload that results in an unexpected error should not delete the event."); - }]; -} - -- (void)testUploadFailedBadRequestUnknownErrorInstanceClient { - id mock = [self createClientWithResponseData:@{} andStatusCode:HTTPCode400BadRequest]; - - XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [responseArrived fulfill]; - }]; + [KeenEventTests + addSimpleEventAndUploadWithMock:mock + andCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual(error.domain, kKeenErrorDomain); + XCTAssertEqual(error.code, KeenErrorCodeResponseError); + XCTAssertEqualObjects(error.userInfo[kKeenErrorHttpStatus], @(HTTPCode400BadRequest)); + [responseArrived fulfill]; + }]; [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval handler:^(NSError *_Nullable error) { @@ -516,29 +498,15 @@ - (void)testUploadFailedRedirectionStatus { id mock = [self createClientWithResponseData:@{} andStatusCode:HTTPCode300MultipleChoices]; XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [responseArrived fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval - handler:^(NSError *_Nullable error) { - // make sure the file wasn't deleted locally - XCTAssertTrue( - [KIODBStore.sharedInstance - getTotalEventCountWithProjectID:[mock config].projectID] == 1, - @"An upload that results in an unexpected error should not delete the event."); - }]; -} - -- (void)testUploadFailedRedirectionStatusInstanceClient { - id mock = [self createClientWithResponseData:@{} andStatusCode:HTTPCode300MultipleChoices]; - - XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [responseArrived fulfill]; - }]; + [KeenEventTests + addSimpleEventAndUploadWithMock:mock + andCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual(error.domain, kKeenErrorDomain); + XCTAssertEqual(error.code, KeenErrorCodeResponseError); + XCTAssertEqualObjects(error.userInfo[kKeenErrorHttpStatus], @(HTTPCode300MultipleChoices)); + [responseArrived fulfill]; + }]; [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval handler:^(NSError *_Nullable error) { @@ -556,10 +524,13 @@ - (void)testUploadSkippedNoNetwork { id mock = [self createClientWithResponseData:nil andStatusCode:HTTPCode200OK andNetworkConnected:@NO]; - [self addSimpleEventAndUploadWithMock:mock - andFinishedBlock:^{ - [uploadFinishedBlockCalled fulfill]; - }]; + [KeenEventTests addSimpleEventAndUploadWithMock:mock + andCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual(error.domain, kKeenErrorDomain); + XCTAssertEqual(error.code, KeenErrorCodeNetworkDisconnected); + [uploadFinishedBlockCalled fulfill]; + }]; [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval handler:^(NSError *_Nullable error) { @@ -584,32 +555,8 @@ - (void)testUploadMultipleEventsSameCollectionSuccess { // and "upload" it XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [mock uploadWithFinishedBlock:^{ - [responseArrived fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval - handler:^(NSError *_Nullable error) { - // make sure the events were deleted locally - XCTAssertTrue([KIODBStore.sharedInstance getTotalEventCountWithProjectID:nil] == 0, - @"There should be no files after a successful upload."); - }]; -} - -- (void)testUploadMultipleEventsSameCollectionSuccessInstanceClient { - NSDictionary *result1 = [self buildResultWithSuccess:YES andErrorCode:nil andDescription:nil]; - NSDictionary *result2 = [self buildResultWithSuccess:YES andErrorCode:nil andDescription:nil]; - NSDictionary *result = - [NSDictionary dictionaryWithObject:[NSArray arrayWithObjects:result1, result2, nil] forKey:@"foo"]; - id mock = [self createClientWithResponseData:result andStatusCode:HTTPCode200OK]; - - // add an event - [mock addEvent:[NSDictionary dictionaryWithObject:@"apple" forKey:@"a"] toEventCollection:@"foo" error:nil]; - [mock addEvent:[NSDictionary dictionaryWithObject:@"apple2" forKey:@"a"] toEventCollection:@"foo" error:nil]; - - // and "upload" it - XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [mock uploadWithFinishedBlock:^{ + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); [responseArrived fulfill]; }]; @@ -637,35 +584,8 @@ - (void)testUploadMultipleEventsDifferentCollectionSuccess { // and "upload" it XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [mock uploadWithFinishedBlock:^{ - [responseArrived fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval - handler:^(NSError *_Nullable error) { - // make sure the files were deleted locally - XCTAssertTrue([KIODBStore.sharedInstance getTotalEventCountWithProjectID:nil] == 0, - @"There should be no events after a successful upload."); - }]; -} - -- (void)testUploadMultipleEventsDifferentCollectionSuccessInstanceClient { - NSDictionary *result1 = [self buildResultWithSuccess:YES andErrorCode:nil andDescription:nil]; - NSDictionary *result2 = [self buildResultWithSuccess:YES andErrorCode:nil andDescription:nil]; - NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObject:result1], - @"foo", - [NSArray arrayWithObject:result2], - @"bar", - nil]; - id mock = [self createClientWithResponseData:result andStatusCode:HTTPCode200OK]; - - // add an event - [mock addEvent:[NSDictionary dictionaryWithObject:@"apple" forKey:@"a"] toEventCollection:@"foo" error:nil]; - [mock addEvent:[NSDictionary dictionaryWithObject:@"bapple" forKey:@"b"] toEventCollection:@"bar" error:nil]; - - // and "upload" it - XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [mock uploadWithFinishedBlock:^{ + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); [responseArrived fulfill]; }]; @@ -678,9 +598,10 @@ - (void)testUploadMultipleEventsDifferentCollectionSuccessInstanceClient { } - (void)testUploadMultipleEventsSameCollectionOneFails { + NSString *errorName = @"InvalidCollectionNameError"; + NSString *errorDescription = @"This is an error description"; NSDictionary *result1 = [self buildResultWithSuccess:YES andErrorCode:nil andDescription:nil]; - NSDictionary *result2 = - [self buildResultWithSuccess:NO andErrorCode:@"InvalidCollectionNameError" andDescription:@"something"]; + NSDictionary *result2 = [self buildResultWithSuccess:NO andErrorCode:errorName andDescription:errorDescription]; NSDictionary *result = [NSDictionary dictionaryWithObject:[NSArray arrayWithObjects:result1, result2, nil] forKey:@"foo"]; id mock = [self createClientWithResponseData:result andStatusCode:HTTPCode200OK]; @@ -691,34 +612,14 @@ - (void)testUploadMultipleEventsSameCollectionOneFails { // and "upload" it XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [mock uploadWithFinishedBlock:^{ - [responseArrived fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval - handler:^(NSError *_Nullable error) { - // make sure the file were deleted locally - XCTAssertTrue([KIODBStore.sharedInstance - getTotalEventCountWithProjectID:[mock config].projectID] == 0, - @"There should be no events after a successful upload."); - }]; -} - -- (void)testUploadMultipleEventsSameCollectionOneFailsInstanceClient { - NSDictionary *result1 = [self buildResultWithSuccess:YES andErrorCode:nil andDescription:nil]; - NSDictionary *result2 = - [self buildResultWithSuccess:NO andErrorCode:@"InvalidCollectionNameError" andDescription:@"something"]; - NSDictionary *result = - [NSDictionary dictionaryWithObject:[NSArray arrayWithObjects:result1, result2, nil] forKey:@"foo"]; - id mock = [self createClientWithResponseData:result andStatusCode:HTTPCode200OK]; - - // add an event - [mock addEvent:[NSDictionary dictionaryWithObject:@"apple" forKey:@"a"] toEventCollection:@"foo" error:nil]; - [mock addEvent:[NSDictionary dictionaryWithObject:@"apple2" forKey:@"a"] toEventCollection:@"foo" error:nil]; - - // and "upload" it - XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [mock uploadWithFinishedBlock:^{ + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + NSArray *errors = error.userInfo[kKeenErrorInnerErrorArrayKey]; + XCTAssertEqual(errors.count, 1); + NSError *itemError = errors.firstObject; + XCTAssertEqualObjects(itemError.userInfo[kKeenResponseErrorNameKey], errorName); + NSDictionary *itemErrorDiectionary = itemError.userInfo[kKeenResponseErrorDictionaryKey]; + XCTAssertEqualObjects(itemErrorDiectionary[kKeenResponseErrorDescriptionKey], errorDescription); [responseArrived fulfill]; }]; @@ -732,9 +633,10 @@ - (void)testUploadMultipleEventsSameCollectionOneFailsInstanceClient { } - (void)testUploadMultipleEventsDifferentCollectionsOneFails { + NSString *errorName = @"InvalidCollectionNameError"; + NSString *errorDescription = @"This is an error description"; NSDictionary *result1 = [self buildResultWithSuccess:YES andErrorCode:nil andDescription:nil]; - NSDictionary *result2 = - [self buildResultWithSuccess:NO andErrorCode:@"InvalidCollectionNameError" andDescription:@"something"]; + NSDictionary *result2 = [self buildResultWithSuccess:NO andErrorCode:errorName andDescription:errorDescription]; NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObject:result1], @"foo", [NSArray arrayWithObject:result2], @@ -748,37 +650,14 @@ - (void)testUploadMultipleEventsDifferentCollectionsOneFails { // and "upload" it XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [mock uploadWithFinishedBlock:^{ - [responseArrived fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval - handler:^(NSError *_Nullable error) { - // make sure the files were deleted locally - XCTAssertTrue([KIODBStore.sharedInstance - getTotalEventCountWithProjectID:[mock config].projectID] == 0, - @"There should be no events after a successful upload."); - }]; -} - -- (void)testUploadMultipleEventsDifferentCollectionsOneFailsInstanceClient { - NSDictionary *result1 = [self buildResultWithSuccess:YES andErrorCode:nil andDescription:nil]; - NSDictionary *result2 = - [self buildResultWithSuccess:NO andErrorCode:@"InvalidCollectionNameError" andDescription:@"something"]; - NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObject:result1], - @"foo", - [NSArray arrayWithObject:result2], - @"bar", - nil]; - id mock = [self createClientWithResponseData:result andStatusCode:HTTPCode200OK]; - - // add an event - [mock addEvent:[NSDictionary dictionaryWithObject:@"apple" forKey:@"a"] toEventCollection:@"foo" error:nil]; - [mock addEvent:[NSDictionary dictionaryWithObject:@"bapple" forKey:@"b"] toEventCollection:@"bar" error:nil]; - - // and "upload" it - XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [mock uploadWithFinishedBlock:^{ + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + NSArray *errors = error.userInfo[kKeenErrorInnerErrorArrayKey]; + XCTAssertEqual(errors.count, 1); + NSError *itemError = errors.firstObject; + XCTAssertEqualObjects(itemError.userInfo[kKeenResponseErrorNameKey], errorName); + NSDictionary *itemErrorDiectionary = itemError.userInfo[kKeenResponseErrorDictionaryKey]; + XCTAssertEqualObjects(itemErrorDiectionary[kKeenResponseErrorDescriptionKey], errorDescription); [responseArrived fulfill]; }]; @@ -792,8 +671,10 @@ - (void)testUploadMultipleEventsDifferentCollectionsOneFailsInstanceClient { } - (void)testUploadMultipleEventsDifferentCollectionsOneFailsForServerReason { + NSString *errorName = @"barf"; + NSString *errorDescription = @"This is an error description"; NSDictionary *result1 = [self buildResultWithSuccess:YES andErrorCode:nil andDescription:nil]; - NSDictionary *result2 = [self buildResultWithSuccess:NO andErrorCode:@"barf" andDescription:@"something"]; + NSDictionary *result2 = [self buildResultWithSuccess:NO andErrorCode:errorName andDescription:errorDescription]; NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObject:result1], @"foo", [NSArray arrayWithObject:result2], @@ -807,7 +688,14 @@ - (void)testUploadMultipleEventsDifferentCollectionsOneFailsForServerReason { // and "upload" it XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [mock uploadWithFinishedBlock:^{ + [mock uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNotNil(error); + NSArray *errors = error.userInfo[kKeenErrorInnerErrorArrayKey]; + XCTAssertEqual(errors.count, 1); + NSError *itemError = errors.firstObject; + XCTAssertEqualObjects(itemError.userInfo[kKeenResponseErrorNameKey], errorName); + NSDictionary *itemErrorDiectionary = itemError.userInfo[kKeenResponseErrorDictionaryKey]; + XCTAssertEqualObjects(itemErrorDiectionary[kKeenResponseErrorDescriptionKey], errorDescription); [responseArrived fulfill]; }]; @@ -820,35 +708,6 @@ - (void)testUploadMultipleEventsDifferentCollectionsOneFailsForServerReason { }]; } -- (void)testUploadMultipleEventsDifferentCollectionsOneFailsForServerReasonInstanceClient { - NSDictionary *result1 = [self buildResultWithSuccess:YES andErrorCode:nil andDescription:nil]; - NSDictionary *result2 = [self buildResultWithSuccess:NO andErrorCode:@"barf" andDescription:@"something"]; - NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObject:result1], - @"foo", - [NSArray arrayWithObject:result2], - @"bar", - nil]; - id mock = [self createClientWithResponseData:result andStatusCode:HTTPCode200OK]; - - // add an event - [mock addEvent:[NSDictionary dictionaryWithObject:@"apple" forKey:@"a"] toEventCollection:@"foo" error:nil]; - [mock addEvent:[NSDictionary dictionaryWithObject:@"bapple" forKey:@"b"] toEventCollection:@"bar" error:nil]; - - // and "upload" it - XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; - [mock uploadWithFinishedBlock:^{ - [responseArrived fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval - handler:^(NSError *_Nullable error) { - // make sure the files were deleted locally - XCTAssertTrue([KIODBStore.sharedInstance - getTotalEventCountWithProjectID:[mock config].projectID] == 1, - @"There should be 1 event after a partial upload."); - }]; -} - - (void)testUploadMultipleTimes { XCTestExpectation *uploadFinishedBlockCalled1 = [self expectationWithDescription:@"Upload 1 should run to completion."]; @@ -857,44 +716,21 @@ - (void)testUploadMultipleTimes { XCTestExpectation *uploadFinishedBlockCalled3 = [self expectationWithDescription:@"Upload 3 should run to completion."]; - KeenClient *client = [KeenClient sharedClientWithProjectID:kDefaultProjectID - andWriteKey:kDefaultWriteKey - andReadKey:kDefaultReadKey]; - client.isRunningTests = YES; - - [client uploadWithFinishedBlock:^{ - [uploadFinishedBlockCalled1 fulfill]; - }]; - [client uploadWithFinishedBlock:^{ - [uploadFinishedBlockCalled2 fulfill]; - }]; - [client uploadWithFinishedBlock:^{ - [uploadFinishedBlockCalled3 fulfill]; - }]; - - [self waitForExpectationsWithTimeout:kTestExpectationTimeoutInterval handler:nil]; -} - -- (void)testUploadMultipleTimesInstanceClient { - XCTestExpectation *uploadFinishedBlockCalled1 = - [self expectationWithDescription:@"Upload 1 should run to completion."]; - XCTestExpectation *uploadFinishedBlockCalled2 = - [self expectationWithDescription:@"Upload 2 should run to completion."]; - XCTestExpectation *uploadFinishedBlockCalled3 = - [self expectationWithDescription:@"Upload 3 should run to completion."]; - KeenClient *client = [[KeenClient alloc] initWithProjectID:kDefaultProjectID andWriteKey:kDefaultWriteKey andReadKey:kDefaultReadKey]; client.isRunningTests = YES; - [client uploadWithFinishedBlock:^{ + [client uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); [uploadFinishedBlockCalled1 fulfill]; }]; - [client uploadWithFinishedBlock:^{ + [client uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); [uploadFinishedBlockCalled2 fulfill]; }]; - [client uploadWithFinishedBlock:^{ + [client uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); [uploadFinishedBlockCalled3 fulfill]; }]; @@ -909,10 +745,13 @@ - (void)testUploadEventDoesntDuplicateWithMultipleCallsToUpload { __block NSInteger requestCount = 0; __block NSInteger uploadCallCount = 0; const NSInteger totalUploadCallCount = 10; - KeenClient* client = [self createClientWithResponseData:result andStatusCode:HTTPCode200OK andNetworkConnected:@YES andRequestValidator:^BOOL(id obj) { - requestCount++; - return @YES; - }]; + KeenClient *client = [self createClientWithResponseData:result + andStatusCode:HTTPCode200OK + andNetworkConnected:@YES + andRequestValidator:^BOOL(id obj) { + requestCount++; + return @YES; + }]; // add an event [client addEvent:[NSDictionary dictionaryWithObject:@"apple" forKey:@"a"] toEventCollection:@"foo" error:nil]; @@ -923,7 +762,8 @@ - (void)testUploadEventDoesntDuplicateWithMultipleCallsToUpload { // and "upload" it XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; for (int i = 0; i < totalUploadCallCount; ++i) { - [client uploadWithFinishedBlock:^{ + [client uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); uploadCallCount++; if (uploadCallCount == totalUploadCallCount) { [responseArrived fulfill]; @@ -942,25 +782,6 @@ - (void)testUploadEventDoesntDuplicateWithMultipleCallsToUpload { } - (void)testTooManyEventsCached { - KeenClient *client = [KeenClient sharedClientWithProjectID:kDefaultProjectID - andWriteKey:kDefaultWriteKey - andReadKey:kDefaultReadKey]; - client.isRunningTests = YES; - NSDictionary *event = [NSDictionary dictionaryWithObjectsAndKeys:@"bar", @"foo", nil]; - // create 5 events - for (int i = 0; i < 5; i++) { - [client addEvent:event toEventCollection:@"something" error:nil]; - } - XCTAssertTrue([KIODBStore.sharedInstance getTotalEventCountWithProjectID:client.config.projectID] == 5, - @"There should be exactly five events."); - // now do one more, should age out 1 old ones - [client addEvent:event toEventCollection:@"something" error:nil]; - // so now there should be 4 left (5 - 2 + 1) - XCTAssertTrue([KIODBStore.sharedInstance getTotalEventCountWithProjectID:client.config.projectID] == 4, - @"There should be exactly four events."); -} - -- (void)testTooManyEventsCachedInstanceClient { KeenClient *client = [[KeenClient alloc] initWithProjectID:kDefaultProjectID andWriteKey:kDefaultWriteKey andReadKey:kDefaultReadKey]; @@ -980,28 +801,6 @@ - (void)testTooManyEventsCachedInstanceClient { } - (void)testInvalidEventCollection { - KeenClient *client = [KeenClient sharedClientWithProjectID:kDefaultProjectID - andWriteKey:kDefaultWriteKey - andReadKey:kDefaultReadKey]; - client.isRunningTests = YES; - - NSDictionary *event = @{ @"a": @"b" }; - // collection can't start with $ - NSError *error = nil; - [client addEvent:event toEventCollection:@"$asd" error:&error]; - XCTAssertNotNil(error, @"collection can't start with $"); - error = nil; - - // collection can't be over 256 chars - NSMutableString *longString = [NSMutableString stringWithCapacity:257]; - for (int i = 0; i < 257; i++) { - [longString appendString:@"a"]; - } - [client addEvent:event toEventCollection:@"$asd" error:&error]; - XCTAssertNotNil(error, @"collection can't be longer than 256 chars"); -} - -- (void)testInvalidEventCollectionInstanceClient { KeenClient *client = [[KeenClient alloc] initWithProjectID:kDefaultProjectID andWriteKey:kDefaultWriteKey andReadKey:kDefaultReadKey]; diff --git a/KeenClientTests/KeenGeoLocationTests.m b/KeenClientTests/KeenGeoLocationTests.m index 260aa55..56d1911 100644 --- a/KeenClientTests/KeenGeoLocationTests.m +++ b/KeenClientTests/KeenGeoLocationTests.m @@ -10,6 +10,7 @@ #import "KeenClientTestable.h" #import "KeenTestConstants.h" #import "KeenGeoLocationTests.h" +#import "KeenConstants.h" @implementation KeenGeoLocationTests @@ -33,11 +34,11 @@ - (void)testGeoLocation { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:@"foo"]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSError *error = nil; NSDictionary *deserializedDict = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; - NSDictionary *deserializedLocation = deserializedDict[@"keen"][@"location"]; + NSDictionary *deserializedLocation = deserializedDict[kKeenEventKeenDataKey][@"location"]; NSArray *deserializedCoords = deserializedLocation[@"coordinates"]; XCTAssertEqualObjects(@-122.47, deserializedCoords[0], @"Longitude was incorrect."); XCTAssertEqualObjects(@37.73, deserializedCoords[1], @"Latitude was incorrect."); @@ -63,11 +64,11 @@ - (void)testGeoLocationDisabled { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:@"bar"]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSError *error = nil; NSDictionary *deserializedDict = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; - NSDictionary *deserializedLocation = deserializedDict[@"keen"][@"location"]; + NSDictionary *deserializedLocation = deserializedDict[kKeenEventKeenDataKey][@"location"]; XCTAssertNil(deserializedLocation, @"No location should have been saved."); } @@ -91,11 +92,11 @@ - (void)testGeoLocationRequestDisabled { [[KIODBStore.sharedInstance getEventsWithMaxAttempts:3 andProjectID:client.config.projectID] objectForKey:@"bar"]; // Grab the first event we get back - NSData *eventData = [eventsForCollection objectForKey:[[eventsForCollection allKeys] objectAtIndex:0]]; + NSData *eventData = eventsForCollection[[eventsForCollection allKeys][0]][@"data"]; NSError *error = nil; NSDictionary *deserializedDict = [NSJSONSerialization JSONObjectWithData:eventData options:0 error:&error]; - NSDictionary *deserializedLocation = deserializedDict[@"keen"][@"location"]; + NSDictionary *deserializedLocation = deserializedDict[kKeenEventKeenDataKey][@"location"]; XCTAssertNil(deserializedLocation, @"No location should have been saved."); // To properly test this, you want to make sure that this triggers a real location authentication request, diff --git a/KeenClientTests/MockNSURLSession.h b/KeenClientTests/MockNSURLSession.h index 81704a7..21166cd 100644 --- a/KeenClientTests/MockNSURLSession.h +++ b/KeenClientTests/MockNSURLSession.h @@ -10,12 +10,13 @@ @interface MockNSURLSession : NSObject -- (instancetype)initWithValidator:(BOOL (^)(id requestObject))validator data:(NSData *)data response:(NSURLResponse *)response error:(NSError *)error; +- (instancetype)initWithValidator:(BOOL (^)(id requestObject))validator + data:(NSData *)data + response:(NSURLResponse *)response + error:(NSError *)error; - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request - completionHandler:(void (^)(NSData *_Nullable data, - NSURLResponse *_Nullable response, - NSError *_Nullable error))completionHandler; + completionHandler: + (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler; @end - diff --git a/KeenClientTests/SdkTrackingHeaderTests.m b/KeenClientTests/SdkTrackingHeaderTests.m index 9bb499d..a63dffb 100644 --- a/KeenClientTests/SdkTrackingHeaderTests.m +++ b/KeenClientTests/SdkTrackingHeaderTests.m @@ -45,7 +45,8 @@ - (void)testSdkTrackingHeadersOnUpload { XCTestExpectation *responseArrived = [self expectationWithDescription:@"response of async request has arrived"]; // and "upload" it - [client uploadWithFinishedBlock:^{ + [client uploadWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); [responseArrived fulfill]; }]; From 12c2b1e736e67406bd2a654819563b6572694b2d Mon Sep 17 00:00:00 2001 From: Brian Baumhover Date: Sat, 16 Sep 2017 20:12:14 -0500 Subject: [PATCH 3/6] Fix issue with KeenErrorCode enum --- KeenClient/KeenConstants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KeenClient/KeenConstants.h b/KeenClient/KeenConstants.h index 6f8539d..f028cf3 100644 --- a/KeenClient/KeenConstants.h +++ b/KeenClient/KeenConstants.h @@ -42,7 +42,7 @@ extern NSString *const kKeenErrorDomain; // Error codes for NSError // clang-format off -NS_ENUM(NSInteger, KeenErrorCode) { +typedef NS_ENUM(NSInteger, KeenErrorCode) { KeenErrorCodeGeneral = 1, KeenErrorCodeNetworkDisconnected = 2, KeenErrorCodeSerialization = 3, From d27044314869cd8b54b48fd4eeab509a8b8b87d2 Mon Sep 17 00:00:00 2001 From: Brian Baumhover Date: Mon, 18 Sep 2017 18:10:53 -0500 Subject: [PATCH 4/6] Add more logging, provide valid key for upload attempts. --- KeenClient/KIOUploader.m | 14 +++++++------- KeenClient/KeenConstants.m | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/KeenClient/KIOUploader.m b/KeenClient/KIOUploader.m index 842a328..48c5e4e 100644 --- a/KeenClient/KIOUploader.m +++ b/KeenClient/KIOUploader.m @@ -125,14 +125,8 @@ - (void)prepareJSONData:(NSData **)jsonData return; } - // Get a mutable copy of dictionary with keen properties - NSMutableDictionary *keenDictionary = [eventDict[kKeenEventKeenDataKey] mutableCopy]; - // Add information about the attempt count - [keenDictionary addEntriesFromDictionary:@{kKeenEventKeenDataAttemptsKey: eventAttempts}]; - - // Replace the existing dictionary with the new one including upload attempts - eventDict[kKeenEventKeenDataKey] = keenDictionary; + [eventDict addEntriesFromDictionary:@{kKeenEventKeenDataAttemptsKey: eventAttempts}]; // add it to the array of events [eventsArray addObject:eventDict]; @@ -197,10 +191,13 @@ - (void)uploadEventsForConfig:(KeenClientConfig *)config completionHandler:(Uplo NSMutableDictionary *eventIDs; [self prepareJSONData:&data andEventIDs:&eventIDs forProjectID:config.projectID error:&error]; if (error != nil) { + KCLogInfo(@"Error preparing JSON data for upload: %@", error); [self runUploadFinishedBlock:completionHandler error:error]; } else if ([data length] == 0) { + KCLogInfo(@"No data available for upload."); [self runUploadFinishedBlock:completionHandler error:nil]; } else { + KCLogInfo(@"Uploading %@ events...", @([eventIDs count])); // loop through events and increment their attempt count for (NSString *collectionName in eventIDs) { for (NSNumber *eid in eventIDs[collectionName]) { @@ -246,6 +243,9 @@ - (void)uploadEventsForConfig:(KeenClientConfig *)config completionHandler:(Uplo - (void)runUploadFinishedBlock:(UploadCompletionBlock)completionBlock error:(NSError *)error { if (completionBlock) { KCLogVerbose(@"Running user-specified block."); + if (nil != error) { + KCLogError(@"Error uploading events: %@", error); + } completionBlock(error); } } diff --git a/KeenClient/KeenConstants.m b/KeenClient/KeenConstants.m index 6d7291a..0ffeb2b 100644 --- a/KeenClient/KeenConstants.m +++ b/KeenClient/KeenConstants.m @@ -23,7 +23,7 @@ NSString *const kKeenEventKeenDataKey = @"keen"; -NSString *const kKeenEventKeenDataAttemptsKey = @"prior_attempts"; +NSString *const kKeenEventKeenDataAttemptsKey = @"keen_prior_upload_attempts"; NSUInteger const kKeenMaxEventsPerCollection = 10000; From 571ada74d2b5bf03098c0133dbb49f30221e6d50 Mon Sep 17 00:00:00 2001 From: Brian Baumhover Date: Mon, 18 Sep 2017 18:16:10 -0500 Subject: [PATCH 5/6] Fix logging event count --- KeenClient/KIOUploader.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/KeenClient/KIOUploader.m b/KeenClient/KIOUploader.m index 48c5e4e..42bddf9 100644 --- a/KeenClient/KIOUploader.m +++ b/KeenClient/KIOUploader.m @@ -197,13 +197,15 @@ - (void)uploadEventsForConfig:(KeenClientConfig *)config completionHandler:(Uplo KCLogInfo(@"No data available for upload."); [self runUploadFinishedBlock:completionHandler error:nil]; } else { - KCLogInfo(@"Uploading %@ events...", @([eventIDs count])); + NSUInteger eventCount = 0; // loop through events and increment their attempt count for (NSString *collectionName in eventIDs) { for (NSNumber *eid in eventIDs[collectionName]) { [self.store incrementEventUploadAttempts:eid]; } + eventCount += [eventIDs[collectionName] count]; } + KCLogInfo(@"Uploading %@ events...", @(eventCount)); [self.isUploadingCondition lock]; self.isUploading = YES; From 7bad70dbbd5dbf7bba7cfb6947a33a4d1192b388 Mon Sep 17 00:00:00 2001 From: Brian Baumhover Date: Mon, 18 Sep 2017 18:19:18 -0500 Subject: [PATCH 6/6] More logging. --- KeenClient/KIOUploader.m | 1 + 1 file changed, 1 insertion(+) diff --git a/KeenClient/KIOUploader.m b/KeenClient/KIOUploader.m index 42bddf9..e9d3d7d 100644 --- a/KeenClient/KIOUploader.m +++ b/KeenClient/KIOUploader.m @@ -216,6 +216,7 @@ - (void)uploadEventsForConfig:(KeenClientConfig *)config completionHandler:(Uplo config:config completionHandler:^(NSData *data, NSURLResponse *response, NSError *responseError) { NSError *localError; + KCLogInfo(@"Got upload completion callback."); // then parse the http response and deal with it appropriately [self handleEventAPIResponse:response andData:data