Skip to content

Commit 8ccc2a6

Browse files
feat: add custom event logging for selectPlacements (#43)
* feat: add custom event logging for selectPlacements * adjust failing test CI workflow * Apply suggestion from @jamesnrokt Co-authored-by: James Newman <james.newman@rokt.com> * add tests for objc path addresses @rmi22186 comments --------- Co-authored-by: James Newman <james.newman@rokt.com>
1 parent e0c2b81 commit 8ccc2a6

6 files changed

Lines changed: 141 additions & 9 deletions

File tree

.github/workflows/pull-request.yml

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ permissions:
1313
checks: write
1414
id-token: write
1515

16+
env:
17+
XCODE_VERSION: "16.4"
18+
DEVICE: "iPhone 16 Pro"
19+
VERSION: ">=18.0"
20+
1621
jobs:
1722
pr-branch-check-name:
1823
name: "Check PR for semantic branch name"
@@ -23,20 +28,30 @@ jobs:
2328

2429
test:
2530
name: Test
31+
timeout-minutes: 15
2632
runs-on: macos-15
2733
steps:
2834
- name: Checkout
29-
uses: actions/checkout@v5
30-
- name: Set up Xcode 16
35+
uses: actions/checkout@v6
36+
37+
- name: Set up Xcode
3138
uses: maxim-lobanov/setup-xcode@v1
3239
with:
33-
xcode-version: 16.3.0
34-
- name: Run Tests
35-
run: >
36-
set -o pipefail &&
37-
xcodebuild test -project mParticle-Rokt.xcodeproj -scheme mParticle_RoktTests -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest'
38-
| xcbeautify --renderer github-actions
39-
shell: bash
40+
xcode-version: ${{ env.XCODE_VERSION }}
41+
42+
- name: Setup specified simulator
43+
uses: futureware-tech/simulator-action@v4
44+
id: simulator
45+
with:
46+
model: ${{ env.DEVICE }}
47+
os: iOS
48+
os_version: ${{ env.VERSION }}
49+
erase_before_boot: true
50+
wait_for_boot: true
51+
shutdown_after_job: true
52+
53+
- name: Run unit tests
54+
run: xcodebuild -project mParticle-Rokt.xcodeproj -scheme mParticle_RoktTests -destination 'id=${{ steps.simulator.outputs.udid }}' test
4055

4156
pr-notify:
4257
if: >

mParticle-Rokt-Swift/MPRoktLayout.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public class MPRoktLayout {
3333
confirmUser(attributes: attributes) { identifyCalled in
3434
let preparedAttributes = MPKitRokt.prepareAttributes(attributes, filteredUser: Optional<FilteredMParticleUser>.none, performMapping: true)
3535

36+
// Log custom event for selectPlacements call
37+
MPKitRokt.logSelectPlacementEvent(preparedAttributes)
38+
3639
MPRoktLayout.mpLog("Initializing RoktLayout with arguments sdkTriggered:\(sdkTriggered.wrappedValue), viewName: \(viewName ?? "nil"), locationName:\(locationName), attributes:\(preparedAttributes)")
3740
self.roktLayout = RoktLayout.init(
3841
sdkTriggered: sdkTriggered,

mParticle-Rokt/MPKitRokt.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@
1818

1919
+ (NSDictionary<NSString *, NSString *> * _Nonnull)prepareAttributes:(NSDictionary<NSString *, NSString *> * _Nonnull)attributes filteredUser:(FilteredMParticleUser * _Nullable)filteredUser performMapping:(BOOL)performMapping;
2020
+ (NSNumber * _Nullable)getRoktHashedEmailUserIdentityType;
21+
+ (void)logSelectPlacementEvent:(NSDictionary<NSString *, NSString *> * _Nonnull)attributes;
2122

2223
@end

mParticle-Rokt/MPKitRokt.m

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
NSString * const kMPPlacementAttributesMapping = @"placementAttributesMapping";
99
NSString * const kMPHashedEmailUserIdentityType = @"hashedEmailUserIdentityType";
1010
NSString * const kMPRoktEmbeddedViewClassName = @"MPRoktEmbeddedView";
11+
NSString * const kMPEventNameSelectPlacements = @"selectPlacements";
1112
NSInteger const kMPRoktKitCode = 181;
1213

1314
static __weak MPKitRokt *roktKit = nil;
@@ -104,6 +105,9 @@ - (MPKitExecStatus *)executeWithIdentifier:(NSString * _Nullable)identifier
104105
[MPKitRokt MPLog:[NSString stringWithFormat:@"Rokt Kit recieved `executeWithIdentifier` method with the following arguments: \n identifier: %@ \n attributes: %@ \n embeddedViews: %@ \n config: %@ \n callbacks: %@ \n filteredUser identities: %@", identifier, attributes, embeddedViews, mpRoktConfig, callbacks, filteredUser.userIdentities]];
105106
NSDictionary<NSString *, NSString *> *finalAtt = [MPKitRokt prepareAttributes:attributes filteredUser:filteredUser performMapping:NO];
106107

108+
// Log custom event for selectPlacements call
109+
[MPKitRokt logSelectPlacementEvent:finalAtt];
110+
107111
//Convert MPRoktConfig to RoktConfig
108112
RoktConfig *roktConfig = [MPKitRokt convertMPRoktConfig:mpRoktConfig];
109113
NSDictionary<NSString *, RoktEmbeddedView *> *confirmedViews = [self confirmEmbeddedViews:embeddedViews];
@@ -786,6 +790,13 @@ + (void)MPLog:(NSString *)string {
786790
}
787791
}
788792

793+
+ (void)logSelectPlacementEvent:(NSDictionary<NSString *, NSString *> * _Nonnull)attributes {
794+
MPEvent *event = [[MPEvent alloc] initWithName:kMPEventNameSelectPlacements type:MPEventTypeOther];
795+
event.customAttributes = attributes;
796+
[[MParticle sharedInstance] logEvent:event];
797+
[MPKitRokt MPLog:[NSString stringWithFormat:@"Logged selectplacements custom event with attributes: %@", attributes]];
798+
}
799+
789800

790801
+ (MPRoktEvent * _Nullable)mapEvent:(RoktEvent *)event {
791802
if (!event) {

mParticle_RoktTests/mParticle_RoktTests.m

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ + (RoktConfig *)convertMPRoktConfig:(MPRoktConfig *)mpRoktConfig;
3535

3636
+ (NSDictionary<NSString *, NSString *> *)transformValuesToString:(NSDictionary<NSString *, id> * _Nullable)originalDictionary;
3737

38+
+ (void)logSelectPlacementEvent:(NSDictionary<NSString *, NSString *> * _Nonnull)attributes;
39+
3840
@end
3941

4042
@interface mParticle_RoktTests : XCTestCase
@@ -727,4 +729,71 @@ - (void)testGetRoktHashedEmailUserIdentityTypeNil {
727729
[mockMPKitRoktClass stopMocking];
728730
}
729731

732+
#pragma mark - logSelectPlacementEvent tests
733+
734+
- (void)testExecuteWithIdentifierLogsSelectPlacementEventWithPreparedAttributes {
735+
id mockRoktSDK = OCMClassMock([Rokt class]);
736+
id mockMParticleInstance = OCMClassMock([MParticle class]);
737+
738+
// Stub the class method sharedInstance to return our mock
739+
id mockMParticleClass = OCMClassMock([MParticle class]);
740+
OCMStub([mockMParticleClass sharedInstance]).andReturn(mockMParticleInstance);
741+
OCMStub([(MParticle *)mockMParticleInstance environment]).andReturn(MPEnvironmentDevelopment);
742+
743+
NSString *identifier = @"TestView";
744+
NSDictionary *attributes = @{@"email": @"test@example.com"};
745+
746+
// Create a mock user with MPID and identities
747+
FilteredMParticleUser *user = [[FilteredMParticleUser alloc] init];
748+
id mockUser = OCMPartialMock(user);
749+
OCMStub([mockUser userId]).andReturn(@(123456));
750+
OCMStub([mockUser userIdentities]).andReturn(@{@(MPIdentityEmail): @"test@example.com"});
751+
OCMStub([mockUser userAttributes]).andReturn(@{});
752+
753+
// Expect logEvent and verify MPEvent object contains prepared attributes (email, mpid, sandbox)
754+
OCMExpect([(MParticle *)mockMParticleInstance logEvent:[OCMArg checkWithBlock:^BOOL(MPEvent *event) {
755+
// Verify the MPEvent object was created correctly
756+
XCTAssertNotNil(event, @"Event object should not be nil");
757+
XCTAssertEqualObjects(event.name, @"selectPlacements", @"Event name should be 'selectPlacements'");
758+
XCTAssertEqual(event.type, MPEventTypeOther, @"Event type should be MPEventTypeOther");
759+
760+
// Verify custom attributes contain prepared user data
761+
XCTAssertEqualObjects(event.customAttributes[@"email"], @"test@example.com", @"Email should be in attributes");
762+
XCTAssertEqualObjects(event.customAttributes[@"mpid"], @"123456", @"MPID should be in attributes");
763+
XCTAssertNotNil(event.customAttributes[@"sandbox"], @"Sandbox should be in attributes");
764+
765+
return YES;
766+
}]]);
767+
768+
// Stub Rokt execute call
769+
OCMStub([mockRoktSDK executeWithViewName:OCMOCK_ANY
770+
attributes:OCMOCK_ANY
771+
placements:OCMOCK_ANY
772+
config:OCMOCK_ANY
773+
onLoad:OCMOCK_ANY
774+
onUnLoad:OCMOCK_ANY
775+
onShouldShowLoadingIndicator:OCMOCK_ANY
776+
onShouldHideLoadingIndicator:OCMOCK_ANY
777+
onEmbeddedSizeChange:OCMOCK_ANY]);
778+
779+
// Call executeWithIdentifier which triggers logSelectPlacementEvent with prepareAttributes
780+
MPKitExecStatus *status = [self.kitInstance executeWithIdentifier:identifier
781+
attributes:attributes
782+
embeddedViews:nil
783+
config:nil
784+
callbacks:nil
785+
filteredUser:user];
786+
787+
// Verify that logEvent was called with the correct MPEvent object
788+
OCMVerifyAll(mockMParticleInstance);
789+
790+
// Verify execution status
791+
XCTAssertNotNil(status);
792+
XCTAssertEqual(status.returnCode, MPKitReturnCodeSuccess);
793+
794+
[mockRoktSDK stopMocking];
795+
[mockMParticleClass stopMocking];
796+
[mockMParticleInstance stopMocking];
797+
}
798+
730799
@end

mParticle_RoktTests/mParticle_Rokt_SwiftTests.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,39 @@ struct mParticle_Rokt_SwiftTests {
252252
#expect(layout.roktLayout != nil, "Layout should handle state changes")
253253
}
254254

255+
// MARK: - SelectPlacements Custom Event Tests
256+
257+
@available(iOS 15, *)
258+
@Test func testPrepareAttributesLogsAttributesEvent() {
259+
// Given
260+
let attributes: [String: String] = ["attr1": "val1"]
261+
262+
// When
263+
let preparedAttributes = MPKitRokt.prepareAttributes(
264+
attributes,
265+
filteredUser: nil,
266+
performMapping: false
267+
)
268+
269+
MPKitRokt.logSelectPlacementEvent(preparedAttributes)
270+
271+
// Then
272+
#expect(preparedAttributes["sandbox"] != nil, "Sandbox attribute should be present")
273+
#expect(preparedAttributes.count >= 1, "Prepared attributes should contain at least the sandbox attribute")
274+
}
275+
276+
@available(iOS 15, *)
277+
@Test func testLogSelectPlacementEventHandlesNilMParticleInstance() {
278+
// Given
279+
let attributes: [String: String] = ["key1": "value1"]
280+
281+
// When
282+
MPKitRokt.logSelectPlacementEvent(attributes)
283+
284+
// Then
285+
#expect(true, "logSelectPlacementEvent should handle MParticle instance state gracefully")
286+
}
287+
255288
// MARK: - Integration Tests
256289

257290
@MainActor @available(iOS 15, *)

0 commit comments

Comments
 (0)