Files
vnidrop/apple/Tests/ApprovalCoordinatorTests.swift
cdricms 17099d32f2 test(apple): CoreGateway seam, XCTest suite, and CI
- Introduce a CoreGateway protocol so feature models depend on a seam
  (CoreRepository conforms); enables faking the core in tests
- Add a VniDropTests target with 42 tests mirroring the KMP suites:
  approval coordinator, send/receive/settings/app models, preferences,
  file previews, invitation decode, message queue, error mapping
- Add a fake gateway/file-system/device-info and fixtures
- Add .github/workflows/apple.yml: build the Rust core, generate the
  project, and run the tests on an iOS Simulator
2026-07-19 23:48:11 +02:00

70 lines
2.7 KiB
Swift

import XCTest
import Combine
@testable import VniDrop
/// Ports `feature/approvals/ApprovalCoordinatorTest.kt` (the gateway-observable
/// parts; notification assertions require a notification-service seam we don't
/// have on Apple yet).
@MainActor
final class ApprovalCoordinatorTests: XCTestCase {
private func makeCoordinator(_ core: FakeCoreGateway) -> ApprovalCoordinator {
ApprovalCoordinator(
repository: core,
preferences: Fixtures.preferences(),
notifications: LocalNotificationService(),
visibility: AppVisibility(),
messages: UiMessageController()
)
}
func testOrdersPendingRequestsByRequestedAt() async {
let core = FakeCoreGateway()
core.requests[1] = [Fixtures.request(id: "new", requestedAt: 20),
Fixtures.request(id: "old", requestedAt: 10)]
let coordinator = makeCoordinator(core)
core.setState(CoreState(isInitialized: true, transfers: [Fixtures.transfer(id: 1, direction: .send, status: .sharing)]))
core.emit(.approvalChanged(transferId: 1))
await waitUntil { !coordinator.state.pending.isEmpty }
XCTAssertEqual(coordinator.state.pending.map(\.id), ["old", "new"])
XCTAssertEqual(coordinator.state.current?.id, "old")
}
func testFailedResponseKeepsRequestVisibleAndClearsResponding() async {
let core = FakeCoreGateway()
core.requests[1] = [Fixtures.request(id: "request", requestedAt: 10)]
core.responseResult = .failure(TestError.unimplemented)
let coordinator = makeCoordinator(core)
core.setState(CoreState(isInitialized: true, transfers: [Fixtures.transfer(id: 1, direction: .send, status: .sharing)]))
core.emit(.approvalChanged(transferId: 1))
await waitUntil { coordinator.state.pending.contains { $0.id == "request" } }
coordinator.accept("request")
await waitUntil { coordinator.state.respondingIds.isEmpty && core.responses.count == 1 }
XCTAssertTrue(coordinator.state.pending.contains { $0.id == "request" })
XCTAssertTrue(coordinator.state.respondingIds.isEmpty)
XCTAssertEqual(core.responses.first?.accepted, true)
}
func testAcceptRespondsPositivelyAndSingleFlights() async {
let core = FakeCoreGateway()
core.requests[1] = [Fixtures.request(id: "request", requestedAt: 10)]
let coordinator = makeCoordinator(core)
core.setState(CoreState(isInitialized: true, transfers: [Fixtures.transfer(id: 1, direction: .send, status: .sharing)]))
core.emit(.approvalChanged(transferId: 1))
await waitUntil { coordinator.state.current != nil }
coordinator.accept("request")
coordinator.accept("request") // second call must be ignored (single-flight)
await waitUntil { core.responses.count >= 1 }
try? await Task.sleep(nanoseconds: 50_000_000)
XCTAssertEqual(core.responses.count, 1)
XCTAssertEqual(core.responses.first?.id, "request")
}
}