Skip to content

Commit be4e367

Browse files
authored
test: Add test target (#28)
1 parent 99a132a commit be4e367

File tree

8 files changed

+287
-12
lines changed

8 files changed

+287
-12
lines changed

Package.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,10 @@ let package = Package(
5858
exclude: ["Dummy.swift", "Info.plist"],
5959
resources: [.process("PrivacyInfo.xcprivacy")],
6060
publicHeadersPath: "."),
61+
.testTarget(
62+
name: "mParticle-AdobeTests",
63+
dependencies: ["mParticle-Adobe"],
64+
path: "mParticle-AdobeTests"
65+
),
6166
]
6267
)

mParticle-Adobe-Media/MPIAdobe.m

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,13 @@ @interface MPIAdobe ()
8181

8282
@implementation MPIAdobe
8383

84-
- (void)sendRequestWithMarketingCloudId:(NSString *)marketingCloudId advertiserId:(NSString *)advertiserId pushToken:(NSString *)pushToken organizationId:(NSString *)organizationId userIdentities:(NSDictionary<NSNumber *, NSString *> *)userIdentities audienceManagerServer:(NSString *)audienceManagerServer completion:(void (^)(NSString *marketingCloudId, NSString *blob, NSString *locationHint, NSError *))completion {
84+
- (void)sendRequestWithMarketingCloudId:(NSString *)marketingCloudId
85+
advertiserId:(NSString *)advertiserId
86+
pushToken:(NSString *)pushToken
87+
organizationId:(NSString *)organizationId
88+
userIdentities:(NSDictionary<NSNumber *, NSString *> *)userIdentities
89+
audienceManagerServer:(NSString *)audienceManagerServer
90+
completion:(void (^)(NSString *marketingCloudId, NSString *blob, NSString *locationHint, NSError *))completion {
8591

8692
if (audienceManagerServer != nil && audienceManagerServer.length > 0) {
8793
host = audienceManagerServer;
@@ -147,8 +153,6 @@ - (void)sendRequestWithMarketingCloudId:(NSString *)marketingCloudId advertiserI
147153
__weak MPIAdobe *weakSelf = self;
148154

149155
[[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
150-
151-
152156
void (^callbackWithCode)(MPIAdobeErrorCode code, NSString *message, NSError *error) = ^void(MPIAdobeErrorCode code, NSString *message, NSError *error) {
153157
MPIAdobeError *adobeError = [[MPIAdobeError alloc] initWithCode:code message:message error:error];
154158
NSError *compositeError = [NSError errorWithDomain:errorDomain code:adobeError.code userInfo:@{MPIAdobeErrorKey:adobeError}];

mParticle-Adobe/MPIAdobe.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
#import <Foundation/Foundation.h>
22

3+
@protocol SessionProtocol
4+
5+
- (NSURLSessionDataTask * _Nonnull)dataTaskWithRequest:(NSURLRequest * _Nonnull)request
6+
completionHandler:(void (NS_SWIFT_SENDABLE ^_Nonnull)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
7+
8+
@end
9+
310
@interface MPIAdobe : NSObject
411

12+
- (instancetype)initWithSession:(id<SessionProtocol>) session;
13+
514
- (void)sendRequestWithMarketingCloudId:(NSString *)marketingCloudId advertiserId:(NSString *)advertiserId pushToken:(NSString *)pushToken organizationId:(NSString *)organizationId userIdentities:(NSDictionary<NSNumber *, NSString *> *)userIdentities audienceManagerServer:(NSString *)audienceManagerServer completion:(void (^)(NSString *marketingCloudId, NSString *locationHint, NSString *blob, NSError *error))completion;
615

716
- (NSString *)marketingCloudIdFromUserDefaults;

mParticle-Adobe/MPIAdobe.m

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
static NSString *const marketingCloudIdUserDefaultsKey = @"ADBMOBILE_PERSISTED_MID";
4343

44+
4445
@interface MPIAdobeError ()
4546

4647
- (id)initWithCode:(MPIAdobeErrorCode)code message:(NSString *)message error:(NSError *)error;
@@ -79,7 +80,23 @@ @interface MPIAdobe ()
7980

8081
@implementation MPIAdobe
8182

82-
- (void)sendRequestWithMarketingCloudId:(NSString *)marketingCloudId advertiserId:(NSString *)advertiserId pushToken:(NSString *)pushToken organizationId:(NSString *)organizationId userIdentities:(NSDictionary<NSNumber *, NSString *> *)userIdentities audienceManagerServer:(NSString *)audienceManagerServer completion:(void (^)(NSString *marketingCloudId, NSString *locationHint, NSString *blob, NSError *))completion {
83+
id<SessionProtocol> _session;
84+
85+
- (instancetype)initWithSession:(id<SessionProtocol>) session {
86+
self = [super init];
87+
if (self != nil) {
88+
_session = session;
89+
}
90+
return self;
91+
}
92+
93+
- (void)sendRequestWithMarketingCloudId:(NSString *)marketingCloudId
94+
advertiserId:(NSString *)advertiserId
95+
pushToken:(NSString *)pushToken
96+
organizationId:(NSString *)organizationId
97+
userIdentities:(NSDictionary<NSNumber *, NSString *> *)userIdentities
98+
audienceManagerServer:(NSString *)audienceManagerServer
99+
completion:(void (^)(NSString *marketingCloudId, NSString *locationHint, NSString *blob, NSError *))completion {
83100

84101
if (audienceManagerServer != nil && audienceManagerServer.length > 0) {
85102
host = audienceManagerServer;
@@ -139,13 +156,14 @@ - (void)sendRequestWithMarketingCloudId:(NSString *)marketingCloudId advertiserI
139156
NSURL *url = components.URL;
140157

141158
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
142-
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
143-
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
144159

145160
__weak MPIAdobe *weakSelf = self;
146161

147-
[[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
148-
162+
[[_session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
163+
__strong typeof(self) strongSelf = weakSelf;
164+
if (!strongSelf) {
165+
return;
166+
}
149167

150168
void (^callbackWithCode)(MPIAdobeErrorCode code, NSString *message, NSError *error) = ^void(MPIAdobeErrorCode code, NSString *message, NSError *error) {
151169
MPIAdobeError *adobeError = [[MPIAdobeError alloc] initWithCode:code message:message error:error];
@@ -166,8 +184,13 @@ - (void)sendRequestWithMarketingCloudId:(NSString *)marketingCloudId advertiserI
166184

167185
NSDictionary *errorDictionary = dictionary[errorResponseKey];
168186
if (errorDictionary) {
169-
NSError *error = [NSError errorWithDomain:serverErrorDomain code:0 userInfo:errorDictionary];
170-
return callbackWithCode(MPIAdobeErrorCodeServerError, @"Server returned an error", error);
187+
if ([errorDictionary isKindOfClass:[NSDictionary class]]) {
188+
NSError *error = [NSError errorWithDomain:serverErrorDomain code:0 userInfo:errorDictionary];
189+
return callbackWithCode(MPIAdobeErrorCodeServerError, @"Server returned an error", error);
190+
} else {
191+
NSError *error = [NSError errorWithDomain:serverErrorDomain code:0 userInfo:@{}];
192+
return callbackWithCode(MPIAdobeErrorCodeServerError, @"Server returned an error", error);
193+
}
171194
}
172195

173196
NSString *marketingCloudId = [dictionary[marketingCloudIdKey] isKindOfClass:[NSString class]] ? dictionary[marketingCloudIdKey] : nil;

mParticle-Adobe/MPKitAdobe.m

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ @interface MPKitAdobe ()
2626

2727
@end
2828

29+
@interface NSURLSession (SessionProtocol) <SessionProtocol>
30+
@end
31+
2932
@implementation MPKitAdobe
3033

3134
static NSString *_midOverride = nil;
@@ -70,7 +73,9 @@ - (MPKitExecStatus *)didFinishLaunchingWithConfiguration:(NSDictionary *)configu
7073

7174
_configuration = configuration;
7275
_started = YES;
73-
_adobe = [[MPIAdobe alloc] init];
76+
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
77+
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
78+
_adobe = [[MPIAdobe alloc] initWithSession: session];
7479

7580
[[NSNotificationCenter defaultCenter] addObserver:self
7681
selector:@selector(didEnterBackground:)
@@ -155,7 +160,13 @@ - (void)sendNetworkRequest {
155160
NSString *pushToken = [self pushToken];
156161
FilteredMParticleUser *user = [self currentUser];
157162
NSDictionary *userIdentities = user.userIdentities;
158-
[_adobe sendRequestWithMarketingCloudId:marketingCloudId advertiserId:advertiserId pushToken:pushToken organizationId:_organizationId userIdentities:userIdentities audienceManagerServer:_audienceManagerServer completion:^(NSString *marketingCloudId, NSString *locationHint, NSString *blob, NSError *error) {
163+
[_adobe sendRequestWithMarketingCloudId:marketingCloudId
164+
advertiserId:advertiserId
165+
pushToken:pushToken
166+
organizationId:_organizationId
167+
userIdentities:userIdentities
168+
audienceManagerServer:_audienceManagerServer
169+
completion:^(NSString *marketingCloudId, NSString *locationHint, NSString *blob, NSError *error) {
159170
if (error) {
160171
NSLog(@"mParticle -> Adobe kit request failed with error: %@", error);
161172
return;

mParticle-Adobe/mParticle_Adobe.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@ FOUNDATION_EXPORT const unsigned char mParticle_AdobeVersionString[];
1111
#if defined(__has_include) && __has_include(<mParticle_Adobe/MPKitAdobe.h>)
1212
#import <mParticle_Adobe/MPKitAdobe.h>
1313
#else
14+
1415
#import "MPKitAdobe.h"
16+
#import "MPIAdobe.h"
17+
1518
#endif
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
//
2+
// MPKitAdobeTests.swift
3+
// mParticle-Adobe
4+
//
5+
// Created by Denis Chilik on 10/9/25.
6+
//
7+
@testable import mParticle_Adobe
8+
import Foundation
9+
import XCTest
10+
11+
import Foundation
12+
13+
extension URLSession: @retroactive SessionProtocol {
14+
}
15+
16+
final class MPAdobeTests: XCTestCase {
17+
var session: SessionProtocolMock!
18+
19+
override func setUp() {
20+
super.setUp()
21+
22+
session = SessionProtocolMock()
23+
}
24+
25+
func testSendRequestEncodeAllParametersIntoURL() {
26+
let sut = MPIAdobe(session: session)!
27+
sut.sendRequest(
28+
withMarketingCloudId: "marketingCloudId",
29+
advertiserId: "advertiserId",
30+
pushToken: "pushToken",
31+
organizationId: "organizationId",
32+
userIdentities: [
33+
NSNumber(value: MPUserIdentity.other.rawValue) : "1",
34+
NSNumber(value: MPUserIdentity.customerId.rawValue) : "2",
35+
NSNumber(value: MPUserIdentity.facebook.rawValue) : "3",
36+
NSNumber(value: MPUserIdentity.twitter.rawValue) : "4",
37+
NSNumber(value: MPUserIdentity.google.rawValue) : "5",
38+
NSNumber(value: MPUserIdentity.microsoft.rawValue) : "6",
39+
NSNumber(value: MPUserIdentity.yahoo.rawValue) : "7",
40+
NSNumber(value: MPUserIdentity.email.rawValue) : "8",
41+
NSNumber(value: MPUserIdentity.alias.rawValue) : "9",
42+
NSNumber(value: MPUserIdentity.facebookCustomAudienceId.rawValue) : "10",
43+
NSNumber(value: MPUserIdentity.other5.rawValue) : "11",
44+
],
45+
audienceManagerServer: "audienceManagerServer"
46+
) { _, _, _, _ in }
47+
48+
let expected = "https://audienceManagerServer/id?d_mid=marketingCloudId&d_cid=20915%2501advertiserId&d_cid=20920%2501pushToken&d_cid_ic=google%25015&d_cid_ic=facebook%25013&d_cid_ic=customerid%25012&d_cid_ic=twitter%25014&d_cid_ic=alias%25019&d_cid_ic=microsoft%25016&d_cid_ic=email%25018&d_cid_ic=yahoo%25017&d_cid_ic=other%25011&d_cid_ic=facebookcustomaudienceid%250110&d_orgid=organizationId&d_ptfm=ios&d_ver=2"
49+
50+
guard
51+
let actualURL = session.dataTaskRequestParam?.url,
52+
let expectedURL = URL(string: expected),
53+
let actualComponents = URLComponents(url: actualURL, resolvingAgainstBaseURL: false),
54+
let expectedComponents = URLComponents(url: expectedURL, resolvingAgainstBaseURL: false)
55+
else {
56+
XCTFail("URLs could not be parsed")
57+
return
58+
}
59+
60+
XCTAssertEqual(actualComponents.scheme, expectedComponents.scheme)
61+
XCTAssertEqual(actualComponents.host, expectedComponents.host)
62+
XCTAssertEqual(actualComponents.path, expectedComponents.path)
63+
64+
let actualItems = Set(actualComponents.queryItems ?? [])
65+
let expectedItems = Set(expectedComponents.queryItems ?? [])
66+
67+
XCTAssertEqual(actualItems, expectedItems)
68+
}
69+
70+
func testCompletionCallback_success() {
71+
let sut = MPIAdobe(session: session)!
72+
sut.sendRequest(
73+
withMarketingCloudId: "",
74+
advertiserId: "",
75+
pushToken: "",
76+
organizationId: "",
77+
userIdentities: [:],
78+
audienceManagerServer: ""
79+
) { marketingCloudId, locationHint, blob, error in
80+
XCTAssertNil(error)
81+
XCTAssertEqual(marketingCloudId, "mock_mid")
82+
XCTAssertEqual(locationHint, "mock_region")
83+
XCTAssertEqual(blob, "mock_blob")
84+
}
85+
86+
let json: [String: Any] = [
87+
"d_mid": "mock_mid",
88+
"d_blob": "mock_blob",
89+
"dcs_region": "mock_region"
90+
]
91+
92+
let data = try! JSONSerialization.data(withJSONObject: json, options: [])
93+
94+
session.dataTaskCompletionHandlerParam?(data, URLResponse(), nil)
95+
}
96+
97+
func testCompletionCallback_success_empty_json() {
98+
let sut = MPIAdobe(session: session)!
99+
sut.sendRequest(
100+
withMarketingCloudId: "",
101+
advertiserId: "",
102+
pushToken: "",
103+
organizationId: "",
104+
userIdentities: [:],
105+
audienceManagerServer: ""
106+
) { marketingCloudId, locationHint, blob, error in
107+
XCTAssertNil(error)
108+
XCTAssertNil(marketingCloudId)
109+
XCTAssertNil(locationHint)
110+
XCTAssertNil(blob)
111+
}
112+
113+
let json: [String: Any] = [:]
114+
115+
let data = try! JSONSerialization.data(withJSONObject: json, options: [])
116+
117+
session.dataTaskCompletionHandlerParam?(data, URLResponse(), nil)
118+
}
119+
120+
func testCompletionCallback_success_parametersNotStrings() {
121+
let sut = MPIAdobe(session: session)!
122+
sut.sendRequest(
123+
withMarketingCloudId: "",
124+
advertiserId: "",
125+
pushToken: "",
126+
organizationId: "",
127+
userIdentities: [:],
128+
audienceManagerServer: ""
129+
) { marketingCloudId, locationHint, blob, error in
130+
XCTAssertNil(error)
131+
XCTAssertNil(marketingCloudId)
132+
XCTAssertNil(locationHint)
133+
XCTAssertNil(blob)
134+
}
135+
136+
let json: [String: Any] = [
137+
"d_mid": 1,
138+
"d_blob": 2,
139+
"dcs_region": 3
140+
]
141+
142+
let data = try! JSONSerialization.data(withJSONObject: json, options: [])
143+
session.dataTaskCompletionHandlerParam?(data, URLResponse(), nil)
144+
}
145+
146+
func testCompletionCallback_success_errorFromBackend() {
147+
let sut = MPIAdobe(session: session)!
148+
sut.sendRequest(
149+
withMarketingCloudId: "",
150+
advertiserId: "",
151+
pushToken: "",
152+
organizationId: "",
153+
userIdentities: [:],
154+
audienceManagerServer: ""
155+
) { marketingCloudId, locationHint, blob, error in
156+
XCTAssertNil(marketingCloudId)
157+
XCTAssertNil(locationHint)
158+
XCTAssertNil(blob)
159+
XCTAssertNotNil(error)
160+
}
161+
162+
let json: [String: Any] = [
163+
"error_msg": [
164+
"some_key": "Invalid request parameters"
165+
]
166+
]
167+
168+
let data = try! JSONSerialization.data(withJSONObject: json, options: [])
169+
session.dataTaskCompletionHandlerParam?(data, URLResponse(), nil)
170+
}
171+
172+
func testCompletionCallback_success_errorErrorMsgContains_shouldNotCrash() {
173+
let sut = MPIAdobe(session: session)!
174+
sut.sendRequest(
175+
withMarketingCloudId: "",
176+
advertiserId: "",
177+
pushToken: "",
178+
organizationId: "",
179+
userIdentities: [:],
180+
audienceManagerServer: ""
181+
) { marketingCloudId, locationHint, blob, error in
182+
XCTAssertNil(marketingCloudId)
183+
XCTAssertNil(locationHint)
184+
XCTAssertNil(blob)
185+
XCTAssertNotNil(error)
186+
}
187+
188+
let json: [String: Any] = [
189+
"error_msg": "Invalid request parameters"
190+
]
191+
192+
let data = try! JSONSerialization.data(withJSONObject: json, options: [])
193+
session.dataTaskCompletionHandlerParam?(data, URLResponse(), nil)
194+
}
195+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// SessionProtocolMock.swift
3+
// mParticle-Adobe
4+
//
5+
// Created by Denis Chilik on 10/14/25.
6+
//
7+
8+
@testable import mParticle_Adobe
9+
10+
class SessionProtocolMock: SessionProtocol {
11+
var dataTaskCalled = false
12+
var dataTaskRequestParam: URLRequest?
13+
var dataTaskCompletionHandlerParam: ((Data?, URLResponse?, (any Error)?) -> Void)?
14+
15+
func dataTask(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTask {
16+
dataTaskCalled = true
17+
dataTaskRequestParam = request
18+
dataTaskCompletionHandlerParam = completionHandler
19+
20+
let config = URLSessionConfiguration.default
21+
let session = URLSession(configuration: config)
22+
23+
return session.dataTask(with: URLRequest(url: URL(string: "https://localhost")!))
24+
}
25+
}

0 commit comments

Comments
 (0)