feat: 优化账号注销流程

This commit is contained in:
2026-08-31 15:56:16 +08:00
parent e529bb5942
commit 4444d328df
16 changed files with 982 additions and 208 deletions
@@ -267,6 +267,40 @@ final class StoreAccountDeregistrationViewModelTests: XCTestCase {
XCTAssertFalse(model.canContinue)
}
/// 验证码与原因在任何查询和提交意图之前校验;空白及超长原因不能触发网络请求。
func testSubmitRejectsMissingOrOversizedRequiredInputWithoutSideEffects() async throws {
let cases = [
(code: "", reason: "正常原因", message: "请输入收到的短信验证码"),
(code: "654321", reason: "", message: "请输入注销原因"),
(code: "654321", reason: " \n", message: "请输入注销原因"),
(code: "654321", reason: String(repeating: "原", count: 51), message: "注销原因不能超过 50 个字")
]
for item in cases {
let service = try DeregistrationServiceStub()
service.current = try StoreAccountDeregistrationFixtures.ready()
let suite = "DeregistrationRequiredReason.\(UUID().uuidString)"
let defaults = try XCTUnwrap(UserDefaults(suiteName: suite))
defer { defaults.removePersistentDomain(forName: suite) }
let store = StoreAccountDeregistrationSubmissionStore(
storeUserID: "101", environment: .testing, defaults: defaults
)
let model = StoreAccountDeregistrationViewModel(storeUserID: 101, submissionStore: store)
await model.refresh(api: service)
model.beginVerification()
let statusCount = service.statusCount
let eligibilityCount = service.eligibilityCount
await model.submit(smsCode: item.code, reason: item.reason, api: service)
XCTAssertEqual(model.errorMessage, item.message)
XCTAssertTrue(service.mutations.isEmpty)
XCTAssertEqual(service.statusCount, statusCount)
XCTAssertEqual(service.eligibilityCount, eligibilityCount)
XCTAssertFalse(model.submissionAttempted)
XCTAssertFalse(store.hasUnresolvedSubmission)
}
}
/// 申请响应丢失可能已被服务端受理,不能直接重试或显示失败后可重新申请。
func testLostApplyResponsePreventsAutomaticRetry() async throws {
let service = try DeregistrationServiceStub()
@@ -285,8 +319,8 @@ final class StoreAccountDeregistrationViewModelTests: XCTestCase {
XCTAssertEqual(model.step, .unresolvedRequest)
}
/// 服务端明确拒绝验证码时允许重新核验,但不能保留之前的提交权限。
func testServerRejectionRequiresFreshEligibility() async throws {
/// 服务端明确拒绝验证码时保留已核验条件并停留在手机验证,不要求重新确认资产。
func testVerificationCodeRejectionStaysInVerification() async throws {
let service = try DeregistrationServiceStub()
service.current = try StoreAccountDeregistrationFixtures.ready()
service.applyError = APIError.serverCode(100001, "验证码错误")
@@ -295,10 +329,10 @@ final class StoreAccountDeregistrationViewModelTests: XCTestCase {
model.beginVerification()
await model.submit(smsCode: "907182", reason: "不再使用", api: service)
XCTAssertFalse(model.submissionAttempted)
XCTAssertFalse(model.canContinue)
XCTAssertEqual(model.errorMessage, "验证码错误")
await model.refresh(api: service)
XCTAssertTrue(model.canContinue)
XCTAssertEqual(model.step, .verification)
XCTAssertNotNil(model.eligibility)
XCTAssertEqual(model.errorMessage, "验证码错误")
}
/// 展示全部真实阻断项,初始加载不会调用任何修改接口。
@@ -471,15 +505,19 @@ private final class DeregistrationServiceStub: StoreAccountDeregistrationServing
var readError: Error?
var applyError: Error?
var mutations: [String] = []
var eligibilityCount = 0
var statusCount = 0
var lastCode: String?
var lastReason: String?
init() throws { current = try StoreAccountDeregistrationFixtures.eligibility() }
func eligibility() async throws -> StoreAccountDeregistrationEligibility {
eligibilityCount += 1
if let readError { throw readError }
return current
}
func status() async throws -> StoreAccountDeregistrationStatus {
statusCount += 1
if let readError { throw readError }
return currentStatus
}