Add order tail flows and migrate queue, message, settlement, and audit modules.

Wire deposit orders, historical shooting, and multi-travel uploads into Orders, and replace home placeholders for queue management, message center, scenic settlement, and withdrawal audit.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-25 13:39:02 +08:00
parent fb8430889b
commit 1a0d1c25f4
63 changed files with 9823 additions and 58 deletions

View File

@ -0,0 +1,125 @@
//
// WithdrawalAuditViewModelTests.swift
// suixinkanTests
//
// Created by Codex on 2026/6/25.
//
import XCTest
@testable import suixinkan
@MainActor
///
final class WithdrawalAuditViewModelTests: XCTestCase {
///
func testLoadFilterAndLoadMore() async throws {
let api = WithdrawalAuditWalletMock()
api.withdrawResponses = [
WalletWithdrawListResponse(total: 3, item: [
try record(id: 1, status: "审核中"),
try record(id: 2, status: "已到账")
]),
WalletWithdrawListResponse(total: 3, item: [
try record(id: 3, status: "待打款")
])
]
let viewModel = WithdrawalAuditViewModel()
await viewModel.reload(api: api)
XCTAssertEqual(api.withdrawPages, [1])
XCTAssertEqual(viewModel.totalCount, 3)
XCTAssertEqual(viewModel.processingCount, 1)
XCTAssertEqual(viewModel.completedCount, 1)
XCTAssertTrue(viewModel.hasMore)
viewModel.selectFilter(.processing)
XCTAssertEqual(viewModel.filteredRecords.map(\.id), [1])
await viewModel.loadMore(api: api)
XCTAssertEqual(api.withdrawPages, [1, 2])
XCTAssertEqual(viewModel.records.map(\.id), [1, 2, 3])
XCTAssertEqual(viewModel.processingCount, 2)
XCTAssertFalse(viewModel.hasMore)
}
///
func testReloadFailureClearsStaleRecordsAndPaging() async throws {
let api = WithdrawalAuditWalletMock()
api.withdrawResponses = [
WalletWithdrawListResponse(total: 2, item: [try record(id: 1, status: "审核中")])
]
let viewModel = WithdrawalAuditViewModel()
await viewModel.reload(api: api)
XCTAssertEqual(viewModel.records.map(\.id), [1])
XCTAssertTrue(viewModel.hasMore)
api.withdrawError = TestError.sample
await viewModel.reload(api: api)
XCTAssertTrue(viewModel.records.isEmpty)
XCTAssertFalse(viewModel.hasMore)
XCTAssertTrue(viewModel.loadFailed)
XCTAssertEqual(viewModel.loadFailureReason, TestError.sample.localizedDescription)
}
///
func testEmptyListAndLastPageDoNotLoadMore() async {
let api = WithdrawalAuditWalletMock()
api.withdrawResponses = [WalletWithdrawListResponse(total: 0, item: [])]
let viewModel = WithdrawalAuditViewModel()
await viewModel.reload(api: api)
await viewModel.loadMore(api: api)
XCTAssertTrue(viewModel.records.isEmpty)
XCTAssertEqual(api.withdrawPages, [1])
XCTAssertFalse(viewModel.hasMore)
}
private func record(id: Int64, amount: String = "10", status: String) throws -> WalletWithdrawRecord {
let data = """
{"id":\(id),"amount":"\(amount)","created_at":"2026-06-25 10:00","status_label":"\(status)","expected_at":"2026-06-26","audit_time":"2026-06-25 11:00","completed_at":""}
""".data(using: .utf8)!
return try JSONDecoder().decode(WalletWithdrawRecord.self, from: data)
}
}
@MainActor
private final class WithdrawalAuditWalletMock: WalletServing {
var withdrawResponses: [WalletWithdrawListResponse] = []
var withdrawError: Error?
private(set) var withdrawPages: [Int] = []
func walletSummary(type: Int) async throws -> WalletSummaryResponse { WalletSummaryResponse() }
func walletEarningDetail(startDate: String, endDate: String, page: Int, pageSize: Int) async throws -> WalletEarningDetailResponse { WalletEarningDetailResponse() }
func walletWithdrawList(page: Int, pageSize: Int) async throws -> WalletWithdrawListResponse {
withdrawPages.append(page)
if let withdrawError { throw withdrawError }
return withdrawResponses.isEmpty ? WalletWithdrawListResponse() : withdrawResponses.removeFirst()
}
func withdrawInfo() async throws -> WithdrawInfoResponse {
let data = #"{"amount_withdrawable":"0","min_withdraw_amount":"1","max_single_withdraw_amount":"100","max_daily_withdraw_amount":"100","user_phone":"13800000000","bank_card":{},"withdraw_info":[]}"#.data(using: .utf8)!
return try JSONDecoder().decode(WithdrawInfoResponse.self, from: data)
}
func withdrawSendSms() async throws {}
func withdrawApply(amount: String, smsCode: String) async throws {}
func bankCardInfo() async throws -> BankCardInfoResponse { BankCardInfoResponse(bankCard: nil) }
func bankList() async throws -> BankListResponse { BankListResponse() }
func areas() async throws -> [AreaNode] { [] }
func bankCardVerifyCode() async throws {}
func updateBankInfo(_ request: UpdateBankInfoRequest) async throws {}
func pointOverview(staffId: Int) async throws -> PointOverviewResponse { PointOverviewResponse() }
func pointWithdrawApply(points: Int, remark: String) async throws {}
func pointWithdrawList(status: Int?, page: Int, pageSize: Int) async throws -> PointWithdrawListResponse { PointWithdrawListResponse() }
}
private enum TestError: LocalizedError {
case sample
var errorDescription: String? {
"测试错误"
}
}