Files
tiny-forge/internal/webhook/inbound_event_test.go
alexei.dolgolyov 82d32181ba feat(webhook): vendor-specific event parsing (Gitea / GitHub / GitLab)
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>
2026-05-11 22:17:53 +03:00

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")
}
}