278 lines
10 KiB
Swift
278 lines
10 KiB
Swift
//
|
||
// CommissionRateLogTests.swift
|
||
// suixinkanTests
|
||
//
|
||
|
||
import UIKit
|
||
import XCTest
|
||
@testable import suixinkan
|
||
|
||
/// 获客员分成比例修改记录模型、接口与分页行为测试。
|
||
@MainActor
|
||
final class CommissionRateLogTests: XCTestCase {
|
||
func testModelDecodesCompleteRecordAndBuildsBusinessDisplayText() throws {
|
||
let data = """
|
||
{
|
||
"list": [{
|
||
"id": 1,
|
||
"binding_id": 10,
|
||
"sale_user_id": 456,
|
||
"store_user_id": 732,
|
||
"before_rate": 10,
|
||
"before_rate_label": "10%(旧)",
|
||
"after_rate": 20,
|
||
"after_rate_label": "20%(新)",
|
||
"operator_type": "store_user",
|
||
"operator_id": 732,
|
||
"operator_name": "李师傅",
|
||
"remark": " 调整合作比例 ",
|
||
"created_at": "2026-07-23 11:00:00"
|
||
}],
|
||
"total": 1,
|
||
"page": 1,
|
||
"page_size": 10
|
||
}
|
||
""".data(using: .utf8)!
|
||
|
||
let response = try JSONDecoder().decode(CommissionRateLogListResponse.self, from: data)
|
||
let item = try XCTUnwrap(response.list.first)
|
||
|
||
XCTAssertEqual(response.pageSize, 10)
|
||
XCTAssertEqual(item.bindingId, 10)
|
||
XCTAssertEqual(item.saleUserId, 456)
|
||
XCTAssertEqual(item.storeUserId, 732)
|
||
XCTAssertEqual(item.operatorType, "store_user")
|
||
XCTAssertEqual(item.operatorId, 732)
|
||
XCTAssertEqual(item.displayBeforeRate, "10%(旧)")
|
||
XCTAssertEqual(item.displayAfterRate, "20%(新)")
|
||
XCTAssertEqual(item.metadataLine, "2026-07-23 11:00")
|
||
XCTAssertEqual(item.displayRemark, "调整合作比例")
|
||
}
|
||
|
||
func testDisplayFallsBackToNumericRateUnknownOperatorAndRawTime() throws {
|
||
let data = """
|
||
{
|
||
"list": [{
|
||
"id": 2,
|
||
"binding_id": 0,
|
||
"sale_user_id": 456,
|
||
"store_user_id": 0,
|
||
"before_rate": 5,
|
||
"before_rate_label": "",
|
||
"after_rate": 15,
|
||
"after_rate_label": " ",
|
||
"operator_type": "",
|
||
"operator_id": 0,
|
||
"operator_name": "",
|
||
"remark": " ",
|
||
"created_at": "服务端时间"
|
||
}],
|
||
"total": 1,
|
||
"page": 1,
|
||
"page_size": 10
|
||
}
|
||
""".data(using: .utf8)!
|
||
|
||
let item = try JSONDecoder().decode(CommissionRateLogListResponse.self, from: data).list[0]
|
||
|
||
XCTAssertEqual(item.displayBeforeRate, "5%")
|
||
XCTAssertEqual(item.displayAfterRate, "15%")
|
||
XCTAssertEqual(item.metadataLine, "服务端时间")
|
||
XCTAssertNil(item.displayRemark)
|
||
}
|
||
|
||
func testAPIBuildsPathAndClampsPaginationParameters() async throws {
|
||
let session = MockURLSession(responses: [envelope(list: [], total: 0, page: 1)])
|
||
let api = OrderAPI(client: APIClient(environment: .testing, session: session))
|
||
|
||
_ = try await api.saleUserCommissionRateLogs(saleUserId: 456, page: 0, pageSize: 99)
|
||
|
||
let request = try XCTUnwrap(session.requests.first)
|
||
XCTAssertEqual(request.httpMethod, "GET")
|
||
XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/sale-user/commission-rate/logs")
|
||
let query = URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)?.queryItems
|
||
XCTAssertEqual(query?.first { $0.name == "sale_user_id" }?.value, "456")
|
||
XCTAssertEqual(query?.first { $0.name == "page" }?.value, "1")
|
||
XCTAssertEqual(query?.first { $0.name == "page_size" }?.value, "50")
|
||
}
|
||
|
||
func testViewModelRetriesFailedPageAndDeduplicatesItems() async throws {
|
||
let page1 = envelope(
|
||
list: [
|
||
record(id: 1, before: 10, after: 20),
|
||
record(id: 1, before: 10, after: 20),
|
||
record(id: 2, before: 20, after: 30),
|
||
],
|
||
total: 3,
|
||
page: 1
|
||
)
|
||
let page2 = envelope(
|
||
list: [
|
||
record(id: 2, before: 20, after: 30),
|
||
record(id: 3, before: 30, after: 40),
|
||
],
|
||
total: 3,
|
||
page: 2
|
||
)
|
||
let response = HTTPURLResponse(
|
||
url: URL(string: "https://api-test.zhifly.cn/mock")!,
|
||
statusCode: 200,
|
||
httpVersion: nil,
|
||
headerFields: nil
|
||
)!
|
||
let session = MockURLSession(results: [
|
||
.success((page1, response)),
|
||
.failure(URLError(.timedOut)),
|
||
.success((page2, response)),
|
||
])
|
||
let api = OrderAPI(client: APIClient(environment: .testing, session: session))
|
||
let viewModel = CommissionRateLogViewModel(saleUserId: 456)
|
||
|
||
await viewModel.refresh(api: api)
|
||
await viewModel.loadMore(api: api)
|
||
|
||
XCTAssertEqual(viewModel.items.map(\.id), [1, 2])
|
||
XCTAssertTrue(viewModel.canLoadMore)
|
||
XCTAssertFalse(viewModel.isLoadingMore)
|
||
|
||
await viewModel.loadMore(api: api)
|
||
|
||
XCTAssertEqual(viewModel.items.map(\.id), [1, 2, 3])
|
||
XCTAssertFalse(viewModel.canLoadMore)
|
||
let requestedPages = session.requests.compactMap { request in
|
||
URLComponents(url: request.url!, resolvingAgainstBaseURL: false)?
|
||
.queryItems?
|
||
.first { $0.name == "page" }?
|
||
.value
|
||
}
|
||
XCTAssertEqual(requestedPages, ["1", "2", "2"])
|
||
}
|
||
|
||
func testTimelineCellOnlyMarksLatestRecord() {
|
||
let item = CommissionRateLogEntity(
|
||
id: 1,
|
||
bindingId: 10,
|
||
saleUserId: 456,
|
||
storeUserId: 732,
|
||
beforeRate: 10,
|
||
beforeRateLabel: "10%",
|
||
afterRate: 20,
|
||
afterRateLabel: "20%",
|
||
operatorType: "store_user",
|
||
operatorId: 732,
|
||
operatorName: "李师傅",
|
||
remark: "",
|
||
createdAt: "2026-07-23 11:00:00"
|
||
)
|
||
let cell = CommissionRateLogTimelineCell(style: .default, reuseIdentifier: nil)
|
||
|
||
cell.configure(with: item, isLatest: true)
|
||
XCTAssertTrue(cell.accessibilityLabel?.contains("最新记录") == true)
|
||
XCTAssertFalse(cell.accessibilityLabel?.contains("李师傅") == true)
|
||
|
||
cell.configure(with: item, isLatest: false)
|
||
XCTAssertFalse(cell.accessibilityLabel?.contains("最新记录") == true)
|
||
}
|
||
|
||
func testPageTitleUsesAcquirerNameAndFallsBackWhenEmpty() {
|
||
let namedController = CommissionRateLogViewController(
|
||
saleUserId: 456,
|
||
acquirerName: " 获客员小王 "
|
||
)
|
||
namedController.setupNavigationBar()
|
||
XCTAssertEqual(namedController.title, "给获客员小王的分成记录")
|
||
|
||
let fallbackController = CommissionRateLogViewController(
|
||
saleUserId: 456,
|
||
acquirerName: " "
|
||
)
|
||
fallbackController.setupNavigationBar()
|
||
XCTAssertEqual(fallbackController.title, "修改记录")
|
||
}
|
||
|
||
func testAcquirerCardUsesUnifiedActionsAndBusinessHierarchy() throws {
|
||
let cell = CooperationAcquirerCell(style: .default, reuseIdentifier: nil)
|
||
cell.configure(
|
||
with: CooperativeSalerEntity(
|
||
saleUserId: 456,
|
||
name: "获客员小王",
|
||
salerPhone: "13800138000",
|
||
commissionRate: 20,
|
||
commissionRateLabel: "20%",
|
||
bindTime: "2026-07-23 11:00:00"
|
||
)
|
||
)
|
||
let fittingSize = cell.contentView.systemLayoutSizeFitting(
|
||
CGSize(width: 390, height: UIView.layoutFittingCompressedSize.height),
|
||
withHorizontalFittingPriority: .required,
|
||
verticalFittingPriority: .fittingSizeLevel
|
||
)
|
||
cell.frame = CGRect(x: 0, y: 0, width: 390, height: fittingSize.height)
|
||
cell.layoutIfNeeded()
|
||
|
||
let remarkButton = try XCTUnwrap(
|
||
subview(in: cell, identifier: "cooperation_acquirer_edit_remark") as? UIButton
|
||
)
|
||
let logButton = try XCTUnwrap(
|
||
subview(in: cell, identifier: "cooperation_acquirer_commission_logs") as? UIButton
|
||
)
|
||
let commissionButton = try XCTUnwrap(
|
||
subview(in: cell, identifier: "cooperation_acquirer_edit_commission") as? UIButton
|
||
)
|
||
let bindTimeLabel = try XCTUnwrap(
|
||
subview(in: cell, identifier: "cooperation_acquirer_bind_time") as? UILabel
|
||
)
|
||
|
||
XCTAssertEqual(remarkButton.bounds.height, AppSpacing.minTouchTarget, accuracy: 0.5)
|
||
XCTAssertEqual(logButton.bounds.height, remarkButton.bounds.height, accuracy: 0.5)
|
||
XCTAssertEqual(commissionButton.bounds.height, remarkButton.bounds.height, accuracy: 0.5)
|
||
XCTAssertEqual(logButton.bounds.width, remarkButton.bounds.width, accuracy: 0.5)
|
||
XCTAssertEqual(commissionButton.bounds.width, remarkButton.bounds.width, accuracy: 0.5)
|
||
XCTAssertEqual(bindTimeLabel.text, "绑定于 2026-07-23 11:00")
|
||
}
|
||
|
||
private func envelope(list: [[String: Any]], total: Int, page: Int) -> Data {
|
||
let object: [String: Any] = [
|
||
"code": 100000,
|
||
"msg": "success",
|
||
"data": [
|
||
"list": list,
|
||
"total": total,
|
||
"page": page,
|
||
"page_size": 10,
|
||
],
|
||
]
|
||
return try! JSONSerialization.data(withJSONObject: object)
|
||
}
|
||
|
||
private func record(id: Int, before: Int, after: Int) -> [String: Any] {
|
||
[
|
||
"id": id,
|
||
"binding_id": 10,
|
||
"sale_user_id": 456,
|
||
"store_user_id": 732,
|
||
"before_rate": before,
|
||
"before_rate_label": "\(before)%",
|
||
"after_rate": after,
|
||
"after_rate_label": "\(after)%",
|
||
"operator_type": "store_user",
|
||
"operator_id": 732,
|
||
"operator_name": "李师傅",
|
||
"remark": "",
|
||
"created_at": "2026-07-23 11:00:00",
|
||
]
|
||
}
|
||
|
||
private func subview(in root: UIView, identifier: String) -> UIView? {
|
||
if root.accessibilityIdentifier == identifier {
|
||
return root
|
||
}
|
||
for child in root.subviews {
|
||
if let match = subview(in: child, identifier: identifier) {
|
||
return match
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
}
|