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:
2026-06-26 15:24:25 +08:00
parent d99a5b1bf8
commit 24a7339b68
10 changed files with 385 additions and 114 deletions

View 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)
}
}