Initial commit: suixinkan_ios UIKit rewrite project.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 14:33:31 +08:00
commit 9edf993432
297 changed files with 47151 additions and 0 deletions

View File

@ -0,0 +1,675 @@
//
// AssetsViewModelTests.swift
// suixinkanTests
//
// Created by Codex on 2026/6/23.
//
import XCTest
@testable import suixinkan_ios
@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)
}
}