Skip to content

Commit 4aa3a04

Browse files
Add payload request assertion
1 parent 9c9b9b0 commit 4aa3a04

File tree

2 files changed

+53
-11
lines changed

2 files changed

+53
-11
lines changed

Sources/CMAB/CmabClient.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
2-
// Copyright 2022, Optimizely, Inc. and contributors
3-
//
2+
// Copyright 2025, Optimizely, Inc. and contributors
3+
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
66
// You may obtain a copy of the License at

Tests/OptimizelyTests-Common/CMABClientTests.swift

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2022, Optimizely, Inc. and contributors
2+
// Copyright 2025, Optimizely, Inc. and contributors
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -64,14 +64,16 @@ class DefaultCmabClientTests: XCTestCase {
6464
let expectation = self.expectation(description: "Completion called")
6565
client.fetchDecision(
6666
ruleId: "abc", userId: "user1",
67-
attributes: ["foo": "bar"], cmabUUID: "uuid"
67+
attributes: ["foo": "bar"],
68+
cmabUUID: "uuid"
6869
) { result in
6970
if case let .success(variationId) = result {
7071
XCTAssertEqual(variationId, "variation-123")
7172
XCTAssertEqual(self.mockSession.callCount, 1)
7273
} else {
7374
XCTFail("Expected success result")
7475
}
76+
self.verifyRequest(ruleId: "abc", userId: "user1", attributes: ["foo": "bar"], cmabUUID: "uuid")
7577
expectation.fulfill()
7678
}
7779
waitForExpectations(timeout: 1)
@@ -85,14 +87,16 @@ class DefaultCmabClientTests: XCTestCase {
8587
let expectation = self.expectation(description: "Completion called")
8688
client.fetchDecision(
8789
ruleId: "abc", userId: "user1",
88-
attributes: ["foo": "bar"], cmabUUID: "uuid"
90+
attributes: ["foo": "bar"],
91+
cmabUUID: "uuid"
8992
) { result in
9093
if case let .success(variationId) = result {
9194
XCTAssertEqual(variationId, "variation-retry")
9295
XCTAssertEqual(self.mockSession.callCount, 2)
9396
} else {
9497
XCTFail("Expected success after retry")
9598
}
99+
self.verifyRequest(ruleId: "abc", userId: "user1", attributes: ["foo": "bar"], cmabUUID: "uuid")
96100
expectation.fulfill()
97101
}
98102
waitForExpectations(timeout: 2)
@@ -106,14 +110,16 @@ class DefaultCmabClientTests: XCTestCase {
106110
let expectation = self.expectation(description: "Completion called")
107111
client.fetchDecision(
108112
ruleId: "abc", userId: "user1",
109-
attributes: ["foo": "bar"], cmabUUID: "uuid"
113+
attributes: ["foo": "bar"],
114+
cmabUUID: "uuid"
110115
) { result in
111116
if case let .success(variationId) = result {
112117
XCTAssertEqual(variationId, "success-third")
113118
XCTAssertEqual(self.mockSession.callCount, 3)
114119
} else {
115120
XCTFail("Expected success after two retries")
116121
}
122+
self.verifyRequest(ruleId: "abc", userId: "user1", attributes: ["foo": "bar"], cmabUUID: "uuid")
117123
expectation.fulfill()
118124
}
119125
waitForExpectations(timeout: 2)
@@ -126,14 +132,16 @@ class DefaultCmabClientTests: XCTestCase {
126132
let expectation = self.expectation(description: "Completion called")
127133
client.fetchDecision(
128134
ruleId: "abc", userId: "user1",
129-
attributes: ["foo": "bar"], cmabUUID: "uuid"
135+
attributes: ["foo": "bar"],
136+
cmabUUID: "uuid"
130137
) { result in
131138
if case let .failure(error) = result {
132139
XCTAssertTrue("\(error)".contains("Exhausted all retries"))
133140
XCTAssertEqual(self.mockSession.callCount, 3)
134141
} else {
135142
XCTFail("Expected failure after all retries")
136143
}
144+
self.verifyRequest(ruleId: "abc", userId: "user1", attributes: ["foo": "bar"], cmabUUID: "uuid")
137145
expectation.fulfill()
138146
}
139147
waitForExpectations(timeout: 2)
@@ -148,13 +156,15 @@ class DefaultCmabClientTests: XCTestCase {
148156
let expectation = self.expectation(description: "Completion called")
149157
client.fetchDecision(
150158
ruleId: "abc", userId: "user1",
151-
attributes: ["foo": "bar"], cmabUUID: "uuid"
159+
attributes: ["foo": "bar"],
160+
cmabUUID: "uuid"
152161
) { result in
153162
if case let .failure(error) = result {
154163
XCTAssertTrue("\(error)".contains("HTTP error code"))
155164
} else {
156165
XCTFail("Expected failure on HTTP error")
157166
}
167+
self.verifyRequest(ruleId: "abc", userId: "user1", attributes: ["foo": "bar"], cmabUUID: "uuid")
158168
expectation.fulfill()
159169
}
160170
waitForExpectations(timeout: 2)
@@ -169,14 +179,16 @@ class DefaultCmabClientTests: XCTestCase {
169179
let expectation = self.expectation(description: "Completion called")
170180
client.fetchDecision(
171181
ruleId: "abc", userId: "user1",
172-
attributes: ["foo": "bar"], cmabUUID: "uuid"
182+
attributes: ["foo": "bar"],
183+
cmabUUID: "uuid"
173184
) { result in
174185
if case let .failure(error) = result {
175186
XCTAssertTrue(error is CmabClientError)
176187
XCTAssertEqual(self.mockSession.callCount, 1)
177188
} else {
178189
XCTFail("Expected failure on invalid JSON")
179190
}
191+
self.verifyRequest(ruleId: "abc", userId: "user1", attributes: ["foo": "bar"], cmabUUID: "uuid")
180192
expectation.fulfill()
181193
}
182194
waitForExpectations(timeout: 2)
@@ -193,18 +205,47 @@ class DefaultCmabClientTests: XCTestCase {
193205
let expectation = self.expectation(description: "Completion called")
194206
client.fetchDecision(
195207
ruleId: "abc", userId: "user1",
196-
attributes: ["foo": "bar"], cmabUUID: "uuid"
208+
attributes: ["foo": "bar"],
209+
cmabUUID: "uuid-1234"
197210
) { result in
198211
if case let .failure(error) = result {
199212
XCTAssertEqual(error as? CmabClientError, .invalidResponse)
200213
XCTAssertEqual(self.mockSession.callCount, 1)
201214
} else {
202215
XCTFail("Expected failure on invalid response structure")
203216
}
217+
self.verifyRequest(ruleId: "abc", userId: "user1", attributes: ["foo": "bar"], cmabUUID: "uuid-1234")
204218
expectation.fulfill()
205219
}
206220
waitForExpectations(timeout: 2)
221+
207222
}
223+
224+
private func verifyRequest(ruleId: String, userId: String, attributes: [String: Any], cmabUUID: String) {
225+
// Assert request body
226+
guard let request = mockSession.lastRequest else {
227+
XCTFail("No request was sent")
228+
return
229+
}
230+
guard let body = request.httpBody else {
231+
XCTFail("No HTTP body in request")
232+
return
233+
}
234+
235+
let json = try! JSONSerialization.jsonObject(with: body, options: []) as! [String: Any]
236+
let instances = json["instances"] as? [[String: Any]]
237+
XCTAssertNotNil(instances)
238+
let instance = instances?.first
239+
XCTAssertEqual(instance?["visitorId"] as? String, userId)
240+
XCTAssertEqual(instance?["experimentId"] as? String, ruleId)
241+
XCTAssertEqual(instance?["cmabUUID"] as? String, cmabUUID)
242+
// You can add further assertions for the attributes, e.g.:
243+
let payloadAttributes = instance?["attributes"] as? [[String: Any]]
244+
XCTAssertEqual(payloadAttributes?.first?["id"] as? String, attributes.keys.first)
245+
XCTAssertEqual(payloadAttributes?.first?["value"] as? String, attributes.values.first as? String)
246+
XCTAssertEqual(payloadAttributes?.first?["type"] as? String, "custom_attribute")
247+
}
248+
208249
}
209250

210251
// MARK: - MockURLSession for ordered responses
@@ -221,12 +262,13 @@ extension DefaultCmabClientTests {
221262
typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void
222263
var responses: [(Data?, URLResponse?, Error?)] = []
223264
var callCount = 0
265+
var lastRequest: URLRequest?
224266

225267
override func dataTask(
226268
with request: URLRequest,
227269
completionHandler: @escaping CompletionHandler
228270
) -> URLSessionDataTask {
229-
271+
self.lastRequest = request
230272
let idx = callCount
231273
callCount += 1
232274
let tuple = idx < responses.count ? responses[idx] : (nil, nil, nil)

0 commit comments

Comments
 (0)