Centralize dependency wiring with Session/Context/Network/UI/Runtime bundles, unify leaf ViewControllers on appServices, and add a test initializer with AppServicesTests. Co-authored-by: Cursor <cursoragent@cursor.com>
105 lines
4.2 KiB
Swift
105 lines
4.2 KiB
Swift
//
|
||
// AppServicesTests.swift
|
||
// suixinkanTests
|
||
//
|
||
// Created by Codex on 2026/6/26.
|
||
//
|
||
|
||
import XCTest
|
||
@testable import suixinkan_ios
|
||
|
||
@MainActor
|
||
/// `AppServices` 依赖容器测试,覆盖分组结构与测试专用初始化。
|
||
final class AppServicesTests: XCTestCase {
|
||
/// 测试专用初始化应创建独立实例,且各 API 共享同一 `APIClient`。
|
||
func testTestingInitializerCreatesGroupedDependenciesWithSharedClient() async throws {
|
||
let session = AppServicesRecordingURLSession(responses: [
|
||
try TestFixture.data(named: "writeoff_list_success"),
|
||
try TestFixture.data(named: "user_info_success")
|
||
])
|
||
let apiClient = APIClient(session: session)
|
||
let services = AppServices(apiClient: apiClient)
|
||
|
||
XCTAssertNotIdentical(services.network.apiClient as AnyObject, AppServices.shared.network.apiClient as AnyObject)
|
||
_ = try await services.ordersAPI.writeOffList(scenicId: 1, storeId: nil, page: 1, pageSize: 1)
|
||
_ = try await services.profileAPI.userInfo()
|
||
XCTAssertEqual(session.requests.count, 2)
|
||
XCTAssertNotNil(services.session.appSession)
|
||
XCTAssertNotNil(services.context.accountContext)
|
||
XCTAssertNotNil(services.ui.toastCenter)
|
||
XCTAssertNotNil(services.runtime.scenicQueueRuntime)
|
||
}
|
||
|
||
/// 扁平属性应转发到对应 bundle,保持现有调用方兼容。
|
||
func testFlatAccessorsForwardToBundles() {
|
||
let services = AppServices(apiClient: APIClient(session: AppServicesRecordingURLSession()))
|
||
|
||
XCTAssertIdentical(services.appSession as AnyObject, services.session.appSession as AnyObject)
|
||
XCTAssertIdentical(services.ordersAPI as AnyObject, services.network.ordersAPI as AnyObject)
|
||
XCTAssertIdentical(services.toastCenter as AnyObject, services.ui.toastCenter as AnyObject)
|
||
XCTAssertIdentical(services.scenicQueueRuntime as AnyObject, services.runtime.scenicQueueRuntime as AnyObject)
|
||
}
|
||
|
||
/// `APIClient` 应通过 `AppSession.token` 提供鉴权 token。
|
||
func testAuthTokenProviderUsesAppSessionToken() async throws {
|
||
let session = AppServicesRecordingURLSession(data: try TestFixture.data(named: "user_info_success"))
|
||
let apiClient = APIClient(session: session)
|
||
let services = AppServices(apiClient: apiClient)
|
||
services.appSession.markLoggedIn(token: "test-token")
|
||
|
||
_ = try await services.profileAPI.userInfo()
|
||
|
||
let request = try XCTUnwrap(session.requests.first)
|
||
XCTAssertEqual(request.value(forHTTPHeaderField: "token"), "test-token")
|
||
}
|
||
|
||
/// 便捷属性应基于 `AccountContext` 派生当前用户与景区信息。
|
||
func testConveniencePropertiesReflectAccountContext() {
|
||
let services = AppServices(apiClient: APIClient(session: AppServicesRecordingURLSession()))
|
||
services.accountContext.applyLogin(
|
||
profile: AccountProfile(
|
||
userId: "42",
|
||
displayName: "测试用户",
|
||
phone: "18600000000",
|
||
avatarURL: nil
|
||
)
|
||
)
|
||
services.accountContext.currentScenic = BusinessScope(id: 7, name: "测试景区", kind: .scenic)
|
||
|
||
XCTAssertEqual(services.userId, "42")
|
||
XCTAssertEqual(services.staffId, 42)
|
||
XCTAssertEqual(services.currentScenicId, 7)
|
||
}
|
||
}
|
||
|
||
/// 记录请求的 URLSession 测试替身。
|
||
private final class AppServicesRecordingURLSession: URLSessionProtocol {
|
||
private(set) var requests: [URLRequest] = []
|
||
private let responses: [Data]
|
||
private var responseIndex = 0
|
||
|
||
/// 初始化实例。
|
||
init(data: Data = Data()) {
|
||
responses = [data]
|
||
}
|
||
|
||
/// 按顺序返回多段响应数据。
|
||
init(responses: [Data]) {
|
||
self.responses = responses
|
||
}
|
||
|
||
/// 记录请求并返回固定响应。
|
||
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
|
||
requests.append(request)
|
||
let data = responses[min(responseIndex, responses.count - 1)]
|
||
responseIndex += 1
|
||
let response = HTTPURLResponse(
|
||
url: request.url ?? URL(string: "https://example.com")!,
|
||
statusCode: 200,
|
||
httpVersion: nil,
|
||
headerFields: nil
|
||
)!
|
||
return (data, response)
|
||
}
|
||
}
|