新增订单长尾流程,并迁移排队、消息、结算与审核模块
将定金订单、历史拍摄与多行程上传接入 Orders,并以真实页面替换首页排队管理、消息中心、景区结算与提现审核占位入口。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,212 @@
|
||||
//
|
||||
// ScenicSettlementViewModelTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
// Created by Codex on 2026/6/25.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
@MainActor
|
||||
/// 景区结算测试,覆盖结算申请和审核记录聚合。
|
||||
final class ScenicSettlementViewModelTests: XCTestCase {
|
||||
/// 测试已有景区会从可申请结算景区中排除。
|
||||
func testSettlementLoadExcludesExistingScenics() async {
|
||||
let permissionAPI = ScenicSettlementPermissionMock()
|
||||
permissionAPI.scenicList = ScenicListAllResponse(total: 3, list: [
|
||||
ScenicListItem(id: 1, name: "已有景区"),
|
||||
ScenicListItem(id: 2, name: "新增景区"),
|
||||
ScenicListItem(id: 3, name: "另一个景区")
|
||||
])
|
||||
let viewModel = ScenicSettlementViewModel()
|
||||
|
||||
await viewModel.load(
|
||||
api: permissionAPI,
|
||||
existingScenics: [BusinessScope(id: 1, name: "已有景区", kind: .scenic)]
|
||||
)
|
||||
|
||||
XCTAssertEqual(viewModel.options.map(\.id), [2, 3])
|
||||
}
|
||||
|
||||
/// 测试金额校验覆盖空值、0、负数、超过两位小数和有效金额。
|
||||
func testSettlementAmountValidation() {
|
||||
let viewModel = ScenicSettlementViewModel()
|
||||
|
||||
XCTAssertEqual(viewModel.amountValidation, .empty)
|
||||
|
||||
viewModel.amountText = "0"
|
||||
XCTAssertEqual(viewModel.amountValidation, .notPositive)
|
||||
|
||||
viewModel.amountText = "-1"
|
||||
XCTAssertEqual(viewModel.amountValidation, .invalidFormat)
|
||||
|
||||
viewModel.amountText = "1.234"
|
||||
XCTAssertEqual(viewModel.amountValidation, .invalidFormat)
|
||||
|
||||
viewModel.amountText = "12.30"
|
||||
XCTAssertEqual(viewModel.amountValidation, .valid)
|
||||
}
|
||||
|
||||
/// 测试多景区提交 body 正确且成功后清空表单。
|
||||
func testSettlementSubmitMultipleScenicsAndClearsForm() async {
|
||||
let submitAPI = ScenicSettlementSubmitMock()
|
||||
let viewModel = ScenicSettlementViewModel()
|
||||
viewModel.options = [
|
||||
ScenicSettlementOption(id: 3, name: "三号", selected: false),
|
||||
ScenicSettlementOption(id: 2, name: "二号", selected: false)
|
||||
]
|
||||
viewModel.toggleScenic(id: 3)
|
||||
viewModel.toggleScenic(id: 2)
|
||||
viewModel.amountText = "99"
|
||||
viewModel.remarkText = " 活动结算 "
|
||||
|
||||
let success = await viewModel.submit(api: submitAPI)
|
||||
|
||||
XCTAssertTrue(success)
|
||||
XCTAssertEqual(submitAPI.requests, [
|
||||
ScenicSettlementSubmitRequest(scenicId: 2, applyAmount: "99.00", applyRemark: "活动结算"),
|
||||
ScenicSettlementSubmitRequest(scenicId: 3, applyAmount: "99.00", applyRemark: "活动结算")
|
||||
])
|
||||
XCTAssertTrue(viewModel.selectedScenicIds.isEmpty)
|
||||
XCTAssertEqual(viewModel.amountText, "")
|
||||
XCTAssertEqual(viewModel.remarkText, "")
|
||||
XCTAssertEqual(viewModel.message, "提交成功,等待审核")
|
||||
}
|
||||
|
||||
/// 测试任一提交失败会保留当前表单并透出错误。
|
||||
func testSettlementSubmitFailureKeepsForm() async {
|
||||
let submitAPI = ScenicSettlementSubmitMock()
|
||||
submitAPI.errorAfterRequestCount = 1
|
||||
let viewModel = ScenicSettlementViewModel()
|
||||
viewModel.options = [
|
||||
ScenicSettlementOption(id: 1, name: "一号", selected: false),
|
||||
ScenicSettlementOption(id: 2, name: "二号", selected: false)
|
||||
]
|
||||
viewModel.toggleScenic(id: 1)
|
||||
viewModel.toggleScenic(id: 2)
|
||||
viewModel.amountText = "10"
|
||||
|
||||
let success = await viewModel.submit(api: submitAPI)
|
||||
|
||||
XCTAssertFalse(success)
|
||||
XCTAssertEqual(viewModel.selectedScenicIds, [1, 2])
|
||||
XCTAssertEqual(viewModel.amountText, "10")
|
||||
XCTAssertEqual(viewModel.message, TestError.sample.localizedDescription)
|
||||
}
|
||||
|
||||
/// 测试审核记录两个通道都成功时汇总正确。
|
||||
func testReviewLoadSuccessAggregatesRecords() async {
|
||||
let permissionAPI = ScenicSettlementPermissionMock()
|
||||
permissionAPI.scenicPendings = ScenicApplicationPendingsResponse(items: [
|
||||
ScenicApplicationPendingResponse(id: 1, scenicName: "景区", status: 1)
|
||||
])
|
||||
permissionAPI.roleApplies = [
|
||||
RoleApplyPendingResponse(id: 2, roleId: 1, roleName: "摄影师", status: 2)
|
||||
]
|
||||
let viewModel = ScenicSettlementReviewViewModel()
|
||||
|
||||
await viewModel.load(api: permissionAPI)
|
||||
|
||||
XCTAssertEqual(viewModel.scenicApplications.count, 1)
|
||||
XCTAssertEqual(viewModel.roleApplications.count, 1)
|
||||
XCTAssertEqual(viewModel.pendingCount, 1)
|
||||
XCTAssertFalse(viewModel.loadFailedAll)
|
||||
}
|
||||
|
||||
/// 测试单通道失败时保留另一通道数据。
|
||||
func testReviewSingleChannelFailureKeepsOtherChannel() async {
|
||||
let permissionAPI = ScenicSettlementPermissionMock()
|
||||
permissionAPI.scenicPendingsError = TestError.sample
|
||||
permissionAPI.roleApplies = [
|
||||
RoleApplyPendingResponse(id: 2, roleId: 1, roleName: "摄影师", status: 1)
|
||||
]
|
||||
let viewModel = ScenicSettlementReviewViewModel()
|
||||
|
||||
await viewModel.load(api: permissionAPI)
|
||||
|
||||
XCTAssertTrue(viewModel.scenicLoadFailed)
|
||||
XCTAssertFalse(viewModel.roleLoadFailed)
|
||||
XCTAssertFalse(viewModel.loadFailedAll)
|
||||
XCTAssertEqual(viewModel.roleApplications.count, 1)
|
||||
}
|
||||
|
||||
/// 测试双通道失败时进入整页失败态。
|
||||
func testReviewBothChannelsFailureMarksFullFailure() async {
|
||||
let permissionAPI = ScenicSettlementPermissionMock()
|
||||
permissionAPI.scenicPendingsError = TestError.sample
|
||||
permissionAPI.roleAppliesError = TestError.sample
|
||||
let viewModel = ScenicSettlementReviewViewModel()
|
||||
|
||||
await viewModel.load(api: permissionAPI)
|
||||
|
||||
XCTAssertTrue(viewModel.loadFailedAll)
|
||||
XCTAssertTrue(viewModel.scenicApplications.isEmpty)
|
||||
XCTAssertTrue(viewModel.roleApplications.isEmpty)
|
||||
}
|
||||
|
||||
/// 测试审核状态文案和语义映射。
|
||||
func testReviewStatusMapping() {
|
||||
let viewModel = ScenicSettlementReviewViewModel()
|
||||
|
||||
XCTAssertEqual(viewModel.statusText(1), "待审核")
|
||||
XCTAssertEqual(viewModel.statusText(2), "已通过")
|
||||
XCTAssertEqual(viewModel.statusText(3), "已驳回")
|
||||
XCTAssertEqual(viewModel.statusText(9), "已取消")
|
||||
XCTAssertEqual(viewModel.statusText(99), "未知")
|
||||
XCTAssertEqual(viewModel.statusIcon(2), "checkmark.circle.fill")
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class ScenicSettlementPermissionMock: ScenicPermissionServing {
|
||||
var scenicList = ScenicListAllResponse(total: 0, list: [])
|
||||
var scenicListError: Error?
|
||||
var scenicPendings = ScenicApplicationPendingsResponse()
|
||||
var scenicPendingsError: Error?
|
||||
var roleApplies: [RoleApplyPendingResponse] = []
|
||||
var roleAppliesError: Error?
|
||||
|
||||
func scenicListAll() async throws -> ScenicListAllResponse {
|
||||
if let scenicListError { throw scenicListError }
|
||||
return scenicList
|
||||
}
|
||||
|
||||
func areas() async throws -> [ScenicAreaNode] { [] }
|
||||
|
||||
func scenicApplicationPendingAll() async throws -> ScenicApplicationPendingsResponse {
|
||||
if let scenicPendingsError { throw scenicPendingsError }
|
||||
return scenicPendings
|
||||
}
|
||||
|
||||
func scenicSubmit(_ request: ScenicApplicationSubmitRequest) async throws {}
|
||||
func scenicApplicationUploadPlaceholder(_ items: [ScenicApplicationUploadPlaceholder]) async throws {}
|
||||
|
||||
func roleApplyAll() async throws -> [RoleApplyPendingResponse] {
|
||||
if let roleAppliesError { throw roleAppliesError }
|
||||
return roleApplies
|
||||
}
|
||||
|
||||
func roleApplySubmit(roleId: Int, scenicIds: [Int]) async throws {}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class ScenicSettlementSubmitMock: ScenicSettlementServing {
|
||||
var requests: [ScenicSettlementSubmitRequest] = []
|
||||
var errorAfterRequestCount: Int?
|
||||
|
||||
func scenicSettlementSubmit(_ request: ScenicSettlementSubmitRequest) async throws {
|
||||
requests.append(request)
|
||||
if let errorAfterRequestCount, requests.count >= errorAfterRequestCount {
|
||||
throw TestError.sample
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum TestError: LocalizedError {
|
||||
case sample
|
||||
|
||||
var errorDescription: String? {
|
||||
"测试错误"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user