143 lines
5.9 KiB
Go
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())
|
|
}
|
|
}
|