// // 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) XCTAssertIdentical(services.network.apiClient as AnyObject, services.apiClient as AnyObject) } /// 扁平属性应转发到对应 bundle,保持现有调用方兼容。 func testFlatAccessorsForwardToBundles() { let services = AppServices(apiClient: APIClient(session: AppServicesRecordingURLSession())) XCTAssertIdentical(services.appSession as AnyObject, services.session.appSession as AnyObject) XCTAssertIdentical(services.authAPI as AnyObject, services.network.account.authAPI as AnyObject) XCTAssertIdentical(services.ordersAPI as AnyObject, services.network.commerce.ordersAPI as AnyObject) XCTAssertIdentical(services.scenicQueueAPI as AnyObject, services.network.operation.scenicQueueAPI as AnyObject) XCTAssertIdentical(services.assetsAPI as AnyObject, services.network.content.assetsAPI 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) } }