Refactor AppServices into grouped bundles and document DI conventions.
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>
This commit is contained in:
104
suixinkan_iosTests/AppServicesTests.swift
Normal file
104
suixinkan_iosTests/AppServicesTests.swift
Normal file
@ -0,0 +1,104 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user