proxy-pool/internal/platform/httpserver/server_test.go
youfak ee7fc85031
Some checks are pending
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run
ci / race (push) Waiting to run
feat: assemble controller HTTP runtime
2026-07-29 11:31:12 +08:00

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)
}
}