Files
ClusterCanvas/service/internal/api/action_widgets_handlers_test.go
T
Squid 6eef839526 Add Overview page with kind-grouped node cards and RAM/disk metrics.
Batch-fetch action widgets so the dashboard can show free/total memory and disk usage without N+1 requests.
2026-07-20 21:25:55 +02:00

180 lines
4.7 KiB
Go

package api
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"codeberg.org/SquidSE/ClusterCanvas/service/internal/auth"
"codeberg.org/SquidSE/ClusterCanvas/service/internal/settings"
)
func TestActionWidgetsListAllFiltersByNodeAccess(t *testing.T) {
configDir := t.TempDir()
_ = seedCompletedSetup(t, configDir)
keyBytes, err := settings.KeyFromEnv()
if err != nil {
t.Fatalf("KeyFromEnv: %v", err)
}
now := time.Now().UTC()
passwordHash, err := auth.HashPassword("correct horse battery staple extra")
if err != nil {
t.Fatalf("HashPassword: %v", err)
}
payload := settings.Settings{
SetupCompleted: true,
Groups: []settings.Group{
{
Name: settings.AdministratorsGroupName,
ScopeKind: settings.GroupScopeGroup,
ScopeName: "Admin Group",
Permissions: allPermissionList(),
},
{
Name: "TeamA",
ScopeKind: settings.GroupScopeGroup,
ScopeName: "Team A",
Permissions: []string{"nodes.read"},
},
},
Security: settings.DefaultSecuritySettings(),
Network: settings.DefaultNetworkSettings(),
}
if err := settings.SaveSettings(configDir, payload); err != nil {
t.Fatalf("SaveSettings: %v", err)
}
if err := settings.SavePasswords(configDir, settings.PasswordStore{
Users: []settings.UserCredential{
{
ID: "admin-id",
Username: "Admin",
PasswordHash: passwordHash,
Enabled: true,
GroupNames: []string{settings.AdministratorsGroupName},
CreatedAt: now,
PasswordChangedAt: now,
},
{
ID: "reader-id",
Username: "reader",
PasswordHash: passwordHash,
Enabled: true,
GroupNames: []string{"TeamA"},
CreatedAt: now,
PasswordChangedAt: now,
},
},
}, keyBytes); err != nil {
t.Fatalf("SavePasswords: %v", err)
}
accessibleNodeID := "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee"
hiddenNodeID := "bbbbbbbb-bbbb-4ccc-8ddd-eeeeeeeeeeee"
if err := settings.SaveNodes(configDir, settings.NodeStore{
Nodes: []settings.Node{
{
ID: accessibleNodeID,
Kind: settings.NodeKindContainer,
Name: "visible",
HostIP: "10.0.0.1",
Username: "root",
GroupName: "TeamA",
CreatedAt: now,
},
{
ID: hiddenNodeID,
Kind: settings.NodeKindVM,
Name: "hidden",
HostIP: "10.0.0.2",
Username: "root",
GroupName: settings.AdministratorsGroupName,
CreatedAt: now,
},
},
}); err != nil {
t.Fatalf("SaveNodes: %v", err)
}
if err := settings.SaveActionWidgets(configDir, settings.ActionWidgetStore{
Widgets: []settings.ActionWidgetSnapshot{
{
NodeID: accessibleNodeID,
ItemID: "item-visible",
GroupID: "group-1",
WidgetType: settings.ActionWidgetTypeMemory,
Title: "Memory",
UpdatedAt: now,
Memory: &settings.MemoryWidgetData{
Total: 1024,
Free: 512,
},
},
{
NodeID: hiddenNodeID,
ItemID: "item-hidden",
GroupID: "group-2",
WidgetType: settings.ActionWidgetTypeDisk,
Title: "Disk",
UpdatedAt: now,
Disk: &settings.DiskWidgetData{
Size: "50G",
Used: "12G",
UsePercent: "24%",
MountedOn: "/",
},
},
},
}); err != nil {
t.Fatalf("SaveActionWidgets: %v", err)
}
router := NewRouter(configDir)
loginRecorder := httptest.NewRecorder()
loginRequest := httptest.NewRequest(
http.MethodPost,
"/api/v1/auth/login",
bytes.NewReader([]byte(`{"username":"reader","password":"correct horse battery staple extra"}`)),
)
loginRequest.Header.Set("Content-Type", "application/json")
router.ServeHTTP(loginRecorder, loginRequest)
if loginRecorder.Code != http.StatusOK {
t.Fatalf("login status = %d body=%s", loginRecorder.Code, loginRecorder.Body.String())
}
var readerCookie *http.Cookie
for _, cookie := range loginRecorder.Result().Cookies() {
if cookie.Name == auth.SessionCookieName {
readerCookie = cookie
break
}
}
if readerCookie == nil {
t.Fatal("reader session cookie missing")
}
listRequest := withSession(
httptest.NewRequest(http.MethodGet, "/api/v1/action-widgets", nil),
readerCookie,
)
listRecorder := httptest.NewRecorder()
router.ServeHTTP(listRecorder, listRequest)
if listRecorder.Code != http.StatusOK {
t.Fatalf("list status = %d body=%s", listRecorder.Code, listRecorder.Body.String())
}
var response actionWidgetsResponse
if err := json.Unmarshal(listRecorder.Body.Bytes(), &response); err != nil {
t.Fatalf("decode: %v", err)
}
if len(response.Widgets) != 1 {
t.Fatalf("widgets = %+v", response.Widgets)
}
if response.Widgets[0].NodeID != accessibleNodeID {
t.Fatalf("node_id = %q", response.Widgets[0].NodeID)
}
}