Skip to content

Commit a3fec13

Browse files
Merge pull request #36 from mparticle-integrations/test/add-unit-tests-for-consent-mapping-logic
test: Add unit tests for consent mapping logic
2 parents f6515e8 + c2a201b commit a3fec13

File tree

7 files changed

+180
-37
lines changed

7 files changed

+180
-37
lines changed

Package.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,18 @@ let package = Package(
2828
exclude: ["Info.plist", "dummy.swift"],
2929
resources: [.process("PrivacyInfo.xcprivacy")],
3030
publicHeadersPath: "."),
31+
32+
.testTarget(
33+
name: "mParticle-Google-Analytics-Firebase-Swift-Tests",
34+
dependencies: ["mParticle-Google-Analytics-Firebase"],
35+
path: "mParticle-Google-Analytics-FirebaseTests/Swift"
36+
),
37+
38+
.testTarget(
39+
name: "mParticle-Google-Analytics-Firebase-Objc-Tests",
40+
dependencies: ["mParticle-Google-Analytics-Firebase"],
41+
path: "mParticle-Google-Analytics-FirebaseTests/Objc",
42+
resources: [.process("GoogleService-Info.plist")]
43+
)
3144
]
3245
)

mParticle-Google-Analytics-Firebase/MPKitFirebaseAnalytics.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@
1717
@property (nonatomic, unsafe_unretained, readonly) BOOL started;
1818
@property (nonatomic, strong, nullable) MPKitAPI *kitApi;
1919

20+
- (nullable NSNumber *)resolvedConsentForMappingKey:(NSString * _Nonnull)mappingKey
21+
defaultKey:(NSString * _Nonnull)defaultKey
22+
gdprConsents:(NSDictionary<NSString *, MPGDPRConsent *> * _Nonnull)gdprConsents
23+
mapping:(NSDictionary<NSString *, NSString *> * _Nullable)mapping;
24+
25+
- (nullable NSArray<NSDictionary *>*)mappingForKey:(NSString* _Nonnull)key;
26+
27+
- (nonnull NSDictionary*)convertToKeyValuePairs: (NSArray<NSDictionary *> * _Nonnull)mappings;
28+
2029
@end
2130

2231
static NSString * _Nonnull const kMPFIRGoogleAppIDKey = @"firebaseAppId";

mParticle-Google-Analytics-Firebase/MPKitFirebaseAnalytics.m

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@
1313
#endif
1414
#endif
1515

16+
@implementation NSString(PRIVATE)
17+
18+
- (NSNumber*)isGranted {
19+
if ([self isEqualToString:@"Granted"]) {
20+
return @(YES);
21+
} else if ([self isEqualToString:@"Denied"]) {
22+
return @(NO);
23+
}
24+
return nil;
25+
}
26+
27+
@end
28+
1629
@interface MPKitFirebaseAnalytics () <MPKitProtocol> {
1730
BOOL forwardRequestsServerSide;
1831
}
@@ -77,6 +90,11 @@ - (MPKitExecStatus *)execStatus:(MPKitReturnCode)returnCode {
7790

7891
#pragma mark MPKitInstanceProtocol methods
7992
- (MPKitExecStatus *)didFinishLaunchingWithConfiguration:(NSDictionary *)configuration {
93+
MParticleUser *currentUser = [[[MParticle sharedInstance] identity] currentUser];
94+
return [self didFinishLaunchingWithConfiguration:configuration withConsentState:currentUser.consentState];
95+
}
96+
97+
- (MPKitExecStatus *)didFinishLaunchingWithConfiguration:(NSDictionary *)configuration withConsentState: (MPConsentState *)consentState {
8098
_configuration = configuration;
8199

82100
if ([FIRApp defaultApp] == nil) {
@@ -88,7 +106,7 @@ - (MPKitExecStatus *)didFinishLaunchingWithConfiguration:(NSDictionary *)configu
88106
[self updateInstanceIDIntegration];
89107
}
90108

91-
[self updateConsent];
109+
[self updateConsent: consentState];
92110

93111
_started = YES;
94112

@@ -356,21 +374,19 @@ - (void)logUserAttributes:(NSDictionary<NSString *, id> *)userAttributes {
356374
}
357375

358376
- (MPKitExecStatus *)setConsentState:(nullable MPConsentState *)state {
359-
[self updateConsent];
377+
[self updateConsent: state];
360378

361379
return [self execStatus:MPKitReturnCodeSuccess];
362380
}
363381

364-
- (void)updateConsent {
382+
- (void)updateConsent:(MPConsentState *)consentState {
365383
NSArray<NSDictionary *> *mappings = [self mappingForKey: @"consentMappingSDK"];
366384
NSDictionary<NSString *, NSString *> *mappingsConfig;
367385
if (mappings != nil) {
368386
mappingsConfig = [self convertToKeyValuePairs: mappings];
369387
}
370388

371-
372-
MParticleUser *currentUser = [[[MParticle sharedInstance] identity] currentUser];
373-
NSDictionary<NSString *, MPGDPRConsent *> *gdprConsents = currentUser.consentState.gdprConsentState;
389+
NSDictionary<NSString *, MPGDPRConsent *> *gdprConsents = consentState.gdprConsentState;
374390

375391
NSNumber *adStorage = [self resolvedConsentForMappingKey:kMPFIRGAAdStorageKey
376392
defaultKey:kMPFIRGA4DefaultAdStorageKey
@@ -650,16 +666,11 @@ - (NSNumber * _Nullable)resolvedConsentForMappingKey:(NSString *)mappingKey
650666

651667
// Fallback to configuration defaults
652668
NSString *value = self->_configuration[defaultKey];
653-
if ([value isEqualToString:@"Granted"]) {
654-
return @(YES);
655-
} else if ([value isEqualToString:@"Denied"]) {
656-
return @(NO);
657-
}
658-
return nil;
669+
return [value isGranted];
659670
}
660671

661672
- (NSArray<NSDictionary *>*)mappingForKey:(NSString*)key {
662-
NSString *mappingJson = _configuration[@"consentMappingSDK"];
673+
NSString *mappingJson = _configuration[key];
663674
if (![mappingJson isKindOfClass:[NSString class]]) {
664675
return nil;
665676
}

mParticle-Google-Analytics-FirebaseTests/Info.plist

Lines changed: 0 additions & 22 deletions
This file was deleted.

mParticle-Google-Analytics-FirebaseTests/GoogleService-Info.plist renamed to mParticle-Google-Analytics-FirebaseTests/Objc/GoogleService-Info.plist

File renamed without changes.

mParticle-Google-Analytics-FirebaseTests/MPKitFirebaseAnalyticsTests.m renamed to mParticle-Google-Analytics-FirebaseTests/Objc/MPKitFirebaseAnalyticsTests.m

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

2222
- (void)setUp {
23-
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
24-
NSString *filePath = [bundlePath stringByAppendingPathComponent:@"GoogleService-Info.plist"];
23+
[super setUp];
24+
25+
// 1. Start with the test bundle
26+
NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
27+
28+
// 2. Locate the auto-generated resource bundle for this test target
29+
NSURL *resourceBundleURL = [testBundle URLForResource:@"mParticle-Google-Analytics-Firebase_mParticle-Google-Analytics-Firebase-Objc-Tests"
30+
withExtension:@"bundle"];
31+
NSBundle *resourceBundle = [NSBundle bundleWithURL:resourceBundleURL];
32+
NSAssert(resourceBundle != nil, @"Resource bundle not found");
33+
34+
// 3. Fetch the plist inside that resource bundle
35+
NSString *filePath = [resourceBundle pathForResource:@"GoogleService-Info" ofType:@"plist"];
36+
NSAssert(filePath != nil, @"GoogleService-Info.plist not found in resource bundle");
37+
38+
// 4. Configure Firebase
2539
FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath];
2640
[FIRApp configureWithOptions:options];
2741
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//
2+
// File.swift
3+
// mParticle-Google-Analytics-Firebase
4+
//
5+
// Created by Nick Dimitrakas on 9/16/25.
6+
//
7+
8+
import XCTest
9+
@testable import mParticle_Google_Analytics_Firebase
10+
11+
final class MPKitFirebaseSwiftTests: XCTestCase {
12+
13+
var kit: MPKitFirebaseAnalytics!
14+
15+
// MARK: - Lifecycle
16+
17+
override func setUpWithError() throws {
18+
try super.setUpWithError()
19+
kit = MPKitFirebaseAnalytics()
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)