Files
Dash/backend/internal/store/store_integration_test.go
T
Tomas Dvorak b17a06fbba 🚀 Dash - Homelab Dashboard
A clean, customizable homelab dashboard inspired by CasaOS.

Features:
- Empty-first dashboard (no demo data)
- 3 themes: Light, Dark, CasaOS glassmorphism
- Widgets: Clock (multi-timezone), Pi-hole, Memos, Immich, Image
- Drag & drop app organization
- Grid + list view for apps
- Groups with collapse/expand
- Proper widget refresh handling
- Visual timezone picker
- Square app cards with hover effects

Stack: Go + Gin + PostgreSQL + Next.js 15 + React 19 + Tailwind CSS + shadcn/ui
2026-05-03 16:13:46 +02:00

144 lines
3.3 KiB
Go

package store
import (
"context"
"encoding/json"
"errors"
"testing"
"time"
"dash/backend/internal/testutil"
)
func TestDashboardLayoutAndGroupDeleteIntegration(t *testing.T) {
pool := testutil.TestPool(t)
st := New(pool)
ctx := context.Background()
infra, err := st.CreateGroup(ctx, "Infra")
if err != nil {
t.Fatal(err)
}
media, err := st.CreateGroup(ctx, "Media")
if err != nil {
t.Fatal(err)
}
service, err := st.CreateService(ctx, ServiceInput{
GroupID: &infra.ID,
Name: "Pi-hole",
URLs: []ServiceURLInput{
{Label: "local", Kind: "local", URL: "http://pihole.local", IsPrimary: true},
{Label: "wan", Kind: "external", URL: "https://pihole.example.com"},
},
})
if err != nil {
t.Fatal(err)
}
dashboard, err := st.Dashboard(ctx)
if err != nil {
t.Fatal(err)
}
if len(dashboard.Groups) != 2 || len(dashboard.Groups[0].Services) != 1 {
t.Fatalf("unexpected dashboard: %+v", dashboard)
}
dashboard, err = st.ApplyLayout(ctx, LayoutInput{
GroupIDs: []string{media.ID, infra.ID},
WidgetIDs: []string{},
UngroupedServices: []string{},
GroupServices: map[string][]string{
media.ID: {service.ID},
infra.ID: {},
},
})
if err != nil {
t.Fatal(err)
}
if dashboard.Groups[0].ID != media.ID || len(dashboard.Groups[0].Services) != 1 {
t.Fatalf("service was not moved to media group: %+v", dashboard.Groups)
}
if err := st.DeleteGroup(ctx, media.ID, false); !errors.Is(err, ErrConflict) {
t.Fatalf("DeleteGroup() error = %v, want conflict", err)
}
if err := st.DeleteGroup(ctx, media.ID, true); err != nil {
t.Fatal(err)
}
service, err = st.Service(ctx, service.ID)
if err != nil {
t.Fatal(err)
}
if service.GroupID != nil {
t.Fatalf("service group id = %v, want nil", *service.GroupID)
}
}
func TestApplyLayoutRejectsPartialServiceSetIntegration(t *testing.T) {
pool := testutil.TestPool(t)
st := New(pool)
ctx := context.Background()
service, err := st.CreateService(ctx, ServiceInput{
Name: "Router",
URLs: []ServiceURLInput{
{Label: "local", Kind: "local", URL: "http://router.local", IsPrimary: true},
},
})
if err != nil {
t.Fatal(err)
}
_, err = st.CreateService(ctx, ServiceInput{
Name: "NAS",
URLs: []ServiceURLInput{
{Label: "local", Kind: "local", URL: "http://nas.local", IsPrimary: true},
},
})
if err != nil {
t.Fatal(err)
}
_, err = st.ApplyLayout(ctx, LayoutInput{
GroupIDs: []string{},
WidgetIDs: []string{},
UngroupedServices: []string{service.ID},
GroupServices: map[string][]string{},
})
if !errors.Is(err, ErrValidation) {
t.Fatalf("ApplyLayout() error = %v, want validation", err)
}
}
func TestWidgetCacheIntegration(t *testing.T) {
pool := testutil.TestPool(t)
st := New(pool)
ctx := context.Background()
widget, err := st.CreateWidget(ctx, WidgetInput{
Type: "clock",
Title: "Clock",
Config: json.RawMessage(`{"timezones":["Europe/Prague"]}`),
})
if err != nil {
t.Fatal(err)
}
now := time.Now().UTC()
err = st.SaveWidgetData(ctx, WidgetData{
WidgetID: widget.ID,
Status: "fresh",
Data: json.RawMessage(`{"ok":true}`),
FetchedAt: &now,
ExpiresAt: &now,
})
if err != nil {
t.Fatal(err)
}
data, err := st.WidgetData(ctx, widget.ID)
if err != nil {
t.Fatal(err)
}
if data.Status != "fresh" || len(data.Data) == 0 {
t.Fatalf("unexpected widget data: %+v", data)
}
}