Files
suixinkan_uikit/suixinkanTests/StatisticsViewModelTests.swift

211 lines
9.0 KiB
Swift

//
// StatisticsViewModelTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
@MainActor
/// ViewModel
final class StatisticsViewModelTests: XCTestCase {
override func tearDown() {
AppStore.shared.roleCode = ""
AppStore.shared.roleName = ""
AppStore.shared.currentScenicId = 0
AppStore.shared.currentStoreId = 0
super.tearDown()
}
func testStatisticsSummaryDecodesRefundAmountAsString() throws {
let json = """
{
"code": 100000,
"msg": "success",
"data": {
"order_amount_sum": "0.00",
"order_count": 0,
"order_price_avg": "0.00",
"received_amount_sum": "0.00",
"refund_amount_sum": "0.00"
}
}
""".data(using: .utf8)!
let envelope = try JSONDecoder().decode(APIEnvelope<StatisticsSummaryResponse>.self, from: json)
XCTAssertEqual(envelope.data?.refundAmountSum, "0.00")
XCTAssertEqual(envelope.data?.orderAmountSum, "0.00")
}
func testRangeValueMapping() {
let viewModel = StatisticsViewModel()
XCTAssertEqual(viewModel.rangeValue(for: "今日"), "1")
XCTAssertEqual(viewModel.rangeValue(for: "昨日"), "2")
XCTAssertEqual(viewModel.rangeValue(for: "7日"), "3")
XCTAssertEqual(viewModel.rangeValue(for: "本月"), "4")
}
func testFormattedSelectedPeriodTimeForToday() {
let viewModel = StatisticsViewModel()
let text = viewModel.formattedSelectedPeriodTime(for: "今日")
let today = formattedToday()
XCTAssertEqual(text, "\(today)\(today)")
}
func testFormattedSelectedPeriodTimeForThisMonth() {
let viewModel = StatisticsViewModel()
let text = viewModel.formattedSelectedPeriodTime(for: "本月")
XCTAssertTrue(text.contains(""))
XCTAssertTrue(text.hasPrefix(firstDayOfMonth()))
}
func testUpdateRangeByShortcutAllClearsTimes() async {
let summaryJSON = """
{"code":100000,"msg":"success","data":{"order_amount_sum":"0","order_count":0,"order_price_avg":"0","received_amount_sum":"0","refund_amount_sum":0}}
""".data(using: .utf8)!
let listJSON = """
{"code":100000,"msg":"success","data":{"total":0,"data":[]}}
""".data(using: .utf8)!
AppStore.shared.currentScenicId = 10
let session = MockURLSession(responses: [listJSON])
let api = StatisticsAPI(client: APIClient(environment: .testing, session: session))
let viewModel = StatisticsViewModel()
await viewModel.updateRangeByShortcut("全部", api: api)
XCTAssertEqual(viewModel.statisticsRangeText, "全部")
XCTAssertEqual(viewModel.listStartTime, "")
XCTAssertEqual(viewModel.listEndTime, "")
XCTAssertEqual(session.requests.count, 1)
XCTAssertEqual(session.requests[0].url?.path, "/api/yf-handset-app/photog/analyse/user/daily")
let queryItems = URLComponents(url: session.requests[0].url!, resolvingAgainstBaseURL: false)?.queryItems ?? []
XCTAssertEqual(queryItems.first(where: { $0.name == "start_time" })?.value, "")
XCTAssertEqual(queryItems.first(where: { $0.name == "end_time" })?.value, "")
}
func testPhotographerSummaryAndDailyRequests() async throws {
let summaryJSON = """
{"code":100000,"msg":"success","data":{"order_amount_sum":"100","order_count":2,"order_price_avg":"50","received_amount_sum":"90","refund_amount_sum":10}}
""".data(using: .utf8)!
let listJSON = """
{"code":100000,"msg":"success","data":{"total":1,"data":[{"date":"2025-12-09","order_count":2,"order_price":"50","refund":"10","received":"90"}]}}
""".data(using: .utf8)!
AppStore.shared.currentScenicId = 10
let session = MockURLSession(responses: [summaryJSON, listJSON])
let api = StatisticsAPI(client: APIClient(environment: .testing, session: session))
let viewModel = StatisticsViewModel()
await viewModel.loadStatistics("7日", api: api)
await viewModel.updateRangeByShortcut("今日", api: api)
XCTAssertEqual(viewModel.statistics?.orderAmountSum, "100")
XCTAssertEqual(viewModel.statisticsList.count, 1)
XCTAssertEqual(session.requests.map { $0.url?.path }, [
"/api/yf-handset-app/photog/analyse/user",
"/api/yf-handset-app/photog/analyse/user/daily",
])
XCTAssertTrue(session.requests[0].url?.query?.contains("range=3") == true)
XCTAssertTrue(session.requests[1].url?.query?.contains("paeg_size=15") == true)
XCTAssertTrue(session.requests[1].url?.query?.contains("scenic_id=10") == true)
}
func testScenicAdminUsesScenicAdminEndpoints() async {
let summaryJSON = """
{"code":100000,"msg":"success","data":{"order_amount_sum":"0","order_count":0,"order_price_avg":"0","received_amount_sum":"0","refund_amount_sum":0}}
""".data(using: .utf8)!
let listJSON = """
{"code":100000,"msg":"success","data":{"total":0,"data":[]}}
""".data(using: .utf8)!
AppStore.shared.roleCode = AppRoleCode.scenicAdmin.rawValue
AppStore.shared.currentScenicId = 20
let session = MockURLSession(responses: [summaryJSON, listJSON])
let api = StatisticsAPI(client: APIClient(environment: .testing, session: session))
let viewModel = StatisticsViewModel()
await viewModel.loadStatistics("今日", api: api)
await viewModel.updateRangeByShortcut("今日", api: api)
XCTAssertEqual(session.requests[0].url?.path, "/api/app/scenic-admin/analyse")
XCTAssertEqual(session.requests[1].url?.path, "/api/app/scenic-admin/analyse/daily")
}
func testStoreAdminUsesStoreEndpoints() async {
let summaryJSON = """
{"code":100000,"msg":"success","data":{"order_amount_sum":"0","order_count":0,"order_price_avg":"0","received_amount_sum":"0","refund_amount_sum":0}}
""".data(using: .utf8)!
let listJSON = """
{"code":100000,"msg":"success","data":{"total":0,"data":[]}}
""".data(using: .utf8)!
AppStore.shared.roleCode = AppRoleCode.storeAdmin.rawValue
AppStore.shared.currentStoreId = 30
let session = MockURLSession(responses: [summaryJSON, listJSON])
let api = StatisticsAPI(client: APIClient(environment: .testing, session: session))
let viewModel = StatisticsViewModel()
await viewModel.loadStatistics("今日", api: api)
await viewModel.updateRangeByShortcut("今日", api: api)
XCTAssertEqual(session.requests[0].url?.path, "/api/app/store/analyse")
XCTAssertTrue(session.requests[0].url?.query?.contains("store_id=30") == true)
XCTAssertEqual(session.requests[1].url?.path, "/api/app/store/analyse/daily")
}
func testStoreAdminWithoutStoreIdDoesNotRequest() async {
AppStore.shared.roleCode = AppRoleCode.storeAdmin.rawValue
AppStore.shared.currentStoreId = 0
let session = MockURLSession(responses: [])
let api = StatisticsAPI(client: APIClient(environment: .testing, session: session))
let viewModel = StatisticsViewModel()
var message: String?
viewModel.onShowMessage = { message = $0 }
await viewModel.loadStatistics("今日", api: api)
XCTAssertEqual(message, "请先选择店铺")
XCTAssertEqual(session.requests.count, 0)
}
func testLoadMoreStopsWhenReachedTotal() async {
let page1JSON = """
{"code":100000,"msg":"success","data":{"total":1,"data":[{"date":"2025-12-09","order_count":1,"order_price":"10","refund":"0","received":"10"}]}}
""".data(using: .utf8)!
AppStore.shared.currentScenicId = 10
let session = MockURLSession(responses: [page1JSON])
let api = StatisticsAPI(client: APIClient(environment: .testing, session: session))
let viewModel = StatisticsViewModel()
await viewModel.updateRangeByShortcut("今日", api: api)
XCTAssertEqual(viewModel.statisticsList.count, 1)
XCTAssertFalse(viewModel.canLoadMore)
await viewModel.loadMore(api: api)
XCTAssertEqual(viewModel.listCurrentPage, 1)
XCTAssertEqual(session.requests.count, 1)
}
private func formattedToday() -> String {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .gregorian)
formatter.locale = Locale(identifier: "zh_CN")
formatter.dateFormat = "yyyy/MM/dd"
return formatter.string(from: Date())
}
private func firstDayOfMonth() -> String {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .gregorian)
formatter.locale = Locale(identifier: "zh_CN")
formatter.dateFormat = "yyyy/MM/dd"
var calendar = Calendar(identifier: .gregorian)
let components = calendar.dateComponents([.year, .month], from: Date())
let firstDay = calendar.date(from: components) ?? Date()
return formatter.string(from: firstDay)
}
}