import XCTest @testable import AIStudyApp @MainActor final class MarkedAsReadTests: 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: - markAsRead func test_markAsRead_from_idle_does_not_crash() { manager.markAsRead() // Guard prevents action when no active session XCTAssertTrue(true) } func test_markAsRead_after_open_attempt_does_not_crash() { let ctx = ReadingMaterialContext( readingTargetType: .knowledgeSource, materialId: "mar-test-001" ) try? manager.openMaterial(ctx) // State may be .failed (noop pushOpened throws), but markAsRead guards manager.markAsRead() XCTAssertTrue(true) } // MARK: - Duplicate guard (View level) func test_duplicate_mark_as_read_is_safe() { let ctx = ReadingMaterialContext( readingTargetType: .knowledgeSource, materialId: "mar-dup-001" ) try? manager.openMaterial(ctx) // Call multiple times — should not crash or produce duplicate side effects for _ in 0..<5 { manager.markAsRead() } XCTAssertTrue(true) } // MARK: - Session close after mark func test_close_after_mark_as_read_does_not_crash() { let ctx = ReadingMaterialContext( readingTargetType: .knowledgeSource, materialId: "mar-close-001" ) try? manager.openMaterial(ctx) manager.markAsRead() manager.closeMaterial() XCTAssertTrue(true) } // MARK: - V1 fallback func test_collector_mark_as_read_does_not_crash() { let collector = ReadingEventCollector.shared collector.open(materialId: "mar-v1-001") collector.markAsRead(materialId: "mar-v1-001") let events = collector.exportPending() let hasMarked = events.contains { event in if case .markedAsRead = event { return true } return false } XCTAssertTrue(hasMarked || !events.isEmpty, "V1 markAsRead should produce event") collector.clearExported(events.count) _ = collector.close(materialId: "mar-v1-001") let remaining = collector.exportPending() collector.clearExported(remaining.count) } // MARK: - Mapper coverage func test_mapper_supports_marked_as_read_event_type() { let eventType = ReadingEventMapper.apiEventType(.markedAsRead) XCTAssertEqual(eventType, "marked_as_read") } }