Files
suixinkan_ios_new/suixinkanTests/PaymentViewModelTests.swift
汉秋 e8bc9b7f44 Add Payment and Wallet modules with home routing integration.
Introduce real payment collection and wallet screens to replace home menu placeholders, wire APIs through RootView, and support bank card OSS uploads plus QR code saving to the photo library.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-22 17:46:01 +08:00

109 lines
4.7 KiB
Swift
Raw 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.

//
// PaymentViewModelTests.swift
// suixinkanTests
//
// Created by Codex on 2026/6/22.
//
import XCTest
@testable import suixinkan
@MainActor
///
final class PaymentViewModelTests: XCTestCase {
///
func testLoadPayCodeWithoutScenicClearsState() async {
let api = PaymentMockAPI()
let viewModel = PaymentCollectionViewModel()
viewModel.staticPayUrl = "old"
await viewModel.loadPayCode(api: api, scenicId: nil)
XCTAssertTrue(viewModel.staticPayUrl.isEmpty)
XCTAssertEqual(api.payCodeScenicIds, [])
XCTAssertEqual(viewModel.errorMessage, "请先选择景区")
}
/// URL
func testLoadPayCodeUsesScenicIdAndAppliesStaticURL() async {
let api = PaymentMockAPI()
api.payCodeResponse = PayCodeResponse(staticPayUrl: "https://pay.example.com/static", dynamicPayUrl: "https://pay.example.com/dynamic")
let viewModel = PaymentCollectionViewModel()
await viewModel.loadPayCode(api: api, scenicId: 9)
XCTAssertEqual(api.payCodeScenicIds, [9])
XCTAssertEqual(viewModel.currentPayUrl, "https://pay.example.com/static")
XCTAssertTrue(viewModel.hasStaticPayCode)
}
/// URL
func testApplyDynamicAmountBuildsDynamicURL() async {
let viewModel = PaymentCollectionViewModel()
viewModel.dynamicPayUrl = "https://pay.example.com/dynamic?scenic_id=9"
viewModel.amountText = "12.345"
viewModel.remarkText = "茶水"
XCTAssertTrue(viewModel.applyDynamicAmount())
XCTAssertTrue(viewModel.currentPayUrl.contains("amount=12.35"))
XCTAssertTrue(viewModel.currentPayUrl.contains("remark="))
XCTAssertEqual(viewModel.status, .waiting)
}
///
func testPollingFindsNewPaymentRecord() async {
let api = PaymentMockAPI()
api.recordResponses = [
PaymentCollectionRecordResponse(list: [
PaymentCollectionRecordItem(orderNumber: "A", userPhone: "138", orderAmount: "8.00", createDate: "2026-06-22", createTime: "10:00")
]),
PaymentCollectionRecordResponse(list: [
PaymentCollectionRecordItem(orderNumber: "A", userPhone: "138", orderAmount: "8.00", createDate: "2026-06-22", createTime: "10:00"),
PaymentCollectionRecordItem(orderNumber: "B", userPhone: "139", orderAmount: "12.35", createDate: "2026-06-22", createTime: "10:01")
])
]
let viewModel = PaymentCollectionViewModel()
viewModel.amountText = "12.35"
await viewModel.primePaymentRecords(api: api, scenicId: 9)
await viewModel.pollUntilPaymentDetected(api: api, scenicId: 9, maxAttempts: 1, intervalNanoseconds: 0)
XCTAssertEqual(viewModel.status, .success(PaymentCollectionRecordItem(orderNumber: "B", userPhone: "139", orderAmount: "12.35", createDate: "2026-06-22", createTime: "10:01")))
}
///
func testRecordGroupsFallbackFromList() {
let response = PaymentCollectionRecordResponse(list: [
PaymentCollectionRecordItem(orderNumber: "A", userPhone: "138", orderAmount: "8.00", createDate: "2026-06-21", createTime: "10:00"),
PaymentCollectionRecordItem(orderNumber: "B", userPhone: "139", orderAmount: "12.00", createDate: "2026-06-21", createTime: "10:01")
])
let groups = PaymentCollectionRecordViewModel.makeGroups(from: response)
XCTAssertEqual(groups.count, 1)
XCTAssertEqual(groups[0].analyse.orderCount, 2)
XCTAssertEqual(groups[0].analyse.orderAmountSum, "20")
}
}
@MainActor
/// API
private final class PaymentMockAPI: PaymentServing {
var payCodeResponse = PayCodeResponse()
var recordResponses: [PaymentCollectionRecordResponse] = []
private(set) var payCodeScenicIds: [Int] = []
private(set) var recordScenicIds: [Int] = []
/// ID
func payCode(scenicId: Int) async throws -> PayCodeResponse {
payCodeScenicIds.append(scenicId)
return payCodeResponse
}
/// ID
func paymentCollectionRecords(scenicId: Int) async throws -> PaymentCollectionRecordResponse {
recordScenicIds.append(scenicId)
return recordResponses.isEmpty ? PaymentCollectionRecordResponse() : recordResponses.removeFirst()
}
}