diff --git a/IntegrationTests/Helpers/XCTestCase+Helpers.h b/IntegrationTests/Helpers/XCTestCase+Helpers.h index 6d05d4fd..51fdb50c 100644 --- a/IntegrationTests/Helpers/XCTestCase+Helpers.h +++ b/IntegrationTests/Helpers/XCTestCase+Helpers.h @@ -7,6 +7,7 @@ typedef void (^TestBlock)(NSInvocation *); @interface XCTestCase (Helpers) - (BOOL)areArraysDeepEqual:(NSArray *)first second:(NSArray *)second; +- (BOOL)areArraysOfDictionariesEqualIgnoringOrder:(NSArray *)first second:(NSArray *)second descriptor:(NSString *)descriptor; - (BOOL)areDictionariesDeepEqual:(NSDictionary *)first second:(NSDictionary *)second; - (BOOL)areObjectsEqual:(id _Nonnull)obj1 second:(id _Nonnull)obj2; diff --git a/IntegrationTests/Helpers/XCTestCase+Helpers.m b/IntegrationTests/Helpers/XCTestCase+Helpers.m index f6be2a3c..a58768a8 100644 --- a/IntegrationTests/Helpers/XCTestCase+Helpers.m +++ b/IntegrationTests/Helpers/XCTestCase+Helpers.m @@ -7,8 +7,8 @@ - (BOOL)areArraysDeepEqual:(NSArray *)first second:(NSArray *)second { NSOrderedCollectionDifference *diff = [first differenceFromArray:second withOptions:0 usingEquivalenceTest:^BOOL(id _Nonnull obj1, id _Nonnull obj2) { - return [self areObjectsEqual:obj1 second:obj2]; - }]; + return [self areObjectsEqual:obj1 second:obj2]; + }]; return ![diff hasChanges]; } else { @@ -16,6 +16,28 @@ - (BOOL)areArraysDeepEqual:(NSArray *)first second:(NSArray *)second { } } +- (BOOL)areArraysOfDictionariesEqualIgnoringOrder:(NSArray *)first second:(NSArray *)second descriptor:(NSString *)descriptor { + if (first.count != second.count) { + return NO; + } + + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:descriptor ascending:YES]; + NSArray *sortedFirst = [first sortedArrayUsingDescriptors:@[sortDescriptor]]; + NSArray *sortedSecond = [second sortedArrayUsingDescriptors:@[sortDescriptor]]; + + for (NSUInteger i = 0; i < sortedFirst.count; i++) { + NSDictionary *dict1 = sortedFirst[i]; + NSDictionary *dict2 = sortedSecond[i]; + + if (![dict1 isEqualToDictionary:dict2]) { + NSLog(@"The arrays are not equal."); + return NO; + } + } + + return true; +} + - (BOOL)areDictionariesDeepEqual:(NSDictionary *)first second:(NSDictionary *)second { if (first.count != second.count) { return NO; diff --git a/IntegrationTests/QNAPIClientIntegrationTests.m b/IntegrationTests/QNAPIClientIntegrationTests.m index 6fecf613..451da9c3 100644 --- a/IntegrationTests/QNAPIClientIntegrationTests.m +++ b/IntegrationTests/QNAPIClientIntegrationTests.m @@ -279,42 +279,49 @@ - (void)testAttributionError { [self waitForExpectationsWithTimeout:self.kRequestTimeout handler:nil]; } -- (void)testProperties { +- (void)testSendProperties { // given - XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Properties call"]; - NSString *uid = [NSString stringWithFormat:@"%@%@", self.kUidPrefix, @"_properties"]; + XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Send properties call"]; + NSString *uid = [NSString stringWithFormat:@"%@%@", self.kUidPrefix, @"_send_properties"]; QNAPIClient *client = [self getClient:uid]; - + NSDictionary *data = @{ - @"customProperty": @"custom property value", - [QNProperties keyForProperty:QONUserPropertyKeyUserID]: @"custom user id", + @"customProperty": @"custom property value", + [QNProperties keyForProperty:QONUserPropertyKeyUserID]: @"custom user id", }; - NSDictionary *expRes = @{ - @"data": @{}, - @"success": @1, - }; + NSArray *expSavedProperties = @[ + @{ + @"key": @"customProperty", + @"value": @"custom property value" + }, + @{ + @"key": @"_q_custom_user_id", + @"value": @"custom user id" + } + ]; // when [client launchRequest:^(NSDictionary * _Nullable initRes, NSError * _Nullable createUserError) { - XCTAssertNil(createUserError); + XCTAssertNil(createUserError); [client sendProperties:data completion:^(NSDictionary *_Nullable res, NSError *_Nullable error) { XCTAssertNotNil(res); XCTAssertNil(error); - XCTAssertTrue([self areDictionariesDeepEqual:expRes second:res]); + XCTAssertTrue([self areArraysDeepEqual:res[@"propertyErrors"] second:@[]]); + XCTAssertTrue([self areArraysOfDictionariesEqualIgnoringOrder:res[@"savedProperties"] second:expSavedProperties descriptor:@"key"]); [completionExpectation fulfill]; }]; }]; - + // then [self waitForExpectationsWithTimeout:self.kRequestTimeout handler:nil]; } -- (void)testPropertiesError { +- (void)testSendPropertiesError { // given - XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Properties error call"]; - NSString *uid = [NSString stringWithFormat:@"%@%@", self.kUidPrefix, @"_properties"]; + XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Send properties error call"]; + NSString *uid = [NSString stringWithFormat:@"%@%@", self.kUidPrefix, @"_send_properties"]; QNAPIClient *client = [self getClient:uid projectKey:self.kIncorrectProjectKey]; NSDictionary *data = @{ @@ -332,6 +339,63 @@ - (void)testPropertiesError { [self waitForExpectationsWithTimeout:self.kRequestTimeout handler:nil]; } +- (void)testGetProperties { + // given + XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Get properties call"]; + NSString *uid = [NSString stringWithFormat:@"%@%@", self.kUidPrefix, @"_get_properties"]; + QNAPIClient *client = [self getClient:uid]; + + NSDictionary *data = @{ + @"customProperty": @"custom property value", + [QNProperties keyForProperty:QONUserPropertyKeyUserID]: @"custom user id", + }; + + NSArray *expRes = @[ + @{ + @"key": @"customProperty", + @"value": @"custom property value" + }, + @{ + @"key": @"_q_custom_user_id", + @"value": @"custom user id" + } + ]; + + // when + [client launchRequest:^(NSDictionary * _Nullable initRes, NSError * _Nullable createUserError) { + XCTAssertNil(createUserError); + + [client sendProperties:data completion:^(NSDictionary *_Nullable sendPropertiesRes, NSError *_Nullable sendPropertiesError) { + XCTAssertNil(sendPropertiesError); + + [client getProperties:^(NSArray *res, NSError *error) { + XCTAssertNil(error); + XCTAssertTrue([self areArraysOfDictionariesEqualIgnoringOrder:res second:expRes descriptor:@"key"]); + [completionExpectation fulfill]; + }]; + }]; + }]; + + // then + [self waitForExpectationsWithTimeout:self.kRequestTimeout handler:nil]; +} + +- (void)testGetPropertiesError { + // given + XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Get properties error call"]; + NSString *uid = [NSString stringWithFormat:@"%@%@", self.kUidPrefix, @"_get_properties"]; + QNAPIClient *client = [self getClient:uid projectKey:self.kIncorrectProjectKey]; + + // when + [client getProperties:^(NSArray *_Nullable res, NSError *_Nullable error) { + [self assertAccessDeniedError:res error:error]; + [completionExpectation fulfill]; + }]; + + // then + [self waitForExpectationsWithTimeout:self.kRequestTimeout handler:nil]; +} + - (void)testCheckTrialIntroEligibility { // given XCTestExpectation *completionExpectation = [self expectationWithDescription:@"CheckTrialIntroEligibility call"]; diff --git a/IntegrationTests/QNOutagerIntegrationTests.m b/IntegrationTests/QNOutagerIntegrationTests.m index 8da40089..03ebded3 100644 --- a/IntegrationTests/QNOutagerIntegrationTests.m +++ b/IntegrationTests/QNOutagerIntegrationTests.m @@ -211,21 +211,27 @@ - (void)testAttribution { [self waitForExpectationsWithTimeout:self.kRequestTimeout handler:nil]; } -- (void)testProperties { +- (void)testSendProperties { // given - XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Properties call"]; - NSString *uid = [NSString stringWithFormat:@"%@%@", self.kUidPrefix, @"_properties"]; + XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Send properties call"]; + NSString *uid = [NSString stringWithFormat:@"%@%@", self.kUidPrefix, @"_send_properties"]; QNAPIClient *client = [self getClient:uid]; NSDictionary *data = @{ @"customProperty": @"custom property value", [QNProperties keyForProperty:QONUserPropertyKeyUserID]: @"custom user id", }; - - NSDictionary *expRes = @{ - @"data": @{}, - @"success": @1, - }; + + NSArray *expSavedProperties = @[ + @{ + @"key": @"customProperty", + @"value": @"custom property value" + }, + @{ + @"key": @"_q_custom_user_id", + @"value": @"custom user id" + } + ]; // when [client launchRequest:^(NSDictionary * _Nullable initRes, NSError * _Nullable createUserError) { @@ -234,7 +240,8 @@ - (void)testProperties { [client sendProperties:data completion:^(NSDictionary *_Nullable res, NSError *_Nullable error) { XCTAssertNotNil(res); XCTAssertNil(error); - XCTAssertTrue([self areDictionariesDeepEqual:expRes second:res]); + XCTAssertTrue([self areArraysDeepEqual:res[@"propertyErrors"] second:@[]]); + XCTAssertTrue([self areArraysOfDictionariesEqualIgnoringOrder:res[@"savedProperties"] second:expSavedProperties descriptor:@"key"]); [completionExpectation fulfill]; }]; }]; @@ -243,6 +250,25 @@ - (void)testProperties { [self waitForExpectationsWithTimeout:self.kRequestTimeout handler:nil]; } +- (void)testGetProperties { + // given + XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Get properties call"]; + NSString *uid = [NSString stringWithFormat:@"%@%@", self.kUidPrefix, @"_get_properties"]; + QNAPIClient *client = [self getClient:uid]; + + // when + [client getProperties:^(NSArray *res, NSError *error) { + XCTAssertNil(res); + XCTAssertNotNil(error); + XCTAssertEqual(error.code, 503); + XCTAssertTrue([error.localizedDescription isEqualToString:@"Internal server error"]); + [completionExpectation fulfill]; + }]; + + // then + [self waitForExpectationsWithTimeout:self.kRequestTimeout handler:nil]; +} + - (void)testCheckTrialIntroEligibility { // given XCTestExpectation *completionExpectation = [self expectationWithDescription:@"CheckTrialIntroEligibility call"]; @@ -397,7 +423,7 @@ - (QNAPIClient *)getClient:(NSString *)uid { - (QNAPIClient *)getClient:(NSString *)uid projectKey:(NSString *)projectKey { QNAPIClient *client = [[QNAPIClient alloc] init]; - [client setBaseURL:@""]; + [client setBaseURL:@"https://outager.qonversion.workers.dev/"]; [client setApiKey:projectKey]; [client setSDKVersion:self.kSDKVersion]; [client setUserID:uid]; diff --git a/Sources/Qonversion/Qonversion/Services/QNAPIClient/QNAPIClient.h b/Sources/Qonversion/Qonversion/Services/QNAPIClient/QNAPIClient.h index bcd43e31..1e7e0880 100644 --- a/Sources/Qonversion/Qonversion/Services/QNAPIClient/QNAPIClient.h +++ b/Sources/Qonversion/Qonversion/Services/QNAPIClient/QNAPIClient.h @@ -5,8 +5,8 @@ typedef void (^QNAPIClientEmptyCompletionHandler)(NSError * _Nullable error); typedef void (^QNAPIClientDictCompletionHandler)(NSDictionary * _Nullable dict, NSError * _Nullable error); -typedef void (^QNAPIClientArrayCompletionHandler)(NSArray * _Nullable dict, NSError * _Nullable error); -typedef void (^QNAPIClientCommonCompletionHandler)(id _Nullable dict, NSError * _Nullable error); +typedef void (^QNAPIClientArrayCompletionHandler)(NSArray * _Nullable array, NSError * _Nullable error); +typedef void (^QNAPIClientCommonCompletionHandler)(id _Nullable data, NSError * _Nullable error); NS_ASSUME_NONNULL_BEGIN diff --git a/Sources/Qonversion/Qonversion/Services/QNAPIClient/QNAPIClient.m b/Sources/Qonversion/Qonversion/Services/QNAPIClient/QNAPIClient.m index 521e29f5..0b198f56 100644 --- a/Sources/Qonversion/Qonversion/Services/QNAPIClient/QNAPIClient.m +++ b/Sources/Qonversion/Qonversion/Services/QNAPIClient/QNAPIClient.m @@ -81,7 +81,7 @@ - (void)processRequestWithoutResponse:(NSURLRequest *)request completion:(QNAPIC - (void)processDictRequest:(NSURLRequest *)request completion:(QNAPIClientDictCompletionHandler)completion { [self processRequest:request parseResponse:YES completion:^(id _Nullable data, NSError * _Nullable error) { - if ([data isKindOfClass:[NSDictionary class]]) { + if (error != nil || [data isKindOfClass:[NSDictionary class]]) { completion(data, error); } else { completion(nil, [QONErrors errorWithCode:QONAPIErrorFailedParseResponse]); @@ -91,7 +91,7 @@ - (void)processDictRequest:(NSURLRequest *)request completion:(QNAPIClientDictCo - (void)processArrayRequest:(NSURLRequest *)request completion:(QNAPIClientArrayCompletionHandler)completion { [self processRequest:request parseResponse:YES completion:^(id _Nullable data, NSError * _Nullable error) { - if ([data isKindOfClass:[NSArray class]]) { + if (error != nil || [data isKindOfClass:[NSArray class]]) { completion(data, error); } else { completion(nil, [QONErrors errorWithCode:QONAPIErrorFailedParseResponse]); diff --git a/Sources/Qonversion/Qonversion/Utils/QNProperties/QNProperties.h b/Sources/Qonversion/Qonversion/Utils/QNProperties/QNProperties.h index f1cdd851..7bccbd2d 100644 --- a/Sources/Qonversion/Qonversion/Utils/QNProperties/QNProperties.h +++ b/Sources/Qonversion/Qonversion/Utils/QNProperties/QNProperties.h @@ -1,4 +1,4 @@ -#import "QONLaunchResult.h" +#import "QONUserProperty.h" @interface QNProperties : NSObject