fix: refresh dashboard update runs live
Container-Image veröffentlichen / container (push) Multi-Arch-Image veröffentlicht und verifiziert

This commit is contained in:
admin committed 2026-08-29 12:45:58 +02:00
1 parent bb60c06c0e
commit fc59092030
11 files changed
+241 -29

No files matched your search

+90
View File
@@ -4,6 +4,8 @@ import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"log/slog"
"net/http"
"net/http/httptest"
"net/url"
@@ -18,6 +20,7 @@ import (
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/config"
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/model"
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/pihole"
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/scheduler"
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/secure"
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/store"
)
@@ -232,6 +235,93 @@ func TestLoginErrorRendersAsToast(t *testing.T) {
}
}
func TestDashboardRendersLiveUpdateHooks(t *testing.T) {
t.Parallel()
templates, err := loadTemplates()
if err != nil {
t.Fatalf("load templates: %v", err)
}
var rendered bytes.Buffer
data := viewData{
Title: "Übersicht",
Page: "dashboard",
CSRF: "csrf-token",
User: model.User{DisplayName: "Admin", Username: "admin"},
IsAdmin: true,
Dashboard: model.Dashboard{
Settings: model.Settings{UpdaterEnabled: true, IPv6Enabled: true},
SchedulerRunning: true,
RecentRuns: []model.UpdateRun{{ID: 1, StartedAt: time.Now(), Status: "running"}},
},
}
if err := templates["dashboard"].ExecuteTemplate(&rendered, "dashboard", data); err != nil {
t.Fatalf("render dashboard: %v", err)
}
body := rendered.String()
for _, expected := range []string{
`data-run-now-button disabled`,
`data-dashboard-ipv4`,
`data-dashboard-ipv6`,
`data-recent-runs aria-live="polite" aria-busy="true"`,
`Aktualisierung läuft`,
`features-24`,
} {
if !strings.Contains(body, expected) {
t.Errorf("rendered dashboard is missing %q", expected)
}
}
}
func TestSchedulerAPIIncludesRecentRunsOnlyForAdministrators(t *testing.T) {
t.Parallel()
ctx := context.Background()
database, err := store.Open(ctx, filepath.Join(t.TempDir(), "app.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = database.Close() })
runID, err := database.CreateUpdateRun(ctx)
if err != nil {
t.Fatal(err)
}
if err := database.FinishUpdateRun(ctx, runID, "ok", "198.51.100.10", "", "1 Ziel aktualisiert"); err != nil {
t.Fatal(err)
}
schedule := scheduler.New(database, nil, nil, nil, slog.Default())
server := &Server{store: database, scheduler: schedule}
adminResponse := httptest.NewRecorder()
adminRequest := httptest.NewRequest(http.MethodGet, "/api/v1/scheduler?runs=1", nil)
server.schedulerAPI(adminResponse, adminRequest, &model.Session{Role: "admin"})
if adminResponse.Code != http.StatusOK {
t.Fatalf("unexpected admin response: status=%d body=%s", adminResponse.Code, adminResponse.Body.String())
}
if got := adminResponse.Header().Get("Cache-Control"); got != "no-store" {
t.Fatalf("unexpected cache policy: %q", got)
}
var adminState schedulerResponse
if err := json.NewDecoder(adminResponse.Body).Decode(&adminState); err != nil {
t.Fatal(err)
}
if adminState.RecentRuns == nil || len(*adminState.RecentRuns) != 1 {
t.Fatalf("administrator did not receive recent runs: %#v", adminState.RecentRuns)
}
if run := (*adminState.RecentRuns)[0]; run.ID != runID || run.Status != "ok" || run.Summary != "1 Ziel aktualisiert" {
t.Fatalf("unexpected recent run: %#v", run)
}
memberResponse := httptest.NewRecorder()
memberRequest := httptest.NewRequest(http.MethodGet, "/api/v1/scheduler?runs=1", nil)
server.schedulerAPI(memberResponse, memberRequest, &model.Session{Role: "member"})
var memberState schedulerResponse
if err := json.NewDecoder(memberResponse.Body).Decode(&memberState); err != nil {
t.Fatal(err)
}
if memberState.RecentRuns != nil {
t.Fatal("member received administrator-only update runs")
}
}
func TestLoginLimiterPrunesExpiredClientsAndBoundsMemory(t *testing.T) {
t.Parallel()
limiter := newLoginLimiter()