import Foundation @testable import suixinkan /// 从已实测响应整理的脱敏测试夹具;覆盖变化场景时显式覆盖字段,不宣称为真实服务端返回。 enum StoreAccountDeregistrationFixtures { /// 2026-08-28 15:29 实测 userinfo 的150015响应;无time字段,data.status为字符串。 static let observedCoolingRestrictionEnvelope = Data(#""" { "code": 150015, "msg": "账号处于注销冷静期,请先撤销注销后再继续使用", "data": { "status": "cooling", "cooling_until": "2026-09-04 15:27:24", "remaining_seconds": 604681 } } """#.utf8) /// 同次150015请求后的真实status响应,仅将注销记录ID替换为测试值301。 static let observedCoolingStatusEnvelope = Data(#""" { "code": 100000, "msg": "success", "data": { "deregister": { "id": 301, "status": 1, "status_label": "冷静期中", "reason": "Test account deregistration", "apply_time": "2026-08-28 15:27:24", "cooling_until": "2026-09-04 15:27:24", "remaining_seconds": 604681, "cancel_time": null, "blocked_code": "", "blocked_reason": "", "completed_at": null } }, "time": "2026-08-28 15:29:22" } """#.utf8) /// 真实样例的结构,身份 ID 替换为测试用的 101/201。 static func eligibilityData(overrides: [String: Any] = [:]) throws -> Data { var data: [String: Any] = [ "can_apply": false, "store_user_id": 101, "finance_identity_id": 201, "wallet_balance_fen": 0, "wallet_balance": "0.00", "points_balance": 0, "wallet_waived": false, "points_waived": false, "unfulfilled_count": 37, "fulfillment_in_progress_count": 0, "risk_end_at": "2026-08-27 15:47:32", "eligible_at": "2026-09-03 15:47:32", "risk_window_hours": 168, "blockers": [ ["code": "ORDER_UNFULFILLED", "message": "账户仍有未履约订单或带单", "action": "complete_orders"], ["code": "RISK_WINDOW_NOT_EXPIRED", "message": "最后一笔业务的风险结束时间尚未超过7天", "action": "wait_risk_window"], ["code": "WALLET_WAIVER_MISSING", "message": "请先确认放弃现金余额", "action": "confirm_wallet_waiver"], ["code": "POINTS_WAIVER_MISSING", "message": "请先确认放弃积分", "action": "confirm_points_waiver"], ], "deregister": NSNull(), ] data.merge(overrides) { _, new in new } return try JSONSerialization.data(withJSONObject: data) } /// 解码已实测结构,供协议替身返回值使用。 static func eligibility(overrides: [String: Any] = [:]) throws -> StoreAccountDeregistrationEligibility { try JSONDecoder().decode(StoreAccountDeregistrationEligibility.self, from: eligibilityData(overrides: overrides)) } /// 构造所有条件满足的规则测试输入;不是通过真实资产放弃或申请获得的响应。 static func ready(overrides: [String: Any] = [:]) throws -> StoreAccountDeregistrationEligibility { var fields: [String: Any] = ["can_apply": true, "wallet_waived": true, "points_waived": true, "unfulfilled_count": 0, "blockers": []] fields.merge(overrides) { _, new in new } return try eligibility(overrides: fields) } /// 2026-08-28 零资产确认后的真实草稿结构,仅将记录 ID 替换为测试值 301。 static func draftRecord(overrides: [String: Any] = [:]) -> [String: Any] { var record: [String: Any] = [ "id": 301, "status": 0, "status_label": "待确认", "reason": "", "apply_time": NSNull(), "cooling_until": NSNull(), "remaining_seconds": 0, "cancel_time": NSNull(), "blocked_code": "", "blocked_reason": "", "completed_at": NSNull(), ] record.merge(overrides) { _, new in new } return record } /// 解码实测草稿状态;覆盖字段时用于验证未知或矛盾记录不会放行。 static func draftStatus(overrides: [String: Any] = [:]) throws -> StoreAccountDeregistrationStatus { try JSONDecoder().decode(StoreAccountDeregistrationStatus.self, from: JSONSerialization.data(withJSONObject: ["deregister": draftRecord(overrides: overrides)])) } /// 两次真实确认之间及之后的条件结构,没有历史业务时风险时间为 null。 static func draftEligibility(pointsWaived: Bool) throws -> StoreAccountDeregistrationEligibility { try eligibility(overrides: [ "can_apply": pointsWaived, "wallet_waived": true, "points_waived": pointsWaived, "unfulfilled_count": 0, "risk_end_at": NSNull(), "eligible_at": NSNull(), "blockers": pointsWaived ? [] : [ ["code": "POINTS_WAIVER_MISSING", "message": "请先确认放弃积分", "action": "confirm_points_waiver"], ], "deregister": draftRecord(), ]) } /// 实测的冷静期和主动撤销记录,ID 脱敏;覆盖字段只用于防御性测试。 static func lifecycleRecord(cancelled: Bool = false, overrides: [String: Any] = [:]) -> [String: Any] { var record = draftRecord(overrides: [ "status": cancelled ? 9 : 1, "status_label": cancelled ? "已撤销" : "冷静期中", "reason": "API test; cancel immediately", "apply_time": "2026-08-28 14:23:40", "cooling_until": "2026-09-04 14:23:40", "remaining_seconds": cancelled ? 0 : 604776, "cancel_time": cancelled ? "2026-08-28 14:24:04" : NSNull(), "blocked_code": cancelled ? "CANCELLED_BY_USER" : "", "blocked_reason": cancelled ? "用户主动撤销注销" : "", ]) record.merge(overrides) { _, new in new } return record } /// 解码冷静期/已撤销实测样例。 static func lifecycleStatus(cancelled: Bool = false, overrides: [String: Any] = [:]) throws -> StoreAccountDeregistrationStatus { try JSONDecoder().decode(StoreAccountDeregistrationStatus.self, from: JSONSerialization.data(withJSONObject: ["deregister": lifecycleRecord(cancelled: cancelled, overrides: overrides)])) } /// 冷静期及撤销后真实查询均返回两项确认失效,不能在冷静期重新确认资产。 static func lifecycleEligibility(cancelled: Bool = false) throws -> StoreAccountDeregistrationEligibility { try eligibility(overrides: ["unfulfilled_count": 0, "risk_end_at": NSNull(), "eligible_at": NSNull(), "blockers": [ ["code": "WALLET_WAIVER_MISSING", "message": "请先确认放弃现金余额", "action": "confirm_wallet_waiver"], ["code": "POINTS_WAIVER_MISSING", "message": "请先确认放弃积分", "action": "confirm_points_waiver"], ], "deregister": lifecycleRecord(cancelled: cancelled)]) } /// 统一成功 Envelope,仅用于 MockURLSession。 static func envelope(_ data: Data) throws -> Data { try JSONSerialization.data(withJSONObject: ["code": 100000, "msg": "success", "data": JSONSerialization.jsonObject(with: data)]) } }