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
This commit is contained in:
173
iOS/KordantTests/LaunchTimeTests.swift
Normal file
173
iOS/KordantTests/LaunchTimeTests.swift
Normal file
@@ -0,0 +1,173 @@
|
||||
import Testing
|
||||
@testable import Kordant
|
||||
import SwiftUI
|
||||
import OSLog
|
||||
|
||||
// MARK: - LaunchTimer Tests
|
||||
|
||||
struct LaunchTimerTests {
|
||||
@Test("LaunchTimer tracks elapsed time since process start")
|
||||
func elapsedSinceProcessStart() {
|
||||
let timer = LaunchTimer.shared
|
||||
let elapsed = timer.elapsedSinceProcessStart
|
||||
#expect(elapsed >= 0)
|
||||
}
|
||||
|
||||
@Test("LaunchTimer measures phase start and end")
|
||||
func measurePhase() {
|
||||
let timer = LaunchTimer.shared
|
||||
let id = timer.startPhase("TestPhase")
|
||||
#expect(id >= 0)
|
||||
|
||||
// Simulate some work
|
||||
try? Task.checkCancellation()
|
||||
|
||||
timer.endPhase("TestPhase", signpostID: id)
|
||||
|
||||
let report = timer.report()
|
||||
#expect(report["TestPhase_start"] != nil)
|
||||
#expect(report["TestPhase_end"] != nil)
|
||||
#expect(report["TestPhase_duration"] != nil)
|
||||
#expect(report["total"] != nil)
|
||||
}
|
||||
|
||||
@Test("LaunchTimer logs events")
|
||||
func logEvent() {
|
||||
let timer = LaunchTimer.shared
|
||||
timer.logEvent("TestEvent", "test message")
|
||||
|
||||
let report = timer.report()
|
||||
#expect(report["TestEvent"] != nil)
|
||||
}
|
||||
|
||||
@Test("LaunchTimer report includes total time")
|
||||
func reportContainsTotal() {
|
||||
let timer = LaunchTimer.shared
|
||||
let report = timer.report()
|
||||
#expect(report["total"] != nil)
|
||||
#expect(report["total"]! >= 0)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Launch Performance Tests
|
||||
|
||||
struct LaunchPerformanceTests {
|
||||
/// Measures AuthService initialization time (should be fast, < 10ms)
|
||||
@Test("AuthService init is fast (no blocking work)")
|
||||
@MainActor
|
||||
func authServiceInitTime() {
|
||||
let keychain = MockKeychainService()
|
||||
let apiClient = MockAuthAPIClient()
|
||||
|
||||
let start = Date()
|
||||
let service = AuthService(keychain: keychain, apiClient: apiClient)
|
||||
let elapsed = -start.timeIntervalSinceNow
|
||||
|
||||
// AuthService init should be nearly instantaneous since restoreSession is deferred
|
||||
#expect(elapsed < 0.01, "AuthService init took \(elapsed)s (expected < 10ms)")
|
||||
}
|
||||
|
||||
/// Measures session restoration time
|
||||
@Test("AuthService restoreSession completes quickly")
|
||||
@MainActor
|
||||
func sessionRestoreTime() async {
|
||||
let keychain = MockKeychainService()
|
||||
let apiClient = MockAuthAPIClient()
|
||||
let service = AuthService(keychain: keychain, apiClient: apiClient)
|
||||
|
||||
let start = Date()
|
||||
service.restoreSession()
|
||||
let elapsed = -start.timeIntervalSinceNow
|
||||
|
||||
// Session restore involves keychain lookups, should be fast
|
||||
#expect(elapsed < 0.05, "Session restore took \(elapsed)s (expected < 50ms)")
|
||||
}
|
||||
|
||||
/// Measures SecurityManager initialization (should be lightweight)
|
||||
@Test("SecurityManager init is fast")
|
||||
@MainActor
|
||||
func securityManagerInitTime() {
|
||||
let start = Date()
|
||||
_ = SecurityManager.shared
|
||||
let elapsed = -start.timeIntervalSinceNow
|
||||
|
||||
// SecurityManager should be lazy, init should be instant
|
||||
#expect(elapsed < 0.01, "SecurityManager init took \(elapsed)s (expected < 10ms)")
|
||||
}
|
||||
|
||||
/// Measures NetworkMonitor initialization (should be lazy)
|
||||
@Test("NetworkMonitor init is fast")
|
||||
func networkMonitorInitTime() {
|
||||
let start = Date()
|
||||
let monitor = NetworkMonitor()
|
||||
let elapsed = -start.timeIntervalSinceNow
|
||||
|
||||
// NetworkMonitor should not start monitoring on init
|
||||
#expect(elapsed < 0.01, "NetworkMonitor init took \(elapsed)s (expected < 10ms)")
|
||||
monitor.stopMonitoring()
|
||||
}
|
||||
|
||||
/// Measures ImageCacheService initialization (should be lazy)
|
||||
@Test("ImageCacheService shared init is fast")
|
||||
@MainActor
|
||||
func imageCacheServiceInitTime() {
|
||||
let start = Date()
|
||||
_ = ImageCacheService.shared
|
||||
let elapsed = -start.timeIntervalSinceNow
|
||||
|
||||
// ImageCacheService should not load metadata on init
|
||||
#expect(elapsed < 0.05, "ImageCacheService init took \(elapsed)s (expected < 50ms)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Lazy Loading Verification Tests
|
||||
|
||||
struct LazyLoadingTests {
|
||||
@Test("AuthService does not restore session in init")
|
||||
@MainActor
|
||||
func authServiceNoRestoreOnInit() {
|
||||
let keychain = MockKeychainService()
|
||||
let apiClient = MockAuthAPIClient()
|
||||
|
||||
// Store a token in keychain
|
||||
try? keychain.store(key: "jwt", value: Data("test-token".utf8))
|
||||
try? keychain.store(key: "currentUser", value: try! JSONEncoder().encode(
|
||||
User(id: "1", name: "Test", email: "test@test.com")
|
||||
))
|
||||
|
||||
let service = AuthService(keychain: keychain, apiClient: apiClient)
|
||||
|
||||
// Session should NOT be restored in init
|
||||
#expect(service.state == .unauthenticated)
|
||||
#expect(service.currentUser == nil)
|
||||
|
||||
// After explicit restore, state should update
|
||||
service.restoreSession()
|
||||
#expect(service.state == .authenticated)
|
||||
}
|
||||
|
||||
@Test("NetworkMonitor does not start monitoring on init")
|
||||
func networkMonitorLazyStart() {
|
||||
let monitor = NetworkMonitor()
|
||||
// The monitor property should exist but monitoring should not have started
|
||||
// We can't directly check the private flag, but we verify the behavior
|
||||
// by checking that the default isConnected value hasn't changed
|
||||
#expect(monitor.isConnected == true) // Default value, not from actual monitoring
|
||||
monitor.stopMonitoring()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Build Configuration Tests
|
||||
|
||||
struct BuildConfigTests {
|
||||
@Test("LaunchTimer is available in all configurations")
|
||||
func launchTimerAvailable() {
|
||||
let timer = LaunchTimer.shared
|
||||
#expect(timer != nil)
|
||||
}
|
||||
|
||||
@Test("Build configuration is accessible")
|
||||
func buildConfig() {
|
||||
#expect(ProcessInfo.processInfo.operatingSystemVersionString.count > 0)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user