SDK-382 BCIT JWT Auth Retry: add real Iterable backend mode#1057
SDK-382 BCIT JWT Auth Retry: add real Iterable backend mode#1057sumeruchat wants to merge 2 commits intomasterfrom
Conversation
The JWT Auth Retry screen used to fabricate every response locally in MockAPIServerURLProtocol, so the SDK's retry logic was never exercised against real Iterable traffic — no real latency, no real JWT validation, no real 401 InvalidJwtPayload bodies. Mirror the Android network-tester so Normal and 401 modes proxy through to api.iterable.com, while 500 and Conn Err stay locally synthesized (you can't force prod to 500). - .normal / .jwt401 now re-issue the request on URLSession.shared with an internal handled-marker so URLProtocol doesn't recurse on itself; .jwt401 swaps Authorization with a locally-forged expired JWT for the SDK's current email, matching MockAuthDelegate's source of truth. - Swallow NSURLErrorCancelled in the forwarded completion to respect the URLProtocol contract after stopLoading(). - MockAPIServer keeps jwtSecret for the swap, drops unused success / jwt401 local helpers, and narrows mockResponse(for:) to 500/ConnErr. - OfflineRetryTestViewController shows a "-> real backend" / "-> local mock" hint next to the response radios. - README documents the per-mode destinations and the test-config.json requirements (dedicated Iterable test project recommended). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1057 +/- ##
=======================================
Coverage 70.72% 70.72%
=======================================
Files 112 112
Lines 9201 9201
=======================================
Hits 6507 6507
Misses 2694 2694 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
This is great, but maybe we can simplify it. The core goal is validating JWT retry against real 401s, and the .jwt401 proxy path does exactly that. But proxying .normal mode adds complexity we don't really need. On android we do proxy on normal mode but almost because it is more complex to not do it on normal mode than it is to just always proxy it. On iOS that does not seem to be the case, we are having a lot of logic just to proxy to normal mode where we don't really want to change anything. My suggestion is to just to call the real api without interception on normal mode and keep proxy for 401 and mock for 500 and connection error |
franco-zalamena-iterable
left a comment
There was a problem hiding this comment.
We can possibly simplify this pr, check above comment
Per Franco's review: on Android the NanoHTTPD proxy is necessary because SDK traffic is force-routed through localhost, so Normal mode has to proxy. On iOS we control interception per-request, so the simpler equivalent of "Normal = real backend" is to have URLProtocol opt out of the request and let URLSession talk to api.iterable.com directly. That drops the Normal-mode proxy path entirely (no URLSession.shared re-issue, no httpBody-forwarding concern). - canInit returns false when apiResponseMode is .normal; the SDK's existing URLSession handles the request with zero tester involvement. - startLoading now only handles .jwt401 (proxy with expired JWT swap) plus .server500 / .connectionError (local synthesis). .normal kept as a defensive no-op in case the mode flips between canInit and here. - UI hint clarifies the new split: "real backend (direct)" vs "real backend (expired JWT)" vs "local mock". - README table updated to match. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Good call @franco-zalamena-iterable — you're right, the iOS equivalent of Android's always-proxy doesn't buy us anything since URLProtocol can just opt out per request. Pushed
|
What
The BCIT tester's JWT Auth Retry screen used to fabricate every response locally, so the SDK's new JWT retry logic was never validated against real Iterable traffic. Mirror the Android network-tester so Normal/401 proxy through to
api.iterable.comwhile 500/ConnErr stay locally synthesized.Changes
MockAPIServerURLProtocol.startLoadingdispatches on response mode: Normal and 401 proxy throughURLSession.shared(handled-marker avoids URLProtocol recursion); 500/ConnErr keep the existing local synthesis.Authorizationis rewritten toBearer <locally-forged expired JWT>using the same email source (IterableAPI.email ?? AppDelegate.currentTestEmail) asMockAuthDelegate, so the backend returns realInvalidJwtPayloadrather than a user-mismatch error.stopLoadingcancels the forwarded task; completion swallowsNSURLErrorCancelledto respect the URLProtocol contract.MockAPIServerexposesjwtSecret, drops unused success/jwt401 local helpers, and narrowsmockResponse(for:)to 500/ConnErr.Impact
Normaland401now send real traffic to whichever Iterable projecttest-config.jsonpoints at. CurrentprojectIdis 28411 — keep using a dedicated test project so prod dashboards stay clean.Testing
How to test:
tests/business-critical-integration/scripts/build-and-run.sh200s fromapi.iterable.com(not the old canned{"successCount":1}).401with{"code":"InvalidJwtPayload", ...}from Iterable; confirm SDK pause + refresh + queue flush when flipped back to Normal.Edge cases:
stopLoadingshould cancel the forwarded task silently (no noisy error).Known unverified items: I have not yet observed a round-trip myself, so POST body forwarding through
URLRequest.httpBodyover URLProtocol is untested end-to-end (known iOS quirk when the SDK uses a body stream). Worth watching the first Normal-mode Fire All for empty-body 400s.