package api import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "codeberg.org/SquidSE/ClusterCanvas/service/internal/settings" ) type groupsResponseTest struct { Groups []settings.Group `json:"groups"` } type apiErrorResponseTest struct { Error string `json:"error"` } func TestGroupsGetEmptyWhenSettingsMissing(t *testing.T) { t.Parallel() configDir := t.TempDir() router := NewRouter(configDir) request := httptest.NewRequest(http.MethodGet, "/api/v1/groups", nil) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, recorder.Code) } var payload groupsResponseTest if err := json.NewDecoder(recorder.Body).Decode(&payload); err != nil { t.Fatalf("decode response: %v", err) } if payload.Groups == nil || len(payload.Groups) != 0 { t.Fatalf("expected empty groups list, got %#v", payload.Groups) } } func TestGroupsUpsertRejectsInvalidPermissions(t *testing.T) { t.Parallel() configDir := t.TempDir() router := NewRouter(configDir) body := []byte(`{ "group": { "name": "Admins", "scope_kind": "node", "scope_name": "node-1", "permissions": ["nodes.nope"] } }`) request := httptest.NewRequest(http.MethodPost, "/api/v1/groups", bytes.NewReader(body)) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, recorder.Code) } var payload apiErrorResponseTest if err := json.NewDecoder(recorder.Body).Decode(&payload); err != nil { t.Fatalf("decode response: %v", err) } if payload.Error == "" { t.Fatal("expected error message") } } func TestGroupsUpsertUpsertsByName(t *testing.T) { t.Parallel() configDir := t.TempDir() router := NewRouter(configDir) createBody := []byte(`{ "group": { "name": "Admins", "scope_kind": "node", "scope_name": "node-1", "permissions": ["nodes.read"] } }`) createReq := httptest.NewRequest(http.MethodPost, "/api/v1/groups", bytes.NewReader(createBody)) createRec := httptest.NewRecorder() router.ServeHTTP(createRec, createReq) if createRec.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, createRec.Code) } updateBody := []byte(`{ "group": { "name": "Admins", "scope_kind": "node", "scope_name": "node-2", "permissions": ["nodes.exec","nodes.update"] } }`) updateReq := httptest.NewRequest(http.MethodPost, "/api/v1/groups", bytes.NewReader(updateBody)) updateRec := httptest.NewRecorder() router.ServeHTTP(updateRec, updateReq) if updateRec.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, updateRec.Code) } getReq := httptest.NewRequest(http.MethodGet, "/api/v1/groups", nil) getRec := httptest.NewRecorder() router.ServeHTTP(getRec, getReq) if getRec.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, getRec.Code) } var getPayload groupsResponseTest if err := json.NewDecoder(getRec.Body).Decode(&getPayload); err != nil { t.Fatalf("decode response: %v", err) } if len(getPayload.Groups) != 1 { t.Fatalf("expected 1 group, got %d", len(getPayload.Groups)) } if getPayload.Groups[0].Name != "Admins" { t.Fatalf("expected group name %q, got %q", "Admins", getPayload.Groups[0].Name) } if getPayload.Groups[0].ScopeName != "node-2" { t.Fatalf("expected scope_name %q, got %q", "node-2", getPayload.Groups[0].ScopeName) } } func TestGroupsDeleteRemovesByName(t *testing.T) { t.Parallel() configDir := t.TempDir() router := NewRouter(configDir) createBody := []byte(`{ "group": { "name": "Admins", "scope_kind": "node", "scope_name": "node-1", "permissions": ["nodes.read"] } }`) createReq := httptest.NewRequest(http.MethodPost, "/api/v1/groups", bytes.NewReader(createBody)) createRec := httptest.NewRecorder() router.ServeHTTP(createRec, createReq) if createRec.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, createRec.Code) } deleteReq := httptest.NewRequest(http.MethodDelete, "/api/v1/groups?name=Admins", nil) deleteRec := httptest.NewRecorder() router.ServeHTTP(deleteRec, deleteReq) if deleteRec.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, deleteRec.Code) } getReq := httptest.NewRequest(http.MethodGet, "/api/v1/groups", nil) getRec := httptest.NewRecorder() router.ServeHTTP(getRec, getReq) if getRec.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, getRec.Code) } var getPayload groupsResponseTest if err := json.NewDecoder(getRec.Body).Decode(&getPayload); err != nil { t.Fatalf("decode response: %v", err) } if len(getPayload.Groups) != 0 { t.Fatalf("expected empty groups, got %#v", getPayload.Groups) } } func TestGroupsDeleteMissingNameFails(t *testing.T) { t.Parallel() configDir := t.TempDir() router := NewRouter(configDir) deleteReq := httptest.NewRequest(http.MethodDelete, "/api/v1/groups", nil) deleteRec := httptest.NewRecorder() router.ServeHTTP(deleteRec, deleteReq) if deleteRec.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, deleteRec.Code) } var payload apiErrorResponseTest if err := json.NewDecoder(deleteRec.Body).Decode(&payload); err != nil { t.Fatalf("decode response: %v", err) } if payload.Error == "" { t.Fatal("expected error message") } }