230 lines
7.5 KiB
Go
230 lines
7.5 KiB
Go
package cluster
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
func TestProjectAdminRuntimeReturnsReadOnlyManifest(t *testing.T) {
|
|
router := chi.NewRouter()
|
|
module := &Module{}
|
|
router.Post("/clusters/{clusterID}/nodes/{nodeID}/admin-runtime/projection", module.projectAdminRuntime)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
|
|
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
|
|
"method":"GET",
|
|
"path":"/admin/ui-manifest",
|
|
"scope":"platform",
|
|
"service_class":"admin-ingress"
|
|
}`)))
|
|
rec := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var response struct {
|
|
SchemaVersion string `json:"schema_version"`
|
|
Status string `json:"status"`
|
|
Reason string `json:"reason"`
|
|
StatusCode int `json:"status_code"`
|
|
Headers map[string]string `json:"headers"`
|
|
Body json.RawMessage `json:"body"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if response.SchemaVersion != "rap.web_ingress.control_api_projection_response.v1" ||
|
|
response.Status != "ready" ||
|
|
response.Reason != "ui_manifest_ready" ||
|
|
response.StatusCode != http.StatusOK ||
|
|
response.Headers["Content-Type"] != "application/json" {
|
|
t.Fatalf("response = %+v", response)
|
|
}
|
|
var body struct {
|
|
SchemaVersion string `json:"schema_version"`
|
|
ClusterID string `json:"cluster_id"`
|
|
NodeID string `json:"node_id"`
|
|
Manifest map[string]any `json:"manifest"`
|
|
}
|
|
if err := json.Unmarshal(response.Body, &body); err != nil {
|
|
t.Fatalf("decode body: %v", err)
|
|
}
|
|
if body.ClusterID != "cluster-1" ||
|
|
body.NodeID != "node-1" ||
|
|
body.Manifest["projection_binding"] != "control_api_read_only" ||
|
|
body.Manifest["mutation_enabled"] != false {
|
|
t.Fatalf("body = %+v", body)
|
|
}
|
|
}
|
|
|
|
func TestProjectAdminRuntimeRejectsMutations(t *testing.T) {
|
|
router := chi.NewRouter()
|
|
module := &Module{}
|
|
router.Post("/clusters/{clusterID}/nodes/{nodeID}/admin-runtime/projection", module.projectAdminRuntime)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
|
|
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
|
|
"method":"POST",
|
|
"path":"/admin/nodes",
|
|
"scope":"platform",
|
|
"service_class":"admin-ingress"
|
|
}`)))
|
|
rec := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var response struct {
|
|
Status string `json:"status"`
|
|
Reason string `json:"reason"`
|
|
StatusCode int `json:"status_code"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if response.Status != "blocked" || response.Reason != "control_api_mutation_rejected" || response.StatusCode != http.StatusForbidden {
|
|
t.Fatalf("response = %+v", response)
|
|
}
|
|
}
|
|
|
|
func TestProjectAdminRuntimeReturnsHealthProjection(t *testing.T) {
|
|
router := chi.NewRouter()
|
|
module := &Module{}
|
|
router.Post("/clusters/{clusterID}/nodes/{nodeID}/admin-runtime/projection", module.projectAdminRuntime)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
|
|
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
|
|
"method":"GET",
|
|
"path":"/readyz",
|
|
"scope":"platform",
|
|
"service_class":"admin-ingress"
|
|
}`)))
|
|
rec := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var response struct {
|
|
Status string `json:"status"`
|
|
Reason string `json:"reason"`
|
|
StatusCode int `json:"status_code"`
|
|
Body json.RawMessage `json:"body"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if response.Status != "ready" || response.Reason != "admin_runtime_projection_ready" || response.StatusCode != http.StatusOK {
|
|
t.Fatalf("response = %+v", response)
|
|
}
|
|
var body struct {
|
|
Projection string `json:"projection"`
|
|
AuditRequired bool `json:"audit_required"`
|
|
}
|
|
if err := json.Unmarshal(response.Body, &body); err != nil {
|
|
t.Fatalf("decode body: %v", err)
|
|
}
|
|
if body.Projection != "read_only" || !body.AuditRequired {
|
|
t.Fatalf("body = %+v", body)
|
|
}
|
|
}
|
|
|
|
func TestProjectAdminRuntimeBlocksUnknownReadProjection(t *testing.T) {
|
|
router := chi.NewRouter()
|
|
module := &Module{}
|
|
router.Post("/clusters/{clusterID}/nodes/{nodeID}/admin-runtime/projection", module.projectAdminRuntime)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
|
|
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
|
|
"method":"GET",
|
|
"path":"/admin/nodes",
|
|
"scope":"platform",
|
|
"service_class":"admin-ingress"
|
|
}`)))
|
|
rec := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var response struct {
|
|
Status string `json:"status"`
|
|
Reason string `json:"reason"`
|
|
StatusCode int `json:"status_code"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if response.Status != "blocked" ||
|
|
response.Reason != "control_api_projection_not_implemented" ||
|
|
response.StatusCode != http.StatusNotImplemented {
|
|
t.Fatalf("response = %+v", response)
|
|
}
|
|
}
|
|
|
|
func TestProjectAdminRuntimeRejectsScopeClassMismatch(t *testing.T) {
|
|
router := chi.NewRouter()
|
|
module := &Module{}
|
|
router.Post("/clusters/{clusterID}/nodes/{nodeID}/admin-runtime/projection", module.projectAdminRuntime)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
|
|
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
|
|
"method":"GET",
|
|
"path":"/admin/ui-manifest",
|
|
"scope":"organization",
|
|
"service_class":"admin-ingress"
|
|
}`)))
|
|
rec := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var response struct {
|
|
Status string `json:"status"`
|
|
Reason string `json:"reason"`
|
|
StatusCode int `json:"status_code"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if response.Status != "blocked" ||
|
|
response.Reason != "control_api_projection_scope_rejected" ||
|
|
response.StatusCode != http.StatusForbidden {
|
|
t.Fatalf("response = %+v", response)
|
|
}
|
|
}
|
|
|
|
func TestProjectAdminRuntimeRejectsInvalidSchema(t *testing.T) {
|
|
router := chi.NewRouter()
|
|
module := &Module{}
|
|
router.Post("/clusters/{clusterID}/nodes/{nodeID}/admin-runtime/projection", module.projectAdminRuntime)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
|
|
"schema_version":"wrong.schema",
|
|
"method":"GET",
|
|
"path":"/readyz",
|
|
"scope":"platform",
|
|
"service_class":"admin-ingress"
|
|
}`)))
|
|
rec := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
}
|