Files
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

113 lines
4.5 KiB
Go

package dashboardapi
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/itworx/pulse/internal/audit"
"github.com/itworx/pulse/internal/auth"
"github.com/itworx/pulse/internal/correlation"
"github.com/itworx/pulse/internal/dashboard"
)
func requestWithPrincipal(method, path, body string, principal *auth.Principal) *httptest.ResponseRecorder {
request := httptest.NewRequest(method, path, strings.NewReader(body))
request.Header.Set("Content-Type", "application/json")
request = request.WithContext(correlation.WithContext(request.Context(), "m3-03-test-correlation"))
if principal != nil {
request = request.WithContext(auth.WithPrincipal(request.Context(), *principal))
}
response := httptest.NewRecorder()
(Handler{Repository: dashboard.Repository{}}).ServeHTTP(response, request)
return response
}
func problemCode(t *testing.T, response *httptest.ResponseRecorder) string {
t.Helper()
var body struct{ Code string }
if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
t.Fatalf("problem response is not JSON: %v", err)
}
return body.Code
}
func TestHandlerRequiresAuthenticationWithProblemResponse(t *testing.T) {
response := requestWithPrincipal(http.MethodGet, "/api/v1/dashboards", "", nil)
if response.Code != http.StatusUnauthorized {
t.Fatalf("status=%d", response.Code)
}
if got := response.Header().Get("Content-Type"); got != "application/problem+json" {
t.Fatalf("content type=%q", got)
}
if got := problemCode(t, response); got != "UNAUTHORIZED" {
t.Fatalf("code=%q", got)
}
if got := response.Header().Get(correlation.Header); got != "m3-03-test-correlation" {
t.Fatalf("correlation=%q", got)
}
}
func TestHandlerEnforcesEditorForMutation(t *testing.T) {
viewer := auth.Principal{Subject: "viewer", Role: auth.RoleViewer}
response := requestWithPrincipal(http.MethodPost, "/api/v1/dashboards", "{}", &viewer)
if response.Code != http.StatusForbidden {
t.Fatalf("status=%d", response.Code)
}
if got := problemCode(t, response); got != "FORBIDDEN" {
t.Fatalf("code=%q", got)
}
}
func TestHandlerEnforcesEditorForPreview(t *testing.T) {
viewer := auth.Principal{Subject: "viewer", Role: auth.RoleViewer}
response := requestWithPrincipal(http.MethodPost, "/api/v1/dashboards/00000000-0000-0000-0000-000000000001/preview", `{"widget":{}}`, &viewer)
if response.Code != http.StatusForbidden || problemCode(t, response) != "FORBIDDEN" {
t.Fatalf("response=%d %s", response.Code, response.Body.String())
}
}
func TestHandlerRejectsInvalidContentTypeAndVersion(t *testing.T) {
editor := auth.Principal{Subject: "editor", Role: auth.RoleEditor}
request := httptest.NewRequest(http.MethodPost, "/api/v1/dashboards", strings.NewReader("{}"))
request.Header.Set("Content-Type", "text/plain")
request = request.WithContext(auth.WithPrincipal(context.Background(), editor))
response := httptest.NewRecorder()
(Handler{Repository: dashboard.Repository{}}).ServeHTTP(response, request)
if response.Code != http.StatusBadRequest || problemCode(t, response) != "INVALID_DOCUMENT" {
t.Fatalf("response=%d %s", response.Code, response.Body.String())
}
response = requestWithPrincipal(http.MethodPost, "/api/v1/dashboards/00000000-0000-0000-0000-000000000001/restore/not-a-number", "{}", &editor)
if response.Code != http.StatusBadRequest || problemCode(t, response) != "INVALID_VERSION" {
t.Fatalf("response=%d %s", response.Code, response.Body.String())
}
}
func TestHandlerRejectsUnsupportedMethod(t *testing.T) {
viewer := auth.Principal{Subject: "viewer", Role: auth.RoleViewer}
response := requestWithPrincipal(http.MethodPut, "/api/v1/dashboards", "{}", &viewer)
if response.Code != http.StatusMethodNotAllowed {
t.Fatalf("status=%d", response.Code)
}
}
func TestHandlerRecordsAuditedSafeDiff(t *testing.T) {
store := &audit.MemoryStore{}
request := httptest.NewRequest(http.MethodPost, "/api/v1/dashboards", strings.NewReader("{}"))
request = request.WithContext(correlation.WithContext(request.Context(), "audit-correlation"))
handler := Handler{Audit: store}
if err := handler.record(request, "subject-1", "dashboard.update_document", "00000000-0000-0000-0000-000000000001", map[string]any{"revision": 1}, map[string]any{"revision": 2}); err != nil {
t.Fatal(err)
}
if len(store.Events) != 1 || store.Events[0].Action != "dashboard.update_document" || store.Events[0].CorrelationID != "audit-correlation" {
t.Fatalf("events=%+v", store.Events)
}
if _, ok := store.Events[0].After["document"]; ok {
t.Fatal("audit event unexpectedly contains full document")
}
}