package git import ( "context" "encoding/json" "testing" "github.com/alexei/tinyforge/internal/workload/plugin" ) func mustConfig(t *testing.T, c Config) json.RawMessage { t.Helper() b, err := json.Marshal(c) if err != nil { t.Fatalf("marshal config: %v", err) } return b } func TestValidate(t *testing.T) { tr := &trigger{} cases := []struct { name string cfg json.RawMessage wantErr bool }{ {"empty body rejected", nil, true}, {"missing mode rejected", mustConfig(t, Config{Repo: "owner/repo"}), true}, {"push mode valid", mustConfig(t, Config{Repo: "owner/repo", Mode: "push", Branch: "main"}), false}, {"push mode without branch (any-branch)", mustConfig(t, Config{Repo: "owner/repo", Mode: "push"}), false}, {"tag mode valid", mustConfig(t, Config{Repo: "owner/repo", Mode: "tag", TagPattern: "v*"}), false}, {"tag mode no pattern (wildcard fallback)", mustConfig(t, Config{Repo: "owner/repo", Mode: "tag"}), false}, {"tag mode bad glob", mustConfig(t, Config{Repo: "owner/repo", Mode: "tag", TagPattern: "v[oops"}), true}, {"unknown mode", mustConfig(t, Config{Repo: "owner/repo", Mode: "merge"}), true}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { err := tr.Validate(tc.cfg) if (err != nil) != tc.wantErr { t.Fatalf("Validate(%s) err=%v want err=%v", tc.name, err, tc.wantErr) } }) } } func TestRefMatches(t *testing.T) { cases := []struct { name string cfg Config ref string want bool }{ {"push main matches", Config{Mode: "push", Branch: "main"}, "refs/heads/main", true}, {"push main rejects other branch", Config{Mode: "push", Branch: "main"}, "refs/heads/dev", false}, {"push tag is rejected in push mode", Config{Mode: "push", Branch: "main"}, "refs/tags/v1.0.0", false}, {"push any-branch", Config{Mode: "push"}, "refs/heads/whatever", true}, {"tag mode v* matches v1.2.3", Config{Mode: "tag", TagPattern: "v*"}, "refs/tags/v1.2.3", true}, {"tag mode v* rejects latest", Config{Mode: "tag", TagPattern: "v*"}, "refs/tags/latest", false}, {"tag mode rejects heads ref", Config{Mode: "tag", TagPattern: "v*"}, "refs/heads/main", false}, {"tag mode empty pattern matches any tag", Config{Mode: "tag"}, "refs/tags/whatever", true}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if got := refMatches(tc.cfg, tc.ref); got != tc.want { t.Errorf("refMatches(%+v, %q) = %v, want %v", tc.cfg, tc.ref, got, tc.want) } }) } } func TestMatch(t *testing.T) { tr := &trigger{} wl := plugin.Workload{ ID: "wkl-1", TriggerConfig: mustConfig(t, Config{Repo: "Owner/Repo", Mode: "push", Branch: "main"}), } t.Run("wrong event kind", func(t *testing.T) { evt := plugin.InboundEvent{Kind: "image-push"} intent, err := tr.Match(context.Background(), plugin.Deps{}, wl, evt) if err != nil || intent != nil { t.Fatalf("expected nil intent, got intent=%v err=%v", intent, err) } }) t.Run("matching push fires intent with sha", func(t *testing.T) { // Branch is populated by the webhook ingress alongside Ref; the // trigger reads either independently. Set both here to mirror the // real wire shape. evt := plugin.InboundEvent{ Kind: "git-push", Git: &plugin.GitEvent{ Repo: "owner/repo", Ref: "refs/heads/main", Branch: "main", CommitSHA: "deadbeef", Pusher: "alice", }, } intent, err := tr.Match(context.Background(), plugin.Deps{}, wl, evt) if err != nil { t.Fatalf("unexpected error: %v", err) } if intent == nil { t.Fatal("expected non-nil intent") } if intent.Reference != "deadbeef" { t.Errorf("intent.Reference = %q, want deadbeef", intent.Reference) } if intent.Reason != "git-push" { t.Errorf("intent.Reason = %q, want git-push", intent.Reason) } if intent.Metadata["branch"] != "main" { t.Errorf("expected branch=main in metadata, got %q", intent.Metadata["branch"]) } }) t.Run("repo case-insensitive comparison", func(t *testing.T) { evt := plugin.InboundEvent{ Kind: "git-push", Git: &plugin.GitEvent{Repo: "OWNER/REPO", Ref: "refs/heads/main"}, } intent, err := tr.Match(context.Background(), plugin.Deps{}, wl, evt) if err != nil { t.Fatalf("unexpected error: %v", err) } if intent == nil { t.Fatal("expected case-insensitive repo match") } }) t.Run("wrong repo returns nil", func(t *testing.T) { evt := plugin.InboundEvent{ Kind: "git-push", Git: &plugin.GitEvent{Repo: "other/repo", Ref: "refs/heads/main"}, } intent, err := tr.Match(context.Background(), plugin.Deps{}, wl, evt) if err != nil || intent != nil { t.Fatalf("expected nil intent, got intent=%v err=%v", intent, err) } }) }