feat: 同步样片管理功能
This commit is contained in:
197
suixinkanTests/SampleManagementViewModelTests.swift
Normal file
197
suixinkanTests/SampleManagementViewModelTests.swift
Normal file
@ -0,0 +1,197 @@
|
||||
//
|
||||
// SampleManagementViewModelTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
@MainActor
|
||||
/// 样片管理 ViewModel 测试。
|
||||
final class SampleManagementViewModelTests: XCTestCase {
|
||||
func testListLoadsRefreshesAndMapsFilterStatus() async {
|
||||
let api = MockSampleAPI()
|
||||
api.listResponses = [
|
||||
SampleMaterialListResponse(total: 12, list: (0 ..< 10).map { makeItem(id: $0) }),
|
||||
SampleMaterialListResponse(total: 12, list: [makeItem(id: 10), makeItem(id: 11)]),
|
||||
SampleMaterialListResponse(total: 1, list: [makeItem(id: 99)]),
|
||||
]
|
||||
let viewModel = SampleListViewModel()
|
||||
|
||||
await viewModel.loadInitial(api: api)
|
||||
await viewModel.loadMoreIfNeeded(lastVisibleIndex: 8, api: api)
|
||||
await viewModel.selectFilter(.pending, api: api)
|
||||
|
||||
XCTAssertEqual(api.listCalls.map(\.status), [4, 4, 0])
|
||||
XCTAssertEqual(api.listCalls.map(\.page), [1, 2, 1])
|
||||
XCTAssertEqual(viewModel.items.map(\.id), [99])
|
||||
}
|
||||
|
||||
func testRejectedSampleCannotToggleListing() async {
|
||||
let api = MockSampleAPI()
|
||||
api.listResponses = [SampleMaterialListResponse(total: 1, list: [makeItem(id: 1, status: 0, listingStatus: 0)])]
|
||||
let viewModel = SampleListViewModel()
|
||||
var messages: [String] = []
|
||||
viewModel.onShowMessage = { messages.append($0) }
|
||||
|
||||
await viewModel.loadInitial(api: api)
|
||||
await viewModel.toggleListing(sampleId: 1, checked: true, api: api)
|
||||
|
||||
XCTAssertEqual(messages.last, "审核通过才可以操作上下架")
|
||||
XCTAssertTrue(api.operationCalls.isEmpty)
|
||||
}
|
||||
|
||||
func testApprovedSampleToggleUpdatesLocalItem() async {
|
||||
let api = MockSampleAPI()
|
||||
api.listResponses = [SampleMaterialListResponse(total: 1, list: [makeItem(id: 2, status: 1, listingStatus: 0)])]
|
||||
let viewModel = SampleListViewModel()
|
||||
|
||||
await viewModel.loadInitial(api: api)
|
||||
await viewModel.toggleListing(sampleId: 2, checked: true, api: api)
|
||||
|
||||
XCTAssertEqual(api.operationCalls.first?.id, 2)
|
||||
XCTAssertEqual(api.operationCalls.first?.listingStatus, 1)
|
||||
XCTAssertEqual(viewModel.items.first?.listingStatus, 1)
|
||||
}
|
||||
|
||||
func testUploadValidationMessages() async {
|
||||
let api = MockSampleAPI()
|
||||
let viewModel = UploadSampleViewModel(
|
||||
scenicListProvider: { [ScenicInfo(id: 3, name: "景区", status: 1, coverImg: nil)] },
|
||||
currentScenicIdProvider: { 3 }
|
||||
)
|
||||
var messages: [String] = []
|
||||
viewModel.onShowMessage = { messages.append($0) }
|
||||
|
||||
await viewModel.uploadSample(api: api)
|
||||
viewModel.updateAlbumName("样片")
|
||||
await viewModel.uploadSample(api: api)
|
||||
await viewModel.addLocalMedia([makeUploadItem(fileName: "a.jpg", ossUrl: "")], uploader: MockSampleUploader(urls: ["https://cdn/a.jpg"]))
|
||||
await viewModel.uploadSample(api: api)
|
||||
await viewModel.setCoverImage(makeUploadItem(fileName: "c.jpg", ossUrl: ""), uploader: MockSampleUploader(urls: ["https://cdn/c.jpg"]))
|
||||
await viewModel.uploadSample(api: api)
|
||||
|
||||
XCTAssertEqual(messages.prefix(4), [
|
||||
"请输入相册名称",
|
||||
"请至少上传一个素材",
|
||||
"请上传封面图片",
|
||||
"请选择关联景区",
|
||||
])
|
||||
XCTAssertNil(api.lastUploadRequest)
|
||||
}
|
||||
|
||||
func testUploadSuccessBuildsSampleRequest() async {
|
||||
let scenic = ScenicInfo(id: 3, name: "景区", status: 1, coverImg: nil)
|
||||
let api = MockSampleAPI()
|
||||
api.spotResponse = ScenicSpotListResponse(list: [ScenicSpotItem(id: 9, name: "打卡点")], total: 1)
|
||||
let viewModel = UploadSampleViewModel(
|
||||
scenicListProvider: { [scenic] },
|
||||
currentScenicIdProvider: { 3 }
|
||||
)
|
||||
let uploader = MockSampleUploader(urls: ["https://cdn/a.jpg", "https://cdn/c.jpg"])
|
||||
var didSucceed = false
|
||||
viewModel.onUploadSuccess = { didSucceed = true }
|
||||
|
||||
viewModel.updateAlbumName("样片")
|
||||
await viewModel.selectScenic(scenic, api: api)
|
||||
viewModel.selectScenicSpot(ScenicSpotItem(id: 9, name: "打卡点"))
|
||||
viewModel.selectProject(SampleProjectItem(id: 7, name: "项目"))
|
||||
await viewModel.addLocalMedia([makeUploadItem(fileName: "a.jpg")], uploader: uploader)
|
||||
await viewModel.setCoverImage(makeUploadItem(fileName: "c.jpg"), uploader: uploader)
|
||||
await viewModel.uploadSample(api: api)
|
||||
|
||||
XCTAssertTrue(didSucceed)
|
||||
XCTAssertEqual(api.lastUploadRequest?.name, "样片")
|
||||
XCTAssertEqual(api.lastUploadRequest?.type, "2")
|
||||
XCTAssertEqual(api.lastUploadRequest?.mediaType, "1")
|
||||
XCTAssertEqual(api.lastUploadRequest?.mediaList.first?.ossUrl, "https://cdn/a.jpg")
|
||||
XCTAssertEqual(api.lastUploadRequest?.coverUrl, "https://cdn/c.jpg")
|
||||
XCTAssertEqual(api.lastUploadRequest?.scenicSpotId, 9)
|
||||
XCTAssertEqual(api.lastUploadRequest?.projectId, 7)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class MockSampleAPI: SampleManagementServing {
|
||||
var listResponses: [SampleMaterialListResponse] = []
|
||||
var listCalls: [(status: Int, keyword: String?, page: Int, pageSize: Int)] = []
|
||||
var operationCalls: [(id: Int, listingStatus: Int)] = []
|
||||
var lastUploadRequest: SampleUploadMaterialRequest?
|
||||
var spotResponse = ScenicSpotListResponse(list: [], total: 0)
|
||||
var projectResponse = SampleProjectListResponse(list: [], total: 0)
|
||||
|
||||
func list(status: Int, keyword: String?, page: Int, pageSize: Int) async throws -> SampleMaterialListResponse {
|
||||
listCalls.append((status, keyword, page, pageSize))
|
||||
guard !listResponses.isEmpty else { return SampleMaterialListResponse() }
|
||||
return listResponses.removeFirst()
|
||||
}
|
||||
|
||||
func detail(id: Int) async throws -> SampleDetail {
|
||||
throw APIError.emptyData
|
||||
}
|
||||
|
||||
func setListingStatus(id: Int, listingStatus: Int) async throws {
|
||||
operationCalls.append((id, listingStatus))
|
||||
}
|
||||
|
||||
func upload(_ request: SampleUploadMaterialRequest) async throws {
|
||||
lastUploadRequest = request
|
||||
}
|
||||
|
||||
func scenicSpotListAll(scenicId: String) async throws -> ScenicSpotListResponse {
|
||||
spotResponse
|
||||
}
|
||||
|
||||
func projectList(scenicId: String, name: String?, page: Int, pageSize: Int) async throws -> SampleProjectListResponse {
|
||||
projectResponse
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class MockSampleUploader: SampleOSSUploading {
|
||||
private var urls: [String]
|
||||
|
||||
init(urls: [String]) {
|
||||
self.urls = urls
|
||||
}
|
||||
|
||||
func uploadSampleFile(
|
||||
data: Data,
|
||||
fileName: String,
|
||||
fileType: Int,
|
||||
scenicId: Int,
|
||||
onProgress: @escaping (Int) -> Void
|
||||
) async throws -> String {
|
||||
onProgress(100)
|
||||
return urls.isEmpty ? "https://cdn/default.jpg" : urls.removeFirst()
|
||||
}
|
||||
}
|
||||
|
||||
private func makeItem(id: Int, status: Int = 1, listingStatus: Int = 0) -> SampleMaterialItem {
|
||||
SampleMaterialItem(
|
||||
id: id,
|
||||
name: "样片\(id)",
|
||||
coverUrl: "",
|
||||
createdAt: "",
|
||||
status: status,
|
||||
downloadCount: 0,
|
||||
likesCount: 0,
|
||||
collectCount: 0,
|
||||
shareCount: 0,
|
||||
scenicSpotName: "打卡点",
|
||||
projectName: "项目",
|
||||
listingStatus: listingStatus
|
||||
)
|
||||
}
|
||||
|
||||
private func makeUploadItem(fileName: String, ossUrl: String = "") -> SampleUploadedMediaItem {
|
||||
SampleUploadedMediaItem(
|
||||
data: Data(repeating: 1, count: 10),
|
||||
fileName: fileName,
|
||||
size: 10,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
ossUrl: ossUrl
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user