[lcfeaturecontrol-review-fixes] Fix JWT log leak, drop macOS/tvOS, coalesce requests
- FeatureControlManager/NotificationsClient: pass debug: false at every
api.request(...) call site — the API default (debug: true) was printing
Authorization: Bearer <jwt> on every request, release builds included.
- Package.swift: drop macOS/tvOS from platforms — LCEssentials.API (used by
LCFeatureControl) is iOS/watchOS-only and the vendored xcframework has no
macOS/tvOS slice.
- FeatureControlManager: coalesce concurrent evaluate() calls on a cold cache
into a single in-flight request per key instead of firing one per caller.
- FeatureControlManager/NotificationsClient: treat a 200 response carrying
{"error": true} as a failure instead of caching/returning it as success.
- FeatureControlNotificationsClient: thread a cursor param through list() so
the already-decoded nextCursor can actually be used to page.
- Tests: 9 new tests (coalescing, envelope-error-on-200, cursor param, a real
object payload decoded through a full evaluate response, date-decode
failures) and removed the 7 remaining force-unwraps in test scaffolding.
- Documentation/FeatureControl.md: new guide for the LCFeatureControl product,
cross-linked from README.md and Extensions.md.
This commit is contained in:
@@ -4,18 +4,10 @@ import XCTest
|
||||
|
||||
final class FeatureControlNotificationsClientTests: XCTestCase {
|
||||
|
||||
private var api: API!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
let cfg = URLSessionConfiguration.ephemeral
|
||||
cfg.protocolClasses = [StubURLProtocol.self]
|
||||
api = API(testConfiguration: cfg)
|
||||
}
|
||||
private var api = API.lce_featureControlTestInstance()
|
||||
|
||||
override func tearDown() {
|
||||
StubURLProtocol.reset()
|
||||
api = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
@@ -77,6 +69,43 @@ final class FeatureControlNotificationsClientTests: XCTestCase {
|
||||
"https://api.example.com/api/feature-control/notifications/read-all")
|
||||
}
|
||||
|
||||
func testListIncludesEncodedCursorWhenProvided() async throws {
|
||||
let body = #"{"error": false, "result": {"items": [], "nextCursor": null}}"#
|
||||
StubURLProtocol.setStub(.init(statusCode: 200, body: Data(body.utf8)))
|
||||
let client = makeClient()
|
||||
|
||||
_ = try await client.list(status: .all, limit: 20, cursor: "cursor abc")
|
||||
|
||||
let url = StubURLProtocol.capturedRequests.first?.url?.absoluteString
|
||||
XCTAssertEqual(url, "https://api.example.com/api/feature-control/notifications?status=all&limit=20&cursor=cursor%20abc")
|
||||
}
|
||||
|
||||
func testListOmitsCursorWhenNil() async throws {
|
||||
let body = #"{"error": false, "result": {"items": [], "nextCursor": null}}"#
|
||||
StubURLProtocol.setStub(.init(statusCode: 200, body: Data(body.utf8)))
|
||||
let client = makeClient()
|
||||
|
||||
_ = try await client.list(status: .all, limit: 20)
|
||||
|
||||
let url = StubURLProtocol.capturedRequests.first?.url?.absoluteString
|
||||
XCTAssertEqual(url, "https://api.example.com/api/feature-control/notifications?status=all&limit=20")
|
||||
}
|
||||
|
||||
func testListThrowsWhenEnvelopeErrorIsTrueDespite200() async {
|
||||
let body = #"{"error": true, "result": {"items": [], "nextCursor": null}}"#
|
||||
StubURLProtocol.setStub(.init(statusCode: 200, body: Data(body.utf8)))
|
||||
let client = makeClient()
|
||||
|
||||
do {
|
||||
_ = try await client.list(status: .all, limit: 20)
|
||||
XCTFail("expected throw")
|
||||
} catch let error as FeatureControlError {
|
||||
XCTAssertEqual(error, .server(code: "UNKNOWN", status: 200))
|
||||
} catch {
|
||||
XCTFail("expected FeatureControlError, got \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testUnauthorizedMapsToFeatureControlErrorNotCrash() async {
|
||||
StubURLProtocol.setStub(.init(statusCode: 401, body: Data(#"{"code":"FEATURE_CONTROL_MISSING_AUTH"}"#.utf8)))
|
||||
let client = makeClient()
|
||||
|
||||
Reference in New Issue
Block a user