From a96e9d2a9cdd93e8563406f44cf4204343319bef Mon Sep 17 00:00:00 2001 From: Fayner Brack Date: Sat, 4 Jul 2026 23:04:40 +1000 Subject: [PATCH] test(ios-readplace): backfill untested logic seams and ratchet coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover error and edge paths that had no tests, and raise their coverage floors: - OAuthService (90%→98%): a malformed 200 body and a 200 missing refresh_token both surface .malformedResponse; refresh without a stored token throws .noRefreshToken and makes no request; revoke without a token skips the network but still clears. - AppSession completeSignIn (79%→83%): a callback carrying error= is denied, a callback with no code is .missingCode, and a failed code exchange surfaces as a failure — none exchanging or logging in. - ReadingListViewModel (94%→95%): a 401 whose refresh also fails routes to the logout callback without an error banner; a collection warning populates warningText. - KeychainTokenStorage (57%→98%): a real simulator-keychain round trip through the production path (add / read / update / remove, key isolation) — the test double never exercised it. - TokenStore (85%→99%): extract the embedded-provisioning-profile parsing into a pure `parseAppGroupId(fromProvisioningProfile:)` and cover it with fixtures (valid profile, no plist, no app-group entitlement, empty group array); the `Bundle.main` read stays the only untested line. --- .../ios-readplace/Shared/TokenStore.swift | 19 +++++-- .../Tests/KeychainTokenStorageTests.swift | 45 +++++++++++++++++ .../ios-readplace/Tests/LoginFlowTests.swift | 47 ++++++++++++++++++ .../Tests/OAuthServiceTests.swift | 42 ++++++++++++++++ .../Tests/ReadingListViewModelTests.swift | 40 +++++++++++++++ .../ios-readplace/Tests/TokenStoreTests.swift | 49 +++++++++++++++++++ .../scripts/coverage-baseline.json | 12 ++--- 7 files changed, 245 insertions(+), 9 deletions(-) create mode 100644 projects/ios-readplace/Tests/KeychainTokenStorageTests.swift diff --git a/projects/ios-readplace/Shared/TokenStore.swift b/projects/ios-readplace/Shared/TokenStore.swift index ab7ef6c9d..d9c1d560e 100644 --- a/projects/ios-readplace/Shared/TokenStore.swift +++ b/projects/ios-readplace/Shared/TokenStore.swift @@ -61,10 +61,23 @@ struct TokenStore { guard let url = Bundle.main.url(forResource: "embedded", withExtension: "mobileprovision"), let data = try? Data(contentsOf: url), + let group = parseAppGroupId(fromProvisioningProfile: data) + else { return AppConfig.appGroupId } + return group + }() + + /// Extracts the first application-group entitlement from an embedded + /// `.mobileprovision` blob, or nil when it can't be read. The profile is a + /// CMS-signed container with a plist inside; the signature isn't verified here + /// (the OS already did at install), so this just scans out the `` + /// slice and reads the entitlement. Pulled out of `resolvedAppGroupId` so the + /// parsing is unit-testable without a real `Bundle.main`. + static func parseAppGroupId(fromProvisioningProfile data: Data) -> String? { + guard let raw = String(data: data, encoding: .isoLatin1), let start = raw.range(of: "") - else { return AppConfig.appGroupId } + else { return nil } let plistString = String(raw[start.lowerBound.. Data { + // A .mobileprovision is a CMS blob with a plist inside; the parser only + // scans out the slice, so wrapping it in arbitrary bytes + // models the real container without needing a signed profile. + Data("....signature-bytes....\(innerPlist)....trailer....".utf8) + } + + func testParsesTheFirstApplicationGroupFromAProfile() { + let plist = """ + + + Entitlements + com.apple.security.application-groups + group.com.rewritten.readplacegroup.other + + """ + XCTAssertEqual( + TokenStore.parseAppGroupId(fromProvisioningProfile: profile(plist)), + "group.com.rewritten.readplace" + ) + } + + func testReturnsNilWhenTheProfileHasNoPlist() { + XCTAssertNil(TokenStore.parseAppGroupId(fromProvisioningProfile: Data("no plist here".utf8))) + } + + func testReturnsNilWhenTheProfileHasNoAppGroupEntitlement() { + let plist = """ + + + Entitlementsapplication-identifierABCDE.com.x + + """ + XCTAssertNil(TokenStore.parseAppGroupId(fromProvisioningProfile: profile(plist))) + } + + func testReturnsNilWhenTheAppGroupArrayIsEmpty() { + let plist = """ + + + Entitlements + com.apple.security.application-groups + + """ + XCTAssertNil(TokenStore.parseAppGroupId(fromProvisioningProfile: profile(plist))) + } } diff --git a/projects/ios-readplace/scripts/coverage-baseline.json b/projects/ios-readplace/scripts/coverage-baseline.json index 6529d41fe..6323ef461 100644 --- a/projects/ios-readplace/scripts/coverage-baseline.json +++ b/projects/ios-readplace/scripts/coverage-baseline.json @@ -24,14 +24,14 @@ "PKCE.swift": 95, "ReadplaceAPI.swift": 94, "URLDetection.swift": 94, - "ReadingListViewModel.swift": 93, + "ReadingListViewModel.swift": 94, "SaveSharedPage.swift": 93, "LoginView.swift": 91, "AffordancePresentation.swift": 91, - "OAuthService.swift": 89, - "TokenStore.swift": 85, - "AppSession.swift": 79, - "ReadplaceApp.swift": 64, - "KeychainTokenStorage.swift": 57 + "TokenStore.swift": 98, + "OAuthService.swift": 97, + "KeychainTokenStorage.swift": 97, + "AppSession.swift": 82, + "ReadplaceApp.swift": 64 } }