新增订单长尾流程,并迁移排队、消息、结算与审核模块
将定金订单、历史拍摄与多行程上传接入 Orders,并以真实页面替换首页排队管理、消息中心、景区结算与提现审核占位入口。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,227 @@
|
||||
//
|
||||
// QueueManagementViewModelTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
// Created by Codex on 2026/6/25.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
@MainActor
|
||||
/// 排队管理列表测试,覆盖点位门禁、列表分页和动作。
|
||||
final class QueueManagementViewModelTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
clearQueueDefaults()
|
||||
}
|
||||
|
||||
func testReloadWithoutScenicClearsState() async {
|
||||
let api = ScenicQueueMock()
|
||||
let viewModel = QueueManagementViewModel()
|
||||
|
||||
await viewModel.reload(api: api, scenicId: nil, userId: "77", spots: sampleSpots)
|
||||
|
||||
XCTAssertTrue(viewModel.items.isEmpty)
|
||||
XCTAssertNil(viewModel.selectedSpotId)
|
||||
XCTAssertFalse(viewModel.queueGatePassed)
|
||||
XCTAssertEqual(viewModel.queueCount, 0)
|
||||
}
|
||||
|
||||
func testReloadWithoutSavedSpotShowsGateAndDoesNotRequestQueue() async {
|
||||
let api = ScenicQueueMock()
|
||||
let viewModel = QueueManagementViewModel()
|
||||
|
||||
await viewModel.reload(api: api, scenicId: 88, userId: "77", spots: sampleSpots)
|
||||
|
||||
XCTAssertEqual(viewModel.scenicSpots.map(\.id), [901, 902])
|
||||
XCTAssertNil(viewModel.selectedSpotId)
|
||||
XCTAssertFalse(viewModel.queueGatePassed)
|
||||
XCTAssertTrue(api.homeRequests.isEmpty)
|
||||
XCTAssertTrue(api.statsRequests.isEmpty)
|
||||
}
|
||||
|
||||
func testReloadWithSavedSpotLoadsHomeAndStats() async {
|
||||
saveSelectedSpot()
|
||||
let api = ScenicQueueMock()
|
||||
api.homeResponses = [ScenicQueueHomeData(list: ScenicQueueHomeListBlock(list: [ticket(id: 1001), ticket(id: 1002)], total: 2), time: "2026-06-25 10:01")]
|
||||
api.statsResponse = ScenicQueueStatsData(queueCount: 5, avgWaitMin: 12.5, time: "2026-06-25 10:02")
|
||||
let viewModel = QueueManagementViewModel()
|
||||
|
||||
await viewModel.reload(api: api, scenicId: 88, userId: "77", spots: sampleSpots)
|
||||
|
||||
XCTAssertEqual(viewModel.selectedSpotId, 901)
|
||||
XCTAssertTrue(viewModel.queueGatePassed)
|
||||
XCTAssertEqual(viewModel.items.map(\.id), [1001, 1002])
|
||||
XCTAssertEqual(viewModel.queueCount, 5)
|
||||
XCTAssertEqual(viewModel.avgWaitMin, 12.5)
|
||||
XCTAssertEqual(viewModel.lastSyncTimeText, "2026-06-25 10:02")
|
||||
XCTAssertEqual(api.homeRequests.first?.type, QueueListType.queueing.rawValue)
|
||||
}
|
||||
|
||||
func testStatsFailureKeepsQueueList() async {
|
||||
saveSelectedSpot()
|
||||
let api = ScenicQueueMock()
|
||||
api.homeResponses = [ScenicQueueHomeData(list: ScenicQueueHomeListBlock(list: [ticket(id: 1001)], total: 1), time: "home-time")]
|
||||
api.statsError = QueueTestError.sample
|
||||
let viewModel = QueueManagementViewModel()
|
||||
|
||||
await viewModel.reload(api: api, scenicId: 88, userId: "77", spots: sampleSpots)
|
||||
|
||||
XCTAssertEqual(viewModel.items.map(\.id), [1001])
|
||||
XCTAssertEqual(viewModel.queueCount, 0)
|
||||
XCTAssertEqual(viewModel.lastSyncTimeText, "home-time")
|
||||
}
|
||||
|
||||
func testCallPassFinishRequeueAndUserMark() async throws {
|
||||
saveSelectedSpot()
|
||||
let api = ScenicQueueMock()
|
||||
api.homeResponses = Array(repeating: ScenicQueueHomeData(list: ScenicQueueHomeListBlock(list: [ticket(id: 1001)], total: 1)), count: 8)
|
||||
let viewModel = QueueManagementViewModel()
|
||||
await viewModel.reload(api: api, scenicId: 88, userId: "77", spots: sampleSpots)
|
||||
|
||||
try await viewModel.callQueue(api: api, id: 1001)
|
||||
XCTAssertEqual(api.callIds, [1001])
|
||||
XCTAssertEqual(viewModel.items.first?.isCalled, 1)
|
||||
|
||||
try await viewModel.passQueue(api: api, scenicId: 88, userId: "77", id: 1001)
|
||||
XCTAssertEqual(api.passIds, [1001])
|
||||
|
||||
try await viewModel.finishQueue(api: api, scenicId: 88, userId: "77", id: 1001)
|
||||
XCTAssertEqual(api.finishIds, [1001])
|
||||
|
||||
try await viewModel.requeue(api: api, scenicId: 88, userId: "77", id: 1001, operatorId: 77)
|
||||
XCTAssertEqual(api.requeueRequests.map(\.recordId), [1001])
|
||||
XCTAssertEqual(api.requeueRequests.map(\.operatorId), [77])
|
||||
XCTAssertEqual(viewModel.selectedListType, .queueing)
|
||||
|
||||
try await viewModel.userMark(
|
||||
api: api,
|
||||
scenicId: 88,
|
||||
userId: "77",
|
||||
request: ScenicQueueUserMarkRequest(uid: 7001, scenicId: 88, markAsFreelancePhotog: 1, queueBanDays: 14, operatorId: 77)
|
||||
)
|
||||
XCTAssertEqual(api.userMarkRequests.first?.uid, 7001)
|
||||
XCTAssertEqual(api.userMarkRequests.first?.queueBanDays, 14)
|
||||
}
|
||||
|
||||
func testSocketSubscriptionAndParse() {
|
||||
XCTAssertEqual(ScenicQueueSocketClient.subscriptionPayloads(scenicSpotId: 901), [
|
||||
#"{"type":306,"params":{"scenic_spot_id":901}}"#,
|
||||
#"{"type":307,"params":{"scenic_spot_id":901}}"#
|
||||
])
|
||||
|
||||
let message = ScenicQueueSocketClient.parseMessage("""
|
||||
{"code":100000,"data":{"action":"scenic_queue_ticket_called","params":{"scenic_spot_id":"901","record_id":"1002","operator_uid":"77","event_id":"evt-1"}}}
|
||||
""")
|
||||
|
||||
XCTAssertEqual(message?.code, 100000)
|
||||
XCTAssertTrue(message?.isScenicQueueEvent == true)
|
||||
XCTAssertEqual(message?.data?.params?.scenicSpotId, 901)
|
||||
XCTAssertEqual(message?.data?.params?.recordId, 1002)
|
||||
XCTAssertEqual(message?.data?.params?.operatorUid, 77)
|
||||
XCTAssertEqual(message?.data?.params?.eventId, "evt-1")
|
||||
}
|
||||
}
|
||||
|
||||
private let sampleSpots = [
|
||||
ScenicSpotItem(id: 901, name: "东门打卡点"),
|
||||
ScenicSpotItem(id: 902, name: "西门打卡点")
|
||||
]
|
||||
|
||||
private func saveSelectedSpot() {
|
||||
ScenicQueueSettingsStore.saveSelectedSpot(id: 901, name: "东门打卡点", userId: "77", scenicId: 88)
|
||||
}
|
||||
|
||||
private func clearQueueDefaults() {
|
||||
let defaults = UserDefaults.standard
|
||||
[
|
||||
ScenicQueueLocalSettings.selectedSpotIdKey,
|
||||
ScenicQueueLocalSettings.selectedSpotNameKey,
|
||||
ScenicQueueSettingsStore.scopedKey(base: ScenicQueueLocalSettings.selectedSpotIdKey, userId: "77", scenicId: 88),
|
||||
ScenicQueueSettingsStore.scopedKey(base: ScenicQueueLocalSettings.selectedSpotNameKey, userId: "77", scenicId: 88),
|
||||
ScenicQueueSettingsStore.scopedSpotKey(base: ScenicQueueLocalSettings.settingsSnapshotKey, userId: "77", scenicId: 88, spotId: 901)
|
||||
].forEach(defaults.removeObject)
|
||||
}
|
||||
|
||||
private func ticket(id: Int64, isCalled: Int = 0) -> ScenicQueueTicket {
|
||||
ScenicQueueTicket(
|
||||
id: id,
|
||||
queueCode: "A\(id)",
|
||||
mobile: "13800000000",
|
||||
status: 0,
|
||||
statusText: "",
|
||||
waitMin: 5,
|
||||
aheadCount: 1,
|
||||
isCalled: isCalled,
|
||||
createdAt: "2026-06-25 10:00:00",
|
||||
calledAt: "",
|
||||
expiredAt: "",
|
||||
finishedAt: "",
|
||||
queueCountToday: 2,
|
||||
uid: 7001
|
||||
)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class ScenicQueueMock: ScenicQueueServing {
|
||||
var homeResponses: [ScenicQueueHomeData] = []
|
||||
var statsResponse = ScenicQueueStatsData()
|
||||
var statsError: Error?
|
||||
var settingResponse = ScenicQueueSettingData()
|
||||
var callIds: [Int64] = []
|
||||
var passIds: [Int64] = []
|
||||
var finishIds: [Int64] = []
|
||||
var requeueRequests: [(recordId: Int64, operatorId: Int)] = []
|
||||
var userMarkRequests: [ScenicQueueUserMarkRequest] = []
|
||||
var saveSettingRequests: [ScenicQueueSaveSettingRequest] = []
|
||||
var homeRequests: [(scenicId: Int, scenicSpotId: Int, type: Int, page: Int, pageSize: Int)] = []
|
||||
var statsRequests: [(scenicId: Int, scenicSpotId: Int)] = []
|
||||
var qrcodeURL = "https://example.com/qrcode.png"
|
||||
var logs = ScenicQueueSettingChangeLogData()
|
||||
|
||||
func scenicQueueStats(scenicId: Int, scenicSpotId: Int) async throws -> ScenicQueueStatsData {
|
||||
statsRequests.append((scenicId, scenicSpotId))
|
||||
if let statsError { throw statsError }
|
||||
return statsResponse
|
||||
}
|
||||
|
||||
func scenicQueueHome(scenicId: Int, scenicSpotId: Int, type: Int, page: Int, pageSize: Int) async throws -> ScenicQueueHomeData {
|
||||
homeRequests.append((scenicId, scenicSpotId, type, page, pageSize))
|
||||
return homeResponses.isEmpty ? ScenicQueueHomeData() : homeResponses.removeFirst()
|
||||
}
|
||||
|
||||
func socketToken() async throws -> SocketTokenResponse { SocketTokenResponse(socketToken: "token") }
|
||||
func scenicQueueCall(id: Int64) async throws -> ScenicQueueCallData {
|
||||
callIds.append(id)
|
||||
return ScenicQueueCallData(id: id, status: 1, statusText: "已叫号")
|
||||
}
|
||||
func scenicQueuePass(id: Int64) async throws -> ScenicQueuePassData {
|
||||
passIds.append(id)
|
||||
return ScenicQueuePassData(id: id)
|
||||
}
|
||||
func scenicQueueFinish(id: Int64) async throws -> ScenicQueueFinishData {
|
||||
finishIds.append(id)
|
||||
return ScenicQueueFinishData(id: id)
|
||||
}
|
||||
func scenicQueueRequeueInsertBefore(recordId: Int64, operatorId: Int) async throws {
|
||||
requeueRequests.append((recordId, operatorId))
|
||||
}
|
||||
func scenicQueueUserMark(_ request: ScenicQueueUserMarkRequest) async throws {
|
||||
userMarkRequests.append(request)
|
||||
}
|
||||
func scenicQueueSetting(scenicId: Int, scenicSpotId: Int?) async throws -> ScenicQueueSettingData { settingResponse }
|
||||
func scenicQueueSaveSetting(_ request: ScenicQueueSaveSettingRequest) async throws {
|
||||
saveSettingRequests.append(request)
|
||||
}
|
||||
func scenicQueueShootQueueQRCode(scenicId: Int, scenicSpotId: Int) async throws -> ScenicQueueShootQueueQRCodeData {
|
||||
ScenicQueueShootQueueQRCodeData(qrcodeUrl: qrcodeURL)
|
||||
}
|
||||
func scenicQueueSettingChangeLog(scenicId: Int, scenicSpotId: Int?, page: Int, pageSize: Int) async throws -> ScenicQueueSettingChangeLogData {
|
||||
logs
|
||||
}
|
||||
}
|
||||
|
||||
private enum QueueTestError: Error {
|
||||
case sample
|
||||
}
|
||||
Reference in New Issue
Block a user