Batch-fetch action widgets so the dashboard can show free/total memory and disk usage without N+1 requests.
180 lines
4.7 KiB
Go
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)
|
|
}
|
|
}
|