SPB Git

spb/zyquo-router Public MIT

One local endpoint, every AI provider — a private OpenAI-compatible LLM gateway for your Mac (170 models, 12 providers).

Swift 95.7% Python 2.3% Shell 1.2% Makefile 0.9%
3.1 KB · 88 lines swift
Raw Blame History
1//2//  HTTPServerTests.swift3//  Zyquo Router4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  Phase 2 gate: bind, serve /health and /v1/models, detect port-in-use,9//  shut down gracefully on cancellation (socket must be reusable right after).10//1112import XCTest13@testable import ZyquoRouter1415final class HTTPServerTests: XCTestCase {16    private static let port = 187871718    private func makeServer(port: Int) -> HTTPServer {19        let routes = Routes(router: RequestRouter())20        return HTTPServer(host: "127.0.0.1", port: port) { request in21            await routes.handle(request)22        }23    }2425    /// Starts a server task and waits until the socket is accepting.26    private func startServer(port: Int) async -> Task<Void, Error> {27        let started = expectation(description: "server started on \(port)")28        let task = Task { [server = makeServer(port: port)] in29            try await server.run { started.fulfill() }30        }31        await fulfillment(of: [started], timeout: 5)32        return task33    }3435    private func get(_ path: String, port: Int) async throws -> (Int, Data) {36        let (data, response) = try await URLSession.shared.data(37            from: URL(string: "http://127.0.0.1:\(port)\(path)")!38        )39        return ((response as! HTTPURLResponse).statusCode, data)40    }4142    func testServeHealthAndModelsThenGracefulStop() async throws {43        let task = await startServer(port: Self.port)4445        let (healthStatus, healthBody) = try await get("/health", port: Self.port)46        XCTAssertEqual(healthStatus, 200)47        let health = try JSONSerialization.jsonObject(with: healthBody) as! [String: Any]48        XCTAssertEqual(health["status"] as? String, "ok")4950        let (modelsStatus, modelsBody) = try await get("/v1/models", port: Self.port)51        XCTAssertEqual(modelsStatus, 200)52        let list = try JSONSerialization.jsonObject(with: modelsBody) as! [String: Any]53        XCTAssertEqual(list["object"] as? String, "list")54        let data = list["data"] as! [[String: Any]]55        XCTAssertEqual(data.count, ModelCatalogData.all.count)56        XCTAssertTrue((data[0]["id"] as! String).contains("/"), "model IDs must be namespaced")5758        // Graceful stop: cancel, wait for run() to return, then the port must59        // be immediately bindable again.60        task.cancel()61        _ = try? await task.value6263        let restarted = await startServer(port: Self.port)64        let (again, _) = try await get("/health", port: Self.port)65        XCTAssertEqual(again, 200)66        restarted.cancel()67        _ = try? await restarted.value68    }6970    func testPortInUseError() async throws {71        let task = await startServer(port: Self.port + 1)7273        let second = makeServer(port: Self.port + 1)74        do {75            try await second.run {}76            XCTFail("second bind should fail")77        } catch let error as ServerError {78            guard case .portInUse(_, let port) = error else {79                return XCTFail("expected portInUse, got \(error)")80            }81            XCTAssertEqual(port, Self.port + 1)82        }8384        task.cancel()85        _ = try? await task.value86    }87}88