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

test: Basic E2E test for events upload #155

Merged
merged 5 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
24 changes: 0 additions & 24 deletions ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,29 +43,5 @@ struct ConfidenceDemoApp: App {
extension ConfidenceDemoApp {
func setup(confidence: Confidence) async throws {
try await confidence.fetchAndActivate()
try confidence.track(
eventName: "all-types",
data: [
"my_string": ConfidenceValue(string: "hello_from_world"),
"my_timestamp": ConfidenceValue(timestamp: Date()),
"my_bool": ConfidenceValue(boolean: true),
"my_date": ConfidenceValue(date: DateComponents(year: 2024, month: 4, day: 3)),
"my_int": ConfidenceValue(integer: 2),
"my_double": ConfidenceValue(double: 3.14),
"my_list": ConfidenceValue(booleanList: [true, false]),
"my_struct": ConfidenceValue(structure: [
"my_nested_struct": ConfidenceValue(structure: [
"my_nested_nested_struct": ConfidenceValue(structure: [
"my_nested_nested_nested_int": ConfidenceValue(integer: 666)
]),
"my_nested_nested_list": ConfidenceValue(dateList: [
DateComponents(year: 2024, month: 4, day: 4),
DateComponents(year: 2024, month: 4, day: 5)
])
]),
"my_nested_string": ConfidenceValue(string: "nested_hello")
])
]
)
}
}
21 changes: 13 additions & 8 deletions Sources/Confidence/Confidence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -286,13 +286,13 @@ extension Confidence {

// Can be configured
internal var region: ConfidenceRegion = .global
internal var metadata: ConfidenceMetadata?
fabriziodemaria marked this conversation as resolved.
Show resolved Hide resolved
internal var initialContext: ConfidenceStruct = [:]

// Injectable for testing
internal var flagApplier: FlagApplier?
internal var storage: Storage?
internal var flagResolver: ConfidenceResolveClient?
internal var debugLogger: DebugLogger?

/**
Initializes the builder with the given credentails.
Expand Down Expand Up @@ -323,6 +323,11 @@ extension Confidence {
return self
}

internal func withDebugLogger(debugLogger: DebugLogger) -> Builder {
self.debugLogger = debugLogger
return self
}

public func withContext(initialContext: ConfidenceStruct) -> Builder {
self.initialContext = initialContext
return self
Expand All @@ -338,12 +343,11 @@ extension Confidence {
}

public func build() -> Confidence {
var debugLogger: DebugLogger?
if loggerLevel != LoggerLevel.NONE {
debugLogger = DebugLoggerImpl(loggerLevel: loggerLevel)
debugLogger?.logContext(action: "InitialContext", context: initialContext)
} else {
debugLogger = nil
if debugLogger == nil {
if loggerLevel != LoggerLevel.NONE {
debugLogger = DebugLoggerImpl(loggerLevel: loggerLevel)
debugLogger?.logContext(action: "InitialContext", context: initialContext)
}
}
let options = ConfidenceClientOptions(
credentials: ConfidenceClientCredentials.clientSecret(secret: clientSecret),
Expand All @@ -353,7 +357,8 @@ extension Confidence {
version: "0.2.4") // x-release-please-version
let uploader = RemoteConfidenceClient(
options: options,
metadata: metadata
metadata: metadata,
debugLogger: debugLogger
)
let httpClient = NetworkClient(baseUrl: BaseUrlMapper.from(region: options.region))
let flagApplier = flagApplier ?? FlagApplierWithRetries(
Expand Down
13 changes: 12 additions & 1 deletion Sources/Confidence/RemoteConfidenceClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,22 @@ public class RemoteConfidenceClient: ConfidenceClient {
switch result {
case .success(let successData):
let status = successData.response.statusCode
let indecesWithError = successData.decodedData?.errors.map { error in
error.index
} ?? []
let successEventNames = events.enumerated()
// Filter only events in batch that have no error reported from backend
.filter { index, _ in
return !(indecesWithError.contains(index))
}
.map { _, event in
event.eventDefinition
}
switch status {
case 200:
// clean up in case of success
debugLogger?.logMessage(
message: "Event upload: HTTP status 200",
message: "Event upload: HTTP status 200. Events: \(successEventNames.joined(separator: ","))",
isWarning: false
)
return true
Expand Down
41 changes: 41 additions & 0 deletions Tests/ConfidenceTests/ConfidenceIntegrationTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,47 @@ class ConfidenceIntegrationTests: XCTestCase {
XCTAssertNil(boolResult.errorMessage)
}

func testTrackEventAllTypes() async throws {
guard let clientToken = self.clientToken else {
throw TestError.missingClientToken
}

let logger = DebugLoggerFake()
let confidence = Confidence.Builder(clientSecret: clientToken)
.withDebugLogger(debugLogger: logger)
.build()

try confidence.track(
eventName: "all-types",
data: [
"my_string": ConfidenceValue(string: "hello_from_world"),
"my_timestamp": ConfidenceValue(timestamp: Date()),
"my_bool": ConfidenceValue(boolean: true),
"my_date": ConfidenceValue(date: DateComponents(year: 2024, month: 4, day: 3)),
"my_int": ConfidenceValue(integer: 2),
"my_double": ConfidenceValue(double: 3.14),
"my_list": ConfidenceValue(booleanList: [true, false]),
"my_struct": ConfidenceValue(structure: [
"my_nested_struct": ConfidenceValue(structure: [
"my_nested_nested_struct": ConfidenceValue(structure: [
"my_nested_nested_nested_int": ConfidenceValue(integer: 666)
]),
"my_nested_nested_list": ConfidenceValue(dateList: [
DateComponents(year: 2024, month: 4, day: 4),
DateComponents(year: 2024, month: 4, day: 5)
])
]),
"my_nested_string": ConfidenceValue(string: "nested_hello")
])
]
)

confidence.flush()
try logger.waitUploadBatchSuccessCount(value: 1, timeout: 5.0)
XCTAssertEqual(logger.getUploadBatchSuccessCount(), 1)
XCTAssertEqual(logger.uploadedEvents, ["all-types"])
}

func testConfidenceFeatureApplies() async throws {
guard let clientToken = self.clientToken else {
throw TestError.missingClientToken
Expand Down
90 changes: 90 additions & 0 deletions Tests/ConfidenceTests/Helpers/DebugLoggerFake.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import Foundation

@testable import Confidence

internal class DebugLoggerFake: DebugLogger {
private let uploadBatchSuccessCounter = ThreadSafeCounter()
public var uploadedEvents: [String] = [] // Holds the "eventDefinition" name of each uploaded event

func logEvent(action: String, event: ConfidenceEvent?) {
// no-op
}

func logMessage(message: String, isWarning: Bool) {
if message.starts(with: "Event upload: HTTP status 200") {
uploadedEvents.append(contentsOf: parseEvents(fromString: message))
uploadBatchSuccessCounter.increment()
}
}

func logFlags(action: String, flag: String) {
// no-op
}

func logContext(action: String, context: ConfidenceStruct) {
// no-op
}

func getUploadBatchSuccessCount() -> Int {
return uploadBatchSuccessCounter.get()
}

func waitUploadBatchSuccessCount(value: Int32, timeout: TimeInterval) throws {
try uploadBatchSuccessCounter.waitUntil(value: value, timeout: timeout)
}

/**
Example
Input: "Event upload: HTTP status 200. Events: event-name1, event-name2"
Output: ["event-name1", "event-name2"]
*/
private func parseEvents(fromString message: String) -> [String] {
guard let eventsStart = message.range(of: "Events:") else {
return []
}

let startIndex = message.index(eventsStart.upperBound, offsetBy: 1)
let endIndex = message.endIndex
let eventsString = message[startIndex..<endIndex]

return eventsString.components(separatedBy: ",")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
}

private class ThreadSafeCounter {
private let queue = DispatchQueue(label: "ThreadSafeCounterQueue")
private var count = 0

func increment() {
queue.sync {
count += 1
}
}

func get() -> Int {
queue.sync {
return count
}
}

func waitUntil(value: Int32, timeout: TimeInterval) throws {
let deadline = DispatchTime.now() + timeout

repeat {
Thread.sleep(forTimeInterval: 0.1) // Shortcut to reduce CPU usage, probably needs refactoring
guard deadline > DispatchTime.now() else {
throw TimeoutError(message: "Timed out waiting for counter to reach \(value)")
}
if (queue.sync {
count >= value
}) {
return
}
} while true
}
}

struct TimeoutError: Error {
let message: String
}
}
Loading