gito/services/core/internal/provider/provider_test.go
toki f3bbb4e579 feat: webhook adapter wiring - router/runtime/provider refactor
- Move old task files to archive
- Update router.go with new adapter wiring
- Update runtime.go with provider integration
- Refactor provider.go with new abstraction
- Update provider tests
2026-06-19 19:45:53 +09:00

297 lines
9 KiB
Go

package provider
import (
"context"
"testing"
"time"
"git.toki-labs.com/toki/gito/services/core/internal/core"
)
func TestConfigNormalizedTrimsIdentityFields(t *testing.T) {
config := Config{
ID: ProviderID(" GitHub "),
Endpoint: " https://api.github.com ",
CredentialRef: " secret://gito/providers/github ",
Capabilities: CapabilityChangeRequest | CapabilityWebhook,
}
got := config.Normalized()
if got.ID != ProviderGitHub {
t.Fatalf("ID: got %q want %q", got.ID, ProviderGitHub)
}
if got.Endpoint != "https://api.github.com" {
t.Fatalf("Endpoint: got %q", got.Endpoint)
}
if got.CredentialRef != "secret://gito/providers/github" {
t.Fatalf("CredentialRef: got %q", got.CredentialRef)
}
}
func TestCapabilityFlags(t *testing.T) {
capabilities := CapabilityChangeRequest | CapabilityWebhook | CapabilityComment
if !capabilities.Has(CapabilityChangeRequest) {
t.Fatal("expected change request capability")
}
if !capabilities.Has(CapabilityWebhook) {
t.Fatal("expected webhook capability")
}
if !capabilities.Has(CapabilityComment) {
t.Fatal("expected comment capability")
}
if capabilities.Has(CapabilityChecks) {
t.Fatal("did not expect checks capability")
}
if capabilities.Has(0) {
t.Fatal("zero capability should not be supported")
}
}
func TestConfigSupportsCapability(t *testing.T) {
config := Config{
ID: ProviderForgejo,
Capabilities: CapabilityWebhook | CapabilityChecks,
}
if !config.Supports(CapabilityWebhook) {
t.Fatal("expected webhook support")
}
if !config.Supports(CapabilityChecks) {
t.Fatal("expected checks support")
}
if config.Supports(CapabilityChangeRequest) {
t.Fatal("did not expect change request support")
}
}
// --- Fake adapter for testing ---
// fakeWebhookAdapter is a test implementation of WebhookAdapter.
type fakeWebhookAdapter struct {
verified bool
verifyErr error
candidates []WebhookCandidate
normalizeErr error
// Recorded requests for verifier and normalizer.
verifyReq WebhookRequest
normalizeReq WebhookRequest
}
func (f *fakeWebhookAdapter) VerifyWebhook(ctx context.Context, req WebhookRequest) error {
if f.verifyErr != nil {
return f.verifyErr
}
f.verified = true
f.verifyReq = req
return nil
}
func (f *fakeWebhookAdapter) NormalizeWebhook(ctx context.Context, req WebhookRequest) ([]WebhookCandidate, error) {
if f.normalizeErr != nil {
return nil, f.normalizeErr
}
f.normalizeReq = req
return f.candidates, nil
}
// verifyWebhookAdapter ensures WebhookAdapter compile-time assertion.
var _ WebhookAdapter = (*fakeWebhookAdapter)(nil)
func TestWebhookAdapterContractCompileTime(t *testing.T) {
// fakeWebhookAdapter is already assigned to var _ WebhookAdapter.
// This test passes if the file compiles.
}
func TestWebhookAdapterContractRequestDelivery(t *testing.T) {
revision := &core.RevisionEvent{
RepoID: "test-repo",
Branch: "main",
Before: "abc123",
After: "def456",
ObservedAt: time.Now().UTC(),
}
now := time.Now().UTC()
req := WebhookRequest{
Provider: ProviderForgejo,
EventType: "push",
ExternalID: "delivery-123",
Payload: []byte(`{"ref":"refs/heads/main"}`),
Headers: map[string][]string{
"X-Forgejo-Signature": {"sha256=abc"},
},
Query: map[string][]string{
"repo_id": {"test-repo"},
},
ReceivedAt: now,
}
fake := &fakeWebhookAdapter{
candidates: []WebhookCandidate{
{
Type: "branch.updated",
Provider: ProviderForgejo,
ExternalID: "delivery-123",
Revision: revision,
},
},
}
ctx := context.Background()
// Verify delivery
if err := fake.VerifyWebhook(ctx, req); err != nil {
t.Fatalf("VerifyWebhook: %v", err)
}
if !fake.verified {
t.Fatal("expected verified to be true")
}
// Normalize delivery
candidates, err := fake.NormalizeWebhook(ctx, req)
if err != nil {
t.Fatalf("NormalizeWebhook: %v", err)
}
if len(candidates) != 1 {
t.Fatalf("expected 1 candidate, got %d", len(candidates))
}
c := candidates[0]
if c.Type != "branch.updated" {
t.Fatalf("Type: got %q want %q", c.Type, "branch.updated")
}
if c.Provider != ProviderForgejo {
t.Fatalf("Provider: got %q want %q", c.Provider, ProviderForgejo)
}
if c.ExternalID != "delivery-123" {
t.Fatalf("ExternalID: got %q want %q", c.ExternalID, "delivery-123")
}
if c.Revision == nil {
t.Fatal("expected Revision to be set")
}
if c.Revision.RepoID != "test-repo" {
t.Fatalf("Revision.RepoID: got %q want %q", c.Revision.RepoID, "test-repo")
}
if c.Revision.Branch != "main" {
t.Fatalf("Revision.Branch: got %q want %q", c.Revision.Branch, "main")
}
// Verify that the request was actually delivered to the verifier.
if fake.verifyReq.Provider == "" {
t.Fatal("expected verifyReq.Provider to be set")
}
if fake.verifyReq.Provider != ProviderForgejo {
t.Fatalf("verifyReq.Provider: got %q want %q", fake.verifyReq.Provider, ProviderForgejo)
}
if fake.verifyReq.EventType != "push" {
t.Fatalf("verifyReq.EventType: got %q want %q", fake.verifyReq.EventType, "push")
}
if fake.verifyReq.ExternalID != "delivery-123" {
t.Fatalf("verifyReq.ExternalID: got %q want %q", fake.verifyReq.ExternalID, "delivery-123")
}
if string(fake.verifyReq.Payload) != `{"ref":"refs/heads/main"}` {
t.Fatalf("verifyReq.Payload: got %q", fake.verifyReq.Payload)
}
if len(fake.verifyReq.Headers) != 1 {
t.Fatalf("verifyReq.Headers: got %d entries, want 1", len(fake.verifyReq.Headers))
}
if sigs, ok := fake.verifyReq.Headers["X-Forgejo-Signature"]; !ok {
t.Fatal("verifyReq.Headers['X-Forgejo-Signature'] not found")
} else if len(sigs) != 1 || sigs[0] != "sha256=abc" {
t.Fatalf("verifyReq.Headers['X-Forgejo-Signature']: got %v, want [sha256=abc]", sigs)
}
if len(fake.verifyReq.Query) != 1 {
t.Fatalf("verifyReq.Query: got %d entries, want 1", len(fake.verifyReq.Query))
}
if repos, ok := fake.verifyReq.Query["repo_id"]; !ok {
t.Fatal("verifyReq.Query['repo_id'] not found")
} else if len(repos) != 1 || repos[0] != "test-repo" {
t.Fatalf("verifyReq.Query['repo_id']: got %v, want [test-repo]", repos)
}
if !fake.verifyReq.ReceivedAt.Equal(now) {
t.Fatalf("verifyReq.ReceivedAt: got %v, want %v", fake.verifyReq.ReceivedAt, now)
}
// Verify that the request was actually delivered to the normalizer.
if fake.normalizeReq.Provider != ProviderForgejo {
t.Fatalf("normalizeReq.Provider: got %q want %q", fake.normalizeReq.Provider, ProviderForgejo)
}
if fake.normalizeReq.EventType != "push" {
t.Fatalf("normalizeReq.EventType: got %q want %q", fake.normalizeReq.EventType, "push")
}
if fake.normalizeReq.ExternalID != "delivery-123" {
t.Fatalf("normalizeReq.ExternalID: got %q want %q", fake.normalizeReq.ExternalID, "delivery-123")
}
if string(fake.normalizeReq.Payload) != `{"ref":"refs/heads/main"}` {
t.Fatalf("normalizeReq.Payload: got %q", fake.normalizeReq.Payload)
}
if len(fake.normalizeReq.Headers) != 1 {
t.Fatalf("normalizeReq.Headers: got %d entries, want 1", len(fake.normalizeReq.Headers))
}
if sigs, ok := fake.normalizeReq.Headers["X-Forgejo-Signature"]; !ok {
t.Fatal("normalizeReq.Headers['X-Forgejo-Signature'] not found")
} else if len(sigs) != 1 || sigs[0] != "sha256=abc" {
t.Fatalf("normalizeReq.Headers['X-Forgejo-Signature']: got %v, want [sha256=abc]", sigs)
}
if len(fake.normalizeReq.Query) != 1 {
t.Fatalf("normalizeReq.Query: got %d entries, want 1", len(fake.normalizeReq.Query))
}
if repos, ok := fake.normalizeReq.Query["repo_id"]; !ok {
t.Fatal("normalizeReq.Query['repo_id'] not found")
} else if len(repos) != 1 || repos[0] != "test-repo" {
t.Fatalf("normalizeReq.Query['repo_id']: got %v, want [test-repo]", repos)
}
if !fake.normalizeReq.ReceivedAt.Equal(now) {
t.Fatalf("normalizeReq.ReceivedAt: got %v, want %v", fake.normalizeReq.ReceivedAt, now)
}
}
func TestWebhookRequestEmptyPayloadPreservation(t *testing.T) {
req := WebhookRequest{
Provider: ProviderGitHub,
EventType: "pull_request",
ExternalID: "",
Payload: []byte{},
Headers: nil,
Query: nil,
ReceivedAt: time.Time{},
}
if len(req.Payload) != 0 {
t.Fatalf("expected empty payload, got %d bytes", len(req.Payload))
}
if req.ExternalID != "" {
t.Fatalf("expected empty ExternalID, got %q", req.ExternalID)
}
if !req.ReceivedAt.IsZero() {
t.Fatalf("expected zero ReceivedAt")
}
}
func TestWebhookCandidateNoSecretRequirement(t *testing.T) {
// WebhookCandidate must not expose any secret-related fields.
// This is verified by the struct definition itself:
// - Type string
// - Provider ProviderID
// - ExternalID string
// - Revision *core.RevisionEvent
// No secret, signature, or token fields.
c := WebhookCandidate{
Type: "branch.updated",
Provider: ProviderForgejo,
ExternalID: "delivery-1",
}
// Verify the struct has no secret-related fields by construction.
// If any secret field were added, compilation would still pass,
// but the test ensures the current minimal metadata contract.
if c.Type == "" {
t.Fatal("Type should be settable")
}
if c.Provider == "" {
t.Fatal("Provider should be settable")
}
}