// // HTTPServerTests.swift // Zyquo Router // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // Phase 2 gate: bind, serve /health and /v1/models, detect port-in-use, // shut down gracefully on cancellation (socket must be reusable right after). // import XCTest @testable import ZyquoRouter final class HTTPServerTests: XCTestCase { private static let port = 18787 private func makeServer(port: Int) -> HTTPServer { let routes = Routes(router: RequestRouter()) return HTTPServer(host: "127.0.0.1", port: port) { request in await routes.handle(request) } } /// Starts a server task and waits until the socket is accepting. private func startServer(port: Int) async -> Task { let started = expectation(description: "server started on \(port)") let task = Task { [server = makeServer(port: port)] in try await server.run { started.fulfill() } } await fulfillment(of: [started], timeout: 5) return task } private func get(_ path: String, port: Int) async throws -> (Int, Data) { let (data, response) = try await URLSession.shared.data( from: URL(string: "http://127.0.0.1:\(port)\(path)")! ) return ((response as! HTTPURLResponse).statusCode, data) } func testServeHealthAndModelsThenGracefulStop() async throws { let task = await startServer(port: Self.port) let (healthStatus, healthBody) = try await get("/health", port: Self.port) XCTAssertEqual(healthStatus, 200) let health = try JSONSerialization.jsonObject(with: healthBody) as! [String: Any] XCTAssertEqual(health["status"] as? String, "ok") let (modelsStatus, modelsBody) = try await get("/v1/models", port: Self.port) XCTAssertEqual(modelsStatus, 200) let list = try JSONSerialization.jsonObject(with: modelsBody) as! [String: Any] XCTAssertEqual(list["object"] as? String, "list") let data = list["data"] as! [[String: Any]] XCTAssertEqual(data.count, ModelCatalogData.all.count) XCTAssertTrue((data[0]["id"] as! String).contains("/"), "model IDs must be namespaced") // Graceful stop: cancel, wait for run() to return, then the port must // be immediately bindable again. task.cancel() _ = try? await task.value let restarted = await startServer(port: Self.port) let (again, _) = try await get("/health", port: Self.port) XCTAssertEqual(again, 200) restarted.cancel() _ = try? await restarted.value } func testPortInUseError() async throws { let task = await startServer(port: Self.port + 1) let second = makeServer(port: Self.port + 1) do { try await second.run {} XCTFail("second bind should fail") } catch let error as ServerError { guard case .portInUse(_, let port) = error else { return XCTFail("expected portInUse, got \(error)") } XCTAssertEqual(port, Self.port + 1) } task.cancel() _ = try? await task.value } }