Ctrl K
ComunidadGuías…

internal/server/settings_backup_form_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// SPDX-License-Identifier: Apache-2.0

package server

import (
	"context"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/X4Applegate/caddyui/internal/auth"
	"github.com/X4Applegate/caddyui/internal/models"
)

// Issue #166, server half. web.TestSettingsFormEnclosesEverySectionControl
// proves the Backup card's controls sit inside the settings form; these
// tests prove the round trip they feed actually works.
//
// Together they cover what a human verifies by hand: tick scheduled backups,
// set a directory/interval/keep count, save, reload, see the values persist,
// then press "Back up now" and get a backup file.
//
// Before the fix the browser sent none of this — the inputs belonged to no
// form at all — so the save was a no-op however correct the handler was.
// That is why the handler-level tests that already existed all passed while
// the feature was completely unreachable from the UI.

// TestBackupSectionSavesAndReloads posts exactly what the fixed form posts,
// then reads the settings back.
func TestBackupSectionSavesAndReloads(t *testing.T) {
	s, _ := newPreflightTestServer(t)
	dir := t.TempDir()

	// Seed another page's value to confirm the backup save stays scoped to
	// its own keys, like every other settings page.
	if err := models.SetSetting(s.DB, settingSiteTitle, "Keep me"); err != nil {
		t.Fatal(err)
	}

	rec := postSettingsPage(t, s, url.Values{
		"settings_section":               {"backup"},
		"backup_schedule_enabled":        {"on"},
		"backup_schedule_dir":            {dir},
		"backup_schedule_interval_hours": {"12"},
		"backup_schedule_keep":           {"5"},
	})
	if rec.Code != http.StatusSeeOther || rec.Header().Get("Location") != "/settings/backup?saved=1" {
		t.Fatalf("backup save → %d %s", rec.Code, rec.Header().Get("Location"))
	}

	// Reload. Persisting these four values is the exact thing issue #166
	// reported as impossible.
	cfg := s.backupScheduleConfig()
	if !cfg.Enabled {
		t.Error("scheduled backups did not stay enabled after save + reload")
	}
	if cfg.Dir != dir {
		t.Errorf("backup dir = %q, want %q", cfg.Dir, dir)
	}
	if cfg.IntervalHours != 12 {
		t.Errorf("interval = %d, want 12", cfg.IntervalHours)
	}
	if cfg.Keep != 5 {
		t.Errorf("keep = %d, want 5", cfg.Keep)
	}

	// Scoped: the backup page does not touch another page's settings.
	if got := setting(t, s, settingSiteTitle); got != "Keep me" {
		t.Errorf("backup save changed the site title: %q", got)
	}

	// Unticking the checkbox clears the flag. A checkbox posts nothing when
	// unchecked, so this is the path that has to turn the feature back off.
	rec = postSettingsPage(t, s, url.Values{
		"settings_section":               {"backup"},
		"backup_schedule_dir":            {dir},
		"backup_schedule_interval_hours": {"12"},
		"backup_schedule_keep":           {"5"},
	})
	if rec.Code != http.StatusSeeOther {
		t.Fatalf("backup save (unticked) → %d", rec.Code)
	}
	if s.backupScheduleConfig().Enabled {
		t.Error("unticking scheduled backups did not disable them")
	}
}

// TestBackupRunUsesSavedDirectory covers the "Back up now" button, which is a
// submit inside the settings form with formaction="/settings/backup/run". It
// must still produce a backup once it is inside a real form.
func TestBackupRunUsesSavedDirectory(t *testing.T) {
	s, _ := newPreflightTestServer(t)
	dir := t.TempDir()

	// With no directory saved, the button explains itself rather than failing
	// silently or 500-ing.
	rec := postBackupRunPage(t, s)
	if rec.Code != http.StatusSeeOther {
		t.Fatalf("backup run with no dir → %d", rec.Code)
	}
	if loc := rec.Header().Get("Location"); !strings.Contains(loc, "backuperr=") {
		t.Errorf("expected a backuperr redirect when no directory is saved, got %q", loc)
	}

	// Save a directory through the settings form, exactly as the UI does.
	rec = postSettingsPage(t, s, url.Values{
		"settings_section":               {"backup"},
		"backup_schedule_enabled":        {"on"},
		"backup_schedule_dir":            {dir},
		"backup_schedule_interval_hours": {"24"},
		"backup_schedule_keep":           {"7"},
	})
	if rec.Code != http.StatusSeeOther {
		t.Fatalf("backup save → %d", rec.Code)
	}

	// Now "Back up now" writes a real file into that directory.
	rec = postBackupRunPage(t, s)
	if rec.Code != http.StatusSeeOther {
		t.Fatalf("backup run → %d %s", rec.Code, rec.Body.String())
	}
	loc := rec.Header().Get("Location")
	if !strings.Contains(loc, "backupok=") {
		t.Fatalf("expected a backupok redirect, got %q", loc)
	}

	entries, err := os.ReadDir(dir)
	if err != nil {
		t.Fatal(err)
	}
	var backups []string
	for _, e := range entries {
		if strings.HasPrefix(e.Name(), backupFilePrefix) && strings.HasSuffix(e.Name(), backupFileSuffix) {
			backups = append(backups, e.Name())
		}
	}
	if len(backups) != 1 {
		t.Fatalf("expected exactly one backup file in %s, got %v", dir, entries)
	}
	info, err := os.Stat(filepath.Join(dir, backups[0]))
	if err != nil {
		t.Fatal(err)
	}
	if info.Size() == 0 {
		t.Errorf("backup %s is empty", backups[0])
	}
}

// postBackupRunPage drives postBackupRun the way the form's "Back up now"
// submit button does.
func postBackupRunPage(t *testing.T, s *Server) *httptest.ResponseRecorder {
	t.Helper()
	req := httptest.NewRequest(http.MethodPost, "/settings/backup/run", strings.NewReader(""))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req = req.WithContext(context.WithValue(req.Context(), auth.ContextUserKey,
		&models.User{ID: 1, Email: "admin@example.com", IsAdmin: true, Role: models.RoleAdmin}))
	rec := httptest.NewRecorder()
	s.postBackupRun(rec, req)
	return rec
}