Files

216 lines
5.0 KiB
Go

package main
import (
"strings"
"testing"
"time"
)
func clearEnv(t *testing.T) {
t.Helper()
for _, k := range []string{"ROUTINE_FIRE_URL", "ROUTINE_FIRE_TOKEN", "CRON_SCHEDULE", "TZ", "TEXT_TEMPLATE", "LOG_LEVEL"} {
t.Setenv(k, "")
}
}
func setBaseEnv(t *testing.T) {
t.Helper()
t.Setenv("ROUTINE_FIRE_URL", "https://example.test/fire")
t.Setenv("ROUTINE_FIRE_TOKEN", "tok")
t.Setenv("CRON_SCHEDULE", "0 9 * * *")
}
func TestLoad_Happy(t *testing.T) {
clearEnv(t)
setBaseEnv(t)
cfg, err := Load()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(cfg.Schedules) != 1 || cfg.Schedules[0] != "0 9 * * *" {
t.Fatalf("schedules = %v", cfg.Schedules)
}
if cfg.Location != time.UTC {
t.Fatalf("default tz should be UTC, got %v", cfg.Location)
}
if cfg.LogLevel != "info" {
t.Fatalf("default log level should be info, got %q", cfg.LogLevel)
}
}
func TestLoad_MultiCronSemicolon(t *testing.T) {
clearEnv(t)
setBaseEnv(t)
t.Setenv("CRON_SCHEDULE", "0 17 * * *;0 22 * * *")
cfg, err := Load()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(cfg.Schedules) != 2 {
t.Fatalf("want 2, got %d: %v", len(cfg.Schedules), cfg.Schedules)
}
}
func TestLoad_MultiCronNewline(t *testing.T) {
clearEnv(t)
setBaseEnv(t)
t.Setenv("CRON_SCHEDULE", "0 17 * * *\n0 22 * * *")
cfg, err := Load()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(cfg.Schedules) != 2 {
t.Fatalf("want 2, got %d: %v", len(cfg.Schedules), cfg.Schedules)
}
}
func TestLoad_MixedSeparatorsAndWhitespace(t *testing.T) {
clearEnv(t)
setBaseEnv(t)
t.Setenv("CRON_SCHEDULE", " 0 17 * * * ;\n 0 22 * * * ;;\n")
cfg, err := Load()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(cfg.Schedules) != 2 {
t.Fatalf("want 2 trimmed schedules, got %v", cfg.Schedules)
}
if cfg.Schedules[0] != "0 17 * * *" || cfg.Schedules[1] != "0 22 * * *" {
t.Fatalf("unexpected trimmed values: %v", cfg.Schedules)
}
}
func TestLoad_MissingFireURL(t *testing.T) {
clearEnv(t)
t.Setenv("ROUTINE_FIRE_TOKEN", "tok")
t.Setenv("CRON_SCHEDULE", "0 9 * * *")
_, err := Load()
if err == nil || !strings.Contains(err.Error(), "ROUTINE_FIRE_URL") {
t.Fatalf("want missing url error, got %v", err)
}
}
func TestLoad_MissingToken(t *testing.T) {
clearEnv(t)
t.Setenv("ROUTINE_FIRE_URL", "https://example.test/fire")
t.Setenv("CRON_SCHEDULE", "0 9 * * *")
_, err := Load()
if err == nil || !strings.Contains(err.Error(), "ROUTINE_FIRE_TOKEN") {
t.Fatalf("want missing token error, got %v", err)
}
}
func TestLoad_EmptyCron(t *testing.T) {
clearEnv(t)
t.Setenv("ROUTINE_FIRE_URL", "https://example.test/fire")
t.Setenv("ROUTINE_FIRE_TOKEN", "tok")
t.Setenv("CRON_SCHEDULE", "")
_, err := Load()
if err == nil || !strings.Contains(err.Error(), "CRON_SCHEDULE") {
t.Fatalf("want missing cron error, got %v", err)
}
}
func TestLoad_InvalidCron(t *testing.T) {
clearEnv(t)
setBaseEnv(t)
t.Setenv("CRON_SCHEDULE", "not-a-cron")
_, err := Load()
if err == nil || !strings.Contains(err.Error(), "invalid cron") {
t.Fatalf("want invalid cron error, got %v", err)
}
}
func TestLoad_InvalidTZ(t *testing.T) {
clearEnv(t)
setBaseEnv(t)
t.Setenv("TZ", "Atlantis/Lost")
_, err := Load()
if err == nil || !strings.Contains(err.Error(), "invalid TZ") {
t.Fatalf("want invalid TZ error, got %v", err)
}
}
func TestLoad_DefaultTemplate(t *testing.T) {
clearEnv(t)
setBaseEnv(t)
cfg, err := Load()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out, err := renderTemplate(cfg, time.Date(2026, 5, 8, 12, 0, 0, 0, time.UTC), "0 9 * * *")
if err != nil {
t.Fatalf("render: %v", err)
}
if !strings.HasPrefix(out, "Scheduled trigger at ") {
t.Fatalf("default template output unexpected: %q", out)
}
}
func TestLoad_CustomTemplate(t *testing.T) {
clearEnv(t)
setBaseEnv(t)
t.Setenv("TEXT_TEMPLATE", "hi {{.Cron}}")
cfg, err := Load()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out, err := renderTemplate(cfg, time.Now(), "0 9 * * *")
if err != nil {
t.Fatalf("render: %v", err)
}
if out != "hi 0 9 * * *" {
t.Fatalf("custom template output: %q", out)
}
}
func TestLoad_InvalidTemplate(t *testing.T) {
clearEnv(t)
setBaseEnv(t)
t.Setenv("TEXT_TEMPLATE", "{{.broken")
_, err := Load()
if err == nil || !strings.Contains(err.Error(), "invalid TEXT_TEMPLATE") {
t.Fatalf("want invalid template error, got %v", err)
}
}
func TestLoad_LogLevelDefault(t *testing.T) {
clearEnv(t)
setBaseEnv(t)
cfg, err := Load()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if cfg.LogLevel != "info" {
t.Fatalf("got %q", cfg.LogLevel)
}
}
// renderTemplate runs cfg.Template with the documented vars; mirror of
// FireClient.renderText for test isolation.
func renderTemplate(cfg *Config, now time.Time, expr string) (string, error) {
data := map[string]any{
"Now": now,
"LocalTime": now.In(cfg.Location).Format("2006-01-02 15:04 MST"),
"Cron": expr,
}
var sb strings.Builder
if err := cfg.Template.Execute(&sb, data); err != nil {
return "", err
}
return sb.String(), nil
}