Skip to content

Commit

Permalink
chore: add tests for batched event sending
Browse files Browse the repository at this point in the history
  • Loading branch information
kaushalkapasi committed Apr 10, 2024
1 parent cf953fb commit ba86750
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 6 deletions.
1 change: 1 addition & 0 deletions DevCycle/Networking/DevCycleService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class DevCycleService: DevCycleServiceProtocol {
}

func publishEvents(events: [DevCycleEvent], user: DevCycleUser, completion: @escaping PublishEventsCompletionHandler) {
print("in real publishEvents")
var eventsRequest = createEventsRequest()
let userEncoder = JSONEncoder()
userEncoder.dateEncodingStrategy = .iso8601
Expand Down
1 change: 1 addition & 0 deletions DevCycleTests/Mocks/URLSessionMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class URLSessionMock: URLSession {
let data = self.data
let error = self.error
return URLSessionDataTaskMock {
print("in completion handler for URL Session Mock")
completionHandler(data, nil, error)
}
}
Expand Down
7 changes: 5 additions & 2 deletions DevCycleTests/Models/DevCycleClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,6 @@ class DevCycleClientTest: XCTestCase {

let variableValue = client.variableValue(key: "some_non_existent_variable", defaultValue: false)
XCTAssertFalse(variableValue)
client.close(callback: nil)
}

func testVariableStringDefaultValue() {
Expand Down Expand Up @@ -318,12 +317,13 @@ class DevCycleClientTest: XCTestCase {
let nsDicDefault: NSDictionary = ["key":"val"]
let variable2 = client.variable(key: "some_non_existent_variable", defaultValue: nsDicDefault)
XCTAssertEqual(variable2.defaultValue, nsDicDefault)
XCTAssertEqual(variable2.type, DVCVariableTypes.JSON)
XCTAssertEqual(variable2.type, DVCVariableTypes.JSON)
}

func testVariableMethodReturnsCorrectVariableForKey() {
let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").build(onInitialized: nil)
client.initialize(callback: nil)
client.setup(service: self.service)
client.config?.userConfig = self.userConfig

let boolVar = client.variable(key: "bool-var", defaultValue: false)
Expand Down Expand Up @@ -354,6 +354,7 @@ class DevCycleClientTest: XCTestCase {
func testVariableMethodReturnSameInstanceOfVariable() {
let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").build(onInitialized: nil)
client.initialize(callback: nil)
client.setup(service: self.service)
client.config?.userConfig = self.userConfig

let boolVar = client.variable(key: "bool-var", defaultValue: false)
Expand All @@ -374,6 +375,7 @@ class DevCycleClientTest: XCTestCase {
func testVariableMethodReturnsDifferentVariableForANewDefaultValue() {
let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").build(onInitialized: nil)
client.initialize(callback: nil)
client.setup(service: self.service)
client.config?.userConfig = self.userConfig

var stringVar = client.variable(key: "string-var", defaultValue: "default value")
Expand Down Expand Up @@ -583,6 +585,7 @@ extension DevCycleClientTest {
}

func publishEvents(events: [DevCycleEvent], user: DevCycleUser, completion: @escaping PublishEventsCompletionHandler) {
print("in mocked DevCycle Client Tests publishEvents")
self.publishCallCount += 1
self.eventPublishCount += events.count
XCTAssert(true)
Expand Down
3 changes: 1 addition & 2 deletions DevCycleTests/Models/EventQueueTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand All @@ -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))
}
Expand Down
140 changes: 138 additions & 2 deletions DevCycleTests/Networking/DevCycleServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,50 @@ class DevCycleServiceTests: XCTestCase {
}

func testProcessConfigReturnsNilIfBrokenJson() throws {
let service = getService()
let data = "{\"config\":\"key}".data(using: .utf8)
let config = processConfig(data)
XCTAssertNil(config)
}

func testFlushingEvents() {
let service = MockDevCycleService()
let eventQueue = EventQueue()
let user = try! DevCycleUser.builder().userId("user1").build()
let expectation = XCTestExpectation(description: "Events are flushed in a single batch")

// Generate 205 custom events and add them to the queue
for i in 0..<10 {
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: 1.0)
XCTAssertEqual(service.makeRequestCallCount, 1, "makeRequest should have been called 1 time")
}

func testFlushingEventsInBatches() {
let service = MockDevCycleService()
let eventQueue = EventQueue()
let user = try! DevCycleUser.builder().userId("user1").build()
let expectation = XCTestExpectation(description: "Events are serially queued and flushed in multiple batches")

// 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 {
Expand Down Expand Up @@ -111,6 +150,104 @@ 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) {
print("in mocked DevCycleServiceTests publishEvents")
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..<endIndex])

let requestBody: [String: Any] = [
"events": batchEvents,
"user": userBody
]

let jsonBody = try? JSONSerialization.data(withJSONObject: requestBody, options: .prettyPrinted)
eventsRequest.httpBody = jsonBody

self.makeRequest(request: eventsRequest) { data, response, error in
// Continue with next batch
startIndex = endIndex
endIndex = min(endIndex + self.testMaxBatchSize, totalEventsCount)

if 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)
Expand All @@ -122,7 +259,6 @@ extension DevCycleServiceTests {
.userId("my_user")
.build()
}

}


0 comments on commit ba86750

Please sign in to comment.