新增线下收款记录和 ai 修图优化

This commit is contained in:
han xin
2026-08-21 15:51:52 +08:00
parent 6fe9928b49
commit 5cf4409bad
40 changed files with 5482 additions and 56 deletions
@@ -0,0 +1,137 @@
import XCTest
@testable import suixinkan
/// 注销账号功能的业务规则测试,覆盖资产确认、七天冷静期与账号恢复。
final class AccountDeletionFeatureTests: XCTestCase {
private var suiteName = ""
private var defaults: UserDefaults!
private var currentDate: Date!
private var service: AccountDeletionMockService!
override func setUp() {
super.setUp()
suiteName = "AccountDeletionFeatureTests.\(UUID().uuidString)"
defaults = UserDefaults(suiteName: suiteName)
defaults.removePersistentDomain(forName: suiteName)
currentDate = Date(timeIntervalSince1970: 1_800_000_000)
service = AccountDeletionMockService(
defaults: defaults,
now: { [weak self] in self?.currentDate ?? Date() },
responseDelayNanoseconds: 0
)
}
override func tearDown() {
defaults.removePersistentDomain(forName: suiteName)
service = nil
currentDate = nil
defaults = nil
suiteName = ""
super.tearDown()
}
/// 预检查应返回全部资产项,并保持中性展示文案。
func testPrecheckReturnsAllAccountAssetsWithoutAbandonmentLabel() async throws {
let precheck = try await service.loadPrecheck(username: "18651857230")
XCTAssertEqual(precheck.assets.map(\.kind), AccountDeletionAssetSummary.Kind.allCases)
XCTAssertEqual(precheck.assets.map(\.valueText), ["¥286.50", "36个", "4个", "8.6 GB"])
XCTAssertFalse(precheck.assets.map(\.title).contains(where: { $0.contains("可放弃") }))
XCTAssertFalse(precheck.assets.map(\.valueText).contains(where: { $0.contains("可放弃") }))
}
/// 用户勾选确认前不允许进入验证页。
func testViewModelRequiresExplicitAssetAcknowledgement() async throws {
let viewModel = AccountDeletionViewModel(username: "18651857230", service: service)
XCTAssertFalse(viewModel.isContinueEnabled)
await viewModel.load()
XCTAssertFalse(viewModel.isContinueEnabled)
XCTAssertTrue(viewModel.isContinueButtonEnabled)
XCTAssertThrowsError(try viewModel.makeVerificationContext()) { error in
XCTAssertEqual(error as? AccountDeletionError, .incompleteAcknowledgement)
}
viewModel.toggleAcknowledgement()
XCTAssertTrue(viewModel.isContinueEnabled)
let context = try viewModel.makeVerificationContext()
XCTAssertEqual(context.maskedPhone, "186****7230")
XCTAssertEqual(Set(context.assets.map(\.kind)), Set(AccountDeletionAssetSummary.Kind.allCases))
}
/// 注销申请必须校验验证码,并确认全部资产项。
func testSubmitRequiresCorrectCodeAndAllAssetKinds() async throws {
do {
_ = try await service.submitDeletion(
username: "18651857230",
verificationCode: "000000",
acknowledgedAssetKinds: Set(AccountDeletionAssetSummary.Kind.allCases),
clientRequestID: UUID()
)
XCTFail("错误验证码不应提交成功")
} catch {
XCTAssertEqual(error as? AccountDeletionError, .invalidVerificationCode)
}
do {
_ = try await service.submitDeletion(
username: "18651857230",
verificationCode: AccountDeletionMockService.verificationCode,
acknowledgedAssetKinds: [.wallet, .works, .projects],
clientRequestID: UUID()
)
XCTFail("未确认全部资产项不应提交成功")
} catch {
XCTAssertEqual(error as? AccountDeletionError, .incompleteAcknowledgement)
}
}
/// 提交后进入七天冷静期,期间再次登录可以取消注销。
func testPendingDeletionCanBeCanceledWithinSevenDays() async throws {
let request = try await submitDeletion(username: "18651857230")
XCTAssertEqual(request.scheduledDeletionAt, currentDate.addingTimeInterval(7 * 24 * 60 * 60))
guard case let .pending(savedRequest) = service.loginState(for: "18651857230") else {
return XCTFail("应进入待注销状态")
}
XCTAssertEqual(savedRequest, request)
currentDate = currentDate.addingTimeInterval(6 * 24 * 60 * 60)
_ = try service.cancelDeletion(username: "18651857230")
XCTAssertEqual(service.loginState(for: "18651857230"), .none)
}
/// 七天后账号进入已注销状态,不能再通过登录恢复。
func testDeletionBecomesCompletedAfterSevenDays() async throws {
_ = try await submitDeletion(username: "18651857230")
currentDate = currentDate.addingTimeInterval(7 * 24 * 60 * 60 + 1)
guard case .completed = service.loginState(for: "18651857230") else {
return XCTFail("冷静期结束后应为已注销状态")
}
do {
_ = try service.cancelDeletion(username: "18651857230")
XCTFail("已超过七天不应恢复账号")
} catch {
XCTAssertEqual(error as? AccountDeletionError, .cancellationExpired)
}
}
/// 手机号展示仅保留前三位和后四位。
func testPhoneMaskingNormalizesStoredUsername() {
XCTAssertEqual(AccountDeletionIdentity.normalizedUsername("+86 186-5185-7230"), "18651857230")
XCTAssertEqual(AccountDeletionIdentity.maskedPhone("+86 186-5185-7230"), "186****7230")
}
private func submitDeletion(username: String) async throws -> AccountDeletionRequest {
try await service.submitDeletion(
username: username,
verificationCode: AccountDeletionMockService.verificationCode,
acknowledgedAssetKinds: Set(AccountDeletionAssetSummary.Kind.allCases),
clientRequestID: UUID()
)
}
}