diff --git a/DevCycleTests/Models/DevCycleClientTests.swift b/DevCycleTests/Models/DevCycleClientTests.swift index 8d4708c..b69c149 100644 --- a/DevCycleTests/Models/DevCycleClientTests.swift +++ b/DevCycleTests/Models/DevCycleClientTests.swift @@ -241,7 +241,8 @@ class DevCycleClientTest: XCTestCase { } func testVariableMethodReturnsDefaultedVariableWhenKeyIsNotInConfig() { - let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").build(onInitialized: nil) + let options = DevCycleOptions.OptionsBuilder().disableAutomaticEventLogging(true).build() + let client = try! self.builder.user(self.user).options(options).sdkKey("my_sdk_key").build(onInitialized: nil) client.config?.userConfig = self.userConfig client.initialize(callback: nil) @@ -322,7 +323,8 @@ class DevCycleClientTest: XCTestCase { } func testVariableMethodReturnsCorrectVariableForKey() { - let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").build(onInitialized: nil) + let options = DevCycleOptions.OptionsBuilder().disableAutomaticEventLogging(true).build() + let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").options(options).build(onInitialized: nil) client.initialize(callback: nil) client.config?.userConfig = self.userConfig @@ -352,7 +354,8 @@ class DevCycleClientTest: XCTestCase { } func testVariableMethodReturnSameInstanceOfVariable() { - let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").build(onInitialized: nil) + let options = DevCycleOptions.OptionsBuilder().disableAutomaticEventLogging(true).build() + let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").options(options).build(onInitialized: nil) client.initialize(callback: nil) client.config?.userConfig = self.userConfig @@ -372,7 +375,8 @@ class DevCycleClientTest: XCTestCase { } func testVariableMethodReturnsDifferentVariableForANewDefaultValue() { - let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").build(onInitialized: nil) + let options = DevCycleOptions.OptionsBuilder().disableAutomaticEventLogging(true).build() + let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").options(options).build(onInitialized: nil) client.initialize(callback: nil) client.config?.userConfig = self.userConfig diff --git a/DevCycleTests/Models/EventQueueTests.swift b/DevCycleTests/Models/EventQueueTests.swift index 5aeaabe..f6beb1e 100644 --- a/DevCycleTests/Models/EventQueueTests.swift +++ b/DevCycleTests/Models/EventQueueTests.swift @@ -82,7 +82,6 @@ private class MockService: DevCycleServiceProtocol { func getConfig(user: DevCycleUser, enableEdgeDB: Bool, extraParams: RequestParams?, completion: @escaping ConfigCompletionHandler) {} func publishEvents(events: [DevCycleEvent], user: DevCycleUser, completion: @escaping PublishEventsCompletionHandler) { - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { completion((nil, nil, nil)) } @@ -101,7 +100,7 @@ class MockWithErrorCodeService: DevCycleServiceProtocol { func getConfig(user: DevCycleUser, enableEdgeDB: Bool, extraParams: RequestParams?, completion: @escaping ConfigCompletionHandler) {} func publishEvents(events: [DevCycleEvent], user: DevCycleUser, completion: @escaping PublishEventsCompletionHandler) { - let error = NSError(domain: "api.devcycle.com", code: self.errorCode) + let error = NSError(domain: "devcycle.com", code: self.errorCode) DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { completion((nil, nil, error)) } diff --git a/DevCycleTests/Networking/DevCycleServiceTests.swift b/DevCycleTests/Networking/DevCycleServiceTests.swift index e8ab575..70f28b5 100644 --- a/DevCycleTests/Networking/DevCycleServiceTests.swift +++ b/DevCycleTests/Networking/DevCycleServiceTests.swift @@ -64,11 +64,30 @@ class DevCycleServiceTests: XCTestCase { } func testProcessConfigReturnsNilIfBrokenJson() throws { - let service = getService() let data = "{\"config\":\"key}".data(using: .utf8) let config = processConfig(data) XCTAssertNil(config) } + + func testFlushingEventsInBatches() { + let service = MockDevCycleService() + let eventQueue = EventQueue() + let user = try! DevCycleUser.builder().userId("user1").build() + let expectation = XCTestExpectation(description: "Events are serially queued") + + // Generate 205 custom events and add them to the queue + for i in 0..<205 { + let event = try! DevCycleEvent.builder().type("event_\(i)").build() + eventQueue.queue(event) + } + eventQueue.flush(service: service, user: user, callback: nil) + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + XCTAssertEqual(eventQueue.events.count, 0) + expectation.fulfill() + } + wait(for: [expectation], timeout: 3.0) + XCTAssertEqual(service.makeRequestCallCount, 3, "makeRequest should have been called 3 times") + } } extension DevCycleServiceTests { @@ -103,6 +122,103 @@ extension DevCycleServiceTests { } } + class MockDevCycleService: DevCycleServiceProtocol { + func getConfig(user: DevCycle.DevCycleUser, enableEdgeDB: Bool, extraParams: DevCycle.RequestParams?, completion: @escaping DevCycle.ConfigCompletionHandler) { + // Empty Stub + } + + func saveEntity(user: DevCycle.DevCycleUser, completion: @escaping DevCycle.SaveEntityCompletionHandler) { + // Empty Stub + } + + var publishEventsCalled = false + var makeRequestCallCount = 0 + let testMaxBatchSize = 100 + + func publishEvents(events: [DevCycleEvent], user: DevCycleUser, completion: @escaping PublishEventsCompletionHandler) { + publishEventsCalled = true + + let url = URL(string: "http://test.com/v1/events")! + var eventsRequest = URLRequest(url: url) + let userEncoder = JSONEncoder() + userEncoder.dateEncodingStrategy = .iso8601 + guard let userId = user.userId, let userData = try? userEncoder.encode(user) else { + return completion((nil, nil, ClientError.MissingUser)) + } + + let eventPayload = self.generateEventPayload(events, userId, nil) + guard let userBody = try? JSONSerialization.jsonObject(with: userData, options: .fragmentsAllowed) else { + return completion((nil, nil, ClientError.InvalidUser)) + } + + let totalEventsCount = eventPayload.count + var startIndex = 0 + var endIndex = min(self.testMaxBatchSize, totalEventsCount) + + while startIndex < totalEventsCount { + let batchEvents = Array(eventPayload[startIndex..= totalEventsCount { + return completion((data, response, nil)) + } + } + } + } + + func makeRequest(request: URLRequest, completion: @escaping CompletionHandler) { + self.makeRequestCallCount += 1 + + // Mock implementation for makeRequest + let mockData = "Successfully flushed 100 events".data(using: .utf8) + let mockResponse = HTTPURLResponse(url: URL(string: "https://example.com")!, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: nil) + completion((mockData, mockResponse, nil)) + } + + private func generateEventPayload(_ events: [DevCycleEvent], _ userId: String, _ featureVariables: [String:String]?) -> [[String:Any]] { + var eventsJSON: [[String:Any]] = [] + let formatter = ISO8601DateFormatter() + + for event in events { + if event.type == nil { + continue + } + let eventDate: Date = event.clientDate ?? Date() + var eventToPost: [String: Any] = [ + "type": event.type!, + "clientDate": formatter.string(from: eventDate), + "user_id": userId, + "featureVars": featureVariables ?? [:] + ] + + if (event.target != nil) { eventToPost["target"] = event.target } + if (event.value != nil) { eventToPost["value"] = event.value } + if (event.metaData != nil) { eventToPost["metaData"] = event.metaData } + if (event.type != "variableDefaulted" && event.type != "variableEvaluated") { + eventToPost["customType"] = event.type + eventToPost["type"] = "customEvent" + } + + eventsJSON.append(eventToPost) + } + + return eventsJSON + } + } + + func getService(_ options: DevCycleOptions? = nil) -> DevCycleService { let user = getTestUser() let config = DVCConfig(sdkKey: "my_sdk_key", user: user) @@ -114,7 +230,6 @@ extension DevCycleServiceTests { .userId("my_user") .build() } - }