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