Files
kandev/apps/backend/internal/backendapp/server_test.go
T
2026-08-24 19:35:07 +00:00

112 lines
3.4 KiB
Go

package backendapp
import (
"net"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
func TestServerListenAddr(t *testing.T) {
tests := []struct {
name string
host string
port int
want string
}{
{name: "blank host keeps port-only address", host: "", port: 38429, want: ":38429"},
{name: "wildcard host", host: "0.0.0.0", port: 38429, want: "0.0.0.0:38429"},
{name: "loopback host", host: "127.0.0.1", port: 38429, want: "127.0.0.1:38429"},
{name: "ipv6 host", host: "::1", port: 38429, want: "[::1]:38429"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := serverListenAddr(tt.host, tt.port); got != tt.want {
t.Fatalf("serverListenAddr(%q, %d) = %q, want %q", tt.host, tt.port, got, tt.want)
}
})
}
}
func TestDesktopHealthTokenTrimsEnv(t *testing.T) {
t.Setenv(desktopHealthTokenEnv, " token-value ")
if got := desktopHealthToken(); got != "token-value" {
t.Fatalf("desktopHealthToken() = %q, want token-value", got)
}
}
// TestHealthHandlerAlwaysOkRegardlessOfReadiness is the regression test for
// R1-1: /health is a liveness probe and must answer 200 (with the desktop
// health token echoed, when configured) whether or not the `ready` flag has
// flipped. Gating it on readiness would bring back the crash loop docs/specs/
// startup-listener-before-recovery/spec.md exists to fix.
func TestHealthHandlerAlwaysOkRegardlessOfReadiness(t *testing.T) {
gin.SetMode(gin.TestMode)
t.Setenv(desktopHealthTokenEnv, "route-health-token")
for _, readyState := range []bool{false, true} {
ready.Store(readyState)
t.Cleanup(func() { ready.Store(false) })
router := gin.New()
router.GET("/health", healthHandler(routeParams{}))
response := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "/health", nil)
router.ServeHTTP(response, request)
if response.Code != http.StatusOK {
t.Fatalf("ready=%v: health status = %d, want %d", readyState, response.Code, http.StatusOK)
}
if got := response.Header().Get(desktopHealthTokenHeader); got != "route-health-token" {
t.Fatalf("ready=%v: health token header = %q, want route-health-token", readyState, got)
}
}
}
// TestReadyHandlerGatesOnReadyFlag is the regression test for the task-02
// requirement that readiness (unlike liveness) reports not-ready during a
// blocked startup and ready once startup completes.
func TestReadyHandlerGatesOnReadyFlag(t *testing.T) {
gin.SetMode(gin.TestMode)
ready.Store(false)
t.Cleanup(func() { ready.Store(false) })
router := gin.New()
router.GET("/ready", readyHandler(routeParams{}))
response := httptest.NewRecorder()
router.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/ready", nil))
if response.Code != http.StatusServiceUnavailable {
t.Fatalf("not-ready status = %d, want %d", response.Code, http.StatusServiceUnavailable)
}
ready.Store(true)
response = httptest.NewRecorder()
router.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/ready", nil))
if response.Code != http.StatusOK {
t.Fatalf("ready status = %d, want %d", response.Code, http.StatusOK)
}
}
func listenOnFreePort(t *testing.T) net.Listener {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen on free port: %v", err)
}
return ln
}
func listenerPort(t *testing.T, ln net.Listener) int {
t.Helper()
tcpAddr, ok := ln.Addr().(*net.TCPAddr)
if !ok {
t.Fatalf("expected TCP listener, got %T", ln.Addr())
}
return tcpAddr.Port
}