diff --git a/UnitTests/ObjCTests/MPRoktTests.m b/UnitTests/ObjCTests/MPRoktTests.m index 39021dac4..8f0158224 100644 --- a/UnitTests/ObjCTests/MPRoktTests.m +++ b/UnitTests/ObjCTests/MPRoktTests.m @@ -8,6 +8,21 @@ #import "MParticleSwift.h" #import "MPIConstants.h" +// Rokt kit identifier for testing +static NSNumber * const kTestRoktKitId = @181; + +// Test helper class that simulates a kit with getSessionId method +@interface MPRoktTestKitInstance : NSObject +@property (nonatomic, copy) NSString *sessionIdToReturn; +- (NSString *)getSessionId; +@end + +@implementation MPRoktTestKitInstance +- (NSString *)getSessionId { + return self.sessionIdToReturn; +} +@end + @interface MPRokt () - (NSArray *> *)getRoktPlacementAttributesMapping; - (NSNumber *)getRoktHashedEmailUserIdentityType; @@ -293,7 +308,7 @@ - (void)testGetRoktPlacementAttributesMapping { self.mockInstance = OCMPartialMock(instance); self.mockContainer = OCMClassMock([MPKitContainer_PRIVATE class]); NSArray *kitConfig = @[@{ - @"id": @181, + @"id": kTestRoktKitId, kMPRemoteConfigKitConfigurationKey: @{ @"AllowJavaScriptResponse": @"True", @"accountId": @12345, @@ -322,7 +337,7 @@ - (void)testGetRoktHashedEmailUserIdentityType { self.mockInstance = OCMPartialMock(instance); self.mockContainer = OCMClassMock([MPKitContainer_PRIVATE class]); NSArray *kitConfig = @[@{ - @"id": @181, + @"id": kTestRoktKitId, kMPRemoteConfigKitConfigurationKey: @{ @"AllowJavaScriptResponse": @"True", @"accountId": @12345, @@ -344,7 +359,7 @@ - (void)testGetRoktHashedEmailUserIdentityTypeReturnsNilWhenNotConfigured { self.mockInstance = OCMPartialMock(instance); self.mockContainer = OCMClassMock([MPKitContainer_PRIVATE class]); NSArray *kitConfig = @[@{ - @"id": @181, + @"id": kTestRoktKitId, kMPRemoteConfigKitConfigurationKey: @{ @"AllowJavaScriptResponse": @"True", @"accountId": @12345, @@ -745,4 +760,93 @@ - (void)testEventsWithIdentifierCallbackInvocation { OCMVerifyAll(self.mockContainer); } +#pragma mark - setSessionId Tests + +- (void)testSetSessionIdForwardsToKitContainer { + MParticle *instance = [MParticle sharedInstance]; + self.mockInstance = OCMPartialMock(instance); + self.mockContainer = OCMClassMock([MPKitContainer_PRIVATE class]); + [[[self.mockInstance stub] andReturn:self.mockContainer] kitContainer_PRIVATE]; + [[[self.mockInstance stub] andReturn:self.mockInstance] sharedInstance]; + + // Set up test parameters + NSString *sessionId = @"test-session-id-12345"; + + // Set up expectations for kit container + XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for async operation"]; + SEL roktSelector = @selector(setSessionId:); + OCMExpect([self.mockContainer forwardSDKCall:roktSelector + event:nil + parameters:[OCMArg checkWithBlock:^BOOL(MPForwardQueueParameters *params) { + XCTAssertEqualObjects(params[0], sessionId); + return true; + }] + messageType:MPMessageTypeEvent + userInfo:nil]).andDo(^(NSInvocation *invocation) { + [expectation fulfill]; + }); + + // Execute method + [self.rokt setSessionId:sessionId]; + + // Wait for async operation + [self waitForExpectationsWithTimeout:0.2 handler:nil]; + + // Verify + OCMVerifyAll(self.mockContainer); +} + +#pragma mark - getSessionId Tests + +- (void)testGetSessionIdReturnsSessionIdFromKit { + MParticle *instance = [MParticle sharedInstance]; + self.mockInstance = OCMPartialMock(instance); + self.mockContainer = OCMClassMock([MPKitContainer_PRIVATE class]); + [[[self.mockInstance stub] andReturn:self.mockContainer] kitContainer_PRIVATE]; + [[[self.mockInstance stub] andReturn:self.mockInstance] sharedInstance]; + + // Create a mock kit register with Rokt kit code + id mockKitRegister = OCMProtocolMock(@protocol(MPExtensionKitProtocol)); + OCMStub([(id)mockKitRegister code]).andReturn(kTestRoktKitId); + + // Create a real kit instance that responds to getSessionId + NSString *expectedSessionId = @"mock-session-id-67890"; + MPRoktTestKitInstance *kitInstance = [[MPRoktTestKitInstance alloc] init]; + kitInstance.sessionIdToReturn = expectedSessionId; + OCMStub([mockKitRegister wrapperInstance]).andReturn(kitInstance); + + // Return the mock kit register from activeKitsRegistry + NSArray *activeKits = @[mockKitRegister]; + OCMStub([self.mockContainer activeKitsRegistry]).andReturn(activeKits); + + // Execute method + NSString *result = [self.rokt getSessionId]; + + // Verify + XCTAssertEqualObjects(result, expectedSessionId, @"Should return the session id from the kit"); +} + +- (void)testGetSessionIdReturnsNilWhenKitInstanceIsNil { + MParticle *instance = [MParticle sharedInstance]; + self.mockInstance = OCMPartialMock(instance); + self.mockContainer = OCMClassMock([MPKitContainer_PRIVATE class]); + [[[self.mockInstance stub] andReturn:self.mockContainer] kitContainer_PRIVATE]; + [[[self.mockInstance stub] andReturn:self.mockInstance] sharedInstance]; + + // Create a mock kit register with Rokt kit code but nil wrapperInstance + id mockKitRegister = OCMProtocolMock(@protocol(MPExtensionKitProtocol)); + OCMStub([(id)mockKitRegister code]).andReturn(kTestRoktKitId); + OCMStub([mockKitRegister wrapperInstance]).andReturn(nil); + + // Return the mock kit register from activeKitsRegistry + NSArray *activeKits = @[mockKitRegister]; + OCMStub([self.mockContainer activeKitsRegistry]).andReturn(activeKits); + + // Execute method + NSString *result = [self.rokt getSessionId]; + + // Verify + XCTAssertNil(result, @"Should return nil when kit wrapper instance is nil"); +} + @end diff --git a/mParticle-Apple-SDK/Include/MPRokt.h b/mParticle-Apple-SDK/Include/MPRokt.h index 78f6ff087..47fb18fc3 100644 --- a/mParticle-Apple-SDK/Include/MPRokt.h +++ b/mParticle-Apple-SDK/Include/MPRokt.h @@ -114,4 +114,22 @@ typedef NS_ENUM(NSInteger, MPColorMode) { */ - (void)close; +/** + * Set the session id to use for the next execute call. + * This is useful for cases where you have a session id from a non-native integration, + * e.g. WebView, and you want the session to be consistent across integrations. + * + * @note Empty strings are ignored and will not update the session. + * + * @param sessionId The session id to be set. Must be a non-empty string. + */ +- (void)setSessionId:(NSString * _Nonnull)sessionId; + +/** + * Get the session id to use within a non-native integration e.g. WebView. + * + * @return The session id or nil if no session is present. + */ +- (NSString * _Nullable)getSessionId; + @end diff --git a/mParticle-Apple-SDK/MPRokt.m b/mParticle-Apple-SDK/MPRokt.m index c750f6b8f..8ddd047f7 100644 --- a/mParticle-Apple-SDK/MPRokt.m +++ b/mParticle-Apple-SDK/MPRokt.m @@ -11,6 +11,7 @@ #import "MPILogger.h" #import "MPIConstants.h" #import "MPIdentityDTO.h" +#import "MPExtensionProtocol.h" // Constants for kit configuration keys static NSString * const kMPKitConfigurationIdKey = @"id"; @@ -172,6 +173,45 @@ - (void)close { }); } +/// Set the session id to use for the next execute call. +/// This is useful for cases where you have a session id from a non-native integration, +/// e.g. WebView, and you want the session to be consistent across integrations. +/// - Note: Empty strings are ignored and will not update the session. +/// - Parameters: +/// - sessionId: The session id to be set. Must be a non-empty string. +- (void)setSessionId:(NSString * _Nonnull)sessionId { + dispatch_async(dispatch_get_main_queue(), ^{ + MPForwardQueueParameters *queueParameters = [[MPForwardQueueParameters alloc] init]; + [queueParameters addParameter:sessionId]; + + [[MParticle sharedInstance].kitContainer_PRIVATE forwardSDKCall:@selector(setSessionId:) + event:nil + parameters:queueParameters + messageType:MPMessageTypeEvent + userInfo:nil + ]; + }); +} + +/// Get the session id to use within a non-native integration e.g. WebView. +/// - Returns: The session id or nil if no session is present. +- (NSString * _Nullable)getSessionId { + __block NSString *result = nil; + + NSArray> *activeKits = [[MParticle sharedInstance].kitContainer_PRIVATE activeKitsRegistry]; + for (id kitRegister in activeKits) { + if ([kitRegister.code integerValue] == kMPRoktKitId) { + id kitInstance = kitRegister.wrapperInstance; + if (kitInstance && [kitInstance respondsToSelector:@selector(getSessionId)]) { + result = [kitInstance performSelector:@selector(getSessionId)]; + break; + } + } + } + + return result; +} + #pragma mark - Private Helper Methods /// Retrieves the Rokt Kit configuration from the kit container.