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) } }