import Foundation import Testing @testable import ClientTLS // B3 · ControlPlaneLoginClient request-building + response-mapping, driven by a // stub transport (no network). Mirrors the PINNED login contract exactly: // // POST /auth/login { "password": "" } // → 201 { "enrollToken": "", "accountId": "", "expiresIn": } private let cpBaseURL = URL(string: "https://cp.terminal.yaojia.wang")! /// Records the last request and replays a canned response. `@unchecked Sendable`: /// the mutable capture is guarded by a lock. private final class LoginStubTransport: EnrollmentTransport, @unchecked Sendable { private let lock = NSLock() private var _lastRequest: URLRequest? private let status: Int private let body: Data init(status: Int, body: Data) { self.status = status self.body = body } var lastRequest: URLRequest? { lock.withLock { _lastRequest } } func send(_ request: URLRequest) async throws -> (Data, HTTPURLResponse) { lock.withLock { _lastRequest = request } let response = HTTPURLResponse( url: request.url!, statusCode: status, httpVersion: "HTTP/1.1", headerFields: nil )! return (body, response) } } private func loginBody( enrollToken: String = "v4.public.enroll-token", accountId: String = "11111111-2222-3333-4444-555555555555", expiresIn: Int = 600 ) -> Data { let json: [String: Any] = [ "enrollToken": enrollToken, "accountId": accountId, "expiresIn": expiresIn, ] return try! JSONSerialization.data(withJSONObject: json) } @Test("login builds a POST /auth/login carrying only the password (never bearer)") func loginBuildsRequest() async throws { // Arrange let stub = LoginStubTransport(status: 201, body: loginBody()) let client = ControlPlaneLoginClient(baseURL: cpBaseURL, transport: stub) // Act _ = try await client.login(password: "operator-secret") // Assert let request = try #require(stub.lastRequest) #expect(request.httpMethod == "POST") #expect(request.url?.absoluteString == "https://cp.terminal.yaojia.wang/auth/login") #expect(request.value(forHTTPHeaderField: "Content-Type") == "application/json") // Login is credential-based, NOT bearer-authed — no Authorization header. #expect(request.value(forHTTPHeaderField: "Authorization") == nil) let sent = try #require(request.httpBody) let object = try #require(try JSONSerialization.jsonObject(with: sent) as? [String: Any]) #expect(object["password"] as? String == "operator-secret") #expect(object.keys.count == 1) // password ONLY — no extra fields leak } @Test("login maps a 201 response into a typed LoginResult") func loginMapsResponse() async throws { // Arrange let stub = LoginStubTransport(status: 201, body: loginBody( enrollToken: "v4.public.abc", accountId: "acct-uuid", expiresIn: 900 )) let client = ControlPlaneLoginClient(baseURL: cpBaseURL, transport: stub) // Act let result = try await client.login(password: "pw") // Assert #expect(result.enrollToken == "v4.public.abc") #expect(result.accountId == "acct-uuid") #expect(result.expiresIn == 900) } @Test("an empty password fails fast without any network call") func loginEmptyPasswordRejected() async throws { // Arrange let stub = LoginStubTransport(status: 201, body: loginBody()) let client = ControlPlaneLoginClient(baseURL: cpBaseURL, transport: stub) // Act / Assert — boundary validation: reject before sending. await #expect(throws: ControlPlaneLoginError.emptyPassword) { _ = try await client.login(password: "") } #expect(stub.lastRequest == nil) // proves zero network happened } @Test("a 401 login response surfaces http with the server error code") func loginRejectSurfacesStatus() async throws { // The login stub seam denies-by-default → { error: "login rejected" }. let body = try! JSONSerialization.data(withJSONObject: ["error": "login rejected"]) let stub = LoginStubTransport(status: 401, body: body) let client = ControlPlaneLoginClient(baseURL: cpBaseURL, transport: stub) await #expect(throws: ControlPlaneLoginError.http(status: 401, code: "login rejected")) { _ = try await client.login(password: "wrong") } } @Test("a malformed 201 body throws malformedResponse") func loginMalformedBody() async throws { let stub = LoginStubTransport(status: 201, body: Data("not json".utf8)) let client = ControlPlaneLoginClient(baseURL: cpBaseURL, transport: stub) await #expect(throws: ControlPlaneLoginError.malformedResponse) { _ = try await client.login(password: "pw") } }