Register hosts under Containers/VMs/Docker with encrypted key storage, and require re-authentication for sensitive account changes.
252 lines
7.7 KiB
Go
252 lines
7.7 KiB
Go
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 TestGroupsGetIncludesAdministratorsFromSetup(t *testing.T) {
|
|
configDir := t.TempDir()
|
|
cookie := seedCompletedSetup(t, configDir)
|
|
router := NewRouter(configDir)
|
|
|
|
request := withSession(httptest.NewRequest(http.MethodGet, "/api/v1/groups", nil), cookie)
|
|
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 len(payload.Groups) != 1 || payload.Groups[0].Name != settings.AdministratorsGroupName {
|
|
t.Fatalf("expected Administrators group from seed, got %#v", payload.Groups)
|
|
}
|
|
}
|
|
|
|
func TestGroupsUpsertRejectsInvalidPermissions(t *testing.T) {
|
|
configDir := t.TempDir()
|
|
cookie := seedCompletedSetup(t, configDir)
|
|
router := NewRouter(configDir)
|
|
|
|
body := []byte(`{
|
|
"group": {
|
|
"name": "Admins",
|
|
"scope_kind": "node",
|
|
"scope_name": "node-1",
|
|
"permissions": ["nodes.nope"]
|
|
}
|
|
}`)
|
|
request := withSession(httptest.NewRequest(http.MethodPost, "/api/v1/groups", bytes.NewReader(body)), cookie)
|
|
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) {
|
|
configDir := t.TempDir()
|
|
cookie := seedCompletedSetup(t, configDir)
|
|
router := NewRouter(configDir)
|
|
|
|
createBody := []byte(`{
|
|
"group": {
|
|
"name": "Admins",
|
|
"scope_kind": "node",
|
|
"scope_name": "node-1",
|
|
"permissions": ["nodes.read"]
|
|
}
|
|
}`)
|
|
createReq := withSession(httptest.NewRequest(http.MethodPost, "/api/v1/groups", bytes.NewReader(createBody)), cookie)
|
|
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 := withSession(httptest.NewRequest(http.MethodPost, "/api/v1/groups", bytes.NewReader(updateBody)), cookie)
|
|
updateRec := httptest.NewRecorder()
|
|
router.ServeHTTP(updateRec, updateReq)
|
|
if updateRec.Code != http.StatusOK {
|
|
t.Fatalf("expected status %d, got %d", http.StatusOK, updateRec.Code)
|
|
}
|
|
|
|
getReq := withSession(httptest.NewRequest(http.MethodGet, "/api/v1/groups", nil), cookie)
|
|
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) != 2 {
|
|
t.Fatalf("expected 2 groups (Administrators + Admins), got %d", len(getPayload.Groups))
|
|
}
|
|
var admins *settings.Group
|
|
for index := range getPayload.Groups {
|
|
if getPayload.Groups[index].Name == "Admins" {
|
|
admins = &getPayload.Groups[index]
|
|
}
|
|
}
|
|
if admins == nil {
|
|
t.Fatal("expected Admins group")
|
|
}
|
|
if admins.ScopeName != "node-2" {
|
|
t.Fatalf("expected scope_name %q, got %q", "node-2", admins.ScopeName)
|
|
}
|
|
}
|
|
|
|
func TestGroupsDeleteRemovesByName(t *testing.T) {
|
|
configDir := t.TempDir()
|
|
cookie := seedCompletedSetup(t, configDir)
|
|
router := NewRouter(configDir)
|
|
|
|
createBody := []byte(`{
|
|
"group": {
|
|
"name": "Admins",
|
|
"scope_kind": "node",
|
|
"scope_name": "node-1",
|
|
"permissions": ["nodes.read"]
|
|
}
|
|
}`)
|
|
createReq := withSession(httptest.NewRequest(http.MethodPost, "/api/v1/groups", bytes.NewReader(createBody)), cookie)
|
|
createRec := httptest.NewRecorder()
|
|
router.ServeHTTP(createRec, createReq)
|
|
if createRec.Code != http.StatusOK {
|
|
t.Fatalf("expected status %d, got %d", http.StatusOK, createRec.Code)
|
|
}
|
|
|
|
deleteReq := withSession(httptest.NewRequest(http.MethodDelete, "/api/v1/groups?name=Admins", nil), cookie)
|
|
deleteRec := httptest.NewRecorder()
|
|
router.ServeHTTP(deleteRec, deleteReq)
|
|
if deleteRec.Code != http.StatusOK {
|
|
t.Fatalf("expected status %d, got %d", http.StatusOK, deleteRec.Code)
|
|
}
|
|
|
|
getReq := withSession(httptest.NewRequest(http.MethodGet, "/api/v1/groups", nil), cookie)
|
|
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 || getPayload.Groups[0].Name != settings.AdministratorsGroupName {
|
|
t.Fatalf("expected only Administrators remaining, got %#v", getPayload.Groups)
|
|
}
|
|
}
|
|
|
|
func TestGroupsDeleteMissingNameFails(t *testing.T) {
|
|
configDir := t.TempDir()
|
|
cookie := seedCompletedSetup(t, configDir)
|
|
router := NewRouter(configDir)
|
|
|
|
deleteReq := withSession(httptest.NewRequest(http.MethodDelete, "/api/v1/groups", nil), cookie)
|
|
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")
|
|
}
|
|
}
|
|
|
|
func TestGroupsDeleteRejectsAdministrators(t *testing.T) {
|
|
configDir := t.TempDir()
|
|
cookie := seedCompletedSetup(t, configDir)
|
|
router := NewRouter(configDir)
|
|
|
|
createBody := []byte(`{
|
|
"group": {
|
|
"name": "Administrators",
|
|
"scope_kind": "group",
|
|
"scope_name": "Admin Group",
|
|
"permissions": ["nodes.read","users.manage"]
|
|
}
|
|
}`)
|
|
createReq := withSession(httptest.NewRequest(http.MethodPost, "/api/v1/groups", bytes.NewReader(createBody)), cookie)
|
|
createRec := httptest.NewRecorder()
|
|
router.ServeHTTP(createRec, createReq)
|
|
if createRec.Code != http.StatusOK {
|
|
t.Fatalf("expected status %d, got %d body=%s", http.StatusOK, createRec.Code, createRec.Body.String())
|
|
}
|
|
|
|
deleteReq := withSession(
|
|
httptest.NewRequest(http.MethodDelete, "/api/v1/groups?name=Administrators", nil),
|
|
cookie,
|
|
)
|
|
deleteRec := httptest.NewRecorder()
|
|
router.ServeHTTP(deleteRec, deleteReq)
|
|
if deleteRec.Code != http.StatusConflict {
|
|
t.Fatalf("expected status %d, got %d body=%s", http.StatusConflict, deleteRec.Code, deleteRec.Body.String())
|
|
}
|
|
|
|
var payload apiErrorResponseTest
|
|
if err := json.NewDecoder(deleteRec.Body).Decode(&payload); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if payload.Error != "Administrators group cannot be deleted" {
|
|
t.Fatalf("unexpected error: %q", payload.Error)
|
|
}
|
|
|
|
getReq := withSession(httptest.NewRequest(http.MethodGet, "/api/v1/groups", nil), cookie)
|
|
getRec := httptest.NewRecorder()
|
|
router.ServeHTTP(getRec, getReq)
|
|
var getPayload groupsResponseTest
|
|
if err := json.NewDecoder(getRec.Body).Decode(&getPayload); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if len(getPayload.Groups) != 1 || getPayload.Groups[0].Name != settings.AdministratorsGroupName {
|
|
t.Fatalf("Administrators group should still exist, got %#v", getPayload.Groups)
|
|
}
|
|
}
|