同步订单详情与扫码核销流程

This commit is contained in:
2026-06-22 17:03:27 +08:00
parent 726f19c124
commit d02fd2c069
15 changed files with 1456 additions and 54 deletions

View File

@ -0,0 +1,120 @@
//
// OrderDetailViewModelTests.swift
// suixinkanTests
//
// Created by Codex on 2026/6/22.
//
import XCTest
@testable import suixinkan
@MainActor
/// ViewModel
final class OrderDetailViewModelTests: XCTestCase {
/// ID
func testLoadRequestsDetailWhenStoreIdExists() async throws {
let item = try firstOrder()
let api = DetailMockOrderService()
api.detailResponses = [try TestFixture.payload(StoreOrderDetailResponse.self, named: "store_order_detail_success")]
let viewModel = OrderDetailViewModel(item: item)
await viewModel.load(api: api, fallbackStoreId: 66)
XCTAssertEqual(api.detailCalls.count, 1)
XCTAssertEqual(api.detailCalls[0].storeId, 66)
XCTAssertEqual(api.detailCalls[0].orderNumber, "ORDV2001")
XCTAssertEqual(viewModel.display.projectName, "亲子跟拍")
XCTAssertEqual(viewModel.projectInfo?.settleSpotNum, 3)
XCTAssertEqual(viewModel.shootingList.first?.scenicSpotName, "东门")
XCTAssertNil(viewModel.errorMessage)
}
/// ID
func testLoadWithoutStoreIdUsesSummaryOnly() async throws {
let item = try firstOrder()
let api = DetailMockOrderService()
let viewModel = OrderDetailViewModel(item: item)
await viewModel.load(api: api, fallbackStoreId: nil)
XCTAssertTrue(api.detailCalls.isEmpty)
XCTAssertEqual(viewModel.display.orderNumber, "ORDV2001")
XCTAssertEqual(viewModel.contextMessage, "缺少门店上下文,当前展示订单摘要。")
}
///
func testLoadFailureKeepsSummaryAndExposesError() async throws {
let item = try firstOrder()
let api = DetailMockOrderService()
api.detailError = APIError.httpStatus(500, "server error")
let viewModel = OrderDetailViewModel(item: item)
await viewModel.load(api: api, fallbackStoreId: 66)
XCTAssertEqual(api.detailCalls.count, 1)
XCTAssertEqual(viewModel.display.orderNumber, "ORDV2001")
XCTAssertEqual(viewModel.contextMessage, "详情加载失败,当前展示订单摘要。")
XCTAssertNotNil(viewModel.errorMessage)
}
///
func testDetailResponseDecodesMixedNumericAndStringFields() throws {
let detail = try TestFixture.payload(StoreOrderDetailResponse.self, named: "store_order_detail_success")
XCTAssertEqual(detail.orderType, 19)
XCTAssertEqual(detail.actualRefundAmount, "0")
XCTAssertEqual(detail.userId, 7001)
XCTAssertEqual(detail.multiTravel?.projectInfo?.singleSpotVideoNum, 1)
XCTAssertEqual(detail.multiTravel?.shootingList.first?.startAvg, 4.8)
}
/// fixture
private func firstOrder() throws -> OrderEntity {
let payload = try TestFixture.payload(ListPayload<OrderEntity>.self, named: "order_v2_success")
return try XCTUnwrap(payload.list.first)
}
}
@MainActor
/// ViewModel
private final class DetailMockOrderService: OrderServing {
var detailResponses: [StoreOrderDetailResponse] = []
var detailError: Error?
private(set) var detailCalls: [(storeId: Int, orderNumber: String)] = []
///
func orderList(
scenicId: Int,
page: Int,
pageSize: Int,
orderStatus: Int?,
userPhone: String?,
startTime: String?,
endTime: String?,
isRefined: Int?,
isScenicAdmin: Bool
) async throws -> ListPayload<OrderEntity> {
XCTFail("orderList should not be called")
return ListPayload(total: 0, list: [])
}
///
func writeOffList(scenicId: Int, storeId: Int?, page: Int, pageSize: Int) async throws -> ListPayload<WriteOffOrderItem> {
XCTFail("writeOffList should not be called")
return ListPayload(total: 0, list: [])
}
///
func writeOff(orderNumber: String) async throws {
XCTFail("writeOff should not be called")
}
///
func storeOrderDetail(storeId: Int, orderNumber: String) async throws -> StoreOrderDetailResponse {
detailCalls.append((storeId: storeId, orderNumber: orderNumber))
if let detailError {
throw detailError
}
return detailResponses.isEmpty ? try TestFixture.payload(StoreOrderDetailResponse.self, named: "store_order_detail_success") : detailResponses.removeFirst()
}
}