diff options
Diffstat (limited to '')
-rw-r--r-- | http/server_test.go | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/http/server_test.go b/http/server_test.go new file mode 100644 index 0000000..7138c73 --- /dev/null +++ b/http/server_test.go @@ -0,0 +1,159 @@ +package http_test + +import ( + "context" + "io" + "net" + "net/http" + "strings" + "testing" + "time" + + httpServer "go.sudomsg.com/kit/http" +) + +// helper to find free TCP ports +func newListener(tb testing.TB) net.Listener { + tb.Helper() + + ln, err := net.Listen("tcp", "localhost:0") + if err != nil { + tb.Fatalf("failed to open listener: %v", err) + } + return ln +} + +func TestOpenConfigListeners(t *testing.T) { + t.Parallel() + t.Run("successful config", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + + cfg := []httpServer.ServerConfig{ + {Network: "tcp", Address: "localhost:0"}, + {Network: "tcp", Address: "localhost:0"}, + } + + lns, err := httpServer.OpenConfigListners(ctx, cfg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + defer lns.CloseAll() + + if got := len(lns); got != len(cfg) { + t.Errorf("expected %d listeners, got %d", len(cfg), got) + } + }) + + t.Run("port conflict triggers cleanup", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + + conflict := newListener(t) + defer conflict.Close() + + cfg := []httpServer.ServerConfig{ + {Network: "tcp", Address: "localhost:0"}, + {Network: "tcp", Address: conflict.Addr().String()}, // will fail + } + + lns, err := httpServer.OpenConfigListners(ctx, cfg) + if err == nil { + defer lns.CloseAll() + t.Fatal("expected error due to conflict, got nil") + } + }) +} + +func TestCloseAll(t *testing.T) { + t.Run("closes all listeners", func(t *testing.T) { + t.Parallel() + ln1 := newListener(t) + ln2 := newListener(t) + + ls := httpServer.Listeners{ln1, ln2} + err := ls.CloseAll() + if err != nil { + t.Errorf("unexpected error from CloseAll: %v", err) + } + + for _, ln := range ls { + if _, err := ln.Accept(); err == nil { + t.Error("expected listener to be closed, but Accept succeeded") + } + } + }) +} + +func TestRunHTTPServers(t *testing.T) { + t.Parallel() + t.Run("basic serve and shutdown", func(t *testing.T) { + t.Parallel() + ctx := t.Context() + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, "hello") + }) + + ln := newListener(t) + addr := ln.Addr().String() + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + go func() { + // Wait for server to be ready + time.Sleep(200 * time.Millisecond) + + r, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://"+addr, nil) + resp, err := http.DefaultClient.Do(r) + if err != nil { + t.Errorf("http.Do error: %v", err) + return + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + if got := strings.TrimSpace(string(body)); got != "hello" { + t.Errorf("unexpected response body: %q", got) + } + + cancel() // shutdown the server + }() + + err := httpServer.RunHTTPServers(ctx, httpServer.Listeners{ln}, handler) + if err != nil { + t.Fatalf("RunHTTPServers failed: %v", err) + } + }) +} + +func TestInvalidConfig(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + config httpServer.ServerConfig + }{ + { + name: "invalid network", + config: httpServer.ServerConfig{Network: "invalid", Address: "localhost:0"}, + }, + { + name: "invalid address", + config: httpServer.ServerConfig{Network: "tcp", Address: "::::"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctx := t.Context() + + _, err := httpServer.OpenConfigListners(ctx, []httpServer.ServerConfig{tt.config}) + if err == nil { + t.Fatal("OpenConfigListners() expected error, got nil") + } + }) + } +} |