新增线下收款记录和 ai 修图优化
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
//
|
||||
// OfflineCollectionFeatureTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
/// 线下收款登记、日汇总、失败回滚与幂等补缴流程测试。
|
||||
final class OfflineCollectionFeatureTests: XCTestCase {
|
||||
private var defaults: UserDefaults!
|
||||
private var calendar: Calendar!
|
||||
private var fixedNow: Date!
|
||||
private var service: OfflineCollectionMockService!
|
||||
private let context = OfflineCollectionContext(
|
||||
collectorId: "U10086",
|
||||
collectorName: "张三",
|
||||
storeId: "S1001",
|
||||
storeName: "那拉提旅拍一店",
|
||||
scenicId: "SC001",
|
||||
scenicName: "那拉提景区"
|
||||
)
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
let suiteName = "OfflineCollectionFeatureTests"
|
||||
defaults = UserDefaults(suiteName: suiteName)!
|
||||
defaults.removePersistentDomain(forName: suiteName)
|
||||
calendar = Calendar(identifier: .gregorian)
|
||||
calendar.timeZone = TimeZone(identifier: "Asia/Shanghai")!
|
||||
fixedNow = calendar.date(from: DateComponents(
|
||||
calendar: calendar,
|
||||
timeZone: calendar.timeZone,
|
||||
year: 2026,
|
||||
month: 8,
|
||||
day: 19,
|
||||
hour: 20,
|
||||
minute: 5
|
||||
))!
|
||||
let now = fixedNow!
|
||||
service = OfflineCollectionMockService(
|
||||
defaults: defaults,
|
||||
calendar: calendar,
|
||||
nowProvider: { now },
|
||||
requestDelayNanoseconds: 0
|
||||
)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
defaults.removePersistentDomain(forName: "OfflineCollectionFeatureTests")
|
||||
service = nil
|
||||
calendar = nil
|
||||
fixedNow = nil
|
||||
defaults = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testMoneyParserUsesIntegerFenAndEnforcesRange() {
|
||||
XCTAssertEqual(OfflineCollectionMoney.parseFen(".5"), 50)
|
||||
XCTAssertEqual(OfflineCollectionMoney.parseFen("299"), 29_900)
|
||||
XCTAssertEqual(OfflineCollectionMoney.parseFen("99999.99"), 9_999_999)
|
||||
XCTAssertNil(OfflineCollectionMoney.parseFen(""))
|
||||
XCTAssertNil(OfflineCollectionMoney.parseFen("0"))
|
||||
XCTAssertNil(OfflineCollectionMoney.parseFen("-1"))
|
||||
XCTAssertNil(OfflineCollectionMoney.parseFen("1.234"))
|
||||
XCTAssertNil(OfflineCollectionMoney.parseFen("100000"))
|
||||
XCTAssertNil(OfflineCollectionMoney.parseFen("1e2"))
|
||||
XCTAssertEqual(OfflineCollectionMoney.display(29_900), "¥299.00")
|
||||
}
|
||||
|
||||
func testDefaultMockDataContainsTodayAndHistoricalOverdueRecords() {
|
||||
let today = service.getDailySummary(date: "2026-08-19", context: context)
|
||||
let overdue = service.getOverdueSummaries(context: context)
|
||||
|
||||
XCTAssertEqual(today.totalCount, 3)
|
||||
XCTAssertEqual(today.totalAmountFen, 89_700)
|
||||
XCTAssertEqual(today.pendingAmountFen, 89_700)
|
||||
XCTAssertEqual(overdue.count, 1)
|
||||
XCTAssertEqual(overdue.first?.businessDate, "2026-08-18")
|
||||
XCTAssertEqual(overdue.first?.pendingCount, 2)
|
||||
XCTAssertEqual(overdue.first?.pendingAmountFen, 50_000)
|
||||
XCTAssertEqual(overdue.first?.hasOverdue, true)
|
||||
}
|
||||
|
||||
func testCreateCollectionUpdatesSummaryAndPersists() async throws {
|
||||
let receipt = try await service.createOfflineCollection(
|
||||
amountFen: 29_900,
|
||||
paymentMethod: .wechat,
|
||||
context: context
|
||||
)
|
||||
|
||||
XCTAssertEqual(receipt.record.status, .pending)
|
||||
XCTAssertEqual(receipt.summary.totalCount, 4)
|
||||
XCTAssertEqual(receipt.summary.totalAmountFen, 119_600)
|
||||
XCTAssertEqual(receipt.summary.pendingAmountFen, 119_600)
|
||||
|
||||
let now = fixedNow!
|
||||
let reloaded = OfflineCollectionMockService(
|
||||
defaults: defaults,
|
||||
calendar: calendar,
|
||||
nowProvider: { now },
|
||||
requestDelayNanoseconds: 0
|
||||
)
|
||||
XCTAssertEqual(reloaded.getDailySummary(date: "2026-08-19", context: context), receipt.summary)
|
||||
}
|
||||
|
||||
func testSuccessfulSettlementGeneratesIndependentBatchAndSettlesSnapshot() async throws {
|
||||
let records = service.getDailyRecords(date: "2026-08-19", context: context)
|
||||
let request = OfflineSettlementRequest(
|
||||
businessDate: "2026-08-19",
|
||||
recordIds: records.map(\.id),
|
||||
amountFen: records.reduce(0) { $0 + $1.amountFen },
|
||||
clientRequestId: "LOCAL-SUCCESS-001"
|
||||
)
|
||||
|
||||
let batch = try await service.settleDailyCollections(request: request, context: context)
|
||||
let summary = service.getDailySummary(date: "2026-08-19", context: context)
|
||||
let settledRecords = service.getDailyRecords(date: "2026-08-19", context: context)
|
||||
|
||||
XCTAssertEqual(batch.amountFen, 89_700)
|
||||
XCTAssertEqual(batch.recordCount, 3)
|
||||
XCTAssertEqual(batch.transactionType, "OFFLINE_COLLECTION_SETTLEMENT")
|
||||
XCTAssertEqual(summary.totalAmountFen, summary.settledAmountFen + summary.pendingAmountFen)
|
||||
XCTAssertEqual(summary.pendingAmountFen, 0)
|
||||
XCTAssertTrue(settledRecords.allSatisfy { $0.status == .settled })
|
||||
XCTAssertTrue(settledRecords.allSatisfy { $0.settlementBatchId == batch.id })
|
||||
}
|
||||
|
||||
func testFailedSettlementDoesNotCreateBatchOrChangeRecords() async throws {
|
||||
let before = service.getDailyRecords(date: "2026-08-19", context: context)
|
||||
let request = OfflineSettlementRequest(
|
||||
businessDate: "2026-08-19",
|
||||
recordIds: before.map(\.id),
|
||||
amountFen: before.reduce(0) { $0 + $1.amountFen },
|
||||
clientRequestId: "LOCAL-FAILURE-001"
|
||||
)
|
||||
service.simulateNextSettlementFailure()
|
||||
|
||||
do {
|
||||
_ = try await service.settleDailyCollections(request: request, context: context)
|
||||
XCTFail("应返回 Mock 补缴失败")
|
||||
} catch {
|
||||
XCTAssertEqual(error as? OfflineCollectionError, .settlementFailed)
|
||||
}
|
||||
|
||||
XCTAssertEqual(service.getDailyRecords(date: "2026-08-19", context: context), before)
|
||||
XCTAssertTrue(service.getSettlementBatches(date: "2026-08-19", context: context).isEmpty)
|
||||
XCTAssertEqual(service.getDailySummary(date: "2026-08-19", context: context).pendingAmountFen, 89_700)
|
||||
}
|
||||
|
||||
func testSettlementOnlySettlesFrozenRecordSnapshot() async throws {
|
||||
let frozenRecords = service.getDailyRecords(date: "2026-08-19", context: context)
|
||||
let request = OfflineSettlementRequest(
|
||||
businessDate: "2026-08-19",
|
||||
recordIds: frozenRecords.map(\.id),
|
||||
amountFen: frozenRecords.reduce(0) { $0 + $1.amountFen },
|
||||
clientRequestId: "LOCAL-SNAPSHOT-001"
|
||||
)
|
||||
let newReceipt = try await service.createOfflineCollection(
|
||||
amountFen: 12_300,
|
||||
paymentMethod: .cash,
|
||||
context: context
|
||||
)
|
||||
|
||||
_ = try await service.settleDailyCollections(request: request, context: context)
|
||||
|
||||
let records = service.getDailyRecords(date: "2026-08-19", context: context)
|
||||
let newRecord = try XCTUnwrap(records.first(where: { $0.id == newReceipt.record.id }))
|
||||
XCTAssertEqual(newRecord.status, .pending)
|
||||
XCTAssertEqual(service.getDailySummary(date: "2026-08-19", context: context).pendingAmountFen, 12_300)
|
||||
}
|
||||
|
||||
func testSameClientRequestIdDoesNotCreateDuplicateBatch() async throws {
|
||||
let records = service.getDailyRecords(date: "2026-08-19", context: context)
|
||||
let request = OfflineSettlementRequest(
|
||||
businessDate: "2026-08-19",
|
||||
recordIds: records.map(\.id),
|
||||
amountFen: records.reduce(0) { $0 + $1.amountFen },
|
||||
clientRequestId: "LOCAL-IDEMPOTENT-001"
|
||||
)
|
||||
|
||||
let first = try await service.settleDailyCollections(request: request, context: context)
|
||||
let second = try await service.settleDailyCollections(request: request, context: context)
|
||||
|
||||
XCTAssertEqual(first, second)
|
||||
XCTAssertEqual(service.getSettlementBatches(date: "2026-08-19", context: context).count, 1)
|
||||
}
|
||||
|
||||
func testRegistrationViewModelValidatesAndSubmitsForm() async {
|
||||
let viewModel = OfflineCollectionRegistrationViewModel(context: context, service: service)
|
||||
var successReceipt: OfflineCollectionRegistrationReceipt?
|
||||
viewModel.onRegistrationSuccess = { successReceipt = $0 }
|
||||
|
||||
XCTAssertEqual(viewModel.paymentMethod, .wechat)
|
||||
viewModel.updateAmount("299")
|
||||
XCTAssertTrue(viewModel.canSubmit)
|
||||
|
||||
await viewModel.submit()
|
||||
|
||||
XCTAssertEqual(successReceipt?.record.amountFen, 29_900)
|
||||
XCTAssertEqual(successReceipt?.record.paymentMethod, .wechat)
|
||||
XCTAssertFalse(viewModel.isSubmitting)
|
||||
XCTAssertEqual(successReceipt?.summary.totalCount, 4)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user