package trigger import ( "encoding/json" "testing" ) func TestPushEventJSONAndValidation(t *testing.T) { data := []byte(`{"kind":"push","repo":"example","old":"0000000000000000000000000000000000000000","new":"abc123","ref":"refs/heads/main"}`) event, err := Decode(data) if err != nil { t.Fatalf("Decode() error = %v", err) } push, ok := event.(PushEvent) if !ok { t.Fatalf("event type = %T", event) } if push.Repo != "example" || push.Ref != "refs/heads/main" || push.New != "abc123" { t.Fatalf("push = %#v", push) } if err := push.Validate(); err != nil { t.Fatalf("Validate() error = %v", err) } } func TestPushEventValidationFailures(t *testing.T) { if (PushEvent{Kind: "manual", Repo: "repo", New: "abc", Ref: "refs/heads/main"}).EventKind() != "push" { t.Fatal("push event kind mismatch") } cases := []PushEvent{ {Kind: "manual", Repo: "repo", New: "abc", Ref: "refs/heads/main"}, {Kind: "push", New: "abc", Ref: "refs/heads/main"}, {Kind: "push", Repo: "repo", Ref: "refs/heads/main"}, {Kind: "push", Repo: "repo", New: "abc"}, } for _, event := range cases { if err := event.Validate(); err == nil { t.Fatalf("invalid push accepted: %#v", event) } } } func TestScheduleEventJSONAndValidation(t *testing.T) { data := []byte(`{"kind":"schedule","repo":"example","job":"nightly","rev":"abc123","ref":"refs/heads/main","minute":"2026-03-04T03:00Z"}`) event, err := Decode(data) if err != nil { t.Fatal(err) } scheduled, ok := event.(ScheduleEvent) if !ok || scheduled.EventKind() != "schedule" || scheduled.Job != "nightly" { t.Fatalf("event=%#v", event) } invalid := scheduled invalid.Minute = "yesterday" if err := invalid.Validate(); err == nil { t.Fatal("invalid minute accepted") } } func TestManualEventRequiresExactlyOneRefOrRev(t *testing.T) { ref := "main" rev := "abc123" valid := ManualEvent{Kind: "manual", Repo: "example", Job: "check", Ref: &ref, RequestedBy: "oli"} if err := valid.Validate(); err != nil { t.Fatalf("valid manual rejected: %v", err) } both := ManualEvent{Kind: "manual", Repo: "example", Job: "check", Ref: &ref, Rev: &rev} if err := both.Validate(); err == nil { t.Fatal("manual with ref and rev accepted") } neither := ManualEvent{Kind: "manual", Repo: "example", Job: "check"} if err := neither.Validate(); err == nil { t.Fatal("manual without ref/rev accepted") } } func TestManualEventValidationFailures(t *testing.T) { ref := "main" if (ManualEvent{Kind: "manual", Repo: "repo", Job: "job", Ref: &ref}).EventKind() != "manual" { t.Fatal("manual event kind mismatch") } cases := []ManualEvent{ {Kind: "push", Repo: "repo", Job: "job", Ref: &ref}, {Kind: "manual", Job: "job", Ref: &ref}, {Kind: "manual", Repo: "repo", Ref: &ref}, } for _, event := range cases { if err := event.Validate(); err == nil { t.Fatalf("invalid manual accepted: %#v", event) } } } func TestDecodeRejectsUnknownKind(t *testing.T) { _, err := Decode([]byte(`{"kind":"timer","repo":"example"}`)) if err == nil { t.Fatal("unknown kind accepted") } if got := err.Error(); got != "unknown trigger kind \"timer\"" { t.Fatalf("unexpected error: %s", got) } } func TestDecodeRejectsMalformedJSONAndInvalidEvents(t *testing.T) { cases := [][]byte{ []byte(`{`), []byte(`{"kind":"push","repo":[]}`), []byte(`{"kind":"manual","repo":[]}`), []byte(`{"kind":"schedule","repo":[]}`), []byte(`{"kind":"push","repo":"repo"}`), []byte(`{"kind":"manual","repo":"repo","job":"job"}`), } for _, data := range cases { if _, err := Decode(data); err == nil { t.Fatalf("invalid JSON/event accepted: %s", data) } } } func TestManualEventEncodesNullRev(t *testing.T) { ref := "main" event := ManualEvent{Kind: "manual", Repo: "example", Job: "check", Ref: &ref, RequestedBy: "oli"} data, err := json.Marshal(event) if err != nil { t.Fatalf("Marshal() error = %v", err) } want := `{"kind":"manual","repo":"example","job":"check","ref":"main","rev":null,"requested_by":"oli"}` if string(data) != want { t.Fatalf("json = %s", data) } }