ios-projects/AIStudyApp/AIStudyAppTests/ReadingAPIClientTests.swift
wangdl 4ba747af46 feat: iOS learning info P0 complete (IOS-INFO-000~017, 038~039)
Models: ReadingMaterialContext, ReadingEventUploadItem, AppContext
Runtime: ReadingRuntimeAdapter, SessionManager, RecoveryService
Reader: MaterialReaderView lifecycle, Heartbeat Timer, Position Adapter
Export: EventMapper, UploadQueue, export→queue→ack flow
Tests: 16 test files covering models, runtime, lifecycle, upload
Docs: ios-learning-info-architecture.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 21:53:36 +08:00

136 lines
4.0 KiB
Swift

import XCTest
@testable import AIStudyApp
final class ReadingAPIClientTests: XCTestCase {
// MARK: - Batch response decoding
func test_batch_response_decodes_all_fields() throws {
let json = """
{
"processed": 5,
"duplicate": 2,
"failed": 1,
"warnings": [
{"eventId": "evt-001", "code": "DUPLICATE_EVENT", "message": "already processed"}
]
}
""".data(using: .utf8)!
let resp = try JSONDecoder().decode(ReadingEventBatchResponse.self, from: json)
XCTAssertEqual(resp.processed, 5)
XCTAssertEqual(resp.duplicate, 2)
XCTAssertEqual(resp.failed, 1)
XCTAssertEqual(resp.warnings?.count, 1)
}
func test_batch_response_no_warnings() throws {
let json = """
{
"processed": 3,
"duplicate": 0,
"failed": 0
}
""".data(using: .utf8)!
let resp = try JSONDecoder().decode(ReadingEventBatchResponse.self, from: json)
XCTAssertEqual(resp.processed, 3)
XCTAssertNil(resp.warnings)
}
func test_batch_response_duplicate_not_counted_as_failed() throws {
let json = """
{
"processed": 1,
"duplicate": 5,
"failed": 0
}
""".data(using: .utf8)!
let resp = try JSONDecoder().decode(ReadingEventBatchResponse.self, from: json)
// Duplicate should not count as failed
XCTAssertEqual(resp.failed, 0)
XCTAssertEqual(resp.duplicate, 5)
}
// MARK: - API service existence
func test_api_service_shared_exists() {
let api = ReadingAPIService.shared
XCTAssertNotNil(api)
}
// MARK: - Other response DTOs
func test_material_reading_progress_decodes() throws {
let json = """
{
"status": "reading",
"lastProgress": 0.65,
"totalActiveSeconds": 1200,
"isMarkedRead": false,
"firstOpenedAt": "2026-01-01T00:00:00Z",
"lastReadAt": "2026-06-12T00:00:00Z"
}
""".data(using: .utf8)!
let dto = try JSONDecoder().decode(MaterialReadingProgressDTO.self, from: json)
XCTAssertEqual(dto.status, "reading")
XCTAssertEqual(dto.totalActiveSeconds, 1200)
XCTAssertFalse(dto.isMarkedRead)
}
func test_continue_learning_response_decodes() throws {
let json = """
{
"type": "knowledge_source",
"materialId": "mat-001",
"title": "Rust Book",
"lastProgress": 0.4,
"totalActiveSeconds": 600,
"lastReadAt": "2026-06-12T00:00:00Z"
}
""".data(using: .utf8)!
let dto = try JSONDecoder().decode(ContinueLearningResponse.self, from: json)
XCTAssertEqual(dto.type, "knowledge_source")
XCTAssertEqual(dto.materialId, "mat-001")
}
func test_continue_learning_response_none_type() throws {
let json = """
{
"type": "none",
"materialId": null,
"title": null,
"lastProgress": null,
"totalActiveSeconds": null,
"lastReadAt": null
}
""".data(using: .utf8)!
let dto = try JSONDecoder().decode(ContinueLearningResponse.self, from: json)
XCTAssertEqual(dto.type, "none")
XCTAssertNil(dto.materialId)
}
func test_learning_summary_decodes() throws {
let json = """
{
"todaySeconds": 300,
"weekSeconds": 2100,
"totalSeconds": 50000,
"activeDays": 45,
"sessionsCount": 120,
"materialsReadCount": 15,
"markedReadCount": 8,
"dailyAverageSeconds": 1100
}
""".data(using: .utf8)!
let dto = try JSONDecoder().decode(LearningSummaryResponse.self, from: json)
XCTAssertEqual(dto.todaySeconds, 300)
XCTAssertEqual(dto.materialsReadCount, 15)
}
}