移除 LoginViewModel 硬编码账号,扩展登录流程测试覆盖;调整 ZLoginSmokeUITests 执行顺序与路由直达逻辑,避免清空 Keychain 影响其它用例。 Co-authored-by: Cursor <cursoragent@cursor.com>
564 lines
23 KiB
Swift
564 lines
23 KiB
Swift
//
|
||
// ScenicPermissionViewModelTests.swift
|
||
// suixinkanTests
|
||
//
|
||
// Created by Codex on 2026/6/23.
|
||
//
|
||
|
||
import Foundation
|
||
import XCTest
|
||
@testable import suixinkan
|
||
|
||
@MainActor
|
||
/// 景区权限申请测试,覆盖角色景区权限申请和新增景区申请流程。
|
||
final class ScenicPermissionViewModelTests: XCTestCase {
|
||
/// 测试角色选项会按角色 ID 去重。
|
||
func testPermissionApplyDeduplicatesRoles() {
|
||
let viewModel = PermissionApplyViewModel()
|
||
|
||
viewModel.bootstrap(rolePermissions: [
|
||
RolePermissionResponse(role: RoleInfo(id: 1, name: "摄影师"), scenic: []),
|
||
RolePermissionResponse(role: RoleInfo(id: 1, name: "摄影师重复"), scenic: [])
|
||
])
|
||
|
||
XCTAssertEqual(viewModel.roleOptions.map(\.id), [1])
|
||
XCTAssertEqual(viewModel.roleOptions.first?.name, "摄影师")
|
||
}
|
||
|
||
/// 测试已拥有的景区会被禁用,不能作为新增申请提交。
|
||
func testPermissionApplyDisablesExistingScenics() async {
|
||
let api = ScenicPermissionServingFake()
|
||
api.scenicList = ScenicListAllResponse(total: 2, list: [
|
||
ScenicListItem(id: 1, name: "已有景区"),
|
||
ScenicListItem(id: 2, name: "新增景区")
|
||
])
|
||
let viewModel = PermissionApplyViewModel()
|
||
viewModel.bootstrap(rolePermissions: [
|
||
RolePermissionResponse(role: RoleInfo(id: 1, name: "摄影师"), scenic: [ScenicInfo(id: 1, name: "已有景区")])
|
||
])
|
||
|
||
viewModel.selectRole(id: 1)
|
||
await viewModel.loadScenicListIfNeeded(api: api)
|
||
viewModel.toggleScenic(id: 1)
|
||
viewModel.toggleScenic(id: 2)
|
||
|
||
XCTAssertTrue(viewModel.scenicOptions.first { $0.id == 1 }?.disabled == true)
|
||
XCTAssertEqual(viewModel.selectedScenicIds, [2])
|
||
XCTAssertTrue(viewModel.canSubmit)
|
||
}
|
||
|
||
/// 测试切换角色后清空当前景区选择。
|
||
func testPermissionApplyChangingRoleClearsSelection() async {
|
||
let api = ScenicPermissionServingFake()
|
||
api.scenicList = ScenicListAllResponse(total: 1, list: [ScenicListItem(id: 2, name: "新增景区")])
|
||
let viewModel = PermissionApplyViewModel()
|
||
viewModel.bootstrap(rolePermissions: [
|
||
RolePermissionResponse(role: RoleInfo(id: 1, name: "摄影师"), scenic: []),
|
||
RolePermissionResponse(role: RoleInfo(id: 2, name: "店长"), scenic: [])
|
||
])
|
||
viewModel.selectRole(id: 1)
|
||
await viewModel.loadScenicListIfNeeded(api: api)
|
||
viewModel.toggleScenic(id: 2)
|
||
|
||
viewModel.selectRole(id: 2)
|
||
|
||
XCTAssertTrue(viewModel.selectedScenicIds.isEmpty)
|
||
XCTAssertTrue(viewModel.scenicOptions.isEmpty)
|
||
}
|
||
|
||
/// 测试无角色或无景区时禁止提交。
|
||
func testPermissionApplyRequiresRoleAndScenicBeforeSubmit() {
|
||
let viewModel = PermissionApplyViewModel()
|
||
|
||
XCTAssertFalse(viewModel.canSubmit)
|
||
|
||
viewModel.bootstrap(rolePermissions: [RolePermissionResponse(role: RoleInfo(id: 1, name: "摄影师"), scenic: [])])
|
||
viewModel.selectRole(id: 1)
|
||
|
||
XCTAssertFalse(viewModel.canSubmit)
|
||
}
|
||
|
||
/// 测试提交成功和失败会更新状态。
|
||
func testPermissionApplySubmitSuccessAndFailure() async {
|
||
let api = ScenicPermissionServingFake()
|
||
let viewModel = PermissionApplyViewModel()
|
||
viewModel.bootstrap(rolePermissions: [RolePermissionResponse(role: RoleInfo(id: 1, name: "摄影师"), scenic: [])])
|
||
viewModel.selectRole(id: 1)
|
||
viewModel.scenicOptions = [PermissionScenicOption(id: 2, name: "新增景区", selected: false, disabled: false)]
|
||
viewModel.toggleScenic(id: 2)
|
||
|
||
await viewModel.submit(api: api)
|
||
|
||
XCTAssertEqual(api.submittedRoleId, 1)
|
||
XCTAssertEqual(api.submittedScenicIds, [2])
|
||
XCTAssertEqual(viewModel.message, "提交成功,等待审核")
|
||
|
||
api.roleApplySubmitError = TestError.sample
|
||
await viewModel.submit(api: api)
|
||
|
||
XCTAssertEqual(viewModel.message, TestError.sample.localizedDescription)
|
||
}
|
||
|
||
/// 测试地区加载失败会进入初始化失败态。
|
||
func testScenicApplicationAreaFailureMarksLoadFailed() async {
|
||
let api = ScenicPermissionServingFake()
|
||
api.areasError = TestError.sample
|
||
let viewModel = ScenicApplicationViewModel()
|
||
|
||
await viewModel.loadInitial(api: api)
|
||
|
||
XCTAssertTrue(viewModel.loadFailed)
|
||
XCTAssertTrue(viewModel.provinces.isEmpty)
|
||
}
|
||
|
||
/// 测试待审核或驳回申请能回填表单。
|
||
func testScenicApplicationPendingRefillsForm() async {
|
||
let api = ScenicPermissionServingFake()
|
||
api.areas = [ScenicAreaNode(id: "zj", name: "浙江", children: [ScenicAreaNode(id: "hz", name: "杭州")])]
|
||
api.scenicPendings = ScenicApplicationPendingsResponse(items: [
|
||
ScenicApplicationPendingResponse(
|
||
id: 1,
|
||
code: "A001",
|
||
scenicId: 9,
|
||
scenicName: "新景区",
|
||
scenicImages: "https://a.com/1.jpg,https://a.com/2.jpg",
|
||
scenicProvince: "浙江",
|
||
scenicCity: "杭州",
|
||
coopType: 3,
|
||
remark: "备注",
|
||
status: 3
|
||
)
|
||
])
|
||
let viewModel = ScenicApplicationViewModel()
|
||
|
||
await viewModel.loadInitial(api: api)
|
||
|
||
XCTAssertEqual(viewModel.scenicName, "新景区")
|
||
XCTAssertEqual(viewModel.remoteImageURLs.count, 2)
|
||
XCTAssertEqual(viewModel.selectedProvince, "浙江")
|
||
XCTAssertEqual(viewModel.selectedCity, "杭州")
|
||
XCTAssertEqual(viewModel.coopType, 3)
|
||
XCTAssertFalse(viewModel.isReadOnly)
|
||
}
|
||
|
||
/// 测试景区申请必填项和协议校验。
|
||
func testScenicApplicationValidation() {
|
||
let viewModel = ScenicApplicationViewModel()
|
||
|
||
XCTAssertEqual(viewModel.validationMessage, "请输入景区名称")
|
||
|
||
viewModel.scenicName = "新景区"
|
||
XCTAssertEqual(viewModel.validationMessage, "请至少上传一张景区图片")
|
||
|
||
viewModel.imageURLs = "https://a.com/1.jpg"
|
||
XCTAssertEqual(viewModel.validationMessage, "请选择省份")
|
||
|
||
viewModel.selectedProvince = "浙江"
|
||
XCTAssertEqual(viewModel.validationMessage, "请选择城市")
|
||
|
||
viewModel.selectedCity = "杭州"
|
||
XCTAssertEqual(viewModel.validationMessage, "请先阅读并同意用户须知与隐私政策")
|
||
|
||
viewModel.agreed = true
|
||
XCTAssertNil(viewModel.validationMessage)
|
||
}
|
||
|
||
/// 测试本地图片会先上传 OSS,再提交最终 URL。
|
||
func testScenicApplicationUploadsBeforeSubmit() async {
|
||
let api = ScenicPermissionServingFake()
|
||
let uploader = OSSUploadServingFake()
|
||
uploader.uploadedURLs = ["https://oss.com/scenic.jpg"]
|
||
let viewModel = ScenicApplicationViewModel()
|
||
viewModel.scenicName = "新景区"
|
||
viewModel.selectedProvince = "浙江"
|
||
viewModel.selectedCity = "杭州"
|
||
viewModel.agreed = true
|
||
viewModel.addLocalImage(data: Data([1, 2, 3]), fileName: "local.jpg")
|
||
|
||
await viewModel.submit(api: api, uploader: uploader)
|
||
|
||
XCTAssertEqual(uploader.uploadedFileNames, ["local.jpg"])
|
||
XCTAssertEqual(api.submittedScenicRequest?.scenicImages, ["https://oss.com/scenic.jpg"])
|
||
XCTAssertEqual(api.uploadPlaceholders.first?.fileName, "local.jpg")
|
||
XCTAssertEqual(viewModel.message, "提交成功")
|
||
}
|
||
|
||
/// 测试图片上传失败时不提交表单。
|
||
func testScenicApplicationUploadFailureDoesNotSubmit() async {
|
||
let api = ScenicPermissionServingFake()
|
||
let uploader = OSSUploadServingFake()
|
||
uploader.uploadError = TestError.sample
|
||
let viewModel = ScenicApplicationViewModel()
|
||
viewModel.scenicName = "新景区"
|
||
viewModel.selectedProvince = "浙江"
|
||
viewModel.selectedCity = "杭州"
|
||
viewModel.agreed = true
|
||
viewModel.addLocalImage(data: Data([1]), fileName: "local.jpg")
|
||
|
||
await viewModel.submit(api: api, uploader: uploader)
|
||
|
||
XCTAssertNil(api.submittedScenicRequest)
|
||
XCTAssertEqual(viewModel.message, TestError.sample.localizedDescription)
|
||
}
|
||
|
||
/// 测试驳回申请初始化时会回填角色和景区选择。
|
||
func testPermissionApplyBootstrapWithRejectedPending() {
|
||
let pending = RoleApplyPendingResponse(
|
||
id: 9,
|
||
code: "AP-001",
|
||
roleId: 2,
|
||
roleName: "店长",
|
||
scenicList: [RoleApplyScenicItem(id: 3, name: "驳回景区")],
|
||
status: 3
|
||
)
|
||
let viewModel = PermissionApplyViewModel(initialPending: pending)
|
||
viewModel.bootstrap(rolePermissions: [
|
||
RolePermissionResponse(role: RoleInfo(id: 1, name: "摄影师", notes: "拍摄"), scenic: []),
|
||
RolePermissionResponse(role: RoleInfo(id: 2, name: "店长", notes: "门店管理"), scenic: [])
|
||
])
|
||
|
||
XCTAssertEqual(viewModel.selectedRoleId, 2)
|
||
XCTAssertEqual(viewModel.selectedScenicIds, [3])
|
||
XCTAssertEqual(viewModel.selectedRoleName, "店长")
|
||
XCTAssertEqual(viewModel.selectedRoleNotes, "门店管理")
|
||
}
|
||
|
||
/// 测试景区选择摘要会根据选择数量变化。
|
||
func testPermissionApplySelectedScenicSummary() {
|
||
let viewModel = PermissionApplyViewModel()
|
||
viewModel.scenicOptions = [
|
||
PermissionScenicOption(id: 1, name: "景区A", selected: false, disabled: false),
|
||
PermissionScenicOption(id: 2, name: "景区B", selected: false, disabled: false)
|
||
]
|
||
viewModel.toggleScenic(id: 1)
|
||
viewModel.toggleScenic(id: 2)
|
||
|
||
XCTAssertEqual(viewModel.selectedScenicSummary, "已选择 2 个景区")
|
||
|
||
viewModel.toggleScenic(id: 2)
|
||
XCTAssertEqual(viewModel.selectedScenicSummary, "景区A")
|
||
}
|
||
|
||
/// 测试已有角色权限列表和景区加载失败态。
|
||
func testPermissionApplyExistingRolesAndScenicLoadFailure() async {
|
||
let api = ScenicPermissionServingFake()
|
||
api.scenicListError = TestError.sample
|
||
let viewModel = PermissionApplyViewModel()
|
||
viewModel.bootstrap(rolePermissions: [
|
||
RolePermissionResponse(role: RoleInfo(id: 1, name: "摄影师"), scenic: [ScenicInfo(id: 1, name: "已有景区")])
|
||
])
|
||
viewModel.selectRole(id: 1)
|
||
|
||
await viewModel.loadScenicListIfNeeded(api: api)
|
||
|
||
XCTAssertEqual(viewModel.existingRoleInfos.first?.scenics.map(\.id), [1])
|
||
XCTAssertTrue(viewModel.scenicLoadFailed)
|
||
XCTAssertEqual(viewModel.scenicLoadFailureReason, TestError.sample.localizedDescription)
|
||
XCTAssertEqual(viewModel.message, "景区列表加载失败,请重试")
|
||
XCTAssertTrue(viewModel.selectedScenicIds.isEmpty)
|
||
}
|
||
|
||
/// 测试权限申请状态页会按申请编号匹配记录。
|
||
func testPermissionApplyStatusLoadByApplyCode() async {
|
||
let api = ScenicPermissionServingFake()
|
||
api.roleApplies = [
|
||
RoleApplyPendingResponse(id: 1, code: "AP-001", roleId: 1, roleName: "摄影师", status: 2),
|
||
RoleApplyPendingResponse(id: 2, code: "AP-002", roleId: 2, roleName: "店长", status: 1)
|
||
]
|
||
let viewModel = PermissionApplyStatusViewModel()
|
||
|
||
await viewModel.load(api: api, applyCode: " AP-002 ")
|
||
|
||
XCTAssertEqual(viewModel.pending?.code, "AP-002")
|
||
XCTAssertFalse(viewModel.loadFailed)
|
||
}
|
||
|
||
/// 测试权限申请状态页无编号时会展示审核中或驳回记录。
|
||
func testPermissionApplyStatusLoadPendingOrRejectedWithoutCode() async {
|
||
let api = ScenicPermissionServingFake()
|
||
api.roleApplies = [
|
||
RoleApplyPendingResponse(id: 1, code: "AP-001", roleId: 1, roleName: "摄影师", status: 2),
|
||
RoleApplyPendingResponse(id: 2, code: "AP-002", roleId: 2, roleName: "店长", status: 3)
|
||
]
|
||
let viewModel = PermissionApplyStatusViewModel()
|
||
|
||
await viewModel.load(api: api, applyCode: nil)
|
||
|
||
XCTAssertEqual(viewModel.pending?.status, 3)
|
||
}
|
||
|
||
/// 测试权限申请状态页加载失败时会进入失败态。
|
||
func testPermissionApplyStatusLoadFailure() async {
|
||
let api = ScenicPermissionServingFake()
|
||
api.roleApplyAllError = TestError.sample
|
||
let viewModel = PermissionApplyStatusViewModel()
|
||
|
||
await viewModel.load(api: api, applyCode: "AP-001")
|
||
|
||
XCTAssertNil(viewModel.pending)
|
||
XCTAssertTrue(viewModel.loadFailed)
|
||
XCTAssertEqual(viewModel.loadFailureReason, TestError.sample.localizedDescription)
|
||
}
|
||
|
||
/// 测试审核中的景区申请会进入只读态。
|
||
func testScenicApplicationReadOnlyWhenPendingReview() async {
|
||
let api = ScenicPermissionServingFake()
|
||
api.areas = [ScenicAreaNode(id: "zj", name: "浙江", children: [ScenicAreaNode(id: "hz", name: "杭州")])]
|
||
api.scenicPendings = ScenicApplicationPendingsResponse(items: [
|
||
ScenicApplicationPendingResponse(
|
||
id: 1,
|
||
code: "A001",
|
||
scenicId: 9,
|
||
scenicName: "审核中景区",
|
||
scenicImages: "https://a.com/1.jpg",
|
||
scenicProvince: "浙江",
|
||
scenicCity: "杭州",
|
||
coopType: 1,
|
||
remark: nil,
|
||
status: 1
|
||
)
|
||
])
|
||
let viewModel = ScenicApplicationViewModel()
|
||
|
||
await viewModel.loadInitial(api: api)
|
||
|
||
XCTAssertTrue(viewModel.isReadOnly)
|
||
XCTAssertTrue(viewModel.isSubmitLocked)
|
||
XCTAssertFalse(viewModel.canSubmit)
|
||
}
|
||
|
||
/// 测试省份切换后会清空不匹配的城市。
|
||
func testScenicApplicationOnProvinceChangeClearsInvalidCity() {
|
||
let viewModel = ScenicApplicationViewModel()
|
||
viewModel.provinces = [
|
||
ScenicAreaNode(id: "zj", name: "浙江", children: [ScenicAreaNode(id: "hz", name: "杭州")]),
|
||
ScenicAreaNode(id: "js", name: "江苏", children: [ScenicAreaNode(id: "nj", name: "南京")])
|
||
]
|
||
viewModel.selectedProvince = "浙江"
|
||
viewModel.onProvinceChange()
|
||
viewModel.selectedCity = "杭州"
|
||
|
||
viewModel.selectedProvince = "江苏"
|
||
viewModel.onProvinceChange()
|
||
|
||
XCTAssertEqual(viewModel.cities.map(\.name), ["南京"])
|
||
XCTAssertEqual(viewModel.selectedCity, "")
|
||
}
|
||
|
||
/// 测试远程图片解析和删除逻辑。
|
||
func testScenicApplicationRemoteImageParsingAndRemoval() {
|
||
let viewModel = ScenicApplicationViewModel()
|
||
viewModel.imageURLs = "https://a.com/1.jpg,https://a.com/2.jpg\nhttps://a.com/3.jpg"
|
||
|
||
XCTAssertEqual(viewModel.remoteImageURLs, [
|
||
"https://a.com/1.jpg",
|
||
"https://a.com/2.jpg",
|
||
"https://a.com/3.jpg"
|
||
])
|
||
XCTAssertEqual(viewModel.uploadPlaceholderCount, 3)
|
||
|
||
viewModel.removeRemoteImage("https://a.com/2.jpg")
|
||
XCTAssertEqual(viewModel.remoteImageURLs, ["https://a.com/1.jpg", "https://a.com/3.jpg"])
|
||
}
|
||
|
||
/// 测试本地图片数量上限为 20 张。
|
||
func testScenicApplicationMaxImageLimit() {
|
||
let viewModel = ScenicApplicationViewModel()
|
||
viewModel.imageURLs = Array(repeating: "https://a.com/x.jpg", count: 19).joined(separator: "\n")
|
||
|
||
viewModel.addLocalImage(data: Data([1]), fileName: "local.jpg")
|
||
XCTAssertEqual(viewModel.uploadPlaceholderCount, 20)
|
||
|
||
viewModel.addLocalImage(data: Data([2]), fileName: "overflow.jpg")
|
||
XCTAssertEqual(viewModel.uploadPlaceholderCount, 20)
|
||
XCTAssertEqual(viewModel.message, "最多上传20张景区图片")
|
||
}
|
||
|
||
/// 测试上传占位策略会从 URL 提取文件名和类型。
|
||
func testScenicUploadPlaceholderPolicy() {
|
||
XCTAssertEqual(
|
||
ScenicUploadPlaceholderPolicy.fileName(from: "https://cdn.example.com/scenic/a.jpg?x=1#preview", fallbackPrefix: "scenic"),
|
||
"a.jpg"
|
||
)
|
||
XCTAssertEqual(ScenicUploadPlaceholderPolicy.fileType(from: "clip.mp4"), "video")
|
||
XCTAssertEqual(ScenicUploadPlaceholderPolicy.fileType(from: "doc.pdf"), "file")
|
||
XCTAssertEqual(ScenicUploadPlaceholderPolicy.fileType(from: "photo.heic"), "image")
|
||
}
|
||
|
||
/// 测试仅远程图片时不会走 OSS 上传,但会提交占位和表单。
|
||
func testScenicApplicationSubmitWithRemoteImagesOnly() async {
|
||
let api = ScenicPermissionServingFake()
|
||
let uploader = OSSUploadServingFake()
|
||
let viewModel = ScenicApplicationViewModel()
|
||
viewModel.scenicName = "新景区"
|
||
viewModel.imageURLs = "https://a.com/1.jpg"
|
||
viewModel.selectedProvince = "浙江"
|
||
viewModel.selectedCity = "杭州"
|
||
viewModel.agreed = true
|
||
|
||
await viewModel.submit(api: api, uploader: uploader)
|
||
|
||
XCTAssertTrue(uploader.uploadedFileNames.isEmpty)
|
||
XCTAssertEqual(api.submittedScenicRequest?.scenicImages, ["https://a.com/1.jpg"])
|
||
XCTAssertEqual(api.uploadPlaceholders.count, 1)
|
||
XCTAssertEqual(viewModel.message, "提交成功")
|
||
}
|
||
|
||
/// 测试没有匹配状态时清空待审核表单。
|
||
func testScenicApplicationClearPendingWhenNoMatchingStatus() async {
|
||
let api = ScenicPermissionServingFake()
|
||
api.areas = [ScenicAreaNode(id: "zj", name: "浙江", children: [ScenicAreaNode(id: "hz", name: "杭州")])]
|
||
api.scenicPendings = ScenicApplicationPendingsResponse(items: [
|
||
ScenicApplicationPendingResponse(
|
||
id: 1,
|
||
code: "A001",
|
||
scenicId: 9,
|
||
scenicName: "已通过",
|
||
scenicImages: "",
|
||
scenicProvince: "浙江",
|
||
scenicCity: "杭州",
|
||
coopType: 1,
|
||
remark: nil,
|
||
status: 2
|
||
)
|
||
])
|
||
let viewModel = ScenicApplicationViewModel()
|
||
viewModel.scenicName = "草稿"
|
||
|
||
await viewModel.loadInitial(api: api)
|
||
|
||
XCTAssertNil(viewModel.pending)
|
||
XCTAssertEqual(viewModel.scenicName, "")
|
||
XCTAssertFalse(viewModel.agreed)
|
||
}
|
||
}
|
||
|
||
@MainActor
|
||
/// 景区权限服务测试替身,用于验证 ViewModel 请求行为。
|
||
private final class ScenicPermissionServingFake: ScenicPermissionServing {
|
||
var scenicList = ScenicListAllResponse(total: 0, list: [])
|
||
var scenicListError: Error?
|
||
var areas: [ScenicAreaNode] = []
|
||
var areasError: Error?
|
||
var scenicPendings = ScenicApplicationPendingsResponse()
|
||
var scenicPendingsError: Error?
|
||
var submittedScenicRequest: ScenicApplicationSubmitRequest?
|
||
var uploadPlaceholders: [ScenicApplicationUploadPlaceholder] = []
|
||
var roleApplies: [RoleApplyPendingResponse] = []
|
||
var roleApplyAllError: Error?
|
||
var submittedRoleId: Int?
|
||
var submittedScenicIds: [Int] = []
|
||
var roleApplySubmitError: Error?
|
||
|
||
/// 返回景区列表或抛出测试错误。
|
||
func scenicListAll() async throws -> ScenicListAllResponse {
|
||
if let scenicListError { throw scenicListError }
|
||
return scenicList
|
||
}
|
||
|
||
/// 返回地区树或抛出测试错误。
|
||
func areas() async throws -> [ScenicAreaNode] {
|
||
if let areasError { throw areasError }
|
||
return areas
|
||
}
|
||
|
||
/// 返回景区申请记录或抛出测试错误。
|
||
func scenicApplicationPendingAll() async throws -> ScenicApplicationPendingsResponse {
|
||
if let scenicPendingsError { throw scenicPendingsError }
|
||
return scenicPendings
|
||
}
|
||
|
||
/// 记录景区申请提交请求。
|
||
func scenicSubmit(_ request: ScenicApplicationSubmitRequest) async throws {
|
||
submittedScenicRequest = request
|
||
}
|
||
|
||
/// 记录景区申请图片占位信息。
|
||
func scenicApplicationUploadPlaceholder(_ items: [ScenicApplicationUploadPlaceholder]) async throws {
|
||
uploadPlaceholders = items
|
||
}
|
||
|
||
/// 返回角色权限申请记录。
|
||
func roleApplyAll() async throws -> [RoleApplyPendingResponse] {
|
||
if let roleApplyAllError { throw roleApplyAllError }
|
||
return roleApplies
|
||
}
|
||
|
||
/// 记录角色权限申请提交请求。
|
||
func roleApplySubmit(roleId: Int, scenicIds: [Int]) async throws {
|
||
if let roleApplySubmitError { throw roleApplySubmitError }
|
||
submittedRoleId = roleId
|
||
submittedScenicIds = scenicIds
|
||
}
|
||
}
|
||
|
||
@MainActor
|
||
/// OSS 上传测试替身,用于验证景区申请图片上传顺序。
|
||
private final class OSSUploadServingFake: OSSUploadServing {
|
||
var uploadedURLs: [String] = []
|
||
var uploadedFileNames: [String] = []
|
||
var uploadError: Error?
|
||
|
||
/// 上传用户头像测试替身。
|
||
func uploadUserAvatar(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||
try await upload(data: data, fileName: fileName, scenicId: scenicId, onProgress: onProgress)
|
||
}
|
||
|
||
/// 上传实名认证图片测试替身。
|
||
func uploadRealNameImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||
try await upload(data: data, fileName: fileName, scenicId: scenicId, onProgress: onProgress)
|
||
}
|
||
|
||
/// 上传云盘文件测试替身。
|
||
func uploadCloudFile(data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||
try await upload(data: data, fileName: fileName, scenicId: scenicId, onProgress: onProgress)
|
||
}
|
||
|
||
/// 上传相册文件测试替身。
|
||
func uploadAlbumFile(data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||
try await upload(data: data, fileName: fileName, scenicId: scenicId, onProgress: onProgress)
|
||
}
|
||
|
||
/// 上传任务文件测试替身。
|
||
func uploadTaskFile(data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||
try await upload(data: data, fileName: fileName, scenicId: scenicId, onProgress: onProgress)
|
||
}
|
||
|
||
/// 上传项目图片测试替身。
|
||
func uploadProjectImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||
try await upload(data: data, fileName: fileName, scenicId: scenicId, onProgress: onProgress)
|
||
}
|
||
|
||
/// 上传打卡点图片测试替身。
|
||
func uploadPunchPointImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||
try await upload(data: data, fileName: fileName, scenicId: scenicId, onProgress: onProgress)
|
||
}
|
||
|
||
/// 上传景区申请图片测试替身。
|
||
func uploadScenicApplicationImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||
try await upload(data: data, fileName: fileName, scenicId: scenicId, onProgress: onProgress)
|
||
}
|
||
|
||
/// 上传银行卡图片测试替身。
|
||
func uploadBankCardImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||
try await upload(data: data, fileName: fileName, scenicId: scenicId, onProgress: onProgress)
|
||
}
|
||
|
||
/// 统一记录上传请求并返回预设 URL。
|
||
private func upload(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||
if let uploadError { throw uploadError }
|
||
uploadedFileNames.append(fileName)
|
||
onProgress(100)
|
||
return uploadedURLs.removeFirst()
|
||
}
|
||
}
|
||
|
||
/// 测试错误实体。
|
||
private enum TestError: LocalizedError {
|
||
case sample
|
||
|
||
var errorDescription: String? {
|
||
"测试错误"
|
||
}
|
||
}
|