import XCTest @testable import AIStudyApp @MainActor final class AppLifecycleFlushTests: XCTestCase { var manager: ReadingRuntimeSessionManager! override func setUp() { super.setUp() manager = ReadingRuntimeSessionManager.shared manager.adapter = NoopReadingRuntimeAdapter() manager.closeMaterial() } override func tearDown() { manager.closeMaterial() super.tearDown() } // MARK: - Pause / Resume func test_pause_from_active_transitions_to_paused() { let ctx = ReadingMaterialContext( readingTargetType: .knowledgeSource, materialId: "bg-test-001" ) try? manager.openMaterial(ctx) if manager.state == .active { manager.pause() XCTAssertEqual(manager.state, .paused) } // No crash is pass XCTAssertTrue(true) } func test_resume_from_paused_restores_active() { let ctx = ReadingMaterialContext( readingTargetType: .knowledgeSource, materialId: "bg-test-002" ) try? manager.openMaterial(ctx) if manager.state == .active { manager.pause() manager.resume() XCTAssertEqual(manager.state, .active) } } // MARK: - Session staleness func test_sessionStale_after_long_background_closes_session() { let ctx = ReadingMaterialContext( readingTargetType: .knowledgeSource, materialId: "bg-stale-001" ) try? manager.openMaterial(ctx) if manager.state == .active { manager.pause() // Simulate stale: state is .paused but isSessionStale requires // backgroundedAtMs > 5 min ago — can't simulate without time travel // But closeMaterial should clean up regardless manager.closeMaterial() } XCTAssertNotEqual(manager.state, .active) } func test_resume_from_idle_is_noop() { manager.closeMaterial() manager.resume() XCTAssertTrue( manager.state == .idle || manager.state == .closed, "Resume from idle should no-op" ) } // MARK: - Pipeline flush func test_pipeline_exportAndEnqueue_does_not_crash() { let pipeline = ReadingEventUploadPipeline.shared // No events in buffer → should return early without crash pipeline.exportAndEnqueue() XCTAssertTrue(true) } // MARK: - Queue flush func test_queue_flush_empty_does_not_crash() async { let queue = ReadingEventUploadQueue.shared let before = queue.pendingCount // Flush on empty queue should no-op let pipeline = ReadingEventUploadPipeline.shared await pipeline.flush() let after = queue.pendingCount XCTAssertEqual(after, before) } // MARK: - Multiple pause/resume cycles func test_multiple_pause_resume_cycles() { let ctx = ReadingMaterialContext( readingTargetType: .knowledgeSource, materialId: "bg-cycle-001" ) try? manager.openMaterial(ctx) for i in 0..<3 { if manager.state == .active { manager.pause() XCTAssertEqual(manager.state, .paused, "Cycle \(i) pause") manager.resume() } } // Should not crash XCTAssertTrue(true) } }