package config import ( "strings" "testing" "time" ) // clearAppriseEnv removes every Apprise variable so a load starts from the // defaults, not from the developer's environment. func clearAppriseEnv(t *testing.T) { t.Helper() for _, k := range []string{ "SCHOOL_APPRISE_URL", "SCHOOL_APPRISE_KEY", "SCHOOL_APPRISE_TOKEN", "SCHOOL_APPRISE_TIMEOUT", "SCHOOL_APPRISE_CHANNELS", "SCHOOL_APPRISE_TAGS", "SCHOOL_APPRISE_PARENTS", "SCHOOL_NOTIFY_TOKEN", } { t.Setenv(k, "") } } func TestLoadAppriseDefaults(t *testing.T) { clearAppriseEnv(t) cfg, err := loadApprise() if err != nil { t.Fatalf("load: %v", err) } if cfg.BaseURL != DefaultAppriseURL { t.Errorf("url = %q, want %q", cfg.BaseURL, DefaultAppriseURL) } if cfg.Enabled() { t.Error("Enabled = true without a key") } if got := strings.Join(cfg.Missing(), ","); got != "SCHOOL_APPRISE_KEY" { t.Errorf("missing = %q", got) } if cfg.Timeout != 15*time.Second { t.Errorf("timeout = %v", cfg.Timeout) } if len(cfg.Channels) != 2 || cfg.Channels[0] != ChannelEmail || cfg.Channels[1] != ChannelWhatsApp { t.Errorf("channels = %#v", cfg.Channels) } // The family recipients are the default, so a missing env value cannot // silently send the briefs nowhere. names := make([]string, 0, len(cfg.Recipients)) for _, r := range cfg.Recipients { names = append(names, r.Name+"="+r.TagBase) } if got := strings.Join(names, ","); got != DefaultAppriseTags { t.Errorf("recipients = %q, want %q", got, DefaultAppriseTags) } harry, ok := cfg.RecipientByName("harry") if !ok { t.Fatal("Harry is not configured") } if got := cfg.ChannelTag(harry, ChannelWhatsApp); got != "harry_whatsapp" { t.Errorf("whatsapp tag = %q", got) } if got := cfg.ChannelTag(harry, ChannelEmail); got != "harry_email" { t.Errorf("email tag = %q", got) } if got := cfg.ParentRecipients(); len(got) != 2 || got[0].Name != "Jo" || got[1].Name != "Sam" { t.Errorf("parents = %#v", got) } if cfg.IsParent("Finn") { t.Error("Finn is not a parent") } } func TestLoadAppriseCustom(t *testing.T) { clearAppriseEnv(t) t.Setenv("SCHOOL_APPRISE_URL", "http://example.test:9000/") t.Setenv("SCHOOL_APPRISE_KEY", "abc") t.Setenv("SCHOOL_APPRISE_TIMEOUT", "3s") t.Setenv("SCHOOL_APPRISE_CHANNELS", "email") t.Setenv("SCHOOL_APPRISE_TAGS", "Harry=h") t.Setenv("SCHOOL_APPRISE_PARENTS", "Harry") t.Setenv("SCHOOL_NOTIFY_TOKEN", "n8n-token") cfg, err := loadApprise() if err != nil { t.Fatalf("load: %v", err) } if !cfg.Enabled() { t.Error("Enabled = false with a key") } if cfg.BaseURL != "http://example.test:9000" { t.Errorf("url = %q", cfg.BaseURL) } if cfg.Timeout != 3*time.Second { t.Errorf("timeout = %v", cfg.Timeout) } if len(cfg.Channels) != 1 || cfg.Channels[0] != ChannelEmail { t.Errorf("channels = %#v", cfg.Channels) } if cfg.NotifyToken != "n8n-token" { t.Errorf("notify token = %q", cfg.NotifyToken) } } // TestLoadAppriseRejectsBadInput proves a typo fails at start-up instead of at // 6:00am, when nobody is watching (FR-12). func TestLoadAppriseRejectsBadInput(t *testing.T) { cases := map[string]func(t *testing.T){ "unknown channel": func(t *testing.T) { t.Setenv("SCHOOL_APPRISE_CHANNELS", "sms") }, "unknown parent": func(t *testing.T) { t.Setenv("SCHOOL_APPRISE_PARENTS", "Nana") }, "bad tag entry": func(t *testing.T) { t.Setenv("SCHOOL_APPRISE_TAGS", "Harry") }, } for name, set := range cases { t.Run(name, func(t *testing.T) { clearAppriseEnv(t) set(t) if _, err := loadApprise(); err == nil { t.Fatal("want an error") } }) } } func TestValidChannels(t *testing.T) { c := AppriseConfig{Channels: []string{ChannelEmail, "sms", ChannelWhatsApp}} ok, bad := c.ValidChannels() if len(ok) != 2 || len(bad) != 1 || bad[0] != "sms" { t.Errorf("ok = %#v bad = %#v", ok, bad) } }