//// Start a mock HTTP server, register stubs that describe how it should respond
//// to incoming requests, make real HTTP calls against it from your tests, then
//// inspect the recorded call history to verify what happened.
////
//// Pass a runtime adapter from `http_server_mock_erlang` or `http_server_mock_js`
//// to `new/1` to select the underlying server implementation.
////
//// ```gleam
//// import gleam/http
//// import http_server_mock
//// import http_server_mock_erlang // or http_server_mock_js
//// import http_server_mock/matcher
//// import http_server_mock/response
//// import http_server_mock/stub_builder
//// import http_server_mock/verify
////
//// pub fn my_test() {
//// let server =
//// http_server_mock.new(http_server_mock_erlang.server())
//// |> http_server_mock.start()
//// |> http_server_mock.with_stub(
//// stub_builder.new()
//// |> stub_builder.matching(matcher.new() |> matcher.method(http.Get) |> matcher.path("/ping"))
//// |> stub_builder.responding_with(response.ok())
//// |> stub_builder.build(),
//// )
////
//// // ... make HTTP requests to http_server_mock.base_url(server) ...
////
//// verify.called_times(server, matcher.new() |> matcher.path("/ping"), 1)
//// http_server_mock.stop(server)
//// }
//// ```
import gleam/list
import gleam/option
import http_server_mock/internal/server.{type MockServer}
import http_server_mock/server_adapter
import http_server_mock/types.{type RecordedRequest, type Stub}
/// Re-exported for use in type annotations by runtime packages and users.
pub type ServerAdapter =
server_adapter.ServerAdapter
/// Phantom type re-exported for use in type annotations.
/// A `MockServer(NotStarted)` has been configured but not yet started.
pub type NotStarted =
server.NotStarted
/// Phantom type re-exported for use in type annotations.
/// A `MockServer(Started)` is running and ready to accept requests.
pub type Started =
server.Started
/// Phantom type re-exported for use in type annotations.
/// A `MockServer(Stopped)` has been shut down and cannot accept requests.
pub type Stopped =
server.Stopped
/// Creates a new server with default configuration (random free port).
///
/// Pass the adapter from your runtime package — `http_server_mock_erlang.server()`
/// or `http_server_mock_js.server()` — then chain `start` to launch.
///
/// ```gleam
/// let server =
/// http_server_mock.new(http_server_mock_erlang.server())
/// |> http_server_mock.start()
/// ```
pub fn new(adapter: ServerAdapter) -> MockServer(NotStarted) {
server.new(adapter)
}
/// Overrides the port the server will bind to when started.
///
/// Prefer the default (port 0) in tests so servers never conflict with each
/// other or with other processes on the machine.
pub fn with_port(
mock_server: MockServer(NotStarted),
port: Int,
) -> MockServer(NotStarted) {
server.with_port(mock_server, port)
}
/// Starts the mock HTTP server.
///
/// Panics if the server could not be started (for example, if the requested
/// port is already in use).
///
/// Call `stop` when the server is no longer needed.
pub fn start(mock_server: MockServer(NotStarted)) -> MockServer(Started) {
case server.start(mock_server) {
Ok(started) -> started
Error(reason) -> panic as { "Failed to start mock server: " <> reason }
}
}
/// Stops the mock server and releases the port it was bound to.
///
/// The returned `MockServer(Stopped)` cannot be passed to any function that
/// requires a running server, making accidental post-stop use a compile error.
pub fn stop(mock_server: MockServer(Started)) -> MockServer(Stopped) {
server.stop(mock_server)
}
/// Returns the base URL of the mock server, e.g. `"http://localhost:54321"`.
///
/// Append your path to this when constructing request URLs in tests.
pub fn base_url(mock_server: MockServer(Started)) -> String {
server.base_url(mock_server)
}
/// Registers a stub with the server.
///
/// Build a `Stub` using `stub_builder.new() |> stub_builder.matching(...) |> stub_builder.responding_with(...) |> stub_builder.build()`,
/// or construct one directly from `http_server_mock/types.{Stub}`.
///
/// Returns `Ok(Nil)` on success, or `Error(reason)` if the stub could not be
/// registered.
pub fn add_stub(
mock_server: MockServer(Started),
stub: Stub,
) -> Result(Nil, String) {
case server.register(mock_server, stub) {
Ok(_) -> Ok(Nil)
Error(reason) -> Error(reason)
}
}
/// Registers a stub with the server and returns the server for chaining.
///
/// Build a `Stub` with `stub_builder` and pass it in:
///
/// ```gleam
/// let server =
/// http_server_mock.new()
/// |> http_server_mock.start()
/// |> http_server_mock.with_stub(
/// stub_builder.new()
/// |> stub_builder.matching(matcher.new() |> matcher.path("/ping"))
/// |> stub_builder.responding_with(response.ok())
/// |> stub_builder.build(),
/// )
/// ```
///
/// Panics on registration failure. Use `add_stub` if you need to handle the error.
pub fn with_stub(
mock_server: MockServer(Started),
stub: Stub,
) -> MockServer(Started) {
case server.register(mock_server, stub) {
Ok(_) -> mock_server
Error(reason) -> panic as { "Failed to register stub: " <> reason }
}
}
/// Removes the stub with the given ID from the server.
///
/// Has no effect if no stub with that ID exists.
pub fn remove_stub(mock_server: MockServer(Started), id: String) -> Nil {
server.remove_stub(mock_server, id)
}
/// Removes all registered stubs from the server.
///
/// Requests made after this call will return 404 until new stubs are registered.
pub fn reset_stubs(mock_server: MockServer(Started)) -> Nil {
server.reset_stubs(mock_server)
}
/// Returns all requests the server has received since it started (or since the
/// last call to `reset_requests` or `reset`).
///
/// Each `RecordedRequest` includes the method, path, query string, headers,
/// body, timestamp, and the ID of the stub that matched it (if any).
pub fn recorded_requests(
mock_server: MockServer(Started),
) -> Result(List(RecordedRequest), String) {
server.recorded_requests(mock_server)
}
/// Clears the server's recorded request history.
///
/// Useful when you want to assert on requests made during a specific part of a
/// test without including earlier setup requests in the count.
pub fn reset_requests(mock_server: MockServer(Started)) -> Nil {
server.reset_requests(mock_server)
}
/// Removes all stubs and clears the recorded request history in one call.
pub fn reset(mock_server: MockServer(Started)) -> Nil {
server.reset(mock_server)
}
/// Returns all requests the server received that did not match any registered stub.
///
/// Useful for diagnosing test failures: if a request you expected to be handled
/// shows up here, it means no stub matched it — check the matcher configuration.
///
/// Each `RecordedRequest` includes the method, path, query string, headers,
/// body, and timestamp. The `matched_stub_id` field will always be `None` for
/// these requests.
pub fn unmatched_requests(
mock_server: MockServer(Started),
) -> Result(List(RecordedRequest), String) {
case server.recorded_requests(mock_server) {
Ok(requests) ->
Ok(list.filter(requests, fn(req) { req.matched_stub_id == option.None }))
Error(reason) -> Error(reason)
}
}