Files
2026-06-26 14:33:31 +08:00

180 lines
8.4 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// LocationReportTests.swift
// suixinkanTests
//
// Created by Codex on 2026/6/24.
//
import XCTest
@testable import suixinkan_ios
@MainActor
/// API
final class LocationReportAPITests: XCTestCase {
/// query
func testReportLocationUsesExpectedQuery() async throws {
let session = LocationRecordingSession(data: Self.submitResponse)
let api = LocationReportAPI(client: APIClient(session: session))
let response = try await api.reportLocation(staffId: 77, latitude: 30.1, longitude: 120.2, address: "入口", type: .immediate, scenicId: 88)
let request = try XCTUnwrap(session.requests.first)
XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/loacation/report")
let query = locationQueryItems(from: request)
XCTAssertEqual(query["staff_id"], "77")
XCTAssertEqual(query["latitude"], "30.1")
XCTAssertEqual(query["longitude"], "120.2")
XCTAssertEqual(query["address"], "入口")
XCTAssertEqual(query["type"], "1")
XCTAssertEqual(query["scenic_id"], "88")
XCTAssertEqual(response.expired, 7200)
}
///
func testHistoryUsesExpectedQueryAndDecodesLossyFields() async throws {
let session = LocationRecordingSession(data: Self.historyResponse)
let api = LocationReportAPI(client: APIClient(session: session))
let payload = try await api.locationReportList(staffId: 77, page: 0, pageSize: 0, type: .marked, startDate: "2026-06-01", endDate: "2026-06-24")
let request = try XCTUnwrap(session.requests.first)
XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/loacation/list")
let query = locationQueryItems(from: request)
XCTAssertEqual(query["staff_id"], "77")
XCTAssertEqual(query["page"], "1")
XCTAssertEqual(query["page_size"], "1")
XCTAssertEqual(query["type"], "2")
XCTAssertEqual(query["start_date"], "2026-06-01")
XCTAssertEqual(query["end_date"], "2026-06-24")
XCTAssertEqual(payload.total, 1)
XCTAssertEqual(payload.list.first?.staffId, 77)
XCTAssertEqual(payload.list.first?.latitude, "30.1")
}
private static let submitResponse = Data(#"{"code":100000,"msg":"ok","data":{"staff_id":77,"expired":"7200","status":"1"}}"#.utf8)
private static let historyResponse = Data(#"{"code":100000,"msg":"ok","data":{"total":"1","list":[{"id":"1","staff_id":"77","type":"2","latitude":30.1,"longitude":"120.2","address":"","ip":"127.0.0.1","remark":"ok","created_at":"2026-06-24 10:00:00"}]}}"#.utf8)
}
@MainActor
/// ViewModel
final class LocationReportViewModelTests: XCTestCase {
/// staffId scenicId
func testSubmitRequiresStaffAndScenic() async {
let api = MockLocationReportService()
let viewModel = LocationReportViewModel()
viewModel.applyCurrentLocation(latitude: 30.1, longitude: 120.2, address: "入口")
let missingStaff = await viewModel.submit(type: .immediate, staffId: nil, scenicId: 88, api: api)
let missingScenic = await viewModel.submit(type: .immediate, staffId: 77, scenicId: nil, api: api)
XCTAssertFalse(missingStaff)
XCTAssertFalse(missingScenic)
XCTAssertEqual(api.reportRequests.count, 0)
}
/// 线使
func testSubmitUsesExpectedTypes() async {
let api = MockLocationReportService()
let viewModel = LocationReportViewModel()
viewModel.applyCurrentLocation(latitude: 30.1, longitude: 120.2, address: "入口")
viewModel.applyMarkedLocation(latitude: 31.1, longitude: 121.2, address: "标记点")
_ = await viewModel.submit(type: .immediate, staffId: 77, scenicId: 88, api: api)
_ = await viewModel.submit(type: .marked, staffId: 77, scenicId: 88, api: api)
_ = await viewModel.setOnline(true, staffId: 77, scenicId: 88, api: api)
XCTAssertEqual(api.reportRequests.map(\.type), [.immediate, .marked, .onlineStatus])
XCTAssertEqual(api.reportRequests[1].latitude, 31.1)
XCTAssertEqual(viewModel.secondsUntilReport, 600)
}
///
func testSubmitFailureKeepsState() async {
let api = MockLocationReportService()
api.shouldFailReport = true
let viewModel = LocationReportViewModel()
viewModel.applyCurrentLocation(latitude: 30.1, longitude: 120.2, address: "入口")
let success = await viewModel.submit(type: .immediate, staffId: 77, scenicId: 88, api: api)
XCTAssertFalse(success)
XCTAssertEqual(viewModel.secondsUntilReport, 0)
XCTAssertNotNil(viewModel.errorMessage)
}
///
func testHistoryFilterAndPagination() async {
let api = MockLocationReportService()
api.historyPages = [
ListPayload(total: 2, list: [LocationReportHistoryItem.fixture(id: 1)]),
ListPayload(total: 2, list: [LocationReportHistoryItem.fixture(id: 2)])
]
let viewModel = LocationReportHistoryViewModel()
viewModel.selectedType = .marked
await viewModel.reload(staffId: 77, api: api)
await viewModel.loadMore(staffId: 77, api: api)
await viewModel.loadMore(staffId: 77, api: api)
XCTAssertEqual(viewModel.items.map(\.id), [1, 2])
XCTAssertEqual(api.historyRequests.map(\.page), [1, 2])
XCTAssertEqual(api.historyRequests.first?.type, .marked)
}
}
/// API URLSession
private final class LocationRecordingSession: URLSessionProtocol {
let data: Data
private(set) var requests: [URLRequest] = []
/// Session
init(data: Data) {
self.data = data
}
///
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
requests.append(request)
let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
return (data, response)
}
}
@MainActor
///
private final class MockLocationReportService: LocationReportServing {
var shouldFailReport = false
var reportRequests: [(staffId: Int, latitude: Double, longitude: Double, address: String, type: LocationReportType, scenicId: Int)] = []
var historyPages: [ListPayload<LocationReportHistoryItem>] = [ListPayload(total: 0, list: [])]
var historyRequests: [(staffId: Int, page: Int, pageSize: Int, type: LocationReportType, startDate: String?, endDate: String?)] = []
func reportLocation(staffId: Int, latitude: Double, longitude: Double, address: String, type: LocationReportType, scenicId: Int) async throws -> LocationReportSubmitResponse {
if shouldFailReport { throw NSError(domain: "location", code: 1) }
reportRequests.append((staffId, latitude, longitude, address, type, scenicId))
return LocationReportSubmitResponse(staffId: "\(staffId)", expired: 600, status: 1)
}
func locationReportList(staffId: Int, page: Int, pageSize: Int, type: LocationReportType, startDate: String?, endDate: String?) async throws -> ListPayload<LocationReportHistoryItem> {
historyRequests.append((staffId, page, pageSize, type, startDate, endDate))
return historyPages.isEmpty ? ListPayload(total: 0, list: []) : historyPages.removeFirst()
}
}
private extension LocationReportHistoryItem {
///
static func fixture(id: Int) -> LocationReportHistoryItem {
let data = Data(#"{"id":\#(id),"staff_id":77,"type":1,"latitude":"30.1","longitude":"120.2","address":"","ip":"","remark":"","created_at":"2026-06-24"}"#.utf8)
return try! JSONDecoder().decode(LocationReportHistoryItem.self, from: data)
}
}
/// query
private func locationQueryItems(from request: URLRequest) -> [String: String] {
guard let url = request.url, let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return [:]
}
return Dictionary(uniqueKeysWithValues: (components.queryItems ?? []).compactMap { item in
item.value.map { (item.name, $0) }
})
}