migration

This commit is contained in:
Daniel Arantes Loverde
2026-08-11 13:22:02 -03:00
parent c4d6998595
commit 7702836fe7
231 changed files with 4329 additions and 1958 deletions

View File

@@ -0,0 +1,104 @@
import Foundation
import Testing
@testable import PediFoods
/// All these tests set the shared `URLProtocolStub.handler` static, so they
/// must not run concurrently with each other (see the note on
/// `SessionStateStorePersistenceTests` for the same reasoning).
@Suite(.serialized)
struct FeatureControlServiceTests {
@MainActor
private func makeService(cacheTTL: TimeInterval = 60) -> FeatureControlService {
let defaults = UserDefaults(suiteName: UUID().uuidString)!
let session = URLProtocolStub.makeSession()
return FeatureControlService(session: session, cacheTTL: cacheTTL, userDefaults: defaults)
}
private let bootstrapSuccessBody = Data("""
{"ok":true,"configVersion":3,"evaluatedAt":"2026-01-01T00:00:00Z","source":"live",
"flags":{"at.promo":true},"raw":{"at.promo":{"enabled":true,"variant":"on"}}}
""".utf8)
@Test("evaluate returns a live snapshot on a successful bootstrap and caches it")
@MainActor
func evaluateSucceedsAndCaches() async {
let service = makeService()
URLProtocolStub.handler = { _ in (.stub(statusCode: 200), self.bootstrapSuccessBody) }
defer { URLProtocolStub.handler = nil }
let context = FeatureControlEvaluationContext(subjectType: "customer", subjectId: "u1", storeId: nil, attributes: [:])
let snapshot = await service.evaluate(context: context, jwt: nil)
#expect(snapshot.source == "live")
#expect(snapshot.configVersion == 3)
#expect(snapshot.isEnabled("at.promo") == true)
}
@Test("evaluate serves from cache on a second call without hitting the network again")
@MainActor
func evaluateServesFromCacheOnSecondCall() async {
let service = makeService()
var requestCount = 0
URLProtocolStub.handler = { [bootstrapSuccessBody] _ in
requestCount += 1
return (.stub(statusCode: 200), bootstrapSuccessBody)
}
defer { URLProtocolStub.handler = nil }
let context = FeatureControlEvaluationContext(subjectType: "customer", subjectId: "u1", storeId: nil, attributes: [:])
_ = await service.evaluate(context: context, jwt: nil)
let second = await service.evaluate(context: context, jwt: nil)
#expect(requestCount == 1)
#expect(second.source == "live")
}
@Test("evaluate falls back to a stale cache entry when the network call fails")
@MainActor
func evaluateFallsBackToCacheOnNetworkError() async {
let service = makeService()
let context = FeatureControlEvaluationContext(subjectType: "customer", subjectId: "u1", storeId: nil, attributes: [:])
URLProtocolStub.handler = { _ in (.stub(statusCode: 200), self.bootstrapSuccessBody) }
_ = await service.evaluate(context: context, jwt: nil)
URLProtocolStub.handler = { _ in (.stub(statusCode: 500), Data()) }
defer { URLProtocolStub.handler = nil }
let refreshed = await service.evaluate(context: context, jwt: nil, forceRefresh: true)
#expect(refreshed.source == "cache_fallback")
#expect(refreshed.isEnabled("at.promo") == true)
}
@Test("evaluate falls back to defaults when the network fails and there's no cache")
@MainActor
func evaluateFallsBackToDefaultsWithNoCache() async {
let service = makeService()
URLProtocolStub.handler = { _ in (.stub(statusCode: 500), Data()) }
defer { URLProtocolStub.handler = nil }
let context = FeatureControlEvaluationContext(subjectType: "customer", subjectId: "u1", storeId: nil, attributes: [:])
let snapshot = await service.evaluate(context: context, jwt: nil)
#expect(snapshot.source == "defaults")
#expect(snapshot.isEnabled("at.promo") == false)
}
@Test("evaluate re-fetches once a cached entry's TTL has expired")
@MainActor
func evaluateRefetchesAfterCacheExpires() async {
let service = makeService(cacheTTL: -1) // already expired the instant it's written
var requestCount = 0
URLProtocolStub.handler = { [bootstrapSuccessBody] _ in
requestCount += 1
return (.stub(statusCode: 200), bootstrapSuccessBody)
}
defer { URLProtocolStub.handler = nil }
let context = FeatureControlEvaluationContext(subjectType: "customer", subjectId: "u1", storeId: nil, attributes: [:])
_ = await service.evaluate(context: context, jwt: nil)
_ = await service.evaluate(context: context, jwt: nil)
#expect(requestCount == 2)
}
}