diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index a23bfc05..968b6a99 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -350,6 +350,7 @@ 6B32A0AD2B9DBE31009ADC57 /* CTTemplatePresenterMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B32A0AC2B9DBE31009ADC57 /* CTTemplatePresenterMock.m */; }; 6B32A0B02B9DC374009ADC57 /* CTTemplateArgumentTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B32A0AF2B9DC374009ADC57 /* CTTemplateArgumentTest.m */; }; 6B32A0B42B9F2E8F009ADC57 /* CTTestTemplateProducer.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B32A0B32B9F2E8F009ADC57 /* CTTestTemplateProducer.m */; }; + 6B453EFE2CF74BE2003C7A89 /* CTEventAdapterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B453EFD2CF74BE2003C7A89 /* CTEventAdapterTest.m */; }; 6B453EF92CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B453EF82CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m */; }; 6B4A0F912B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B4A0F902B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m */; }; 6B535FB62AD56C60002A2663 /* CTMultiDelegateManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B535FB42AD56C60002A2663 /* CTMultiDelegateManager.h */; }; @@ -914,6 +915,7 @@ 6B32A0B12B9F2A75009ADC57 /* CTCustomTemplatesManager+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CTCustomTemplatesManager+Tests.h"; sourceTree = ""; }; 6B32A0B22B9F2E8F009ADC57 /* CTTestTemplateProducer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTTestTemplateProducer.h; sourceTree = ""; }; 6B32A0B32B9F2E8F009ADC57 /* CTTestTemplateProducer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTTestTemplateProducer.m; sourceTree = ""; }; + 6B453EFD2CF74BE2003C7A89 /* CTEventAdapterTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTEventAdapterTest.m; sourceTree = ""; }; 6B453EF72CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTInAppDisplayViewControllerMock.h; sourceTree = ""; }; 6B453EF82CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTInAppDisplayViewControllerMock.m; sourceTree = ""; }; 6B4A0F902B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTInAppTriggerManagerTest.m; sourceTree = ""; }; @@ -1476,6 +1478,7 @@ 6BA3B2E72B07E207004E834B /* CTTriggersMatcher+Tests.m */, 6B4A0F902B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m */, 6BB778CF2BEE4C3400A41628 /* CTNotificationActionTest.m */, + 6B453EFD2CF74BE2003C7A89 /* CTEventAdapterTest.m */, 4806346E2CEB620400E39E9B /* CTInAppDisplayViewControllerTests.m */, 6B453EF72CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.h */, 6B453EF82CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m */, @@ -2546,6 +2549,7 @@ D02AC2DB276044F70031C1BE /* CleverTapSDKTests.m in Sources */, 32394C2129FA264B00956058 /* CTPreferencesTest.m in Sources */, 6BD334F02AF545C80099E33E /* CTInAppStoreTest.m in Sources */, + 6B453EFE2CF74BE2003C7A89 /* CTEventAdapterTest.m in Sources */, 32394C1F29FA251E00956058 /* CTEventBuilderTest.m in Sources */, 6B12F7692C9466460045D743 /* CTJsonTemplateProducerTest.m in Sources */, 6BB778D02BEE4C3400A41628 /* CTNotificationActionTest.m in Sources */, diff --git a/CleverTapSDK/CTUtils.h b/CleverTapSDK/CTUtils.h index 26a1b247..6e3c1f7a 100644 --- a/CleverTapSDK/CTUtils.h +++ b/CleverTapSDK/CTUtils.h @@ -15,4 +15,14 @@ + (NSNumber * _Nullable)numberFromString:(NSString * _Nullable)string; + (NSNumber * _Nullable)numberFromString:(NSString * _Nullable)string withLocale:(NSLocale * _Nullable)locale; +/** + * Get the CT normalized version of an event or a property name. + */ ++ (NSString * _Nullable)getNormalizedName:(NSString * _Nullable)name; + +/** + * Check if two event/property names are equal with applied CT normalization + */ ++ (BOOL)areEqualNormalizedName:(NSString * _Nullable)firstName andName:(NSString * _Nullable)secondName; + @end diff --git a/CleverTapSDK/CTUtils.m b/CleverTapSDK/CTUtils.m index 14f49047..92500f14 100644 --- a/CleverTapSDK/CTUtils.m +++ b/CleverTapSDK/CTUtils.m @@ -142,4 +142,34 @@ + (NSNumber * _Nullable)numberFromString:(NSString * _Nullable)string withLocale return nil; } ++ (NSString * _Nullable)getNormalizedName:(NSString * _Nullable)name { + if (name) { + // Lowercase with English locale for consistent behavior with the backend + // and across different device locales. + NSString *normalizedName = [name stringByReplacingOccurrencesOfString:@" " withString:@""]; + NSLocale *englishLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; + normalizedName = [normalizedName lowercaseStringWithLocale:englishLocale]; + normalizedName = [normalizedName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + return normalizedName; + } + + return nil; +} + ++ (BOOL)areEqualNormalizedName:(NSString * _Nullable)firstName + andName:(NSString * _Nullable)secondName { + if (firstName == nil && secondName == nil) { + return YES; + } + + if (firstName == nil || secondName == nil) { + return NO; + } + + NSString *normalizedFirstName = [CTUtils getNormalizedName:firstName]; + NSString *normalizedSecondName = [CTUtils getNormalizedName:secondName]; + + return [normalizedFirstName isEqualToString:normalizedSecondName]; +} + @end diff --git a/CleverTapSDK/CTValidator.m b/CleverTapSDK/CTValidator.m index a5329611..c3c5c070 100644 --- a/CleverTapSDK/CTValidator.m +++ b/CleverTapSDK/CTValidator.m @@ -2,6 +2,7 @@ #import "CTValidationResult.h" #import "CTConstants.h" #import "CTKnownProfileFields.h" +#import "CTUtils.h" static const int kMaxKeyChars = 120; static const int kMaxValueChars = 1024; @@ -231,7 +232,7 @@ + (BOOL)isRestrictedEventName:(NSString *)name { NSArray *restrictedNames = @[@"Notification Sent", @"Notification Viewed", @"Notification Clicked", @"UTM Visited", @"App Launched", @"Stayed", @"App Uninstalled", @"wzrk_d", @"wzrk_fetch", @"SCCampaignOptOut", CLTAP_GEOFENCE_ENTERED_EVENT_NAME, CLTAP_GEOFENCE_EXITED_EVENT_NAME]; for (NSString *x in restrictedNames) - if ([name.lowercaseString isEqualToString:x.lowercaseString]) { + if ([CTUtils areEqualNormalizedName:name andName:x]) { // The event name is restricted CTValidationResult *error = [[CTValidationResult alloc] init]; [error setErrorCode:513]; @@ -244,7 +245,7 @@ + (BOOL)isRestrictedEventName:(NSString *)name { + (BOOL)isDiscaredEventName:(NSString *)name { for (NSString *x in discardedEvents) - if ([name.lowercaseString isEqualToString:x.lowercaseString]) { + if ([CTUtils areEqualNormalizedName:name andName:x]) { // The event name is discarded CTValidationResult *error = [[CTValidationResult alloc] init]; [error setErrorCode:513]; diff --git a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m index 301f551f..fc961ac6 100644 --- a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m +++ b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m @@ -8,6 +8,7 @@ #import "CTEventAdapter.h" #import "CTConstants.h" +#import "CTUtils.h" static NSDictionary *systemPropToKey; @@ -95,19 +96,41 @@ - (CTTriggerValue *)propertyValueNamed:(NSString *)name { } - (id)getActualPropertyValue:(NSString *)propertyName { - id value = self.eventProperties[propertyName]; + id value = [self getPropertyValue:propertyName]; + if (value == nil) { if ([propertyName isEqualToString:CLTAP_PROP_CAMPAIGN_ID]) { - value = self.eventProperties[CLTAP_PROP_WZRK_ID]; + value = [self getPropertyValue:CLTAP_PROP_WZRK_ID]; } else if ([propertyName isEqualToString:CLTAP_PROP_WZRK_ID]) { - value = self.eventProperties[CLTAP_PROP_CAMPAIGN_ID]; + value = [self getPropertyValue:CLTAP_PROP_CAMPAIGN_ID]; } else if ([propertyName isEqualToString:CLTAP_PROP_VARIANT]) { - value = self.eventProperties[CLTAP_PROP_WZRK_PIVOT]; + value = [self getPropertyValue:CLTAP_PROP_WZRK_PIVOT]; } else if ([propertyName isEqualToString:CLTAP_PROP_WZRK_PIVOT]) { - value = self.eventProperties[CLTAP_PROP_VARIANT]; + value = [self getPropertyValue:CLTAP_PROP_VARIANT]; } else if (systemPropToKey[propertyName]) { // Map App Fields - value = self.eventProperties[systemPropToKey[propertyName]]; + value = [self getPropertyValue:systemPropToKey[propertyName]]; + } + } + return value; +} + +- (id)getPropertyValue:(NSString *)propertyName { + id value = self.eventProperties[propertyName]; + + if (value == nil) { + // Normalize the property name + propertyName = [CTUtils getNormalizedName:propertyName]; + value = self.eventProperties[propertyName]; + } + + if (value == nil) { + // Check if event properties name are normalized equal + for (NSString *key in self.eventProperties) { + if ([CTUtils areEqualNormalizedName:key andName:propertyName]) { + value = self.eventProperties[key]; + break; + } } } return value; @@ -120,6 +143,20 @@ - (CTTriggerValue *)itemValueNamed:(NSString *)name { NSMutableArray *itemValues = [NSMutableArray new]; for (NSDictionary *item in self.items) { id value = item[name]; + if (value == nil) { + NSString *normalizedName = [CTUtils getNormalizedName:name]; + value = item[normalizedName]; + } + if (value == nil) { + // Normalize the keys in the dictionary + NSMutableDictionary *normalizedItem = [NSMutableDictionary dictionary]; + for (NSString *key in item) { + NSString *normalizedKey = [CTUtils getNormalizedName:key]; + normalizedItem[normalizedKey] = item[key]; + } + value = normalizedItem[[CTUtils getNormalizedName:name]]; + } + if (value) { [itemValues addObject:value]; } diff --git a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m index b6366b13..7f68301a 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m +++ b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m @@ -11,6 +11,7 @@ #import "CTTriggerValue.h" #import "CTConstants.h" #import "CTTriggerEvaluator.h" +#import "CTUtils.h" @implementation CTTriggersMatcher @@ -30,9 +31,8 @@ - (BOOL)matchEventWhenTriggers:(NSArray *)whenTriggers event:(CTEventAdapter *)e } - (BOOL)match:(CTTriggerAdapter *)trigger event:(CTEventAdapter *)event { - - BOOL eventNameMatch = [[event eventName] isEqualToString:[trigger eventName]]; - BOOL profileAttrNameMatch = [event profileAttrName] != nil && [[event profileAttrName] isEqualToString:[trigger profileAttrName]]; + BOOL eventNameMatch = [CTUtils areEqualNormalizedName:[event eventName] andName:[trigger eventName]]; + BOOL profileAttrNameMatch = [event profileAttrName] != nil && [CTUtils areEqualNormalizedName:[event profileAttrName] andName:[trigger profileAttrName]]; if (!eventNameMatch && !profileAttrNameMatch) { return NO; } diff --git a/CleverTapSDKTests/CTUtilsTest.m b/CleverTapSDKTests/CTUtilsTest.m index 16f12e0c..71fe4103 100644 --- a/CleverTapSDKTests/CTUtilsTest.m +++ b/CleverTapSDKTests/CTUtilsTest.m @@ -176,4 +176,23 @@ - (void)test_numberFromStringWithLocale { XCTAssertNil([CTUtils numberFromString:@"12.3" withLocale:locale]); } +- (void)testGetNormalizedName { + XCTAssertNil([CTUtils getNormalizedName:nil]); + XCTAssertEqualObjects(@"", [CTUtils getNormalizedName:@""]); + XCTAssertEqualObjects(@"event1", [CTUtils getNormalizedName:@"Event 1"]); + XCTAssertEqualObjects(@"event1", [CTUtils getNormalizedName:@"EVENT 1"]); + XCTAssertEqualObjects(@"event1", [CTUtils getNormalizedName:@"event1"]); +} + +- (void)testAreEqualNormalizedNames { + XCTAssertTrue([CTUtils areEqualNormalizedName:nil andName:nil]); + XCTAssertTrue([CTUtils areEqualNormalizedName:@"" andName:@""]); + XCTAssertTrue([CTUtils areEqualNormalizedName:@"Event 1" andName:@"Event1"]); + XCTAssertTrue([CTUtils areEqualNormalizedName:@"Event 1" andName:@"event1"]); + XCTAssertTrue([CTUtils areEqualNormalizedName:@"Event 1" andName:@"EVENT 1"]); + XCTAssertFalse([CTUtils areEqualNormalizedName:@"" andName:nil]); + XCTAssertFalse([CTUtils areEqualNormalizedName:@"Event 1" andName:nil]); + XCTAssertFalse([CTUtils areEqualNormalizedName:@"Event 1" andName:@"Event 2"]); +} + @end diff --git a/CleverTapSDKTests/InApps/CTEventAdapterTest.m b/CleverTapSDKTests/InApps/CTEventAdapterTest.m new file mode 100644 index 00000000..0d218ae5 --- /dev/null +++ b/CleverTapSDKTests/InApps/CTEventAdapterTest.m @@ -0,0 +1,195 @@ +// +// CTEventAdapterTest.m +// CleverTapSDKTests +// +// Created by Nikola Zagorchev on 27.11.24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import +#import +#import "CTEventAdapter.h" +#import "CTConstants.h" + +@interface CTEventAdapterTest : XCTestCase + +@end + +@implementation CTEventAdapterTest + +- (void)testPropertyValueNamed { + NSString *value = @"value"; + NSDictionary *eventProperties = @{ + @"prop1": value + }; + CTEventAdapter *eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"prop1"] stringValue]); + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@" prop 1 "] stringValue]); + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"Prop1"] stringValue]); + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"Prop 1"] stringValue]); + XCTAssertNil([eventAdapter propertyValueNamed:@"Prop 1 1"]); + + eventProperties = @{ + @"prop 1": value + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"prop1"] stringValue]); + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@" prop 1 "] stringValue]); + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"Prop1"] stringValue]); + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"Prop 1"] stringValue]); + + eventProperties = @{ + @"prop 1": @"value1", + @"prop1": value, + @"Prop 1": @"value2" + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"prop1"] stringValue]); + XCTAssertEqualObjects(eventProperties[@"Prop 1"], [[eventAdapter propertyValueNamed:@"Prop 1"] stringValue]); + // The dictionary is unordered - the order is not the same as defined in code + NSString *firstPropertyKey = eventAdapter.eventProperties.allKeys[0]; + NSString *expectedValue = eventAdapter.eventProperties[firstPropertyKey]; + XCTAssertEqualObjects(expectedValue, [[eventAdapter propertyValueNamed:@" prop 1"] stringValue]); + + eventProperties = @{ + @"prop 1": @"value1", + @"prop 1": @"value2", + @"Prop 1": @"value3", + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + // The dictionary is unordered - the order is not the same as defined in code + firstPropertyKey = eventAdapter.eventProperties.allKeys[0]; + expectedValue = eventAdapter.eventProperties[firstPropertyKey]; + XCTAssertEqualObjects(expectedValue, [[eventAdapter propertyValueNamed:@"prop1"] stringValue]); +} + +- (void)testSystemPropertyValueNamed { + NSDictionary *eventProperties = @{ + CLTAP_PROP_WZRK_ID: @"wzrk_id value", + CLTAP_APP_VERSION: @"Version value", + CLTAP_SDK_VERSION: @"SDK Version value" + }; + CTEventAdapter *eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(@"wzrk_id value", [[eventAdapter propertyValueNamed:CLTAP_PROP_CAMPAIGN_ID] stringValue]); + XCTAssertEqualObjects(@"Version value", [[eventAdapter propertyValueNamed:@"CT App Version"] stringValue]); + XCTAssertEqualObjects(@"SDK Version value", [[eventAdapter propertyValueNamed:@"CT SDK Version"] stringValue]); + + eventProperties = @{ + CLTAP_PROP_CAMPAIGN_ID: @"Campaign id value", + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(@"Campaign id value", [[eventAdapter propertyValueNamed:CLTAP_PROP_WZRK_ID] stringValue]); + + eventProperties = @{ + CLTAP_PROP_VARIANT: @"Variant value" + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(@"Variant value", [[eventAdapter propertyValueNamed:CLTAP_PROP_WZRK_PIVOT] stringValue]); + + eventProperties = @{ + CLTAP_PROP_WZRK_PIVOT: @"wzrk_pivot value", + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(@"wzrk_pivot value", [[eventAdapter propertyValueNamed:CLTAP_PROP_VARIANT] stringValue]); +} + +- (void)testSystemPropertyValueNamedNormalized { + NSDictionary *eventProperties = @{ + CLTAP_PROP_WZRK_ID: @"wzrk_id value", + CLTAP_APP_VERSION: @"Version value", + }; + CTEventAdapter *eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(@"wzrk_id value", [[eventAdapter propertyValueNamed:CLTAP_PROP_CAMPAIGN_ID] stringValue]); + XCTAssertEqualObjects(@"Version value", [[eventAdapter propertyValueNamed:@"CT App Version"] stringValue]); + // The system property names must be exact match + XCTAssertNil([eventAdapter propertyValueNamed:CLTAP_PROP_CAMPAIGN_ID.lowercaseString]); + XCTAssertNil([eventAdapter propertyValueNamed:@"CT App Version".lowercaseString]); + XCTAssertNil([eventAdapter propertyValueNamed:@"CTApp Version"]); + + // The property name is normalized if it matches the system property name evaluated + eventProperties = @{ + // CLTAP_PROP_WZRK_ID @"wzrk_id" + @"wzrk_ID": @"wzrk_id value", + // CLTAP_APP_VERSION @"Version" + @"version": @"Version value" + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(@"wzrk_id value", [[eventAdapter propertyValueNamed:CLTAP_PROP_CAMPAIGN_ID] stringValue]); + XCTAssertEqualObjects(@"Version value", [[eventAdapter propertyValueNamed:@"CT App Version"] stringValue]); +} + +- (void)testItemValueNamed { + NSArray *items = @[ + @{ + @"productName": @"product 1", + @"price": @5.99 + }, + @{ + @"productName": @"product 2", + @"price": @5.50 + }]; + CTEventAdapter *eventAdapter = [self eventAdapterWithItems:items]; + XCTAssertEqualObjects((@[@"product 1", @"product 2"]), [[eventAdapter itemValueNamed:@"productName"] arrayValue]); + XCTAssertEqualObjects((@[@5.99, @5.50]), [[eventAdapter itemValueNamed:@"price"] arrayValue]); + XCTAssertNil([eventAdapter itemValueNamed:@"none"]); + + // Nil Items + CTEventAdapter *eventAdapterNilItems = [self eventAdapterWithItems:nil]; + XCTAssertNil([eventAdapterNilItems itemValueNamed:@"none"]); +} + +- (void)testItemValueNamedNormalized { + NSArray *items = @[ + @{ + @"productName": @"product 1" + }, + @{ + @"productName": @"product 2" + }]; + NSArray *expectedProductNames = @[@"product 1", @"product 2"]; + + CTEventAdapter *eventAdapter = [self eventAdapterWithItems:items]; + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"productName"] arrayValue]); + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"ProductName"] arrayValue]); + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"product Name"] arrayValue]); + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"Product Name"] arrayValue]); +} + +- (void)testItemValueNamedNormalizedItem { + NSArray *items = @[ + @{ + @"product Name": @"product 1" + }, + @{ + @"ProductName": @"product 2" + }, + @{ + @"Product Name": @"product 3" + }, + @{ + @"product name": @"product 4" + }, + @{ + @"product_name": @"product 5" + }]; + NSArray *expectedProductNames = @[@"product 1", @"product 2", @"product 3", @"product 4"]; + + CTEventAdapter *eventAdapter = [self eventAdapterWithItems:items]; + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"productName"] arrayValue]); + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"ProductName"] arrayValue]); + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"product Name"] arrayValue]); + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"Product Name"] arrayValue]); + + XCTAssertEqualObjects((@[@"product 5"]), [[eventAdapter itemValueNamed:@"product_name"] arrayValue]); + XCTAssertEqualObjects((@[@"product 5"]), [[eventAdapter itemValueNamed:@"Product_Name"] arrayValue]); +} + +- (CTEventAdapter *)eventAdapterWithProperties:(NSDictionary *)eventProperties { + return [[CTEventAdapter alloc] initWithEventName:@"event" eventProperties:eventProperties andLocation:kCLLocationCoordinate2DInvalid]; +} + +- (CTEventAdapter *)eventAdapterWithItems:(NSArray *)items { + return [[CTEventAdapter alloc] initWithEventName:@"event" eventProperties:@{} location:kCLLocationCoordinate2DInvalid andItems:items]; +} + +@end diff --git a/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m b/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m index e66f4280..87718b0e 100644 --- a/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m +++ b/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m @@ -322,7 +322,6 @@ - (void)testEvaluateWithInApps { } - (void)testEvaluateUserAttribute { - self.helper.inAppStore.serverSideInApps = @[ @{ @"ti": @1, @@ -343,19 +342,79 @@ - (void)testEvaluateUserAttribute { }], @"profileAttrName": @"Customer Type", }] - }, - ]; + }]; + NSDictionary *profile = @{ @"Customer Type": @{ @"newValue": @"Gold", @"oldValue": @"Premium" } }; + [self.evaluationManager evaluateOnUserAttributeChange:profile]; + XCTAssertEqualObjects((@[@1]), self.evaluationManager.evaluatedServerSideInAppIdsForProfile); +} - +- (void)testEvaluateUserAttributeNormalized { + self.helper.inAppStore.serverSideInApps = @[ + @{ + @"ti": @1, + @"whenTriggers": @[@{ + @"eventProperties": @[@{ + @"propertyName": @"newValue", + @"propertyValue": @"Gold", + }], + @"profileAttrName": @"Customer Type", + }] + }]; + + NSDictionary *profile = @{ + @"CustomerType": @{ + @"newValue": @"Gold", + @"oldValue": @"Premium" + } + }; [self.evaluationManager evaluateOnUserAttributeChange:profile]; XCTAssertEqualObjects((@[@1]), self.evaluationManager.evaluatedServerSideInAppIdsForProfile); - XCTAssertNotEqualObjects((@[@2]), self.evaluationManager.evaluatedServerSideInAppIdsForProfile); + + profile = @{ + @"customer type": @{ + @"newValue": @"Gold", + @"oldValue": @"Premium" + } + }; + [self.evaluationManager evaluateOnUserAttributeChange:profile]; + XCTAssertEqualObjects((@[@1, @1]), self.evaluationManager.evaluatedServerSideInAppIdsForProfile); +} + +- (void)testEvaluateUserAttributeNormalizedMultiple { + self.helper.inAppStore.serverSideInApps = @[ + @{ + @"ti": @1, + @"whenTriggers": @[@{ + @"eventProperties": @[@{ + @"propertyName": @"newValue", + @"propertyValue": @"Gold", + }], + @"profileAttrName": @"Customer Type", + }] + }]; + + NSDictionary *profile = @{ + @"CustomerType": @{ + @"newValue": @"Gold", + @"oldValue": @"Premium" + }, + @"customer type": @{ + @"newValue": @"Gold", + @"oldValue": @"Premium" + }, + @"customerType": @{ + @"newValue": @"Gold", + @"oldValue": @"Premium" + } + }; + [self.evaluationManager evaluateOnUserAttributeChange:profile]; + XCTAssertEqualObjects((@[@1, @1, @1]), self.evaluationManager.evaluatedServerSideInAppIdsForProfile); } - (void)testEvaluateCharged { diff --git a/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m b/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m index df26c716..81c976af 100644 --- a/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m +++ b/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m @@ -146,6 +146,72 @@ - (void)testMatchEventWithoutProps { XCTAssertFalse(matchNoProps); } +- (void)testMatchEventWithNormalizedName { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"event1", + @"eventProperties": @[ + ] + } + ]; + + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"Event 1" eventProperties:@{ + @"prop1": @"clevertap" + }]; + XCTAssertTrue(match); +} + +#pragma mark Profile Event + +- (void)testMatchProfileEvent { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"profile1 changed", + @"profileAttrName": @"profile1", + @"eventProperties": @[ + @{ + @"propertyName": @"newValue", + @"operator": @0, + @"propertyValue": @150 + }, + @{ + @"propertyName": @"oldValue", + @"operator": @1, + @"propertyValue": @"Equals" + } + ] + } + ]; + + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + + NSDictionary *eventProperties = @{ + @"newValue": @160, + @"oldValue": @"Equals" + }; + + CTEventAdapter *eventAdapter = [[CTEventAdapter alloc] initWithEventName:@"profile1_changed" profileAttrName:@"profile1" eventProperties:eventProperties andLocation:kCLLocationCoordinate2DInvalid]; + BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:eventAdapter]; + XCTAssertTrue(match); + + eventAdapter = [[CTEventAdapter alloc] initWithEventName:@"profile 1_changed" profileAttrName:@"profile 1" eventProperties:eventProperties andLocation:kCLLocationCoordinate2DInvalid]; + match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:eventAdapter]; + XCTAssertTrue(match); + + eventAdapter = [[CTEventAdapter alloc] initWithEventName:@"Profile 1_changed" profileAttrName:@"Profile 1" eventProperties:eventProperties andLocation:kCLLocationCoordinate2DInvalid]; + match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:eventAdapter]; + XCTAssertTrue(match); + + eventAdapter = [[CTEventAdapter alloc] initWithEventName:@"profile 1_changed" profileAttrName:@"profile 1" eventProperties:eventProperties andLocation:kCLLocationCoordinate2DInvalid]; + match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:eventAdapter]; + XCTAssertTrue(match); + + eventAdapter = [[CTEventAdapter alloc] initWithEventName:@"Profile_1_changed" profileAttrName:@"Profile_1" eventProperties:eventProperties andLocation:kCLLocationCoordinate2DInvalid]; + match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:eventAdapter]; + XCTAssertFalse(match); +} + #pragma mark Charged Event - (void)testMatchChargedEvent { @@ -289,6 +355,44 @@ - (void)testMatchChargedEventItemArrayEquals { XCTAssertTrue(match); } +- (void)testMatchChargedEventItemArrayEqualsNormalized { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"Charged", + @"eventProperties": @[ + @{ + @"propertyName": @"prop1", + @"operator": @1, + @"propertyValue": @150 + }], + @"itemProperties": @[ + @{ + @"propertyName": @"product name", + @"operator": @1, + @"propertyValue": @[@"product 1"] + }, + @{ + @"propertyName": @"price", + @"operator": @1, + @"propertyValue": @[@5.99] + }] + } + ]; + + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + + BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{ + @"Prop 1": @150, + } items:@[ + @{ + @"ProductName": @"product 1", + @"Price": @5.99 + } + ]]; + + XCTAssertTrue(match); +} + - (void)testMatchChargedEventItemArrayContains { NSArray *whenTriggers = @[ @{ @@ -326,6 +430,32 @@ - (void)testMatchChargedEventItemArrayContains { XCTAssertTrue(match); } +- (void)testMatchChargedEventItemArrayContainsNormalized { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"Charged", + @"eventProperties": @[], + @"itemProperties": @[ + @{ + @"propertyName": @"product name", + @"operator": @3, + @"propertyValue": @[@"product 1", @"product 2"] + }] + } + ]; + + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + + BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{} items:@[ + @{ + @"Product Name": @"product 1", + @"price": @5.50 + } + ]]; + + XCTAssertTrue(match); +} + #pragma mark Equals - (void)testMatchEqualsPrimitives { @@ -757,6 +887,56 @@ - (void)testMatchEqualsExtectedStringWithActualString { XCTAssertFalse(match); } +- (void)testMatchEqualsPropertyNameWithNormalization { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"event1", + @"eventProperties": @[ + @{ + @"propertyName": @"prop1", + @"operator": @1, + @"propertyValue": @"test" + } + ] + } + ]; + + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + + BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ + @"prop 1": @"test" + }]; + XCTAssertTrue(match); + + match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ + @"Prop 1": @"test" + }]; + XCTAssertTrue(match); + + match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"E vent1" eventProperties:@{ + @"Prop 1": @"test" + }]; + XCTAssertTrue(match); + + match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ + @"Prop.1": @"test" + }]; + XCTAssertFalse(match); + + match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ + @"Prop 1": @"test1", + @"Prop1": @"test", + }]; + XCTAssertFalse(match); + + match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ + @"Prop1": @"test1", + @"prop 1": @"test2", + @"prop1": @"test", + }]; + XCTAssertTrue(match); +} + - (void)testMatchEqualsExtectedNumberWithActualString { NSArray *whenTriggers = @[ @{