Files
suixinkan_ios_uikit/suixinkan_iosTests/PushNotificationTests.swift
汉秋 a1c031c9b7 Integrate CYLTabBar center scan button and unify UIKit navigation.
Add CYLTabBarController with a global scan entry, promote MainTabBarController to the window root after login, replace RouterPath-based navigation with AppNavigator, and fix Profile diffable cell reconfiguration crashes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 16:36:18 +08:00

182 lines
6.9 KiB
Swift
Raw 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.

//
// PushNotificationTests.swift
// suixinkanTests
//
// Created by Codex on 2026/6/26.
//
import Foundation
import XCTest
@testable import suixinkan_ios
/// APNs tokenpayload token
@MainActor
final class PushNotificationTests: XCTestCase {
func testAPNsDeviceTokenUsesLowercaseHexString() {
let token = APNsDeviceToken.hexString(from: Data([0x00, 0x0f, 0x10, 0xab, 0xff]))
XCTAssertEqual(token, "000f10abff")
}
func testPushPayloadRoutesTopLevelFields() {
XCTAssertEqual(PushPayload(userInfo: ["type": "1"]).route, .payment)
XCTAssertEqual(PushPayload(userInfo: ["route": "verification_order"]).route, .verificationOrder)
XCTAssertEqual(PushPayload(userInfo: ["uri": "/scenic-queue"]).route, .queue)
XCTAssertEqual(PushPayload(userInfo: ["action": "task_management"]).route, .task)
XCTAssertEqual(PushPayload(userInfo: ["route": "photographer_orders"]).route, .order)
}
func testPushPayloadRoutesNestedJSONString() {
let payload = PushPayload(userInfo: [
"extras": #"{"data":{"uri":"message_center","title":""}}"#
])
XCTAssertEqual(payload.route, .messageCenter)
}
func testPushPayloadRoutesNestedDictionaryAndChineseKeywords() {
let payload = PushPayload(userInfo: [
"data": [
"extra": [
"action": "叫号提醒"
]
]
])
XCTAssertEqual(payload.route, .queue)
}
func testPushPayloadFallsBackToMessageCenter() {
let payload = PushPayload(userInfo: ["title": "系统消息"])
XCTAssertEqual(payload.route, .messageCenter)
}
func testRegisterPushTokenUsesCurrentBackendEndpointAndLegacyQueryName() async throws {
let session = PushRecordingURLSession()
let api = PushAPI(client: APIClient(session: session))
try await api.registerJPushId("000f10abff")
let request = try XCTUnwrap(session.requests.first)
XCTAssertEqual(request.httpMethod, "POST")
XCTAssertEqual(request.url?.path, "/api/app/user/register-jpush-id")
XCTAssertEqual(queryItems(from: request)["jpush_reg_id"], "000f10abff")
}
func testPushManagerRoutesHomeDestinationsThroughHomeNavigationStack() {
let services = AppServices(apiClient: APIClient(session: PushRecordingURLSession()))
let host = PushNavigationHost(services: services)
let manager = pushManager(navigator: services.appNavigator)
services.appNavigator.attach(host: host)
manager.handleRemoteNotification(PushPayload(userInfo: ["route": "payment_qr"]))
XCTAssertEqual(host.selectedTab, .home)
XCTAssertTrue(host.navigationController(for: .home)?.viewControllers.last is PaymentCollectionViewController)
manager.handleRemoteNotification(PushPayload(userInfo: ["route": "task_management"]))
XCTAssertTrue(host.navigationController(for: .home)?.viewControllers.last is TaskManagementViewController)
XCTAssertEqual(host.navigationController(for: .home)?.viewControllers.count, 2)
manager.handleRemoteNotification(PushPayload(userInfo: ["uri": "/scenic-queue"]))
XCTAssertTrue(host.navigationController(for: .home)?.viewControllers.last is QueueManagementViewController)
XCTAssertEqual(host.navigationController(for: .home)?.viewControllers.count, 2)
manager.handleRemoteNotification(PushPayload(userInfo: ["title": "未知通知"]))
XCTAssertTrue(host.navigationController(for: .home)?.viewControllers.last is MessageCenterViewController)
XCTAssertEqual(host.navigationController(for: .home)?.viewControllers.count, 2)
}
func testPushManagerRoutesOrdersEntry() {
let services = AppServices(apiClient: APIClient(session: PushRecordingURLSession()))
let host = PushNavigationHost(services: services)
let manager = pushManager(navigator: services.appNavigator)
services.appNavigator.attach(host: host)
manager.handleRemoteNotification(PushPayload(userInfo: ["route": "photographer_orders"]))
XCTAssertEqual(host.selectedTab, .orders)
XCTAssertEqual(host.appliedOrdersEntry, .storeOrders)
manager.handleRemoteNotification(PushPayload(userInfo: ["route": "verification_order"]))
XCTAssertEqual(host.selectedTab, .orders)
XCTAssertEqual(host.appliedOrdersEntry, .verificationOrders)
}
private func pushManager(navigator: AppNavigator) -> PushNotificationManager {
let session = AppSession()
let manager = PushNotificationManager()
manager.configure(
api: PushAPI(client: APIClient(session: PushRecordingURLSession())),
session: session,
navigator: navigator
)
return manager
}
}
/// 宿
@MainActor
private final class PushNavigationHost: AppNavigationHost {
private var controllers: [AppTab: TabNavigationController] = [:]
var selectedTab: AppTab = .home
var appliedOrdersEntry: OrdersEntry?
/// Tab
init(services: AppServices) {
for tab in AppTab.allCases {
controllers[tab] = TabNavigationController(tab: tab, services: services)
}
}
/// Tab
func selectTab(_ tab: AppTab) {
selectedTab = tab
}
/// Tab
func navigationController(for tab: AppTab) -> TabNavigationController? {
controllers[tab]
}
/// Tab
func selectedNavigationController() -> TabNavigationController? {
controllers[selectedTab]
}
/// Tab
func resetAllStacks() {
for controller in controllers.values {
controller.popToRootViewController(animated: false)
}
selectedTab = .home
}
///
func applyOrdersEntry(_ entry: OrdersEntry, scannedCode: String?) {
appliedOrdersEntry = entry
}
}
private final class PushRecordingURLSession: URLSessionProtocol {
private(set) var requests: [URLRequest] = []
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
requests.append(request)
let data = #"{"code":100000,"msg":"success","data":{}}"#.data(using: .utf8)!
let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
return (data, response)
}
}
private func queryItems(from request: URLRequest) -> [String: String] {
guard let url = request.url,
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
else {
return [:]
}
return Dictionary(uniqueKeysWithValues: (components.queryItems ?? []).compactMap { item in
guard let value = item.value else { return nil }
return (item.name, value)
})
}