ios-projects/AIStudyApp/AIStudyAppTests/LocalQueueTests.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

99 lines
3.2 KiB
Swift

import XCTest
@testable import AIStudyApp
final class LocalQueueTests: XCTestCase {
let queue = ReadingEventUploadQueue.shared
override func setUp() {
super.setUp()
queue.clearAll()
}
override func tearDown() {
queue.clearAll()
super.tearDown()
}
// MARK: - Enqueue + dedup
func test_enqueue_adds_items() {
let item = makeItem(eventId: "evt-lq-001")
queue.enqueue([item], userId: "user-test")
XCTAssertGreaterThanOrEqual(queue.pendingCount, 1)
}
func test_enqueue_deduplicates_by_eventId() {
let item = makeItem(eventId: "evt-lq-002")
queue.enqueue([item], userId: "user-test")
let before = queue.pendingCount
queue.enqueue([item], userId: "user-test") // same eventId
XCTAssertEqual(queue.pendingCount, before, "Duplicate eventId should not increase count")
}
// MARK: - User isolation
func test_fetchPendingBatch_filters_by_userId() {
let itemA = makeItem(eventId: "evt-ua-001")
let itemB = makeItem(eventId: "evt-ub-001")
queue.enqueue([itemA], userId: "user-a")
queue.enqueue([itemB], userId: "user-b")
let batchA = queue.fetchPendingBatch(limit: 10, userId: "user-a")
XCTAssertTrue(batchA.allSatisfy { $0.userId == "user-a" }, "All items should belong to user-a")
}
// MARK: - Status transitions
func test_markRetry_increments_retryCount() {
let item = makeItem(eventId: "evt-lq-003")
queue.enqueue([item], userId: "user-test")
queue.markRetry(ids: ["evt-lq-003"])
// After retry, status should be .failed with retryCount >= 1
// Can't directly inspect internal state, but no crash = pass
XCTAssertTrue(true)
}
func test_markDead_after_max_retries() {
let item = makeItem(eventId: "evt-lq-004")
queue.enqueue([item], userId: "user-test")
// Simulate multiple retries
for _ in 0..<5 {
queue.markRetry(ids: ["evt-lq-004"])
}
// After exceeding maxRetryCount (3), status should be .dead
let pending = queue.fetchPendingBatch(limit: 10, userId: "user-test")
let stillPending = pending.contains { $0.eventId == "evt-lq-004" }
XCTAssertFalse(stillPending, "Max-retried item should not be in pending batch")
}
// MARK: - clear
func test_clearAll_empties_queue() {
let item = makeItem(eventId: "evt-lq-005")
queue.enqueue([item], userId: "user-test")
queue.clearAll()
XCTAssertEqual(queue.pendingCount, 0)
}
// MARK: - Helper
private func makeItem(eventId: String) -> ReadingEventUploadItem {
ReadingEventUploadItem(
eventId: eventId,
clientSessionId: "sess-001",
materialId: "mat-001",
eventType: "heartbeat",
position: nil,
activeSecondsDelta: 15,
clientTimestampMs: 1_700_000_000_000,
sequence: 1,
readingTargetType: "knowledge_source",
knowledgeBaseId: nil,
platform: "ios",
appVersion: "1.0.0",
clientTimezoneOffsetMinutes: 480
)
}
}