import XCTest @testable import AIStudyApp @MainActor final class MaterialReaderLifecycleTests: 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: - Open / Close func test_open_session_transitions_to_active_or_failed() { let ctx = ReadingMaterialContext( readingTargetType: .knowledgeSource, materialId: "mat-lifecycle-001" ) // Noop adapter throws on pushOpened → state goes to .failed // But startSession succeeds, so sessionId should be set then cleared do { try manager.openMaterial(ctx) } catch { // Expected with noop adapter } // State should not be idle (either active briefly or failed) XCTAssertNotNil(manager.state) } func test_close_after_open_does_not_crash() { let ctx = ReadingMaterialContext( readingTargetType: .knowledgeSource, materialId: "mat-lifecycle-002" ) try? manager.openMaterial(ctx) manager.closeMaterial() // Should reach here without crash XCTAssertTrue(true) } // MARK: - Pause / Resume func test_pause_from_active_transitions_to_paused() { // Noop adapter: startSession succeeds, pushOpened fails // So after open, state is .failed, not .active // Pause should no-op for non-active manager.pause() // Should not crash XCTAssertTrue(true) } func test_resume_from_paused_transitions_to_active() { manager.resume() // Should not crash when not paused XCTAssertTrue(true) } // MARK: - Duplicate open func test_duplicate_open_sequence_does_not_crash() { let ctx = ReadingMaterialContext( readingTargetType: .temporaryFile, materialId: "mat-dup-001" ) try? manager.openMaterial(ctx) // Second open should throw or be handled do { try manager.openMaterial(ctx) } catch { // Expected } manager.closeMaterial() XCTAssertTrue(true) } // MARK: - Scene phase simulation func test_multiple_pause_resume_cycles_do_not_crash() { for _ in 0..<3 { manager.pause() manager.resume() } XCTAssertTrue(true, "Multiple pause/resume cycles should not crash") } func test_close_during_active_session_clears_state() { let ctx = ReadingMaterialContext( readingTargetType: .knowledgeSource, materialId: "mat-clear-001" ) try? manager.openMaterial(ctx) manager.closeMaterial() manager.closeMaterial() // double close XCTAssertNil(manager.activeSessionId) } }