diff --git a/Branch-TestBed/Branch-SDK-Tests/BNCClassSerializationTests.m b/Branch-TestBed/Branch-SDK-Tests/BNCClassSerializationTests.m new file mode 100644 index 000000000..f1af287d9 --- /dev/null +++ b/Branch-TestBed/Branch-SDK-Tests/BNCClassSerializationTests.m @@ -0,0 +1,120 @@ +// +// BNCClassSerializationTests.m +// Branch-SDK-Tests +// +// Created by Ernest Cho on 3/28/24. +// Copyright © 2024 Branch, Inc. All rights reserved. +// + +#import +#import "BranchEvent.h" +#import "BranchOpenRequest.h" +#import "BranchInstallRequest.h" + +@interface BranchEvent() +// private BranchEvent methods used to build a BranchEventRequest +- (NSDictionary *)buildEventDictionary; +@end + +@interface BranchOpenRequest() +- (NSString *)getActionName; +@end + +@interface BNCClassSerializationTests : XCTestCase + +@end + +// Test serialization of replayable requests +@implementation BNCClassSerializationTests + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +// BranchEventRequest is creation is tightly coupled with the BranchEvent class +// In order to test building it, we need to expose some private methods. :( +- (BranchEventRequest *)buildBranchEventRequest { + BranchEvent *event = [BranchEvent standardEvent:BranchStandardEventPurchase]; + NSURL *url = [NSURL URLWithString:@"https://api3.branch.io/v2/event/standard"]; + NSDictionary *eventDictionary = [event buildEventDictionary]; + + BranchEventRequest *request = [[BranchEventRequest alloc] initWithServerURL:url eventDictionary:eventDictionary completion:nil]; + return request; +} + +- (void)testBranchEventRequestArchive { + BranchEventRequest *request = [self buildBranchEventRequest]; + + // archive the event + NSError *error = nil; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:request requiringSecureCoding:YES error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(data); + + // unarchive the event + id object = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[BranchEventRequest.class]] fromData:data error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(object); + + // check object + XCTAssertTrue([object isKindOfClass:BranchEventRequest.class]); + BranchEventRequest *unarchivedRequest = (BranchEventRequest *)object; + + XCTAssertTrue([request.serverURL.absoluteString isEqualToString:unarchivedRequest.serverURL.absoluteString]); + XCTAssertTrue(request.eventDictionary.count == unarchivedRequest.eventDictionary.count); + XCTAssertNil(unarchivedRequest.completion); +} + +- (void)testBranchOpenRequestArchive { + BranchOpenRequest *request = [[BranchOpenRequest alloc] initWithCallback:nil]; + request.urlString = @"https://branch.io"; + + // archive the event + NSError *error = nil; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:request requiringSecureCoding:YES error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(data); + + // unarchive the event + id object = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[BranchOpenRequest.class]] fromData:data error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(object); + + // check object + XCTAssertTrue([object isKindOfClass:BranchOpenRequest.class]); + BranchOpenRequest *unarchivedRequest = (BranchOpenRequest *)object; + + XCTAssertTrue([request.urlString isEqualToString:unarchivedRequest.urlString]); + XCTAssertNil(unarchivedRequest.callback); + XCTAssertTrue([@"open" isEqualToString:[unarchivedRequest getActionName]]); +} + +- (void)testBranchInstallRequestArchive { + BranchInstallRequest *request = [[BranchInstallRequest alloc] initWithCallback:nil]; + request.urlString = @"https://branch.io"; + + // archive the event + NSError *error = nil; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:request requiringSecureCoding:YES error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(data); + + // unarchive the event + id object = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[BranchInstallRequest.class]] fromData:data error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(object); + + // check object + XCTAssertTrue([object isKindOfClass:BranchInstallRequest.class]); + BranchInstallRequest *unarchivedRequest = (BranchInstallRequest *)object; + + XCTAssertTrue([request.urlString isEqualToString:unarchivedRequest.urlString]); + XCTAssertNil(unarchivedRequest.callback); + XCTAssertTrue([@"install" isEqualToString:[unarchivedRequest getActionName]]); +} + +@end diff --git a/Branch-TestBed/Branch-TestBed.xcodeproj/project.pbxproj b/Branch-TestBed/Branch-TestBed.xcodeproj/project.pbxproj index 855bb949d..23e32a6c7 100644 --- a/Branch-TestBed/Branch-TestBed.xcodeproj/project.pbxproj +++ b/Branch-TestBed/Branch-TestBed.xcodeproj/project.pbxproj @@ -187,6 +187,8 @@ 5F644C482B7AA811000DCD78 /* BNCCallbackMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F644BB72B7AA811000DCD78 /* BNCCallbackMap.m */; }; 5F644C492B7AA811000DCD78 /* BNCEventUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F644BB82B7AA811000DCD78 /* BNCEventUtils.m */; }; 5F67F48E228F535500067429 /* BNCEncodingUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F67F48D228F535500067429 /* BNCEncodingUtilsTests.m */; }; + 5F6D86D92BB5E9650068B536 /* BNCClassSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F6D86D82BB5E9650068B536 /* BNCClassSerializationTests.m */; }; + 5F83B9ED2433BAAA0054A022 /* BNCServerInterface.Test.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D16837E2098C901008819E3 /* BNCServerInterface.Test.m */; }; 5F86501A2B76DA3200364BDE /* NSMutableDictionaryBranchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F8650192B76DA3200364BDE /* NSMutableDictionaryBranchTests.m */; }; 5F892EC5236116CD0023AEC1 /* NSErrorBranchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F892EC4236116CC0023AEC1 /* NSErrorBranchTests.m */; }; 5F8B7B4021B5F5CD009CE0A6 /* libBranch.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 466B58381B17773000A69EDE /* libBranch.a */; }; @@ -475,6 +477,7 @@ 5F644BB72B7AA811000DCD78 /* BNCCallbackMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BNCCallbackMap.m; sourceTree = ""; }; 5F644BB82B7AA811000DCD78 /* BNCEventUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BNCEventUtils.m; sourceTree = ""; }; 5F67F48D228F535500067429 /* BNCEncodingUtilsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BNCEncodingUtilsTests.m; sourceTree = ""; }; + 5F6D86D82BB5E9650068B536 /* BNCClassSerializationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BNCClassSerializationTests.m; sourceTree = ""; }; 5F73FC8023314697000EBD32 /* BNCJSONUtilityTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BNCJSONUtilityTests.m; sourceTree = ""; }; 5F8650192B76DA3200364BDE /* NSMutableDictionaryBranchTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSMutableDictionaryBranchTests.m; sourceTree = ""; }; 5F892EC4236116CC0023AEC1 /* NSErrorBranchTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSErrorBranchTests.m; sourceTree = ""; }; @@ -648,6 +651,7 @@ 5F892EC4236116CC0023AEC1 /* NSErrorBranchTests.m */, 5F8650192B76DA3200364BDE /* NSMutableDictionaryBranchTests.m */, 4D16839E2098C901008819E3 /* NSStringBranchTests.m */, + 5F6D86D82BB5E9650068B536 /* BNCClassSerializationTests.m */, ); path = "Branch-SDK-Tests"; sourceTree = ""; @@ -1371,6 +1375,7 @@ C15CC9DE2ABCB549003CC339 /* BNCCurrencyTests.m in Sources */, 5F205D0823186AF700C776D1 /* BNCUserAgentCollectorTests.m in Sources */, 5FCF7EAD29DC96A7008D629E /* BNCURLFilterSkiplistUpgradeTests.m in Sources */, + 5F6D86D92BB5E9650068B536 /* BNCClassSerializationTests.m in Sources */, E7A728BD2AA9A112009343B7 /* BNCAPIServerTest.m in Sources */, 4D1683C12098C902008819E3 /* BNCApplicationTests.m in Sources */, 4D1683B92098C902008819E3 /* BNCSystemObserverTests.m in Sources */, diff --git a/Sources/BranchSDK/BNCServerRequestQueue.m b/Sources/BranchSDK/BNCServerRequestQueue.m index 58e298337..3beb7136e 100755 --- a/Sources/BranchSDK/BNCServerRequestQueue.m +++ b/Sources/BranchSDK/BNCServerRequestQueue.m @@ -284,6 +284,19 @@ - (void)retrieve { } } +// It's been reported that unarchive can fail in some situations. In that case, remove the queued requests file. +- (void)removeSaveFile { + NSURL *fileURL = [BNCServerRequestQueue URLForQueueFile]; + if (fileURL) { + NSError *error; + [NSFileManager.defaultManager removeItemAtURL:fileURL error:&error]; + + if (error) { + [[BranchLogger shared] logError:@"Failed to remove archived queue" error:error]; + } + } +} + - (NSMutableArray *)unarchiveQueueFromData:(NSData *)data { NSMutableArray *queue = [NSMutableArray new]; @@ -317,7 +330,8 @@ - (id)unarchiveObjectFromData:(NSData *)data { id object = [NSKeyedUnarchiver unarchivedObjectOfClasses:[BNCServerRequestQueue encodableClasses] fromData:data error:&error]; if (error) { - [[BranchLogger shared] logWarning:@"Failed to unarchive" error:error]; + [[BranchLogger shared] logError:@"Failed to unarchive" error:error]; + [self removeSaveFile]; } return object; @@ -331,7 +345,7 @@ - (id)unarchiveObjectFromData:(NSData *)data { NSArray *tmp = @[ [BranchOpenRequest class], [BranchInstallRequest class], - [BranchEventRequest class], + [BranchEventRequest class] ]; requestClasses = [NSSet setWithArray:tmp]; }); diff --git a/Sources/BranchSDK/BranchEvent.m b/Sources/BranchSDK/BranchEvent.m index 81f46abd4..e7634173f 100644 --- a/Sources/BranchSDK/BranchEvent.m +++ b/Sources/BranchSDK/BranchEvent.m @@ -134,8 +134,11 @@ - (instancetype)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) return self; - self.serverURL = [decoder decodeObjectOfClass:NSString.class forKey:@"serverURL"]; - self.eventDictionary = [decoder decodeObjectOfClass:NSDictionary.class forKey:@"eventDictionary"]; + self.serverURL = [decoder decodeObjectOfClass:NSURL.class forKey:@"serverURL"]; + + NSSet *classes = [NSSet setWithArray:@[NSDictionary.class, NSArray.class, NSString.class, NSNumber.class]]; + self.eventDictionary = [decoder decodeObjectOfClasses:classes forKey:@"eventDictionary"]; + return self; } diff --git a/Sources/BranchSDK/BranchOpenRequest.m b/Sources/BranchSDK/BranchOpenRequest.m index 275595ce4..96a6f7110 100644 --- a/Sources/BranchSDK/BranchOpenRequest.m +++ b/Sources/BranchSDK/BranchOpenRequest.m @@ -253,6 +253,21 @@ - (NSString *)getActionName { return @"open"; } +- (instancetype)initWithCoder:(NSCoder *)decoder { + self = [super initWithCoder:decoder]; + if (!self) return self; + self.urlString = [decoder decodeObjectOfClass:NSString.class forKey:@"urlString"]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + [coder encodeObject:self.urlString forKey:@"urlString"]; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} #pragma - Open Response Lock Handling