Files
suixinkan_ios_uikit/suixinkan_iosTests/AppFeatureServicesTests.swift
汉秋 0d8f97417b Refactor AppServices into assembly, feature facades, and lifecycle coordinator.
Extract dependency wiring and session lifecycle from the container and RootViewController, split NetworkBundle by domain, and add module-level feature services while keeping flat accessors for compatibility.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 09:32:44 +08:00

170 lines
7.0 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// AppFeatureServicesTests.swift
// suixinkanTests
//
// Created by Codex on 2026/6/29.
//
import XCTest
@testable import suixinkan_ios
@MainActor
///
final class AppFeatureServicesTests: XCTestCase {
///
func testOrdersFeatureServicesReflectsCurrentContext() {
let services = makeServices()
applyContext(to: services)
let feature = services.ordersFeatureServices
XCTAssertIdentical(feature.api as AnyObject, services.ordersAPI as AnyObject)
XCTAssertEqual(feature.currentScenicId, 7)
XCTAssertEqual(feature.currentStoreId, 9)
XCTAssertEqual(feature.currentRoleId, 3)
XCTAssertIdentical(feature.navigator as AnyObject, services.appNavigator as AnyObject)
XCTAssertIdentical(feature.toastCenter as AnyObject, services.toastCenter as AnyObject)
XCTAssertIdentical(feature.globalLoading as AnyObject, services.globalLoading as AnyObject)
}
///
func testAssetsFeatureServicesUsesSharedTransferStore() {
let services = makeServices()
applyContext(to: services)
let feature = services.assetsFeatureServices
XCTAssertIdentical(feature.api as AnyObject, services.assetsAPI as AnyObject)
XCTAssertIdentical(feature.uploader as AnyObject, services.ossUploadService as AnyObject)
XCTAssertIdentical(feature.transferStore as AnyObject, CloudTransferStore.shared as AnyObject)
XCTAssertEqual(feature.currentScenicId, 7)
}
/// App
func testQueueFeatureServicesUsesSharedRuntimeAndContext() {
let services = makeServices()
applyContext(to: services)
let feature = services.queueFeatureServices
XCTAssertIdentical(feature.api as AnyObject, services.scenicQueueAPI as AnyObject)
XCTAssertIdentical(feature.runtime as AnyObject, ScenicQueueRuntime.shared as AnyObject)
XCTAssertIdentical(feature.scenicSpotContext as AnyObject, services.scenicSpotContext as AnyObject)
XCTAssertEqual(feature.currentScenicId, 7)
XCTAssertEqual(feature.userId, "42")
}
/// 退
func testProfileFeatureLogoutStopsQueueRuntime() {
let services = makeServices()
applyContext(to: services)
services.appSession.markLoggedIn(token: "token")
ScenicQueueSettingsStore.saveSelectedSpot(id: 11, name: "一号点", userId: "42", scenicId: 7)
let runtime = ScenicQueueRuntime.shared
defer {
runtime.stop()
clearQueueSelection(userId: "42", scenicId: 7)
}
runtime.update(api: AppFeatureScenicQueueMock(), userId: "42", scenicId: 7, scenePhase: .active)
XCTAssertTrue(runtime.isMonitoring)
services.profileFeatureServices.logout()
XCTAssertFalse(runtime.isMonitoring)
XCTAssertFalse(services.appSession.isLoggedIn)
XCTAssertNil(services.accountContext.profile)
}
///
private func makeServices() -> AppServices {
AppServices(apiClient: APIClient(session: AppFeatureRecordingURLSession()))
}
///
private func applyContext(to services: AppServices) {
services.accountContext.applyLogin(
profile: AccountProfile(
userId: "42",
displayName: "测试用户",
phone: "18600000000",
avatarURL: nil
)
)
services.accountContext.replaceScopes(
scenic: [BusinessScope(id: 7, name: "测试景区", kind: .scenic)],
stores: [BusinessScope(id: 9, name: "测试门店", kind: .store, parentScenicId: 7)]
)
services.permissionContext.currentRole = RoleInfo(id: 3, name: "店长")
}
///
private func clearQueueSelection(userId: String, scenicId: Int) {
let defaults = UserDefaults.standard
defaults.removeObject(forKey: ScenicQueueLocalSettings.selectedSpotIdKey)
defaults.removeObject(forKey: ScenicQueueLocalSettings.selectedSpotNameKey)
defaults.removeObject(forKey: ScenicQueueSettingsStore.scopedKey(
base: ScenicQueueLocalSettings.selectedSpotIdKey,
userId: userId,
scenicId: scenicId
))
defaults.removeObject(forKey: ScenicQueueSettingsStore.scopedKey(
base: ScenicQueueLocalSettings.selectedSpotNameKey,
userId: userId,
scenicId: scenicId
))
}
}
///
@MainActor
private final class AppFeatureScenicQueueMock: ScenicQueueServing {
func scenicQueueStats(scenicId: Int, scenicSpotId: Int) async throws -> ScenicQueueStatsData {
ScenicQueueStatsData()
}
func scenicQueueHome(
scenicId: Int,
scenicSpotId: Int,
type: Int,
page: Int,
pageSize: Int
) async throws -> ScenicQueueHomeData {
ScenicQueueHomeData()
}
func socketToken() async throws -> SocketTokenResponse { SocketTokenResponse(socketToken: "token") }
func scenicQueueCall(id: Int64) async throws -> ScenicQueueCallData { ScenicQueueCallData(id: id) }
func scenicQueuePass(id: Int64) async throws -> ScenicQueuePassData { ScenicQueuePassData(id: id) }
func scenicQueueFinish(id: Int64) async throws -> ScenicQueueFinishData { ScenicQueueFinishData(id: id) }
func scenicQueueRequeueInsertBefore(recordId: Int64, operatorId: Int) async throws {}
func scenicQueueUserMark(_ request: ScenicQueueUserMarkRequest) async throws {}
func scenicQueueSetting(scenicId: Int, scenicSpotId: Int?) async throws -> ScenicQueueSettingData { ScenicQueueSettingData() }
func scenicQueueSaveSetting(_ request: ScenicQueueSaveSettingRequest) async throws {}
func scenicQueueShootQueueQRCode(scenicId: Int, scenicSpotId: Int) async throws -> ScenicQueueShootQueueQRCodeData {
ScenicQueueShootQueueQRCodeData(qrcodeUrl: "")
}
func scenicQueueSettingChangeLog(
scenicId: Int,
scenicSpotId: Int?,
page: Int,
pageSize: Int
) async throws -> ScenicQueueSettingChangeLogData {
ScenicQueueSettingChangeLogData()
}
}
/// URLSession
private final class AppFeatureRecordingURLSession: URLSessionProtocol {
///
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
let response = HTTPURLResponse(
url: request.url ?? URL(string: "https://example.com")!,
statusCode: 200,
httpVersion: nil,
headerFields: nil
)!
return (Data(), response)
}
}