Skip to content

Commit 2b47ff3

Browse files
test: Add unit tests for consent mapping logic (#38)
* add test target for swift tests * extract logic and make functions available in swift * create tests for updateConsent logic in swift * Revert "extract logic and make functions available in swift" This reverts commit 0b4dedc. * add extension to NSString * remove singleton from update consent * add wrapper to didFinishLaunching Co-Authored-By: denischilik <[email protected]> * corrected not using key parameter * make helper functions available for testing * create tests for helper functions * got objc tests working in xcode UI * remove unused info.plist * adjust naming for test targets --------- Co-authored-by: denischilik <[email protected]>
1 parent cd35d45 commit 2b47ff3

File tree

7 files changed

+181
-37
lines changed

7 files changed

+181
-37
lines changed

Package.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,18 @@ let package = Package(
4343
exclude: ["Info.plist", "dummy.swift"],
4444
resources: [.process("PrivacyInfo.xcprivacy")],
4545
publicHeadersPath: "."),
46+
47+
.testTarget(
48+
name: "mParticle-Google-Analytics-Firebase-GA4-Swift-Tests",
49+
dependencies: ["mParticle-Google-Analytics-Firebase-GA4"],
50+
path: "mParticle-Google-Analytics-Firebase-GA4Tests/Swift"
51+
),
52+
53+
.testTarget(
54+
name: "mParticle-Google-Analytics-Firebase-GA4-Objc-Tests",
55+
dependencies: ["mParticle-Google-Analytics-Firebase-GA4"],
56+
path: "mParticle-Google-Analytics-Firebase-GA4Tests/Objc",
57+
resources: [.process("GoogleService-Info.plist")]
58+
)
4659
]
4760
)

mParticle-Google-Analytics-Firebase-GA4/MPKitFirebaseGA4Analytics.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@
2020
+ (void)setCustomNameStandardization:(NSString * _Nonnull (^_Nullable)(NSString * _Nonnull name))standardization;
2121
+ (NSString * _Nonnull (^_Nullable)(NSString * _Nonnull name))customNameStandardization;
2222

23+
- (nullable NSNumber *)resolvedConsentForMappingKey:(NSString * _Nonnull)mappingKey
24+
defaultKey:(NSString * _Nonnull)defaultKey
25+
gdprConsents:(NSDictionary<NSString *, MPGDPRConsent *> * _Nonnull)gdprConsents
26+
mapping:(NSDictionary<NSString *, NSString *> * _Nullable)mapping;
27+
28+
- (nullable NSArray<NSDictionary *>*)mappingForKey:(NSString* _Nonnull)key;
29+
30+
- (nonnull NSDictionary*)convertToKeyValuePairs: (NSArray<NSDictionary *> * _Nonnull)mappings;
31+
2332
@end
2433

2534
static NSString * _Nonnull const kMPFIRGA4ExternalUserIdentityType = @"externalUserIdentityType";

mParticle-Google-Analytics-Firebase-GA4/MPKitFirebaseGA4Analytics.m

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@
1515

1616
static NSString* (^customNameStandardization)(NSString* name) = nil;
1717

18+
@implementation NSString(PRIVATE)
19+
20+
- (NSNumber*)isGranted {
21+
if ([self isEqualToString:@"Granted"]) {
22+
return @(YES);
23+
} else if ([self isEqualToString:@"Denied"]) {
24+
return @(NO);
25+
}
26+
return nil;
27+
}
28+
29+
@end
30+
1831
@interface MPKitFirebaseGA4Analytics () <MPKitProtocol> {
1932
BOOL forwardRequestsServerSide;
2033
}
@@ -89,6 +102,11 @@ - (MPKitExecStatus *)execStatus:(MPKitReturnCode)returnCode {
89102

90103
#pragma mark MPKitInstanceProtocol methods
91104
- (MPKitExecStatus *)didFinishLaunchingWithConfiguration:(NSDictionary *)configuration {
105+
MParticleUser *currentUser = [[[MParticle sharedInstance] identity] currentUser];
106+
return [self didFinishLaunchingWithConfiguration:configuration withConsentState:currentUser.consentState];
107+
}
108+
109+
- (MPKitExecStatus *)didFinishLaunchingWithConfiguration:(NSDictionary *)configuration withConsentState: (MPConsentState *)consentState {
92110
_configuration = configuration;
93111

94112
if ([FIRApp defaultApp] == nil) {
@@ -100,8 +118,7 @@ - (MPKitExecStatus *)didFinishLaunchingWithConfiguration:(NSDictionary *)configu
100118
}
101119

102120
[self updateInstanceIDIntegration];
103-
104-
[self updateConsent];
121+
[self updateConsent: consentState];
105122

106123
_started = YES;
107124

@@ -382,21 +399,21 @@ - (void)logUserAttributes:(NSDictionary<NSString *, id> *)userAttributes {
382399
}
383400

384401
- (MPKitExecStatus *)setConsentState:(nullable MPConsentState *)state {
385-
[self updateConsent];
402+
[self updateConsent: state];
386403

387404
return [self execStatus:MPKitReturnCodeSuccess];
388405
}
389406

390-
- (void)updateConsent {
407+
- (void)updateConsent:(MPConsentState *)consentState {
391408
NSArray<NSDictionary *> *mappings = [self mappingForKey: @"consentMappingSDK"];
392409
NSDictionary<NSString *, NSString *> *mappingsConfig;
393410
if (mappings != nil) {
394411
mappingsConfig = [self convertToKeyValuePairs: mappings];
395412
}
396413

397414

398-
MParticleUser *currentUser = [[[MParticle sharedInstance] identity] currentUser];
399-
NSDictionary<NSString *, MPGDPRConsent *> *gdprConsents = currentUser.consentState.gdprConsentState;
415+
416+
NSDictionary<NSString *, MPGDPRConsent *> *gdprConsents = consentState.gdprConsentState;
400417

401418
NSNumber *adStorage = [self resolvedConsentForMappingKey:kMPFIRGA4AdStorageKey
402419
defaultKey:kMPFIRGA4DefaultAdStorageKey
@@ -684,16 +701,11 @@ - (NSNumber * _Nullable)resolvedConsentForMappingKey:(NSString *)mappingKey
684701

685702
// Fallback to configuration defaults
686703
NSString *value = self->_configuration[defaultKey];
687-
if ([value isEqualToString:@"Granted"]) {
688-
return @(YES);
689-
} else if ([value isEqualToString:@"Denied"]) {
690-
return @(NO);
691-
}
692-
return nil;
704+
return [value isGranted];
693705
}
694706

695707
- (NSArray<NSDictionary *>*)mappingForKey:(NSString*)key {
696-
NSString *mappingJson = _configuration[@"consentMappingSDK"];
708+
NSString *mappingJson = _configuration[key];
697709
if (![mappingJson isKindOfClass:[NSString class]]) {
698710
return nil;
699711
}

mParticle-Google-Analytics-Firebase-GA4Tests/Info.plist

Lines changed: 0 additions & 22 deletions
This file was deleted.
File renamed without changes.

mParticle-Google-Analytics-Firebase-GA4Tests/MPKitFirebaseGA4AnalyticsTests.m renamed to mParticle-Google-Analytics-Firebase-GA4Tests/Objc/MPKitFirebaseGA4AnalyticsTests.m

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,22 @@ @interface mParticle_Firebase_AnalyticsTests : XCTestCase
2222
@implementation mParticle_Firebase_AnalyticsTests
2323

2424
- (void)setUp {
25-
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
26-
NSString *filePath = [bundlePath stringByAppendingPathComponent:@"GoogleService-Info.plist"];
25+
[super setUp];
26+
27+
// 1. Start with the test bundle
28+
NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
29+
30+
// 2. Locate the auto-generated resource bundle for this test target
31+
NSURL *resourceBundleURL = [testBundle URLForResource:@"mParticle-Google-Analytics-Firebase-GA4_mParticle-Google-Analytics-Firebase-GA4-Objc-Tests"
32+
withExtension:@"bundle"];
33+
NSBundle *resourceBundle = [NSBundle bundleWithURL:resourceBundleURL];
34+
NSAssert(resourceBundle != nil, @"Resource bundle not found");
35+
36+
// 3. Fetch the plist inside that resource bundle
37+
NSString *filePath = [resourceBundle pathForResource:@"GoogleService-Info" ofType:@"plist"];
38+
NSAssert(filePath != nil, @"GoogleService-Info.plist not found in resource bundle");
39+
40+
// 4. Configure Firebase
2741
FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath];
2842
[FIRApp configureWithOptions:options];
2943
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//
2+
// MPKitFirebaseGA4SwiftTests.swift
3+
// mParticle-Google-Analytics-Firebase-GA4
4+
//
5+
// Created by Nick Dimitrakas on 9/12/25.
6+
//
7+
8+
import XCTest
9+
@testable import mParticle_Google_Analytics_Firebase_GA4
10+
11+
final class MPKitFirebaseGA4AnalyticsTests: XCTestCase {
12+
13+
var kit: MPKitFirebaseGA4Analytics!
14+
15+
// MARK: - Lifecycle
16+
17+
override func setUpWithError() throws {
18+
try super.setUpWithError()
19+
kit = MPKitFirebaseGA4Analytics()
20+
kit.configuration = [:]
21+
}
22+
23+
override func tearDownWithError() throws {
24+
kit = nil
25+
try super.tearDownWithError()
26+
}
27+
28+
// MARK: - convertToKeyValuePairs
29+
30+
func test_convertToKeyValuePairs_createsLowercasedMapping() {
31+
let mappings: [[String: String]] = [
32+
["value": "ad_storage", "map": "Advertising"],
33+
["value": "analytics_storage", "map": "Analytics"]
34+
]
35+
36+
let result = kit.convert(toKeyValuePairs: mappings)
37+
XCTAssertEqual(result["ad_storage"] as! String, "advertising")
38+
XCTAssertEqual(result["analytics_storage"] as! String, "analytics")
39+
}
40+
41+
// MARK: - mappingForKey
42+
43+
func test_mappingForKey_withValidJSON_returnsArray() {
44+
let jsonString = """
45+
[
46+
{ "value": "ad_storage", "map": "Advertising" },
47+
{ "value": "analytics_storage", "map": "Analytics" }
48+
]
49+
"""
50+
kit.configuration["consentMappingSDK"] = jsonString
51+
52+
let result = kit.mapping(forKey: "consentMappingSDK")
53+
XCTAssertNotNil(result)
54+
XCTAssertEqual(result!.count, 2)
55+
}
56+
57+
func test_mappingForKey_withInvalidJSON_returnsNil() {
58+
kit.configuration["consentMappingSDK"] = "{ not valid json }"
59+
let result = kit.mapping(forKey: "consentMappingSDK")
60+
XCTAssertNil(result)
61+
}
62+
63+
// MARK: - resolvedConsentForMappingKey
64+
65+
func test_resolvedConsentForMappingKey_withGDPRMapping_returnsTrue() {
66+
let consent = MPGDPRConsent()
67+
consent.consented = true
68+
let gdprConsents = ["advertising": consent]
69+
70+
let mapping = ["ad_storage": "advertising"]
71+
72+
let result = kit.resolvedConsent(
73+
forMappingKey: "ad_storage",
74+
defaultKey: "defaultAdStorageConsentSDK",
75+
gdprConsents: gdprConsents,
76+
mapping: mapping
77+
)
78+
XCTAssertEqual(result, true)
79+
}
80+
81+
func test_resolvedConsentForMappingKey_withGDPRMapping_returnsFalse() {
82+
let consent = MPGDPRConsent()
83+
consent.consented = false
84+
let gdprConsents = ["advertising": consent]
85+
86+
let mapping = ["ad_storage": "advertising"]
87+
88+
let result = kit.resolvedConsent(
89+
forMappingKey: "ad_storage",
90+
defaultKey: "defaultAdStorageConsentSDK",
91+
gdprConsents: gdprConsents,
92+
mapping: mapping
93+
)
94+
XCTAssertEqual(result, false)
95+
}
96+
97+
func test_resolvedConsentForMappingKey_withDefaultValue_returnsFalse() {
98+
kit.configuration["defaultAdStorageConsentSDK"] = "Denied"
99+
100+
let result = kit.resolvedConsent(
101+
forMappingKey: "ad_storage",
102+
defaultKey: "defaultAdStorageConsentSDK",
103+
gdprConsents: [:],
104+
mapping: [:]
105+
)
106+
XCTAssertEqual(result, false)
107+
}
108+
109+
func test_resolvedConsentForMappingKey_withNoMappingOrDefault_returnsNil() {
110+
let result = kit.resolvedConsent(
111+
forMappingKey: "ad_storage",
112+
defaultKey: "defaultAdStorageConsentSDK",
113+
gdprConsents: [:],
114+
mapping: [:]
115+
)
116+
XCTAssertNil(result)
117+
}
118+
}

0 commit comments

Comments
 (0)