// // APIClientTests.swift // suixinkanTests // import XCTest @testable import suixinkan @MainActor /// APIClient 测试,覆盖 Envelope 解包、业务码失败和 token 注入。 final class APIClientTests: XCTestCase { func testSendDecodesSuccessEnvelope() async throws { let responseData = try TestJSON.envelope(data: AppConfigResponse(enableRegister: true)) let session = MockURLSession(responses: [responseData]) let client = APIClient( environment: .testing, session: session, appVersion: "1.0.0", osType: "iOS" ) let config: AppConfigResponse = try await client.send( APIRequest(method: .get, path: "/api/app/config") ) XCTAssertTrue(config.enableRegister) XCTAssertEqual(session.requests.count, 1) XCTAssertEqual(session.requests[0].value(forHTTPHeaderField: "X-APP-VERSION"), "1.0.0") XCTAssertEqual(session.requests[0].value(forHTTPHeaderField: "X-OS-TYPE"), "iOS") } func testSendInjectsTokenHeaderWhenProviderBound() async throws { let responseData = try TestJSON.envelope(data: AppConfigResponse(enableRegister: false)) let session = MockURLSession(responses: [responseData]) let client = APIClient(environment: .testing, session: session) client.bindAuthTokenProvider { "test-token-abc" } _ = try await client.send(APIRequest(method: .get, path: "/api/app/config")) XCTAssertEqual(session.requests[0].value(forHTTPHeaderField: "token"), "test-token-abc") } func testSendThrowsServerCodeErrorForBusinessFailure() async throws { let responseData = try TestJSON.errorEnvelope(code: 100001, msg: "密码错误") let session = MockURLSession(responses: [responseData]) let client = APIClient(environment: .testing, session: session) do { let _: AppConfigResponse = try await client.send( APIRequest(method: .get, path: "/api/app/config") ) XCTFail("业务码失败时应抛出错误") } catch let error as APIError { guard case let .serverCode(code, message) = error else { return XCTFail("期望 serverCode,实际为 \(error)") } XCTAssertEqual(code, 100001) XCTAssertEqual(message, "密码错误") } } func testSendThrowsHTTPStatusError() async throws { let session = MockURLSession(results: [ .success(( Data(), HTTPURLResponse( url: URL(string: "https://api-test.zhifly.cn/mock")!, statusCode: 500, httpVersion: nil, headerFields: nil )! )), ]) let client = APIClient(environment: .testing, session: session) do { let _: AppConfigResponse = try await client.send( APIRequest(method: .get, path: "/api/app/config") ) XCTFail("HTTP 500 时应抛出错误") } catch let error as APIError { guard case let .httpStatus(statusCode, _) = error else { return XCTFail("期望 httpStatus,实际为 \(error)") } XCTAssertEqual(statusCode, 500) } } func testTokenInvalidCodePostsSessionDidExpireNotification() async { let expectation = expectation(forNotification: NotificationName.sessionDidExpire, object: nil) let responseData = try! TestJSON.errorEnvelope(code: 180024, msg: "token 失效") let session = MockURLSession(responses: [responseData]) let client = APIClient(environment: .testing, session: session) _ = try? await client.send(APIRequest(method: .get, path: "/api/app/config")) await fulfillment(of: [expectation], timeout: 1) } }