新增线下收款记录和 ai 修图优化
This commit is contained in:
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user