Skip to content

Commit

Permalink
fix: TypeMismatch doesn't trigger apply (#172)
Browse files Browse the repository at this point in the history
* test: apply on null test

* fix: TypeMismatch doesnt trigger apply

* refactor: Clearer code paths

* fixup! refactor: Clearer code paths

---------

Co-authored-by: Nicklas Lundin <[email protected]>
  • Loading branch information
fabriziodemaria and nicklasl authored Nov 8, 2024
1 parent 75f8f0b commit df38f0b
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 12 deletions.
51 changes: 39 additions & 12 deletions Sources/Confidence/FlagEvaluation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,15 @@ extension FlagResolution {
)
}

if resolvedFlag.resolveReason != .targetingKeyError {
Task {
await flagApplier?.apply(flagName: parsedKey.flag, resolveToken: self.resolveToken)
}
} else {
return Evaluation(
value: defaultValue,
variant: nil,
reason: .targetingKeyError,
errorCode: .invalidContext,
errorMessage: "Invalid targeting key"
)
if let evaluation = checkBackendErrors(resolvedFlag: resolvedFlag, defaultValue: defaultValue) {
return evaluation
}

guard let value = resolvedFlag.value else {
// No backend error, but nil value returned. This can happend with "noSegmentMatch" or "archived", for example
Task {
await flagApplier?.apply(flagName: parsedKey.flag, resolveToken: self.resolveToken)
}
return Evaluation(
value: defaultValue,
variant: resolvedFlag.variant,
Expand All @@ -77,6 +71,9 @@ extension FlagResolution {
resolveReason = .stale
}
if let typedValue = typedValue {
Task {
await flagApplier?.apply(flagName: parsedKey.flag, resolveToken: self.resolveToken)
}
return Evaluation(
value: typedValue,
variant: resolvedFlag.variant,
Expand All @@ -87,6 +84,9 @@ extension FlagResolution {
} else {
// `null` type from backend instructs to use client-side default value
if parsedValue == .init(null: ()) {
Task {
await flagApplier?.apply(flagName: parsedKey.flag, resolveToken: self.resolveToken)
}
return Evaluation(
value: defaultValue,
variant: resolvedFlag.variant,
Expand All @@ -105,6 +105,9 @@ extension FlagResolution {
}
}
} else {
Task {
await flagApplier?.apply(flagName: parsedKey.flag, resolveToken: self.resolveToken)
}
return Evaluation(
value: defaultValue,
variant: resolvedFlag.variant,
Expand All @@ -125,6 +128,30 @@ extension FlagResolution {
}
// swiftlint:enable function_body_length

private func checkBackendErrors<T>(resolvedFlag: ResolvedValue, defaultValue: T) -> Evaluation<T>? {
if resolvedFlag.resolveReason == .targetingKeyError {
return Evaluation(
value: defaultValue,
variant: nil,
reason: .targetingKeyError,
errorCode: .invalidContext,
errorMessage: "Invalid targeting key"
)
} else if resolvedFlag.resolveReason == .error ||
resolvedFlag.resolveReason == .unknown ||
resolvedFlag.resolveReason == .unspecified {
return Evaluation(
value: defaultValue,
variant: nil,
reason: .error,
errorCode: .evaluationError,
errorMessage: "Unknown error from backend"
)
} else {
return nil
}
}

// swiftlint:disable:next cyclomatic_complexity
private func getTyped<T>(value: ConfidenceValue) -> T? {
if let value = self as? T {
Expand Down
41 changes: 41 additions & 0 deletions Tests/ConfidenceTests/ConfidenceTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,46 @@ class ConfidenceTest: XCTestCase {
XCTAssertEqual(flagApplier.applyCallCount, 1)
}

func testResolveAndApplyIntegerFlagNullValue() async throws {
class FakeClient: ConfidenceResolveClient {
var resolveStats: Int = 0
var resolvedValues: [ResolvedValue] = []
func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult {
self.resolveStats += 1
return .init(resolvedValues: resolvedValues, resolveToken: "token")
}
}

let client = FakeClient()
client.resolvedValues = [
ResolvedValue(
value: .init(structure: ["size": .init(null: ())]),
flag: "flag",
resolveReason: .match)
]

let confidence = Confidence.Builder(clientSecret: "test")
.withContext(initialContext: ["targeting_key": .init(string: "user2")])
.withFlagResolverClient(flagResolver: client)
.withFlagApplier(flagApplier: flagApplier)
.build()

try await confidence.fetchAndActivate()
let evaluation = confidence.getEvaluation(
key: "flag.size",
defaultValue: 4)

XCTAssertEqual(client.resolveStats, 1)
XCTAssertEqual(evaluation.value, 4)
XCTAssertNil(evaluation.errorCode)
XCTAssertNil(evaluation.errorMessage)
XCTAssertEqual(evaluation.reason, .match)
XCTAssertNil(evaluation.variant)
XCTAssertEqual(client.resolveStats, 1)
await fulfillment(of: [flagApplier.applyExpectation], timeout: 1)
XCTAssertEqual(flagApplier.applyCallCount, 1)
}

func testResolveAndApplyIntegerFlagTwice() async throws {
class FakeClient: ConfidenceResolveClient {
var resolveStats: Int = 0
Expand Down Expand Up @@ -654,6 +694,7 @@ class ConfidenceTest: XCTestCase {
XCTAssertNil(evaluation.errorMessage, "")
XCTAssertEqual(evaluation.reason, .error)
XCTAssertEqual(evaluation.variant, nil)
XCTAssertEqual(flagApplier.applyCallCount, 0)
}

func testConcurrentActivate() async {
Expand Down

0 comments on commit df38f0b

Please sign in to comment.