82d32181ba
The /api/webhook/workloads/{secret} ingress now short-circuits on a
recognized X-*-Event header before falling back to the generic
simple-body parser. Vendor parsers populate fields the generic
parser cannot (image digest, GitEvent.Vendor, registry host).
internal/webhook/vendor_parsers.go covers:
- Gitea package events (X-Gitea-Event: package, container type)
- GitHub registry_package + package events (CONTAINER package_type)
- GitHub / Gitea push events with vendor stamping
- GitLab Push Hook + Tag Push Hook with path_with_namespace mapping
When a vendor parser claims a request (ok=true), it's authoritative
— a malformed Gitea package payload surfaces as an error rather
than silently re-parsing as generic. The generic {image} /
{ref + repository.full_name} fallback stays in place for legacy
CIs that send those shapes.
Coverage: internal/webhook/vendor_parsers_test.go +
inbound_event_test.go (round-trip through buildInboundEvent).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
99 lines
2.9 KiB
Go
99 lines
2.9 KiB
Go
package webhook
|
|
|
|
import (
|
|
"net/http"
|
|
"testing"
|
|
)
|
|
|
|
func TestBuildInboundEventEmpty(t *testing.T) {
|
|
evt, err := buildInboundEvent(nil, http.Header{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
if evt.Kind != "manual" {
|
|
t.Errorf("empty body should be manual, got %q", evt.Kind)
|
|
}
|
|
if evt.Manual == nil || evt.Manual.Actor != "webhook" {
|
|
t.Errorf("expected manual.Actor=webhook, got %+v", evt.Manual)
|
|
}
|
|
}
|
|
|
|
func TestBuildInboundEventImagePush(t *testing.T) {
|
|
body := []byte(`{"image":"registry.example.com/owner/app:v1.2.3"}`)
|
|
evt, err := buildInboundEvent(body, http.Header{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
if evt.Kind != "image-push" || evt.Image == nil {
|
|
t.Fatalf("expected image-push, got kind=%q image=%+v", evt.Kind, evt.Image)
|
|
}
|
|
if evt.Image.Registry != "registry.example.com" {
|
|
t.Errorf("Registry=%q want registry.example.com", evt.Image.Registry)
|
|
}
|
|
if evt.Image.Repo != "owner/app" {
|
|
t.Errorf("Repo=%q want owner/app", evt.Image.Repo)
|
|
}
|
|
if evt.Image.Tag != "v1.2.3" {
|
|
t.Errorf("Tag=%q want v1.2.3", evt.Image.Tag)
|
|
}
|
|
}
|
|
|
|
func TestBuildInboundEventGitPush(t *testing.T) {
|
|
body := []byte(`{
|
|
"ref":"refs/heads/main",
|
|
"after":"abc123",
|
|
"repository":{"full_name":"owner/repo","clone_url":"https://example.com/owner/repo.git"},
|
|
"pusher":{"name":"alice"}
|
|
}`)
|
|
evt, err := buildInboundEvent(body, http.Header{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
if evt.Kind != "git-push" || evt.Git == nil {
|
|
t.Fatalf("expected git-push, got kind=%q git=%+v", evt.Kind, evt.Git)
|
|
}
|
|
if evt.Git.Repo != "owner/repo" {
|
|
t.Errorf("Repo=%q want owner/repo", evt.Git.Repo)
|
|
}
|
|
if evt.Git.Branch != "main" {
|
|
t.Errorf("Branch=%q want main", evt.Git.Branch)
|
|
}
|
|
if evt.Git.CommitSHA != "abc123" {
|
|
t.Errorf("CommitSHA=%q want abc123", evt.Git.CommitSHA)
|
|
}
|
|
if evt.Git.Pusher != "alice" {
|
|
t.Errorf("Pusher=%q want alice", evt.Git.Pusher)
|
|
}
|
|
}
|
|
|
|
func TestBuildInboundEventGitTag(t *testing.T) {
|
|
body := []byte(`{"ref":"refs/tags/v2.0.0","after":"deadbeef"}`)
|
|
evt, err := buildInboundEvent(body, http.Header{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
if evt.Kind != "git-tag" {
|
|
t.Errorf("kind=%q want git-tag", evt.Kind)
|
|
}
|
|
if evt.Git == nil || evt.Git.Tag != "v2.0.0" {
|
|
t.Errorf("expected tag=v2.0.0, got %+v", evt.Git)
|
|
}
|
|
if evt.Git.Branch != "" {
|
|
t.Errorf("tag event should not have Branch, got %q", evt.Git.Branch)
|
|
}
|
|
}
|
|
|
|
func TestBuildInboundEventInvalidJSON(t *testing.T) {
|
|
if _, err := buildInboundEvent([]byte(`not json`), http.Header{}); err == nil {
|
|
t.Fatal("expected error on bad JSON")
|
|
}
|
|
}
|
|
|
|
func TestBuildInboundEventMissingFields(t *testing.T) {
|
|
// Body present but neither image nor ref — caller must get a useful
|
|
// error pointing at the missing field rather than a silent default.
|
|
if _, err := buildInboundEvent([]byte(`{"foo":"bar"}`), http.Header{}); err == nil {
|
|
t.Fatal("expected error on payload without image or ref")
|
|
}
|
|
}
|