Files
suixinkan_ios_new/suixinkanTests/Assets/AssetsViewModelTests.swift
汉秋 fb8430889b Add Projects, Schedule, and Invite modules with home routing.
Migrate project management, schedule management, and photographer invitation flows from placeholders, including project image OSS upload and shared business models.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 15:57:15 +08:00

676 lines
29 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// AssetsViewModelTests.swift
// suixinkanTests
//
// Created by Codex on 2026/6/23.
//
import XCTest
@testable import suixinkan
@MainActor
/// ViewModel
final class AssetsViewModelTests: XCTestCase {
/// OSS
func testCloudUploadStopsWhenPermissionDenied() async {
let api = MockAssetsService()
api.uploadPermission = false
api.uploadReason = "空间不足"
let uploader = MockAssetsUploader()
let viewModel = CloudStorageViewModel()
let success = await viewModel.upload(
localFiles: [CloudLocalUploadFile(data: Data([1]), fileName: "a.jpg", fileType: 2)],
scenicId: 9,
api: api,
uploadService: uploader,
transferStore: CloudTransferStore()
)
XCTAssertFalse(success)
XCTAssertEqual(viewModel.errorMessage, "空间不足")
XCTAssertEqual(uploader.uploadedFiles.count, 0)
XCTAssertEqual(api.cloudUploadRequests.count, 0)
}
/// OSS
func testCloudUploadUploadsOSSBeforeCloudRecord() async {
let api = MockAssetsService()
let uploader = MockAssetsUploader()
let transferStore = CloudTransferStore()
let viewModel = CloudStorageViewModel()
let success = await viewModel.upload(
localFiles: [CloudLocalUploadFile(data: Data([1, 2]), fileName: "a.jpg", fileType: 2)],
scenicId: 9,
api: api,
uploadService: uploader,
transferStore: transferStore
)
XCTAssertTrue(success)
XCTAssertEqual(uploader.uploadedFiles.map(\.fileName), ["a.jpg"])
XCTAssertEqual(api.cloudUploadRequests.first?.fileUrl, "https://cdn.example.com/a.jpg")
XCTAssertEqual(transferStore.records.first?.status, .success)
}
///
func testCloudPaginationAndFolderNavigation() async {
let folder = CloudDriveFile(id: 2, parentFolderId: 0, fileUrl: "", name: "子目录", type: 99)
let api = MockAssetsService()
api.cloudPages = [
ListPayload(total: 2, list: [folder]),
ListPayload(total: 2, list: [CloudDriveFile(id: 3, parentFolderId: 0, fileUrl: "https://cdn/a.jpg", name: "a.jpg", type: 2)])
]
let viewModel = CloudStorageViewModel()
await viewModel.reload(api: api)
await viewModel.loadMore(api: api)
await viewModel.enterFolder(folder, api: api)
await viewModel.popToFolder(at: 0, api: api)
XCTAssertEqual(api.cloudListRequests.first?.parentFolderId, 0)
XCTAssertEqual(api.cloudListRequests[1].page, 2)
XCTAssertEqual(api.cloudListRequests[2].parentFolderId, 2)
XCTAssertEqual(viewModel.currentFolderId, 0)
}
///
func testMediaListFilterAndPagination() async {
let api = MockAssetsService()
api.mediaListResponses = [
MediaLibraryListResponse.fixture(total: 2, ids: [1]),
MediaLibraryListResponse.fixture(total: 2, ids: [2])
]
let viewModel = MediaLibraryViewModel(kind: .material)
viewModel.keyword = " 航拍 "
viewModel.selectedAuditFilter = .approved
await viewModel.reload(api: api)
await viewModel.loadMore(api: api)
XCTAssertEqual(api.mediaListRequests.first?.keyword, "航拍")
XCTAssertEqual(api.mediaListRequests.first?.status, 1)
XCTAssertEqual(api.mediaListRequests.last?.page, 2)
XCTAssertEqual(viewModel.items.map(\.id), [1, 2])
}
/// 使
func testSampleMediaListUsesSampleKind() async {
let api = MockAssetsService()
let viewModel = MediaLibraryViewModel(kind: .sample)
await viewModel.reload(api: api)
XCTAssertEqual(api.mediaListRequests.first?.kind, .sample)
}
///
func testRejectedMediaCannotToggleListing() async {
let api = MockAssetsService()
let viewModel = MediaLibraryViewModel()
let item = MediaLibraryItem.fixture(id: 1, status: 2, listingStatus: 0)
let success = await viewModel.toggleListing(item, api: api)
XCTAssertFalse(success)
XCTAssertEqual(api.mediaOperationRequests.count, 0)
XCTAssertEqual(viewModel.errorMessage, "审核通过后才能上下架")
}
///
func testSampleToggleListingUsesSampleType() async {
let api = MockAssetsService()
let viewModel = MediaLibraryViewModel(kind: .sample)
let item = MediaLibraryItem.fixture(id: 1, status: 1, listingStatus: 0)
let success = await viewModel.toggleListing(item, api: api)
XCTAssertTrue(success)
XCTAssertEqual(api.mediaOperationRequests.first?.type, 2)
}
/// OSS URL
func testMediaUploadUploadsFilesBeforeSubmit() async {
let api = MockAssetsService()
let uploader = MockAssetsUploader()
let viewModel = MediaLibraryEditorViewModel()
viewModel.name = "航拍素材"
viewModel.selectedSpotId = 6
viewModel.tagsText = "航拍,夜景"
viewModel.setCover(MediaLocalUploadFile(data: Data([1]), fileName: "cover.jpg", fileType: 2, width: 100, height: 80))
viewModel.addMediaFiles([MediaLocalUploadFile(data: Data([2]), fileName: "a.jpg", fileType: 2, width: 120, height: 90)])
let success = await viewModel.submit(scenicId: 9, api: api, uploadService: uploader)
XCTAssertTrue(success)
XCTAssertEqual(uploader.uploadedFiles.map(\.fileName), ["cover.jpg", "a.jpg"])
XCTAssertEqual(api.mediaUploadRequests.first?.coverUrl, "https://cdn.example.com/cover.jpg")
XCTAssertEqual(api.mediaUploadRequests.first?.mediaList.first?.ossUrl, "https://cdn.example.com/a.jpg")
XCTAssertEqual(api.mediaUploadRequests.first?.materialTag, "航拍,夜景")
}
///
func testMediaUploadFailureDoesNotSubmit() async {
let api = MockAssetsService()
let uploader = MockAssetsUploader()
uploader.shouldFail = true
let viewModel = MediaLibraryEditorViewModel()
viewModel.name = "航拍素材"
viewModel.selectedSpotId = 6
viewModel.setCover(MediaLocalUploadFile(data: Data([1]), fileName: "cover.jpg", fileType: 2, width: 100, height: 80))
viewModel.addMediaFiles([MediaLocalUploadFile(data: Data([2]), fileName: "a.jpg", fileType: 2, width: 120, height: 90)])
let success = await viewModel.submit(scenicId: 9, api: api, uploadService: uploader)
XCTAssertFalse(success)
XCTAssertEqual(api.mediaUploadRequests.count, 0)
}
///
func testSampleUploadRequiresProject() async {
let api = MockAssetsService()
let uploader = MockAssetsUploader()
let viewModel = MediaLibraryEditorViewModel()
viewModel.name = "亲子样片"
viewModel.selectedSpotId = 6
viewModel.setCover(MediaLocalUploadFile(data: Data([1]), fileName: "cover.jpg", fileType: 2, width: 100, height: 80))
viewModel.addMediaFiles([MediaLocalUploadFile(data: Data([2]), fileName: "a.jpg", fileType: 2, width: 120, height: 90)])
let success = await viewModel.submit(kind: .sample, scenicId: 9, api: api, uploadService: uploader)
XCTAssertFalse(success)
XCTAssertEqual(viewModel.errorMessage, "请选择关联项目")
XCTAssertEqual(api.mediaUploadRequests.count, 0)
}
///
func testSampleUploadUploadsFilesAndSubmitsProject() async {
let api = MockAssetsService()
let uploader = MockAssetsUploader()
let viewModel = MediaLibraryEditorViewModel()
viewModel.name = "亲子样片"
viewModel.selectedSpotId = 6
viewModel.selectedProjectId = 88
viewModel.setCover(MediaLocalUploadFile(data: Data([1]), fileName: "cover.jpg", fileType: 2, width: 100, height: 80))
viewModel.addMediaFiles([MediaLocalUploadFile(data: Data([2]), fileName: "a.jpg", fileType: 2, width: 120, height: 90)])
let success = await viewModel.submit(kind: .sample, scenicId: 9, api: api, uploadService: uploader)
XCTAssertTrue(success)
XCTAssertEqual(uploader.uploadedFiles.map(\.fileName), ["cover.jpg", "a.jpg"])
XCTAssertEqual(api.mediaUploadRequests.first?.type, 2)
XCTAssertEqual(api.mediaUploadRequests.first?.projectId, 88)
}
///
func testSampleDuplicateSubmitIsIgnored() async {
let api = MockAssetsService()
let uploader = MockAssetsUploader()
let viewModel = MediaLibraryEditorViewModel()
viewModel.name = "亲子样片"
viewModel.selectedSpotId = 6
viewModel.selectedProjectId = 88
viewModel.isSubmitting = true
viewModel.setCover(MediaLocalUploadFile(data: Data([1]), fileName: "cover.jpg", fileType: 2, width: 100, height: 80))
viewModel.addMediaFiles([MediaLocalUploadFile(data: Data([2]), fileName: "a.jpg", fileType: 2, width: 120, height: 90)])
let success = await viewModel.submit(kind: .sample, scenicId: 9, api: api, uploadService: uploader)
XCTAssertFalse(success)
XCTAssertEqual(api.mediaUploadRequests.count, 0)
XCTAssertEqual(uploader.uploadedFiles.count, 0)
}
///
func testAlbumListClearsWithoutScenic() async {
let api = MockAssetsService()
let viewModel = AlbumListViewModel()
await viewModel.reload(api: api, scenicId: nil)
XCTAssertTrue(viewModel.folders.isEmpty)
XCTAssertEqual(api.albumFolderListRequests.count, 0)
XCTAssertEqual(viewModel.errorMessage, "缺少当前景区,无法加载相册")
}
///
func testAlbumListFilterAndPagination() async {
let api = MockAssetsService()
api.albumFolderPages = [
ListPayload(total: 2, list: [AlbumFolderItem.fixture(id: 1, name: "旅拍")]),
ListPayload(total: 2, list: [AlbumFolderItem.fixture(id: 2, name: "亲子")])
]
let viewModel = AlbumListViewModel()
viewModel.searchText = " 旅拍 "
viewModel.startTime = "2026-06-01"
viewModel.endTime = "2026-06-24"
await viewModel.reload(api: api, scenicId: 9)
await viewModel.loadMore(api: api, scenicId: 9)
XCTAssertEqual(api.albumFolderListRequests.first?.name, "旅拍")
XCTAssertEqual(api.albumFolderListRequests.first?.startTime, "2026-06-01")
XCTAssertEqual(api.albumFolderListRequests.last?.page, 2)
XCTAssertEqual(viewModel.folders.map(\.id), [1, 2])
}
///
func testAlbumCreateRefreshesList() async {
let api = MockAssetsService()
api.albumFolderPages = [ListPayload(total: 1, list: [AlbumFolderItem.fixture(id: 3, name: "新相册")])]
let viewModel = AlbumListViewModel()
let success = await viewModel.createFolder(name: " 新相册 ", remark: " 备注 ", scenicId: 9, api: api)
XCTAssertTrue(success)
XCTAssertEqual(api.albumAddRequests.first?.name, "新相册")
XCTAssertEqual(api.albumAddRequests.first?.remark, "备注")
XCTAssertEqual(viewModel.folders.first?.id, 3)
}
/// Tab
func testAlbumDetailLoadsInfoAndSwitchesTab() async {
let api = MockAssetsService()
api.albumFilePages = [
ListPayload(total: 1, list: [AlbumFileItem.fixture(id: 11, fileType: 2)]),
ListPayload(total: 1, list: [AlbumFileItem.fixture(id: 12, fileType: 1)])
]
let viewModel = AlbumDetailViewModel(folderId: 3)
await viewModel.reload(api: api, scenicId: 9)
await viewModel.selectTab(.video, api: api, scenicId: 9)
XCTAssertEqual(api.albumInfoRequests, [3, 3])
XCTAssertEqual(api.albumFileListRequests.map { $0.fileType }, [2, 1])
XCTAssertEqual(viewModel.files.first?.id, 12)
XCTAssertEqual(viewModel.page, 1)
}
///
func testAlbumDetailMutationsUseExpectedRequests() async {
let api = MockAssetsService()
let viewModel = AlbumDetailViewModel(folderId: 3)
let file = AlbumFileItem.fixture(id: 11, fileType: 2)
_ = await viewModel.delete(file: file, scenicId: 9, api: api)
_ = await viewModel.setCover(file: file, scenicId: 9, api: api)
_ = await viewModel.rename(name: " 改名 ", scenicId: 9, api: api)
_ = await viewModel.updateRemark(" 新备注 ", scenicId: 9, api: api)
XCTAssertEqual(api.albumDeleteRequests.first?.idList, [11])
XCTAssertEqual(api.albumEditRequests[0].coverFileId, 11)
XCTAssertEqual(api.albumEditRequests[1].name, "改名")
XCTAssertEqual(api.albumEditRequests[2].remark, "新备注")
}
/// OSS
func testAlbumTrailerUploadUploadsOSSBeforeRegisteringFile() async {
let api = MockAssetsService()
api.albumFolderPages = [ListPayload(total: 1, list: [AlbumFolderItem.fixture(id: 3, name: "旅拍")])]
let uploader = MockAssetsUploader()
let viewModel = AlbumTrailerViewModel()
viewModel.addLocalFiles([AlbumLocalUploadFile(data: Data([1]), fileName: "a.jpg", fileType: 2)])
await viewModel.loadFolders(api: api, scenicId: 9)
let success = await viewModel.submit(scenicId: 9, api: api, uploadService: uploader)
XCTAssertTrue(success)
XCTAssertEqual(uploader.uploadedFiles.map(\.fileName), ["a.jpg"])
XCTAssertEqual(api.albumUploadURLRequests.first?.fileURL, "https://cdn.example.com/a.jpg")
XCTAssertTrue(viewModel.localFiles.isEmpty)
XCTAssertEqual(viewModel.uploadProgress, 100)
}
///
func testAlbumTrailerUploadFailureDoesNotRegisterFile() async {
let api = MockAssetsService()
let uploader = MockAssetsUploader()
uploader.shouldFail = true
let viewModel = AlbumTrailerViewModel()
viewModel.selectedFolderId = 3
viewModel.addLocalFiles([AlbumLocalUploadFile(data: Data([1]), fileName: "a.jpg", fileType: 2)])
let success = await viewModel.submit(scenicId: 9, api: api, uploadService: uploader)
XCTAssertFalse(success)
XCTAssertEqual(api.albumUploadURLRequests.count, 0)
}
}
///
@MainActor
private final class MockAssetsService: AssetsServing {
struct CloudListRequest: Equatable {
let parentFolderId: Int
let name: String
let type: Int
let orderBy: Int
let page: Int
let pageSize: Int
}
struct MediaListRequest: Equatable {
let kind: MediaLibraryKind
let keyword: String
let status: Int?
let page: Int
}
struct ProjectListRequest: Equatable {
let scenicId: Int
let name: String?
let page: Int
let pageSize: Int
}
struct AlbumFolderListRequest: Equatable {
let scenicId: Int
let page: Int
let pageSize: Int
let name: String
let startTime: String?
let endTime: String?
}
struct AlbumUploadURLRequest: Equatable {
let scenicId: Int
let fileURL: String
let folderId: Int
}
var cloudPages: [ListPayload<CloudDriveFile>] = [ListPayload(total: 0, list: [])]
var mediaListResponses: [MediaLibraryListResponse] = [MediaLibraryListResponse.fixture(total: 0, ids: [])]
var projectPages: [ListPayload<PhotographerProjectItem>] = [ListPayload(total: 0, list: [])]
var albumFolderPages: [ListPayload<AlbumFolderItem>] = [ListPayload(total: 0, list: [])]
var albumFilePages: [ListPayload<AlbumFileItem>] = [ListPayload(total: 0, list: [])]
var uploadPermission = true
var uploadReason = ""
var cloudListRequests: [CloudListRequest] = []
var cloudUploadRequests: [CloudFileUploadRequest] = []
var mediaListRequests: [MediaListRequest] = []
var mediaOperationRequests: [MediaAlbumOperationRequest] = []
var mediaUploadRequests: [MediaAlbumUploadRequest] = []
var mediaEditRequests: [MediaAlbumEditRequest] = []
var projectListRequests: [ProjectListRequest] = []
var albumFolderListRequests: [AlbumFolderListRequest] = []
var albumInfoRequests: [Int] = []
var albumFileListRequests: [(scenicId: Int, folderId: Int, fileType: Int, page: Int)] = []
var albumAddRequests: [(scenicId: Int, name: String, remark: String)] = []
var albumEditRequests: [AlbumFolderEditRequest] = []
var albumDeleteRequests: [AlbumFileDeleteRequest] = []
var albumUploadURLRequests: [AlbumUploadURLRequest] = []
///
func cloudFileList(parentFolderId: Int, name: String, type: Int, orderBy: Int, page: Int, pageSize: Int) async throws -> ListPayload<CloudDriveFile> {
cloudListRequests.append(CloudListRequest(parentFolderId: parentFolderId, name: name, type: type, orderBy: orderBy, page: page, pageSize: pageSize))
return cloudPages.isEmpty ? ListPayload(total: 0, list: []) : cloudPages.removeFirst()
}
///
func cloudFolderCreate(_ request: CloudFolderCreateRequest) async throws {}
///
func cloudFileUpload(_ request: CloudFileUploadRequest) async throws {
cloudUploadRequests.append(request)
}
///
func cloudFileDelete(_ request: CloudFileDeleteRequest) async throws {}
///
func cloudFileMove(_ request: CloudFileMoveRequest) async throws {}
///
func cloudFolderEdit(_ request: CloudFolderModifyRequest) async throws {}
///
func cloudFileEdit(_ request: CloudFileModifyRequest) async throws {}
///
func checkCloudUploadPermission() async throws -> CheckCloudUploadResponse {
try JSONDecoder().decode(CheckCloudUploadResponse.self, from: #"{"can_upload":\#(uploadPermission ? 1 : 0),"reason":"\#(uploadReason)"}"#.data(using: .utf8)!)
}
///
func mediaAlbumList(kind: MediaLibraryKind, keyword: String, status: Int?, page: Int, pageSize: Int) async throws -> MediaLibraryListResponse {
mediaListRequests.append(MediaListRequest(kind: kind, keyword: keyword, status: status, page: page))
return mediaListResponses.isEmpty ? MediaLibraryListResponse.fixture(total: 0, ids: []) : mediaListResponses.removeFirst()
}
///
func mediaAlbumDetail(id: Int, kind: MediaLibraryKind) async throws -> MediaLibraryDetail {
MediaLibraryDetail.fixture(id: id)
}
///
func mediaAlbumOperation(_ request: MediaAlbumOperationRequest) async throws {
mediaOperationRequests.append(request)
}
///
func mediaAlbumDelete(_ request: MediaAlbumDeleteRequest) async throws {}
///
func mediaAlbumAddTag(_ request: MediaAlbumAddTagRequest) async throws {}
///
func mediaAlbumUpload(_ request: MediaAlbumUploadRequest) async throws {
mediaUploadRequests.append(request)
}
///
func mediaAlbumEdit(_ request: MediaAlbumEditRequest) async throws {
mediaEditRequests.append(request)
}
///
func projectList(scenicId: Int, name: String?, page: Int, pageSize: Int) async throws -> ListPayload<PhotographerProjectItem> {
projectListRequests.append(ProjectListRequest(scenicId: scenicId, name: name, page: page, pageSize: pageSize))
return projectPages.isEmpty ? ListPayload(total: 0, list: []) : projectPages.removeFirst()
}
///
func albumFolderList(scenicId: Int, page: Int, pageSize: Int, name: String, startTime: String?, endTime: String?) async throws -> ListPayload<AlbumFolderItem> {
albumFolderListRequests.append(AlbumFolderListRequest(scenicId: scenicId, page: page, pageSize: pageSize, name: name, startTime: startTime, endTime: endTime))
return albumFolderPages.isEmpty ? ListPayload(total: 0, list: []) : albumFolderPages.removeFirst()
}
///
func albumFolderInfo(id: Int) async throws -> AlbumFolderItem {
albumInfoRequests.append(id)
return AlbumFolderItem.fixture(id: id, name: "相册\(id)")
}
///
func albumFileList(scenicId: Int, folderId: Int, fileType: Int, page: Int, pageSize: Int) async throws -> ListPayload<AlbumFileItem> {
albumFileListRequests.append((scenicId: scenicId, folderId: folderId, fileType: fileType, page: page))
return albumFilePages.isEmpty ? ListPayload(total: 0, list: []) : albumFilePages.removeFirst()
}
///
func addAlbumFolder(scenicId: Int, name: String, remark: String) async throws {
albumAddRequests.append((scenicId: scenicId, name: name, remark: remark))
}
///
func editAlbumFolder(folderId: Int, coverFileId: Int?, name: String?, remark: String?) async throws {
albumEditRequests.append(AlbumFolderEditRequest(folderId: folderId, coverFileId: coverFileId, name: name, remark: remark))
}
///
func deleteAlbumFiles(folderId: Int, idList: [Int]) async throws {
albumDeleteRequests.append(AlbumFileDeleteRequest(idList: idList, folderId: folderId))
}
///
func albumFileUploadURL(scenicId: Int, fileURL: String, folderId: Int) async throws {
albumUploadURLRequests.append(AlbumUploadURLRequest(scenicId: scenicId, fileURL: fileURL, folderId: folderId))
}
}
/// OSS CDN URL
@MainActor
private final class MockAssetsUploader: OSSUploadServing {
struct UploadedFile: Equatable {
let fileName: String
let fileType: Int
let scenicId: Int
}
var uploadedFiles: [UploadedFile] = []
var shouldFail = false
///
func uploadUserAvatar(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
try await upload(fileName: fileName, fileType: 2, scenicId: scenicId, onProgress: onProgress)
}
///
func uploadRealNameImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
try await upload(fileName: fileName, fileType: 2, scenicId: scenicId, onProgress: onProgress)
}
///
func uploadCloudFile(data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
try await upload(fileName: fileName, fileType: fileType, scenicId: scenicId, onProgress: onProgress)
}
///
func uploadAlbumFile(data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
try await upload(fileName: fileName, fileType: fileType, scenicId: scenicId, onProgress: onProgress)
}
///
func uploadTaskFile(data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
try await upload(fileName: fileName, fileType: fileType, scenicId: scenicId, onProgress: onProgress)
}
///
func uploadProjectImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
try await upload(fileName: fileName, fileType: 2, scenicId: scenicId, onProgress: onProgress)
}
///
func uploadPunchPointImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
try await upload(fileName: fileName, fileType: 2, scenicId: scenicId, onProgress: onProgress)
}
///
func uploadScenicApplicationImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
try await upload(fileName: fileName, fileType: 2, scenicId: scenicId, onProgress: onProgress)
}
///
func uploadBankCardImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
try await upload(fileName: fileName, fileType: 2, scenicId: scenicId, onProgress: onProgress)
}
///
private func upload(fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
if shouldFail {
throw APIError.networkFailed("上传失败")
}
uploadedFiles.append(UploadedFile(fileName: fileName, fileType: fileType, scenicId: scenicId))
onProgress(100)
return "https://cdn.example.com/\(fileName)"
}
}
private extension MediaLibraryListResponse {
///
static func fixture(total: Int, ids: [Int]) -> MediaLibraryListResponse {
let list = ids.map { MediaLibraryItem.fixture(id: $0, status: 1, listingStatus: 0) }
let payload = [
"total": total,
"list": list.map { ["id": $0.id, "name": $0.name, "status": $0.status, "listing_status": $0.listingStatus] },
"order": ["total_num": 0, "avg_order_amount": 0, "refund_total": 0, "avg_change": 0]
] as [String: Any]
let data = try! JSONSerialization.data(withJSONObject: payload)
return try! JSONDecoder().decode(MediaLibraryListResponse.self, from: data)
}
}
private extension MediaLibraryItem {
///
static func fixture(id: Int, status: Int, listingStatus: Int) -> MediaLibraryItem {
let payload: [String: Any] = [
"id": id,
"name": "素材\(id)",
"cover_url": "",
"created_at": "",
"status": status,
"download_count": 0,
"likes_count": 0,
"collect_count": 0,
"share_count": 0,
"scenic_spot_name": "",
"project_name": "",
"listing_status": listingStatus
]
let data = try! JSONSerialization.data(withJSONObject: payload)
return try! JSONDecoder().decode(MediaLibraryItem.self, from: data)
}
}
private extension MediaLibraryDetail {
///
static func fixture(id: Int) -> MediaLibraryDetail {
let payload: [String: Any] = [
"id": id,
"name": "素材\(id)",
"cover_url": "",
"description": "",
"status": 1,
"listing_status": 0,
"likes_count": 0,
"download_count": 0,
"collect_count": 0,
"share_count": 0,
"uploader_name": "",
"created_at": "",
"media_list": [],
"scenic_name": "",
"project_name": ""
]
let data = try! JSONSerialization.data(withJSONObject: payload)
return try! JSONDecoder().decode(MediaLibraryDetail.self, from: data)
}
}
private extension AlbumFolderItem {
///
static func fixture(id: Int, name: String) -> AlbumFolderItem {
let payload: [String: Any] = [
"id": id,
"name": name,
"cover_file_id": 0,
"count_video": 1,
"count_image": 2,
"created_at": "2026",
"remark": "备注"
]
let data = try! JSONSerialization.data(withJSONObject: payload)
return try! JSONDecoder().decode(AlbumFolderItem.self, from: data)
}
}
private extension AlbumFileItem {
///
static func fixture(id: Int, fileType: Int) -> AlbumFileItem {
let fileName = fileType == 1 ? "v.mp4" : "a.jpg"
let payload: [String: Any] = [
"id": id,
"file_name": fileName,
"file_type": fileType,
"file_url": "https://cdn.example.com/\(fileName)",
"cover_url": "",
"file_size": "2048",
"file_size_human": "2KB",
"remark": ""
]
let data = try! JSONSerialization.data(withJSONObject: payload)
return try! JSONDecoder().decode(AlbumFileItem.self, from: data)
}
}