Files
Kordant/iOS/KordantTests/ATTServiceTests.swift
Michael Freno e33ddf3002 feat: complete Tasks 21-28 — backend integration, security hardening, UI tests & CI
- Add Apple Sign-In backend (JWKS verification, account linking, session management)
- Implement push notification deep linking with NotificationDeepLinkRouter
- Add jailbreak detection, runtime integrity monitoring, secure enclave service
- Implement OAuth social login, token refresh, and secure logout flows
- Add image caching (memory/disk), optimizer, upload queue, async semaphore
- Implement notification analytics, type preferences, and category setup
- Expand UI test suite with UITestBase, accessibility, auth flow, performance tests
- Add CI pipeline for iOS UI tests (3 device sizes) and performance benchmarks
- Restructure Xcode project to manual groups with KordantWidgets target
- Add SwiftLint, Swift Collections/Algorithms/GoogleSignIn dependencies
- Update project.yml for XcodeGen with new targets and configurations
2026-06-02 15:01:38 -04:00

230 lines
7.4 KiB
Swift

import Testing
@testable import Kordant
import AppTrackingTransparency
// MARK: - ATTService Tests
struct ATTServiceTests {
/// Creates an ATTService instance with an isolated UserDefaults suite for testing.
@MainActor
private func makeService() -> ATTService {
let defaults = UserDefaults(suiteName: UUID().uuidString)!
return ATTService(defaults: defaults)
}
// MARK: - Initial State
@Test("ATTService starts with notDetermined status and no permission requested")
@MainActor
func initialState() {
let service = makeService()
#expect(service.trackingStatus == .notDetermined)
#expect(service.hasRequestedPermission == false)
#expect(service.hasShownExplanation == false)
#expect(service.analyticsMode == .anonymous)
#expect(service.shouldShowATTPrompt() == true)
#expect(service.shouldShowExplanation() == true)
}
@Test("ATTService restores persisted permission-requested state")
@MainActor
func persistedRequestState() {
let defaults = UserDefaults(suiteName: UUID().uuidString)!
defaults.set(true, forKey: "kordant.att.requested")
let service = ATTService(defaults: defaults)
#expect(service.hasRequestedPermission == true)
#expect(service.shouldShowATTPrompt() == false)
}
@Test("ATTService restores persisted explanation-shown state")
@MainActor
func persistedExplanationState() {
let defaults = UserDefaults(suiteName: UUID().uuidString)!
defaults.set(true, forKey: "kordant.att.explanationShown")
let service = ATTService(defaults: defaults)
#expect(service.hasShownExplanation == true)
#expect(service.shouldShowExplanation() == false)
}
// MARK: - Authorization Status Checks
@Test("ATTService isTrackingAuthorized returns true only for .authorized")
@MainActor
func trackingAuthorized() {
let service = makeService()
#expect(service.isTrackingAuthorized() == false)
// Simulate authorized state
service.trackingStatus = .authorized
#expect(service.isTrackingAuthorized() == true)
service.trackingStatus = .denied
#expect(service.isTrackingAuthorized() == false)
}
@Test("ATTService isTrackingDenied returns true only for .denied")
@MainActor
func trackingDenied() {
let service = makeService()
service.trackingStatus = .denied
#expect(service.isTrackingDenied() == true)
service.trackingStatus = .authorized
#expect(service.isTrackingDenied() == false)
}
@Test("ATTService isTrackingRestricted returns true only for .restricted")
@MainActor
func trackingRestricted() {
let service = makeService()
service.trackingStatus = .restricted
#expect(service.isTrackingRestricted() == true)
service.trackingStatus = .authorized
#expect(service.isTrackingRestricted() == false)
}
// MARK: - Analytics Mode
@Test("ATTService analytics mode is anonymous for all non-authorized states")
@MainActor
func analyticsModeForDenied() {
let service = makeService()
service.trackingStatus = .denied
service.refreshStatus()
#expect(service.analyticsMode == .anonymous)
#expect(service.analyticsMode.usesIDFA == false)
}
@Test("ATTService analytics mode is anonymous for restricted state")
@MainActor
func analyticsModeForRestricted() {
let service = makeService()
service.trackingStatus = .restricted
service.refreshStatus()
#expect(service.analyticsMode == .anonymous)
#expect(service.analyticsMode.usesIDFA == false)
}
@Test("ATTService analytics mode is full for authorized state")
@MainActor
func analyticsModeForAuthorized() {
let service = makeService()
service.trackingStatus = .authorized
service.refreshStatus()
#expect(service.analyticsMode == .full)
#expect(service.analyticsMode.usesIDFA == true)
}
@Test("ATTService analytics mode is anonymous for notDetermined")
@MainActor
func analyticsModeForNotDetermined() {
let service = makeService()
#expect(service.analyticsMode == .anonymous)
#expect(service.analyticsMode.usesIDFA == false)
}
// MARK: - Prompt Logic
@Test("ATTService shouldShowATTPrompt returns false after permission requested")
@MainActor
func promptNotShownAfterRequest() {
let service = makeService()
#expect(service.shouldShowATTPrompt() == true)
service.hasRequestedPermission = true
#expect(service.shouldShowATTPrompt() == false)
}
@Test("ATTService shouldShowATTPrompt returns false if already authorized")
@MainActor
func promptNotShownIfAuthorized() {
let service = makeService()
service.trackingStatus = .authorized
#expect(service.shouldShowATTPrompt() == false)
}
@Test("ATTService shouldShowATTPrompt returns false if denied previously")
@MainActor
func promptNotShownIfDenied() {
let service = makeService()
service.hasRequestedPermission = true
service.trackingStatus = .denied
#expect(service.shouldShowATTPrompt() == false)
}
@Test("ATTService shouldShowExplanation returns true only before explanation shown")
@MainActor
func explanationLogic() {
let service = makeService()
#expect(service.shouldShowExplanation() == true)
service.markExplanationShown()
#expect(service.shouldShowExplanation() == false)
#expect(service.hasShownExplanation == true)
}
// MARK: - State Management
@Test("ATTService markExplanationShown persists state")
@MainActor
func markExplanationPersists() {
let defaults = UserDefaults(suiteName: UUID().uuidString)!
let service = ATTService(defaults: defaults)
service.markExplanationShown()
// Create a new instance and verify persistence
let service2 = ATTService(defaults: defaults)
#expect(service2.hasShownExplanation == true)
}
@Test("ATTService refreshStatus updates tracking status")
@MainActor
func refreshStatus() {
let service = makeService()
#expect(service.trackingStatus == .notDetermined)
// Simulate status change (in a real scenario the system updates this)
service.trackingStatus = .denied
#expect(service.analyticsMode == .denied) // indirect: analyticsMode should become anonymous
}
@Test("ATTService resetPermissionState clears request flag")
@MainActor
func resetPermissionState() {
let defaults = UserDefaults(suiteName: UUID().uuidString)!
let service = ATTService(defaults: defaults)
service.hasRequestedPermission = true
defaults.set(true, forKey: "kordant.att.requested")
service.resetPermissionState()
#expect(service.hasRequestedPermission == false)
}
}
// MARK: - AnalyticsMode Tests
struct AnalyticsModeTests {
@Test("AnalyticsMode usesIDFA is true only for full mode")
func usesIDFA() {
#expect(AnalyticsMode.anonymous.usesIDFA == false)
#expect(AnalyticsMode.full.usesIDFA == true)
}
@Test("AnalyticsMode raw values are correct")
func rawValues() {
#expect(AnalyticsMode.anonymous.rawValue == "anonymous")
#expect(AnalyticsMode.full.rawValue == "full")
}
}