feat: update wallet punch point and report success
This commit is contained in:
272
suixinkanTests/WalletFeatureTests.swift
Normal file
272
suixinkanTests/WalletFeatureTests.swift
Normal file
@ -0,0 +1,272 @@
|
||||
//
|
||||
// WalletFeatureTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
/// 钱包功能模型、API 和 ViewModel 测试。
|
||||
@MainActor
|
||||
final class WalletFeatureTests: XCTestCase {
|
||||
func testWalletModelsDecodeLossyFields() throws {
|
||||
let withdrawJSON = Data(#"{"total":"2","item":[{"id":"10","amount":88.5,"created_at":"2026-07-01","settlement_status":"40","withdraw_status":1,"expected_at":"T+1","status_label":"打款中"}]}"#.utf8)
|
||||
let withdraw = try JSONDecoder().decode(WalletWithdrawListResponse.self, from: withdrawJSON)
|
||||
|
||||
XCTAssertEqual(withdraw.total, 2)
|
||||
XCTAssertEqual(withdraw.item[0].id, 10)
|
||||
XCTAssertEqual(withdraw.item[0].amount, "88.5")
|
||||
XCTAssertEqual(withdraw.item[0].settlementStatus, 40)
|
||||
|
||||
let earningJSON = Data(#"{"total_amount":12.3,"total_points":"7","total":"1","list":[{"date":"2026-07-01","day_amount":"12.30","day_points":"7","items":[{"id":"1","amount":"12.30","points":"7","type_label":"订单收益","order_number_suffix":"001","created_at":"10:00","withdraw_label":"可提现","source":"wallet"}]}]}"#.utf8)
|
||||
let earning = try JSONDecoder().decode(EarningDetailResponse.self, from: earningJSON)
|
||||
|
||||
XCTAssertEqual(earning.totalAmount, "12.3")
|
||||
XCTAssertEqual(earning.totalPoints, 7)
|
||||
XCTAssertEqual(earning.list[0].items[0].points, 7)
|
||||
|
||||
let pointJSON = Data(#"{"total":"1","list":[{"id":"2","points":"24","amount":"2.0","status":"2","payment_time":"2026-07-02","created_at":"2026-07-01"}]}"#.utf8)
|
||||
let points = try JSONDecoder().decode(PointWithdrawListResponse.self, from: pointJSON)
|
||||
|
||||
XCTAssertEqual(points.total, 1)
|
||||
XCTAssertEqual(points.list[0].points, 24)
|
||||
XCTAssertEqual(points.list[0].amount, 2.0)
|
||||
}
|
||||
|
||||
func testWalletAPIUsesExpectedRequests() async throws {
|
||||
let session = MockURLSession(responses: [
|
||||
Self.envelope(#"{"amount_total":"100.00","amount_current_balance":"80.00","amount_withdrawable":"50.00"}"#),
|
||||
Self.envelope(#"{"total":0,"item":[]}"#),
|
||||
Self.envelope(#"{"total_amount":"0.00","total_points":0,"total":0,"list":[]}"#),
|
||||
Self.envelope(#"{"amount_withdrawable":"50.00","min_withdraw_amount":"1","max_single_withdraw_amount":"100","max_daily_withdraw_amount":"200","user_phone":"13800138000","bank_card":{"real_name":"张三","bank_name":"招商银行","card_number":"622200001234"},"settlement":"T+1","withdraw_info":["说明"]}"#),
|
||||
Self.envelope(#"{}"#),
|
||||
Self.envelope(#"{}"#),
|
||||
Self.envelope(#"{"total_points":100,"available_points":80,"withdrawn_points":60,"pending_points":20,"time":"2026"}"#),
|
||||
Self.envelope(#"{}"#),
|
||||
Self.envelope(#"{"total":0,"list":[]}"#),
|
||||
])
|
||||
let api = WalletAPI(client: APIClient(environment: .testing, session: session))
|
||||
|
||||
_ = try await api.walletSummary(type: 0)
|
||||
_ = try await api.walletWithdrawList(page: 0, pageSize: 0)
|
||||
_ = try await api.earningDetail(startDate: "2026-07-01", endDate: "2026-07-09", page: 2, pageSize: 10)
|
||||
_ = try await api.withdrawInfo()
|
||||
try await api.withdrawSendSMS()
|
||||
try await api.withdrawApply(amount: "12.30", smsCode: "123456")
|
||||
_ = try await api.pointOverview(staffId: 9)
|
||||
try await api.pointWithdrawApply(points: 24, remark: "积分提现申请")
|
||||
_ = try await api.pointWithdrawList(status: nil, page: 1, pageSize: 10)
|
||||
|
||||
XCTAssertEqual(session.requests.map { $0.url?.path }, [
|
||||
"/api/yf-handset-app/photog/wallet/summary",
|
||||
"/api/yf-handset-app/photog/wallet/withdraw-list",
|
||||
"/api/yf-handset-app/photog/wallet/earning-detail",
|
||||
"/api/yf-handset-app/photog/wallet/withdraw-info",
|
||||
"/api/yf-handset-app/photog/wallet/withdraw-send-sms",
|
||||
"/api/yf-handset-app/photog/wallet/withdraw-apply",
|
||||
"/api/yf-handset-app/photog/point/overview",
|
||||
"/api/yf-handset-app/photog/point/withdraw/apply",
|
||||
"/api/yf-handset-app/photog/point/withdraw/list",
|
||||
])
|
||||
XCTAssertEqual(Self.queryItems(from: session.requests[1])["page"], "1")
|
||||
XCTAssertEqual(Self.queryItems(from: session.requests[2])["start_date"], "2026-07-01")
|
||||
XCTAssertEqual(Self.queryItems(from: session.requests[6])["staff_id"], "9")
|
||||
XCTAssertEqual(Self.bodyString(from: session.requests[5]).contains(#""sms_code":"123456""#), true)
|
||||
XCTAssertEqual(Self.bodyString(from: session.requests[7]).contains(#""points":24"#), true)
|
||||
XCTAssertEqual(Self.bodyString(from: session.requests[8]).contains(#""page_size":10"#), true)
|
||||
}
|
||||
|
||||
func testWalletViewModelLoadsAndPaginates() async {
|
||||
let api = FakeWalletPageService()
|
||||
let viewModel = WalletViewModel(staffIdProvider: { 9 })
|
||||
|
||||
await viewModel.loadInitial(api: api)
|
||||
|
||||
XCTAssertEqual(viewModel.summary?.amountWithdrawable, "50.00")
|
||||
XCTAssertEqual(viewModel.availablePoints, 80)
|
||||
XCTAssertEqual(viewModel.withdrawRecords.count, 1)
|
||||
XCTAssertEqual(viewModel.selectedTab, .withdraw)
|
||||
|
||||
await viewModel.selectTab(.transaction, api: api)
|
||||
XCTAssertEqual(viewModel.transactionEntries.count, 2)
|
||||
XCTAssertEqual(viewModel.totalIncomeLabel, "¥ 12.30")
|
||||
|
||||
await viewModel.selectFilter(.last30Days, api: api)
|
||||
XCTAssertEqual(viewModel.filter, .last30Days)
|
||||
XCTAssertGreaterThanOrEqual(api.earningQueries.count, 2)
|
||||
}
|
||||
|
||||
func testWalletWithdrawDestinationFlow() async throws {
|
||||
let viewModel = WalletViewModel()
|
||||
|
||||
var profile = FakeWalletProfileService(realNameStatus: nil, bankCardStatus: nil)
|
||||
var destination = try await viewModel.resolveWithdrawDestination(profileAPI: profile)
|
||||
XCTAssertEqual(destination, .realNameAuth)
|
||||
|
||||
profile = FakeWalletProfileService(realNameStatus: 1, bankCardStatus: nil)
|
||||
destination = try await viewModel.resolveWithdrawDestination(profileAPI: profile)
|
||||
XCTAssertEqual(destination, .realNamePending)
|
||||
|
||||
profile = FakeWalletProfileService(realNameStatus: 2, bankCardStatus: nil)
|
||||
destination = try await viewModel.resolveWithdrawDestination(profileAPI: profile)
|
||||
XCTAssertEqual(destination, .withdrawalSettings)
|
||||
|
||||
profile = FakeWalletProfileService(realNameStatus: 2, bankCardStatus: 1)
|
||||
destination = try await viewModel.resolveWithdrawDestination(profileAPI: profile)
|
||||
XCTAssertEqual(destination, .bankCardPending)
|
||||
|
||||
profile = FakeWalletProfileService(realNameStatus: 2, bankCardStatus: 2)
|
||||
destination = try await viewModel.resolveWithdrawDestination(profileAPI: profile)
|
||||
XCTAssertEqual(destination, .withdraw)
|
||||
}
|
||||
|
||||
func testWithdrawViewModelValidatesAndSubmits() async {
|
||||
let api = FakeWalletPageService()
|
||||
let viewModel = WithdrawViewModel()
|
||||
|
||||
await viewModel.load(api: api)
|
||||
viewModel.updateAmount("0012.345")
|
||||
viewModel.updateVerificationCode("12a456")
|
||||
|
||||
XCTAssertEqual(viewModel.amount, "12.34")
|
||||
XCTAssertEqual(viewModel.verificationCode, "12456")
|
||||
|
||||
let success = await viewModel.submit(api: api)
|
||||
XCTAssertTrue(success)
|
||||
XCTAssertEqual(api.withdrawApplyRequests.first?.amount, "12.34")
|
||||
}
|
||||
|
||||
func testPointsRedemptionViewModelClampsAndRefreshes() async {
|
||||
let api = FakeWalletPageService()
|
||||
let viewModel = PointsRedemptionViewModel(staffIdProvider: { 9 })
|
||||
|
||||
await viewModel.loadInitial(api: api)
|
||||
viewModel.updatePointsInput("999")
|
||||
XCTAssertEqual(viewModel.pointsInput, "60")
|
||||
|
||||
await viewModel.applyWithdraw(api: api)
|
||||
|
||||
XCTAssertEqual(api.pointApplyRequests, [60])
|
||||
XCTAssertEqual(viewModel.pointsInput, "")
|
||||
XCTAssertEqual(viewModel.withdrawRecords.count, 1)
|
||||
}
|
||||
|
||||
private static func envelope(_ dataJSON: String) -> Data {
|
||||
Data(#"{"code":100000,"msg":"success","data":\#(dataJSON)}"#.utf8)
|
||||
}
|
||||
|
||||
private static func queryItems(from request: URLRequest) -> [String: String] {
|
||||
URLComponents(url: request.url!, resolvingAgainstBaseURL: false)?
|
||||
.queryItems?
|
||||
.reduce(into: [String: String]()) { result, item in
|
||||
result[item.name] = item.value
|
||||
} ?? [:]
|
||||
}
|
||||
|
||||
private static func bodyString(from request: URLRequest) -> String {
|
||||
String(data: request.httpBody ?? Data(), encoding: .utf8) ?? ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// 钱包页面服务测试替身。
|
||||
private final class FakeWalletPageService: WalletPageServing {
|
||||
private(set) var earningQueries: [(start: String, end: String, page: Int)] = []
|
||||
private(set) var withdrawApplyRequests: [(amount: String, smsCode: String)] = []
|
||||
private(set) var pointApplyRequests: [Int] = []
|
||||
|
||||
func walletSummary(type: Int) async throws -> WalletSummaryResponse {
|
||||
WalletSummaryResponse(amountTotal: "100.00", amountCurrentBalance: "80.00", amountWithdrawable: "50.00")
|
||||
}
|
||||
|
||||
func walletTransactionList(
|
||||
startDate: String,
|
||||
endDate: String,
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
type: Int
|
||||
) async throws -> WalletTransactionListResponse {
|
||||
WalletTransactionListResponse()
|
||||
}
|
||||
|
||||
func walletWithdrawList(page: Int, pageSize: Int) async throws -> WalletWithdrawListResponse {
|
||||
WalletWithdrawListResponse(
|
||||
total: 1,
|
||||
item: [WalletWithdrawItem(id: page, amount: "10.00", createdAt: "2026-07-01", settlementStatus: 40, statusLabel: "打款中")]
|
||||
)
|
||||
}
|
||||
|
||||
func earningDetail(startDate: String, endDate: String, page: Int, pageSize: Int) async throws -> EarningDetailResponse {
|
||||
earningQueries.append((startDate, endDate, page))
|
||||
return EarningDetailResponse(
|
||||
totalAmount: "12.30",
|
||||
totalPoints: 7,
|
||||
total: 1,
|
||||
list: [
|
||||
EarningDetailGroup(
|
||||
date: "2026-07-01",
|
||||
dayAmount: "12.30",
|
||||
dayPoints: 7,
|
||||
items: [EarningDetailItem(id: page, amount: "12.30", points: 7, typeLabel: "订单收益", createdAt: "10:00", withdrawLabel: "可提现")]
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func withdrawInfo() async throws -> WithdrawInfoResponse {
|
||||
WithdrawInfoResponse(
|
||||
amountWithdrawable: "50.00",
|
||||
minWithdrawAmount: "1",
|
||||
maxSingleWithdrawAmount: "100",
|
||||
maxDailyWithdrawAmount: "200",
|
||||
userPhone: "13800138000",
|
||||
bankCard: WithdrawBankCardInfo(realName: "张三", bankName: "招商银行", cardNumber: "622200001234"),
|
||||
settlement: "T+1",
|
||||
withdrawInfo: ["说明"]
|
||||
)
|
||||
}
|
||||
|
||||
func withdrawSendSMS() async throws {}
|
||||
|
||||
func withdrawApply(amount: String, smsCode: String) async throws {
|
||||
withdrawApplyRequests.append((amount, smsCode))
|
||||
}
|
||||
|
||||
func pointOverview(staffId: Int) async throws -> PointOverviewResponse {
|
||||
PointOverviewResponse(totalPoints: 100, availablePoints: 80, withdrawnPoints: 60, pendingPoints: 20)
|
||||
}
|
||||
|
||||
func pointWithdrawApply(points: Int, remark: String) async throws {
|
||||
pointApplyRequests.append(points)
|
||||
}
|
||||
|
||||
func pointWithdrawList(status: Int?, page: Int, pageSize: Int) async throws -> PointWithdrawListResponse {
|
||||
PointWithdrawListResponse(total: 1, list: [PointWithdrawItem(id: page, points: 24, amount: 2, status: 1, createdAt: "2026-07-01")])
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// 钱包前置资料服务测试替身。
|
||||
private struct FakeWalletProfileService: WalletProfileServing {
|
||||
let realNameStatus: Int?
|
||||
let bankCardStatus: Int?
|
||||
|
||||
func realNameInfo() async throws -> RealNameInfoResponse {
|
||||
let data: Data = if let realNameStatus {
|
||||
Data(#"{"real_name_info":{"real_name":"张三","id_card_no":"110101199001010010","audit_status":\#(realNameStatus),"is_long_valid":1}}"#.utf8)
|
||||
} else {
|
||||
Data(#"{"real_name_info":null}"#.utf8)
|
||||
}
|
||||
return try JSONDecoder().decode(RealNameInfoResponse.self, from: data)
|
||||
}
|
||||
|
||||
func bankCardInfo() async throws -> BankCardInfoResponse {
|
||||
let data: Data = if let bankCardStatus {
|
||||
Data(#"{"bank_card":{"real_name":"张三","bank_name":"招商银行","branch_name":"支行","card_number":"622200001234","audit_status":\#(bankCardStatus),"audit_status_label":"审核中"}}"#.utf8)
|
||||
} else {
|
||||
Data(#"{"bank_card":null}"#.utf8)
|
||||
}
|
||||
return try JSONDecoder().decode(BankCardInfoResponse.self, from: data)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user