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 9, 2024
1 parent 353db69 commit 1580127
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 4 deletions.
7 changes: 5 additions & 2 deletions DevCycleTests/Models/EventQueueTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,11 @@ private class MockService: DevCycleServiceProtocol {

func publishEvents(events: [DevCycleEvent], user: DevCycleUser, completion: @escaping PublishEventsCompletionHandler) {

let url = URL(string: "devcycle.com/v1/events")!
let mockResponse = HTTPURLResponse(url: url, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: nil)

DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
completion((nil, nil, nil))
completion((nil, mockResponse, nil))
}
}

Expand All @@ -101,7 +104,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
127 changes: 125 additions & 2 deletions DevCycleTests/Networking/DevCycleServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -103,6 +122,111 @@ 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: "https://devcycle.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)

print(totalEventsCount)
print(self.testMaxBatchSize)

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)
Log.debug("Post Events Payload: \(String(data: jsonBody!, encoding: .utf8) ?? "")")
eventsRequest.httpBody = jsonBody

self.makeRequest(request: eventsRequest) { data, response, error in
if error != nil || data == nil {
return completion((data, response, error))
}
// 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 = "Mock Data".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 {
Log.debug("Skipping event, missing type: \(event)", tags: ["event"])
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 @@ -114,7 +238,6 @@ extension DevCycleServiceTests {
.userId("my_user")
.build()
}

}


6 changes: 6 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ platform :ios do
buildlog_path: "./fastlane/fastlane-buildlog",
scheme: "DevCycle",
workspace: "DevCycle.xcworkspace",
clean: true,
force_quit_simulator: true,
result_bundle: true
)
end
Expand All @@ -64,6 +66,8 @@ platform :tvos do
buildlog_path: "./fastlane/fastlane-buildlog",
scheme: "DevCycle",
workspace: "DevCycle.xcworkspace",
clean: true,
force_quit_simulator: true,
result_bundle: true
)
end
Expand All @@ -76,6 +80,8 @@ platform :watchos do
derived_data_path: "~/Library/Developer/Xcode/DerivedData",
buildlog_path: "./fastlane/fastlane-buildlog",
scheme: "DevCycle",
clean: true,
force_quit_simulator: true,
result_bundle: true
)
end
Expand Down

0 comments on commit 1580127

Please sign in to comment.