Sus::Fixtures::Async::HTTPSourceSusFixturesAsyncHTTPServerContext

module ServerContext

ServerContext provides a test fixture for HTTP server and client testing. It sets up an HTTP server with a bound endpoint and provides a corresponding client for making requests. This context handles the lifecycle of both server and client, ensuring proper setup and teardown.

The context automatically:

Definitions

def protocol

The HTTP protocol version to use for the server.

Signature

returns Class

The protocol class (defaults to HTTP/1.1).

Implementation

def protocol
	::Async::HTTP::Protocol::HTTP1
end

def url

The base URL for the HTTP server.

Signature

returns String

The URL string with host and port (defaults to localhost:0 for auto-assigned port).

Implementation

def url
	"http://localhost:0"
end

def bound_urls

Get all bound URLs for the server endpoints.

Signature

returns Array(String)

Array of URLs the server is bound to, sorted alphabetically.

Implementation

def bound_urls
	urls = []
	
	@client_endpoint.each do |address_endpoint|
		address = address_endpoint.address
		
		host = address.ip_address
		if address.ipv6?
			host = "[#{host}]"
		end
		
		port = address.ip_port
		
		urls << "#{endpoint.scheme}://#{host}:#{port}"
	end
	
	urls.sort!
	
	return urls
end

def bound_url

Get the first bound URL for the server.

Signature

returns String

The first bound URL, typically used for single-endpoint testing.

Implementation

def bound_url
	bound_urls.first
end

def endpoint_options

Options for configuring the HTTP endpoint.

Signature

returns Hash

Hash of endpoint options including port reuse and protocol settings.

Implementation

def endpoint_options
	{reuse_port: true, protocol: protocol}
end

def endpoint

The HTTP endpoint configuration.

Signature

returns Async::HTTP::Endpoint

Parsed endpoint with configured options.

Implementation

def endpoint
	::Async::HTTP::Endpoint.parse(url, **endpoint_options)
end

def retries

Number of retries for client requests.

Signature

returns Integer

Number of retry attempts (defaults to 1).

Implementation

def retries
	1
end

def app

The HTTP application/middleware to serve.

Signature

returns Object

The application object that handles HTTP requests (defaults to HelloWorld middleware).

Implementation

def app
	::Protocol::HTTP::Middleware::HelloWorld
end

def middleware

The middleware stack for the HTTP server.

Signature

returns Object

The middleware configuration (defaults to the app).

Implementation

def middleware
	app
end

def make_server_endpoint(bound_endpoint)

Create the server endpoint from a bound endpoint.

Signature

parameter bound_endpoint Async::HTTP::Endpoint

The bound endpoint.

returns Async::HTTP::Endpoint

The server endpoint configuration.

Implementation

def make_server_endpoint(bound_endpoint)
	bound_endpoint
end

def make_server(endpoint)

Create an HTTP server instance.

Signature

parameter endpoint Async::HTTP::Endpoint

The endpoint to bind the server to.

returns Async::HTTP::Server

The configured HTTP server.

Implementation

def make_server(endpoint)
	::Async::HTTP::Server.new(middleware, endpoint)
end

def server

The HTTP server instance.

Signature

returns Async::HTTP::Server

The running HTTP server.

Implementation

def server
	@server
end

def client

The HTTP client instance.

Signature

returns Async::HTTP::Client

The HTTP client configured to connect to the server.

Implementation

def client
	@client
end

def client_endpoint

The client endpoint configuration.

Signature

returns Async::HTTP::Endpoint

The endpoint the client uses to connect to the server.

Implementation

def client_endpoint
	@client_endpoint
end

def make_client_endpoint(bound_endpoint)

Create a client endpoint from a bound endpoint.

Signature

parameter bound_endpoint Async::HTTP::Endpoint

The bound server endpoint.

returns Async::HTTP::Endpoint

The client endpoint with local address and timeout.

Implementation

def make_client_endpoint(bound_endpoint)
	# Pass through the timeout:
	bound_endpoint.local_address_endpoint(timeout: endpoint.timeout)
end

def make_client(endpoint, **options)

Create an HTTP client instance.

Signature

parameter endpoint Async::HTTP::Endpoint

The endpoint to connect to.

parameter options Hash

Additional client options.

returns Async::HTTP::Client

The configured HTTP client.

Implementation

def make_client(endpoint, **options)
	options[:retries] = retries unless options.key?(:retries)
	
	::Async::HTTP::Client.new(endpoint, **options)
end

def before

Set up the server and client before running tests. This method binds the endpoint, starts the server, and creates a client.

Implementation

def before
	super
	
	# We bind the endpoint before running the server so that we know incoming connections will be accepted:
	@bound_endpoint = endpoint.bound
	
	@server_endpoint = make_server_endpoint(@bound_endpoint)
	mock(@server_endpoint) do |wrapper|
		wrapper.replace(:protocol) {endpoint.protocol}
		wrapper.replace(:scheme) {endpoint.scheme}
		wrapper.replace(:authority) {endpoint.authority}
		wrapper.replace(:path) {endpoint.path}
	end
	
	@server = make_server(@server_endpoint)
	
	@server_task = @server.run
	
	@client_endpoint = make_client_endpoint(@bound_endpoint)
	mock(@client_endpoint) do |wrapper|
		wrapper.replace(:protocol) {endpoint.protocol}
		wrapper.replace(:scheme) {endpoint.scheme}
		wrapper.replace(:authority) {endpoint.authority}
		wrapper.replace(:path) {endpoint.path}
	end
	
	@client = make_client(@client_endpoint)
end

def after(error = nil)

Clean up resources after running tests. This method closes the client, stops the server, and closes the bound endpoint.

Signature

parameter error Exception | Nil

Any error that occurred during the test.

Implementation

def after(error = nil)
	# We add a timeout here, to avoid hanging in `@client.close`:
	::Async::Task.current.with_timeout(1) do
		@client&.close
		@server_task&.stop
		@server_task&.wait
		@bound_endpoint&.close
	end
	
	super
end