Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: handle status codes for retrying in uploader #95

Merged
merged 3 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,25 @@ public class RemoteConfidenceClient: ConfidenceClient {
sendTime: timeString,
sdk: Sdk(id: metadata.name, version: metadata.version)
)

do {
let result: HttpClientResult<PublishEventResponse> =
try await self.httpClient.post(path: ":publish", data: request)
switch result {
case .success(let successData):
guard successData.response.status == .ok else {
throw successData.response.mapStatusToError(error: successData.decodedError)
}
let indexedErrorsCount = successData.decodedData?.errors.count ?? 0
if indexedErrorsCount > 0 {
Logger(subsystem: "com.confidence.client", category: "network").error(
"Backend reported errors for \(indexedErrorsCount) event(s) in batch")
let status = successData.response.statusCode
switch status {
case 200:
// clean up in case of success
return true
case 429:
// we shouldn't clean up for rate limiting
return false
case 400...499:
// if batch couldn't be processed, we should clean it up
return true
default:
return false
}
return true
case .failure(let errorData):
throw handleError(error: errorData)
}
Expand Down
58 changes: 0 additions & 58 deletions Tests/ConfidenceProviderTests/EventSenderEngineTest.swift

This file was deleted.

120 changes: 120 additions & 0 deletions Tests/ConfidenceTests/EventSenderEngineTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import Foundation
import Common
import XCTest

@testable import Confidence

final class MinSizeFlushPolicy: FlushPolicy {
private var maxSize = 5
private var size = 0
func reset() {
size = 0
}

func hit(event: ConfidenceEvent) {
size += 1
}

func shouldFlush() -> Bool {
return size >= maxSize
}
}

final class ImmidiateFlushPolicy: FlushPolicy {
private var size = 0

func reset() {
size = 0
}

func hit(event: ConfidenceEvent) {
size += 1
}

func shouldFlush() -> Bool {
return size > 0
}
}

final class EventSenderEngineTest: XCTestCase {
func testAddingEventsWithSizeFlushPolicyWorks() throws {
let flushPolicies = [MinSizeFlushPolicy()]
let uploader = EventUploaderMock()
let eventSenderEngine = EventSenderEngineImpl(
clientSecret: "CLIENT_SECRET",
uploader: uploader,
storage: EventStorageMock(),
flushPolicies: flushPolicies
)

let expectation = XCTestExpectation(description: "Upload finished")
let cancellable = uploader.subject.sink { _ in
expectation.fulfill()
}

var events: [ConfidenceEvent] = []
for i in 0..<5 {
events.append(ConfidenceEvent(
name: "\(i)",
payload: [:],
eventTime: Date.backport.now)
)
eventSenderEngine.emit(definition: "\(i)", payload: [:], context: [:])
}

wait(for: [expectation], timeout: 5)
let uploadRequest = try XCTUnwrap(uploader.calledRequest)
XCTAssertTrue(uploadRequest.map { $0.eventDefinition } == events.map { $0.name })

uploader.reset()
eventSenderEngine.emit(definition: "Hello", payload: [:], context: [:])
XCTAssertNil(uploader.calledRequest)
cancellable.cancel()
}

func testRemoveEventsFromStorageOnBadRequest() throws {
MockedClientURLProtocol.mockedOperation = .badRequest
let client = RemoteConfidenceClient(
options: ConfidenceClientOptions(credentials: ConfidenceClientCredentials.clientSecret(secret: "")),
session: MockedClientURLProtocol.mockedSession(),
metadata: ConfidenceMetadata(name: "", version: ""))

let flushPolicies = [ImmidiateFlushPolicy()]
let storage = EventStorageMock()
let eventSenderEngine = EventSenderEngineImpl(
clientSecret: "CLIENT_SECRET",
uploader: client,
storage: storage,
flushPolicies: flushPolicies
)
eventSenderEngine.emit(definition: "testEvent", payload: ConfidenceStruct(), context: ConfidenceStruct())
let expectation = expectation(description: "events batched")
storage.eventsRemoved{
expectation.fulfill()
}
wait(for: [expectation], timeout: 2)

XCTAssertEqual(storage.isEmpty(), true)
}

func testKeepEventsInStorageForRetry() throws {
MockedClientURLProtocol.mockedOperation = .needRetryLater
let client = RemoteConfidenceClient(
options: ConfidenceClientOptions(credentials: ConfidenceClientCredentials.clientSecret(secret: "")),
session: MockedClientURLProtocol.mockedSession(),
metadata: ConfidenceMetadata(name: "", version: ""))

let flushPolicies = [ImmidiateFlushPolicy()]
let storage = EventStorageMock()
let eventSenderEngine = EventSenderEngineImpl(
clientSecret: "CLIENT_SECRET",
uploader: client,
storage: storage,
flushPolicies: flushPolicies
)

eventSenderEngine.emit(definition: "testEvent", payload: ConfidenceStruct(), context: ConfidenceStruct())

XCTAssertEqual(storage.isEmpty(), false)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ final class EventUploaderMock: ConfidenceClient {
final class EventStorageMock: EventStorage {
private var events: [ConfidenceEvent] = []
private var batches: [String: [ConfidenceEvent]] = [:]
var removeCallback: () -> Void = {}

func startNewBatch() throws {
batches[("\(batches.count)")] = events
events.removeAll()
Expand All @@ -42,5 +44,14 @@ final class EventStorageMock: EventStorage {

func remove(id: String) throws {
batches.removeValue(forKey: id)
removeCallback()
}

internal func isEmpty() -> Bool {
return self.events.isEmpty && self.batches.isEmpty
}

internal func eventsRemoved(callback: @escaping () -> Void) {
removeCallback = callback
}
}
3 changes: 3 additions & 0 deletions Tests/ConfidenceTests/Helpers/MockedClientURLProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class MockedClientURLProtocol: URLProtocol {
case malformedResponse
case badRequest
case success
case needRetryLater
}

override class func canInit(with request: URLRequest) -> Bool {
Expand Down Expand Up @@ -65,6 +66,8 @@ class MockedClientURLProtocol: URLProtocol {
switch MockedClientURLProtocol.mockedOperation {
case .badRequest:
respondWithError(statusCode: 400, code: 0, message: "explanation about malformed request")
case .needRetryLater:
respondWithError(statusCode: 502, code: 0, message: "service unavailable")
case .malformedResponse:
malformedResponse()
case .firstEventFails:
Expand Down
25 changes: 0 additions & 25 deletions Tests/ConfidenceTests/RemoteConfidenceClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,31 +56,6 @@ class RemoteConfidenceClientTest: XCTestCase {
XCTAssertTrue(processed)
}

func testBadRequestThrows() async throws {
MockedClientURLProtocol.mockedOperation = .badRequest
let client = RemoteConfidenceClient(
options: ConfidenceClientOptions(
credentials: ConfidenceClientCredentials.clientSecret(secret: "")),
session: MockedClientURLProtocol.mockedSession(),
metadata: ConfidenceMetadata(name: "", version: ""))

var caughtError: ConfidenceError?
do {
_ = try await client.upload(events: [
NetworkEvent(
eventDefinition: "testEvent",
payload: NetworkStruct.init(fields: [:]),
eventTime: Date.backport.nowISOString
)
])
} catch {
// swiftlint:disable:next force_cast
caughtError = error as! ConfidenceError?
}
let expectedError = ConfidenceError.badRequest(message: "explanation about malformed request")
XCTAssertEqual(caughtError, expectedError)
}

func testNMalformedResponseThrows() async throws {
MockedClientURLProtocol.mockedOperation = .malformedResponse
let client = RemoteConfidenceClient(
Expand Down
Loading