159 lines
4.3 KiB
Go
159 lines
4.3 KiB
Go
package httpserver
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestServeRunsIndependentEndpointsAndShutsDownTogether(t *testing.T) {
|
|
t.Parallel()
|
|
first := mustListen(t)
|
|
second := mustListen(t)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
result := make(chan error, 1)
|
|
|
|
go func() {
|
|
result <- Serve(ctx, testOptions(),
|
|
Endpoint{Name: "distribution", Listener: first, Handler: textHandler("distribution")},
|
|
Endpoint{Name: "admin", Listener: second, Handler: textHandler("admin")},
|
|
)
|
|
}()
|
|
|
|
assertBody(t, first.Addr().String(), "distribution")
|
|
assertBody(t, second.Addr().String(), "admin")
|
|
cancel()
|
|
|
|
select {
|
|
case err := <-result:
|
|
if err != nil {
|
|
t.Fatalf("Serve() error = %v", err)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Serve() did not stop after cancellation")
|
|
}
|
|
}
|
|
|
|
func TestServeRejectsInvalidEndpointDefinitions(t *testing.T) {
|
|
t.Parallel()
|
|
listener := mustListen(t)
|
|
defer listener.Close()
|
|
|
|
tests := []struct {
|
|
name string
|
|
endpoints []Endpoint
|
|
}{
|
|
{name: "empty"},
|
|
{name: "missing name", endpoints: []Endpoint{{Listener: listener, Handler: textHandler("ok")}}},
|
|
{name: "missing listener", endpoints: []Endpoint{{Name: "api", Handler: textHandler("ok")}}},
|
|
{name: "missing handler", endpoints: []Endpoint{{Name: "api", Listener: listener}}},
|
|
{name: "duplicate name", endpoints: []Endpoint{
|
|
{Name: "api", Listener: listener, Handler: textHandler("one")},
|
|
{Name: "api", Listener: listener, Handler: textHandler("two")},
|
|
}},
|
|
}
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
err := Serve(context.Background(), testOptions(), test.endpoints...)
|
|
if !errors.Is(err, ErrInvalidEndpoint) {
|
|
t.Fatalf("Serve() error = %v, want %v", err, ErrInvalidEndpoint)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestServeStopsSiblingWhenEndpointFails(t *testing.T) {
|
|
t.Parallel()
|
|
failed := mustListen(t)
|
|
sibling := mustListen(t)
|
|
failedAddress := failed.Addr().String()
|
|
if err := failed.Close(); err != nil {
|
|
t.Fatalf("Close(%s): %v", failedAddress, err)
|
|
}
|
|
|
|
err := Serve(context.Background(), testOptions(),
|
|
Endpoint{Name: "failed", Listener: failed, Handler: textHandler("failed")},
|
|
Endpoint{Name: "sibling", Listener: sibling, Handler: textHandler("sibling")},
|
|
)
|
|
if err == nil {
|
|
t.Fatal("Serve() error = nil, want endpoint failure")
|
|
}
|
|
connection, dialErr := net.DialTimeout("tcp", sibling.Addr().String(), 100*time.Millisecond)
|
|
if dialErr == nil {
|
|
connection.Close()
|
|
t.Fatal("sibling listener remained open after endpoint failure")
|
|
}
|
|
}
|
|
|
|
func TestListenAndServeValidatesAllBindingsBeforeOpeningSockets(t *testing.T) {
|
|
t.Parallel()
|
|
err := ListenAndServe(context.Background(), testOptions(),
|
|
Binding{Name: "api", Address: "\x00", Handler: textHandler("one")},
|
|
Binding{Name: "api", Address: "127.0.0.1:0", Handler: textHandler("two")},
|
|
)
|
|
if !errors.Is(err, ErrInvalidEndpoint) {
|
|
t.Fatalf("ListenAndServe() error = %v, want %v", err, ErrInvalidEndpoint)
|
|
}
|
|
}
|
|
|
|
func TestListenAndServeTreatsCancellationBeforeBindingAsGracefulStop(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
|
|
err := ListenAndServe(ctx, testOptions(), Binding{
|
|
Name: "api", Address: "127.0.0.1:0", Handler: textHandler("ok"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ListenAndServe() error = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
func testOptions() Options {
|
|
return Options{
|
|
ReadHeaderTimeout: time.Second,
|
|
ReadTimeout: time.Second,
|
|
WriteTimeout: time.Second,
|
|
IdleTimeout: time.Second,
|
|
ShutdownTimeout: time.Second,
|
|
MaxHeaderBytes: 16 << 10,
|
|
}
|
|
}
|
|
|
|
func mustListen(t *testing.T) net.Listener {
|
|
t.Helper()
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("Listen(): %v", err)
|
|
}
|
|
return listener
|
|
}
|
|
|
|
func textHandler(body string) http.Handler {
|
|
return http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
|
|
_, _ = io.WriteString(writer, body)
|
|
})
|
|
}
|
|
|
|
func assertBody(t *testing.T, address, want string) {
|
|
t.Helper()
|
|
client := &http.Client{Timeout: time.Second}
|
|
response, err := client.Get("http://" + address)
|
|
if err != nil {
|
|
t.Fatalf("GET %s: %v", address, err)
|
|
}
|
|
defer response.Body.Close()
|
|
body, err := io.ReadAll(response.Body)
|
|
if err != nil {
|
|
t.Fatalf("ReadAll(): %v", err)
|
|
}
|
|
if string(body) != want {
|
|
t.Fatalf("body = %q, want %q", body, want)
|
|
}
|
|
}
|