Files
Gardomatic/internal/api/application_settings_test.go
2026-09-14 19:58:31 +02:00

122 lines
4.0 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"gardomatic.kleiax.de/internal/mailer"
"gardomatic.kleiax.de/internal/storage"
)
type applicationSettingsTestModel struct {
settings storage.ApplicationSettings
calls int
asOf time.Time
}
func (m *applicationSettingsTestModel) Get() (storage.ApplicationSettings, error) {
return m.settings, nil
}
func (m *applicationSettingsTestModel) Update(value storage.ApplicationSettings) (storage.ApplicationSettings, error) {
m.settings = value
return value, nil
}
func (m *applicationSettingsTestModel) RemoveExpiredPlants(asOf time.Time, _, _ int) (int, error) {
m.calls++
m.asOf = asOf
return 2, nil
}
func TestLifecycleMaintenanceHonorsEnabledSettingAndCutoff(t *testing.T) {
app, _, _ := newGardenTestApplication()
model := &applicationSettingsTestModel{settings: storage.ApplicationSettings{LifecycleStatusEnabled: true, LifecycleRemovalMonth: 12, LifecycleRemovalDay: 1, Timezone: "Europe/Berlin"}}
app.models.ApplicationSettings = model
if err := app.runLifecycleMaintenance(time.Date(2026, 11, 30, 12, 0, 0, 0, time.UTC)); err != nil {
t.Fatal(err)
}
if model.calls != 0 {
t.Fatalf("maintenance ran before cutoff")
}
if err := app.runLifecycleMaintenance(time.Date(2026, 12, 1, 12, 0, 0, 0, time.UTC)); err != nil {
t.Fatal(err)
}
if model.calls != 1 || model.asOf.Format("2006-01-02") != "2026-12-01" {
t.Fatalf("maintenance calls=%d cutoff=%v", model.calls, model.asOf)
}
}
func TestShowAdminEnvironmentMasksSecrets(t *testing.T) {
app, _, _ := newGardenTestApplication()
app.config = Config{
DB: DatabaseConfig{Dsn: "postgres://user:secret@db/gardomatic"},
Mail: mailer.Config{Password: "smtp-secret"},
}
response := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "/v1/admin/environment", nil)
app.showAdminEnvironmentHandler(response, request)
if response.Code != http.StatusOK {
t.Fatalf("status: got %d, want %d", response.Code, http.StatusOK)
}
body := response.Body.String()
if strings.Contains(body, "secret") {
t.Fatalf("environment response exposes a secret: %s", body)
}
var result struct {
Variables []environmentVariable `json:"variables"`
}
if err := json.Unmarshal(response.Body.Bytes(), &result); err != nil {
t.Fatal(err)
}
for _, name := range []string{"GARDOMATIC_DB_DSN", "GARDOMATIC_DEMO_ACCOUNT_EMAIL", "GARDOMATIC_SMTP_PASSWORD", "GARDOMATIC_SMTP_SENDER"} {
found := false
for _, variable := range result.Variables {
found = found || variable.Name == name
}
if !found {
t.Errorf("missing environment variable %s", name)
}
}
}
func TestSendAdminTestMailValidatesAndDelivers(t *testing.T) {
app, _, _ := newGardenTestApplication()
mailPath := filepath.Join(t.TempDir(), "mail.log")
configuredMailer, err := mailer.New(mailer.Config{Mode: mailer.ModeFile, Sender: "gardomatic@example.com", FilePath: mailPath})
if err != nil {
t.Fatal(err)
}
app.mailer = configuredMailer
invalidResponse := httptest.NewRecorder()
invalidRequest := httptest.NewRequest(http.MethodPost, "/v1/admin/test-mail", strings.NewReader(`{"email":"not-an-email"}`))
app.sendAdminTestMailHandler(invalidResponse, invalidRequest)
if invalidResponse.Code != http.StatusUnprocessableEntity {
t.Fatalf("invalid status: got %d, want %d; body: %s", invalidResponse.Code, http.StatusUnprocessableEntity, invalidResponse.Body.String())
}
response := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "/v1/admin/test-mail", strings.NewReader(`{"email":" Test@Example.com "}`))
app.sendAdminTestMailHandler(response, request)
if response.Code != http.StatusAccepted {
t.Fatalf("status: got %d, want %d; body: %s", response.Code, http.StatusAccepted, response.Body.String())
}
content, err := os.ReadFile(mailPath)
if err != nil {
t.Fatal(err)
}
for _, want := range []string{"test@example.com", "Gardomatic Testmail", "Gardomatic funktion"} {
if !strings.Contains(string(content), want) {
t.Errorf("test mail is missing %q: %s", want, content)
}
}
}