Skip to content

Commit

Permalink
Adds RemoteConfigTests to test EmbraceConfigurable implementation
Browse files Browse the repository at this point in the history
Also tests logic around enabling config based on device_id + configured threshold
  • Loading branch information
atreat committed Sep 6, 2024
1 parent 39633d9 commit c7e7797
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 227 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ extension DeviceIdentifier {
/// Returns the integer value of the device identifier using the number of digits from the suffix
/// - Parameters:
/// - digitCount: The number of digits to use for the deviceId calculation
public func intValue(digitCount: Int) -> UInt64 {
public func intValue(digitCount: UInt) -> UInt64 {
var deviceIdHexValue: UInt64 = UInt64.max // defaults to everything disabled

let hexValue = hex
if hexValue.count >= digitCount {
deviceIdHexValue = UInt64.init(hexValue.suffix(digitCount), radix: 16) ?? .max
deviceIdHexValue = UInt64.init(hexValue.suffix(Int(digitCount)), radix: 16) ?? .max
}

return deviceIdHexValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@ import EmbraceCommonInternal
public class RemoteConfig {

// config requests
@ThreadSafe private var payload: RemoteConfigPayload
@ThreadSafe var payload: RemoteConfigPayload
let fetcher: RemoteConfigFetcher

// threshold values
let deviceIdUsedDigits = 6
var deviceIdHexValue: UInt64 = UInt64.max // defaults to everything disabled
let deviceIdUsedDigits: UInt
let deviceIdHexValue: UInt64

@ThreadSafe private(set) var updating = false

public init(
payload: RemoteConfigPayload = RemoteConfigPayload(),
fetcher: RemoteConfigFetcher,
deviceIdHexValue: UInt64,
updating: Bool = false
deviceIdHexValue: UInt64 = UInt64.max /* default to everything disabled */,
deviceIdUsedDigits: UInt
) {
self.payload = payload
self.fetcher = fetcher
self.deviceIdHexValue = deviceIdHexValue
self.updating = updating
self.deviceIdUsedDigits = deviceIdUsedDigits
}

public func update() {
Expand Down Expand Up @@ -72,7 +72,19 @@ extension RemoteConfig {
return Self.isEnabled(hexValue: deviceIdHexValue, digits: deviceIdUsedDigits, threshold: threshold)
}

static func isEnabled(hexValue: UInt64, digits: Int, threshold: Float) -> Bool {
/// Algorithm to determine if percentage threshold is enabled for the hexValue
/// Given a `hexValue` (derived from DeviceIdentifier to persist across app launches)
/// Determine the max value for the probability `space` by using the number of `digits` (16 ^ `n`)
/// If the `hexValue` is within the `threshold`
/// ```
/// space = 16^numDigits
/// result = (hexValue / space) * 100.0 < threshold
/// ```
/// - Parameters:
/// - hexValue: The value to test
/// - digits: The number of digits used to calculate the total space. Must match the number of digits used to determine the hexValue
/// - threshold: The percentage threshold to test against. Values between 0.0 and 100.0
static func isEnabled(hexValue: UInt64, digits: UInt, threshold: Float) -> Bool {
let space = powf(16, Float(digits))
let result = (Float(hexValue) / space) * 100

Expand Down
13 changes: 13 additions & 0 deletions Sources/EmbraceConfigInternal/InternalLogLimits.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,17 @@ import Foundation
self.warning = warning
self.error = error
}

public override func isEqual(_ object: Any?) -> Bool {
guard let other = object as? Self else {
return false
}

return
trace == other.trace &&
debug == other.debug &&
info == other.info &&
warning == other.warning &&
error == other.error
}
}
4 changes: 3 additions & 1 deletion Sources/EmbraceCore/Internal/Embrace+Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ extension Embrace {
)

let fetcher = RemoteConfigFetcher(options: options, logger: logger)
let usedDigits = UInt(6)
return RemoteConfig(
fetcher: fetcher,
deviceIdHexValue: deviceId.intValue(digitCount: 6)
deviceIdHexValue: deviceId.intValue(digitCount: usedDigits),
deviceIdUsedDigits: usedDigits
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import TestSupport
@testable import EmbraceConfigInternal
import EmbraceCommonInternal

class RemoteConfigTests: XCTestCase {
class RemoteConfigFetcherTests: XCTestCase {
static var urlSessionConfig: URLSessionConfiguration!
let logger = MockLogger()

private var apiBaseUrl: String {
"https://embrace.\(testName).com/config"
Expand All @@ -17,8 +18,8 @@ class RemoteConfigTests: XCTestCase {
override func setUpWithError() throws {
let config = URLSessionConfiguration.ephemeral
config.httpMaximumConnectionsPerHost = .max
RemoteConfigTests.urlSessionConfig = config
RemoteConfigTests.urlSessionConfig.protocolClasses = [EmbraceHTTPMock.self]
Self.urlSessionConfig = config
Self.urlSessionConfig.protocolClasses = [EmbraceHTTPMock.self]
}

func fetcherOptions(
Expand All @@ -39,7 +40,7 @@ class RemoteConfigTests: XCTestCase {
sdkVersion: sdkVersion,
appVersion: appVersion,
userAgent: userAgent,
urlSessionConfiguration: RemoteConfigTests.urlSessionConfig
urlSessionConfiguration: Self.urlSessionConfig
)
}

Expand Down Expand Up @@ -85,8 +86,6 @@ class RemoteConfigTests: XCTestCase {
EmbraceHTTPMock.mock(url: url, response: .withData(Data(), statusCode: 404))
}

let logger = MockLogger()

// MARK: buildURL
func test_buildURL_addsCorrectQuery() throws {
let fetcher = RemoteConfigFetcher(options: fetcherOptions(), logger: logger)
Expand Down Expand Up @@ -172,73 +171,4 @@ class RemoteConfigTests: XCTestCase {
}
wait(for: [expectation])
}

// func test_isSDKEnabled() {
// // given a config
// let config = EmbraceConfig(
// options: testOptions(deviceId: TestConstants.deviceId),
// notificationCenter: NotificationCenter.default,
// logger: logger
// )
//
// // then isSDKEnabled returns the correct values
// config.payload.sdkEnabledThreshold = 100
// XCTAssertTrue(config.isSDKEnabled)
//
// config.payload.sdkEnabledThreshold = 0
// XCTAssertFalse(config.isSDKEnabled)
// }
//
// func test_isBackgroundSessionEnabled() {
// // given a config
// let config = EmbraceConfig(
// options: testOptions(deviceId: TestConstants.deviceId),
// notificationCenter: NotificationCenter.default,
// logger: logger
// )
//
// // then isBackgroundSessionEnabled returns the correct values
// config.payload.backgroundSessionThreshold = 100
// XCTAssertTrue(config.isBackgroundSessionEnabled)
//
// config.payload.backgroundSessionThreshold = 0
// XCTAssertFalse(config.isBackgroundSessionEnabled)
// }
//
// func test_networkSpansForwardingEnabled() {
// // given a config
// let config = EmbraceConfig(
// options: testOptions(deviceId: TestConstants.deviceId),
// notificationCenter: NotificationCenter.default,
// logger: logger
// )
//
// // then isNetworkSpansForwardingEnabled returns the correct values
// config.payload.networkSpansForwardingThreshold = 100
// XCTAssertTrue(config.isNetworkSpansForwardingEnabled)
//
// config.payload.networkSpansForwardingThreshold = 0
// XCTAssertFalse(config.isNetworkSpansForwardingEnabled)
// }
// func test_internalLogsLimits() {
// // given a config
// let config = EmbraceConfig(
// options: testOptions(deviceId: TestConstants.deviceId),
// notificationCenter: NotificationCenter.default,
// logger: logger
// )
//
// // then test_internalLogsTraceLimit returns the correct values
// config.payload.internalLogsTraceLimit = 10
// config.payload.internalLogsDebugLimit = 20
// config.payload.internalLogsInfoLimit = 30
// config.payload.internalLogsWarningLimit = 40
// config.payload.internalLogsErrorLimit = 50
//
// XCTAssertEqual(config.internalLogsTraceLimit, 10)
// XCTAssertEqual(config.internalLogsDebugLimit, 20)
// XCTAssertEqual(config.internalLogsInfoLimit, 30)
// XCTAssertEqual(config.internalLogsWarningLimit, 40)
// XCTAssertEqual(config.internalLogsErrorLimit, 50)
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//
// Copyright © 2024 Embrace Mobile, Inc. All rights reserved.
//

import XCTest
import TestSupport
@testable import EmbraceConfigInternal

final class RemoteConfigTests: XCTestCase {

let fetcher = RemoteConfigFetcher(
options: .init(
apiBaseUrl: "https://localhost:8080/config",
queue: DispatchQueue(label: "com.test.embrace.queue", attributes: .concurrent),
appId: TestConstants.appId,
deviceId: TestConstants.deviceId,
osVersion: TestConstants.osVersion,
sdkVersion: TestConstants.sdkVersion,
appVersion: TestConstants.appVersion,
userAgent: TestConstants.userAgent,
urlSessionConfiguration: URLSessionConfiguration.default
),
logger: MockLogger()
)

func mockSuccessfulResponse() throws {
var url = try XCTUnwrap(URL(string: "\(fetcher.options.apiBaseUrl)/v2/config"))

if #available(iOS 16.0, *) {
url.append(queryItems: [
.init(name: "appId", value: fetcher.options.appId),
.init(name: "osVersion", value: fetcher.options.osVersion),
.init(name: "appVersion", value: fetcher.options.appVersion),
.init(name: "deviceId", value: fetcher.options.deviceId.hex),
.init(name: "sdkVersion", value: fetcher.options.sdkVersion)
])
} else {
XCTFail("This will fail on versions prior to iOS 16.0")
}

let path = Bundle.module.path(
forResource: "remote_config",
ofType: "json",
inDirectory: "Mocks"
)!
let data = try Data(contentsOf: URL(fileURLWithPath: path))
EmbraceHTTPMock.mock(url: url, response: .withData(data, statusCode: 200))
}

// MARK: Tests

func test_isEnabled_returnsCorrectValues() {
// True if threshold 100
XCTAssertTrue(RemoteConfig.isEnabled(hexValue: 15, digits: 1, threshold: 100.0))
// False if threshold 0
XCTAssertFalse(RemoteConfig.isEnabled(hexValue: 0, digits: 1, threshold: 0.0))
// True if threshold just under (128 limit)
XCTAssertTrue(RemoteConfig.isEnabled(hexValue: 127, digits: 2, threshold: 50.0))
// False if threshold just over (128 limit)
XCTAssertFalse(RemoteConfig.isEnabled(hexValue: 129, digits: 2, threshold: 50.0))
}

func test_isSdkEnabled_usesPayloadThreshold() {
// given a config
let config = RemoteConfig(fetcher: fetcher, deviceIdHexValue: 128, deviceIdUsedDigits: 2)

// then isSDKEnabled returns the correct values
config.payload.sdkEnabledThreshold = 100
XCTAssertTrue(config.isSDKEnabled)

config.payload.sdkEnabledThreshold = 0
XCTAssertFalse(config.isSDKEnabled)

config.payload.sdkEnabledThreshold = 51
XCTAssertTrue(config.isSDKEnabled)

config.payload.sdkEnabledThreshold = 49
XCTAssertFalse(config.isSDKEnabled)
}

func test_isBackgroundSessionEnabled() {
// given a config
let config = RemoteConfig(fetcher: fetcher, deviceIdHexValue: 128, deviceIdUsedDigits: 2)

// then isBackgroundSessionEnabled returns the correct values
config.payload.backgroundSessionThreshold = 100
XCTAssertTrue(config.isBackgroundSessionEnabled)

config.payload.backgroundSessionThreshold = 0
XCTAssertFalse(config.isBackgroundSessionEnabled)

config.payload.backgroundSessionThreshold = 51
XCTAssertTrue(config.isBackgroundSessionEnabled)

config.payload.backgroundSessionThreshold = 49
XCTAssertFalse(config.isBackgroundSessionEnabled)
}

func test_networkSpansForwardingEnabled() {
// given a config
let config = RemoteConfig(fetcher: fetcher, deviceIdHexValue: 128, deviceIdUsedDigits: 2)

// then isNetworkSpansForwardingEnabled returns the correct values
config.payload.networkSpansForwardingThreshold = 100
XCTAssertTrue(config.isNetworkSpansForwardingEnabled)

config.payload.networkSpansForwardingThreshold = 0
XCTAssertFalse(config.isNetworkSpansForwardingEnabled)

config.payload.networkSpansForwardingThreshold = 51
XCTAssertTrue(config.isNetworkSpansForwardingEnabled)

config.payload.networkSpansForwardingThreshold = 49
XCTAssertFalse(config.isNetworkSpansForwardingEnabled)
}

func test_internalLogLimits() {
// given a config
let config = RemoteConfig(fetcher: fetcher, deviceIdHexValue: 128, deviceIdUsedDigits: 2)

config.payload.internalLogsTraceLimit = 10
config.payload.internalLogsDebugLimit = 20
config.payload.internalLogsInfoLimit = 30
config.payload.internalLogsWarningLimit = 40
config.payload.internalLogsErrorLimit = 50

XCTAssertEqual(
config.internalLogLimits,
InternalLogLimits(trace: 10, debug: 20, info: 30, warning: 40, error: 50)
)
}
}
Loading

0 comments on commit c7e7797

Please sign in to comment.