Add sample management and upload flows to Assets module.
Extend media library for sample kind with project selection, wire home routing for sample entries, and expand Assets tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -95,6 +95,16 @@ final class AssetsViewModelTests: XCTestCase {
|
||||
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()
|
||||
@ -108,6 +118,18 @@ final class AssetsViewModelTests: XCTestCase {
|
||||
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()
|
||||
@ -145,6 +167,61 @@ final class AssetsViewModelTests: XCTestCase {
|
||||
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()
|
||||
@ -274,11 +351,19 @@ private final class MockAssetsService: AssetsServing {
|
||||
}
|
||||
|
||||
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
|
||||
@ -296,6 +381,7 @@ private final class MockAssetsService: AssetsServing {
|
||||
|
||||
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
|
||||
@ -306,6 +392,7 @@ private final class MockAssetsService: AssetsServing {
|
||||
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)] = []
|
||||
@ -347,12 +434,12 @@ private final class MockAssetsService: AssetsServing {
|
||||
|
||||
/// 返回素材列表。
|
||||
func mediaAlbumList(kind: MediaLibraryKind, keyword: String, status: Int?, page: Int, pageSize: Int) async throws -> MediaLibraryListResponse {
|
||||
mediaListRequests.append(MediaListRequest(keyword: keyword, status: status, page: page))
|
||||
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) async throws -> MediaLibraryDetail {
|
||||
func mediaAlbumDetail(id: Int, kind: MediaLibraryKind) async throws -> MediaLibraryDetail {
|
||||
MediaLibraryDetail.fixture(id: id)
|
||||
}
|
||||
|
||||
@ -377,6 +464,12 @@ private final class MockAssetsService: AssetsServing {
|
||||
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))
|
||||
|
||||
Reference in New Issue
Block a user