package job import ( "log/slog" "testing" "time" "code.chimeric.al/chimerical/odidere/internal/config" ) func TestConfigValidate(t *testing.T) { tests := []struct { name string config config.JobConfig wantErr bool }{ { name: "empty config", config: config.JobConfig{}, wantErr: true, }, { name: "missing name", config: config.JobConfig{ Schedule: "0 9 * * *", Task: "do something", }, wantErr: true, }, { name: "missing task", config: config.JobConfig{ Name: "test", Schedule: "0 9 * * *", }, wantErr: true, }, { name: "missing schedule", config: config.JobConfig{ Name: "test", Task: "do something", }, wantErr: true, }, { name: "negative max iterations", config: config.JobConfig{ Name: "test", Task: "do something", Schedule: "0 9 * * *", MaxIterations: -1, }, wantErr: true, }, { name: "minimal valid config", config: config.JobConfig{ Name: "test", Task: "do something", Schedule: "0 9 * * *", }, wantErr: false, }, { name: "full valid config", config: config.JobConfig{ Name: "test", Task: "do something", Schedule: "0 9 * * *", Timeout: "10m", MaxIterations: 20, SystemMessage: "You are an assistant.", }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.config.Validate() if (err != nil) != tt.wantErr { t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestConfigMaxIterations(t *testing.T) { // Zero means unlimited cfg := config.JobConfig{Name: "test"} if got := cfg.MaxIterations; got != 0 { t.Errorf("default max iterations = %d, want 0", got) } cfg.MaxIterations = 0 if got := cfg.MaxIterations; got != 0 { t.Errorf("explicit zero max iterations = %d, want 0", got) } // Positive value passes through cfg.MaxIterations = 25 if got := cfg.MaxIterations; got != 25 { t.Errorf("custom max iterations = %d, want 25", got) } } func TestConfigTimeout(t *testing.T) { cfg := config.JobConfig{Name: "test"} if cfg.Timeout != "" { t.Errorf("default timeout = %q, want empty", cfg.Timeout) } cfg.Timeout = "10m" got, err := time.ParseDuration(cfg.Timeout) if err != nil { t.Fatalf("ParseDuration(%q) error = %v", cfg.Timeout, err) } if got.Minutes() != 10 { t.Errorf("parsed timeout = %v, want 10m", got) } } func TestNew(t *testing.T) { cfg := config.JobConfig{ Name: "test", Task: "do something", Schedule: "0 9 * * *", Timeout: "10m", MaxIterations: 20, SystemMessage: "You are an assistant.", } j, err := New(cfg, slog.Default(), nil, "test-model") if err != nil { t.Fatalf("New() error = %v", err) } if j.Name() != "test" { t.Errorf("Name() = %q, want %q", j.Name(), "test") } if j.Schedule() != "0 9 * * *" { t.Errorf("Schedule() = %q, want %q", j.Schedule(), "0 9 * * *") } if j.SystemMessage() != "You are an assistant." { t.Errorf("SystemMessage() = %q, want %q", j.SystemMessage(), "You are an assistant.") } if j.Task() != "do something" { t.Errorf("Task() = %q, want %q", j.Task(), "do something") } if j.Timeout().Minutes() != 10 { t.Errorf("Timeout() = %v, want 10m", j.Timeout()) } if j.MaxIterations() != 20 { t.Errorf("MaxIterations() = %d, want 20", j.MaxIterations()) } if j.Model() != "test-model" { t.Errorf("Model() = %q, want %q", j.Model(), "test-model") } if j.Cron() == nil { t.Error("Cron() = nil, want non-nil") } } func TestNew_InvalidSchedule(t *testing.T) { cfg := config.JobConfig{ Name: "test", Task: "do something", Schedule: "not-a-cron", } _, err := New(cfg, slog.Default(), nil, "test-model") if err == nil { t.Fatal("New() expected error for invalid schedule") } } func TestNew_InvalidTimeout(t *testing.T) { cfg := config.JobConfig{ Name: "test", Task: "do something", Schedule: "0 9 * * *", Timeout: "not-a-duration", } _, err := New(cfg, slog.Default(), nil, "test-model") if err == nil { t.Fatal("New() expected error for invalid timeout") } }