138 lines
5.7 KiB
Swift
138 lines
5.7 KiB
Swift
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()
|
|
)
|
|
}
|
|
}
|