Files
story/internal/httpapi/router_test.go
T

143 lines
5.9 KiB
Go

package httpapi
import (
"context"
"encoding/json"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"data-terminal-cloud/internal/alerts"
"data-terminal-cloud/internal/billing"
"data-terminal-cloud/internal/device"
"data-terminal-cloud/internal/deviceauth"
"data-terminal-cloud/internal/diagnostics"
"data-terminal-cloud/internal/health"
"data-terminal-cloud/internal/identity"
"data-terminal-cloud/internal/media"
"data-terminal-cloud/internal/opsaccess"
"data-terminal-cloud/internal/opsconfig"
"data-terminal-cloud/internal/saas"
"data-terminal-cloud/internal/sharing"
"data-terminal-cloud/internal/webevents"
)
func TestLiveEndpointReturnsRequestID(t *testing.T) {
checker := health.NewChecker(time.Second, map[string]health.CheckFunc{})
router := NewRouter(RouterOptions{ServiceName: "api", Readiness: checker})
handler := Middleware(slog.Default(), "test", 1024, nil, router)
request := httptest.NewRequest(http.MethodGet, "/health/live", nil)
request.Header.Set("X-Request-Id", "test-request")
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
}
if recorder.Header().Get("X-Request-Id") != "test-request" {
t.Fatalf("request id header was not preserved")
}
var body map[string]any
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
t.Fatalf("decode response: %v", err)
}
if body["request_id"] != "test-request" {
t.Fatalf("unexpected request_id: %#v", body["request_id"])
}
}
func TestMetricsExposeHTTPSeries(t *testing.T) {
handler := Middleware(slog.Default(), "metrics-test", 1024, nil, http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
writer.WriteHeader(http.StatusNoContent)
}))
request := httptest.NewRequest(http.MethodGet, "/example", nil)
request.Pattern = "GET /example"
handler.ServeHTTP(httptest.NewRecorder(), request)
recorder := httptest.NewRecorder()
MetricsHandler().ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/metrics", nil))
if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), `voice_platform_http_requests_total{method="GET",route="GET /example",service="metrics-test",status="204"} 1`) {
t.Fatalf("HTTP metric missing from scrape")
}
}
func TestReadyEndpointReturnsServiceUnavailable(t *testing.T) {
checker := health.NewChecker(time.Second, map[string]health.CheckFunc{
"database": func(context.Context) error { return context.DeadlineExceeded },
})
router := NewRouter(RouterOptions{ServiceName: "api", Readiness: checker})
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/health/ready", nil))
if recorder.Code != http.StatusServiceUnavailable {
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusServiceUnavailable)
}
}
func TestNewRouterRegistersCompleteAPIAndCallbackSurface(t *testing.T) {
checker := health.NewChecker(time.Second, map[string]health.CheckFunc{})
apiRouter := NewRouter(RouterOptions{
ServiceName: "api",
Readiness: checker,
TokenVerifier: new(identity.OIDCVerifier),
IdentityRepository: new(identity.Repository),
SaaSService: new(saas.Service),
DeviceVerifier: fakeDeviceVerifier{err: deviceauth.ErrCredentialsMissing},
DeviceService: new(device.Repository),
DeviceTelemetrySubmitter: &fakeTelemetrySubmitter{},
MediaService: new(media.Service),
WebEventService: new(webevents.Service),
SharingService: new(sharing.Service),
OpsConfigService: new(opsconfig.Service),
BillingService: new(billing.Service),
AlertService: new(alerts.Service),
DiagnosticService: new(diagnostics.Service),
OpsAccessService: new(opsaccess.Service),
ConversationCreator: &fakeConversationCreator{},
ConversationActivator: &fakeConversationActivator{},
DeviceConversationStateReader: &fakeDeviceConversationStateReader{},
ConversationHeartbeater: &fakeConversationHeartbeater{},
ConversationStopper: &fakeConversationStopper{},
ConversationTranscriptSubmitter: &fakeConversationTranscriptSubmitter{},
})
for _, target := range []string{
"/api/v1/me",
"/api/v1/households",
"/api/v1/ops/alerts",
"/api/v1/ops/sensitive-access-requests",
"/api/v1/events",
} {
recorder := httptest.NewRecorder()
apiRouter.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, target, nil))
if recorder.Code != http.StatusUnauthorized {
t.Fatalf("protected route %s status=%d body=%s", target, recorder.Code, recorder.Body.String())
}
}
recorder := httptest.NewRecorder()
apiRouter.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/device/v2/bootstrap", nil))
if recorder.Code != http.StatusUnauthorized {
t.Fatalf("device route status=%d body=%s", recorder.Code, recorder.Body.String())
}
_, pattern := apiRouter.(*http.ServeMux).Handler(httptest.NewRequest(http.MethodGet, "/api/v1/public/shares/"+strings.Repeat("a", 36), nil))
if pattern != "GET /api/v1/public/shares/{id}" {
t.Fatalf("public sharing route pattern=%q", pattern)
}
callbackRouter := NewRouter(RouterOptions{
ServiceName: "callbacks",
Readiness: checker,
ProviderCallbackSecret: []byte("01234567890123456789012345678901"),
ProviderCallbackSkew: time.Minute,
ProviderCallbackProcessor: &fakeProviderCallbackProcessor{},
})
recorder = httptest.NewRecorder()
callbackRouter.ServeHTTP(recorder, httptest.NewRequest(http.MethodPost, "/callbacks/v1/vertc/voice-chat", strings.NewReader(`{}`)))
if recorder.Code != http.StatusUnauthorized {
t.Fatalf("callback route status=%d body=%s", recorder.Code, recorder.Body.String())
}
}