Skip to content
quarry Artifact [ed2d5bcdd1]
Login

Artifact [ed2d5bcdd1]

Artifact ed2d5bcdd166ee28f281b2b93b59a33558f5fa0c228cdf713a595b5d77f41724:


package api

import (
	"context"
	"errors"
	"fmt"
	"testing"

	"craftdesign.group/quarry/internal/ops"
	"craftdesign.group/quarry/internal/webhook"
)

// fakeHooks answers a fixed outcome, or a fixed error, so the op's own
// mapping is what is under test rather than a network.
type fakeHooks struct {
	out webhook.Outcome
	err error
}

func (f fakeHooks) Ping(string, int64) (webhook.Outcome, error)           { return f.out, f.err }
func (f fakeHooks) Deliveries(string, int64, int) ([]HookDelivery, error) { return nil, nil }
func (f fakeHooks) Redeliver(string, int64) (webhook.Outcome, error)      { return f.out, f.err }

func hookRegistry(t *testing.T, h fakeHooks) *ops.Registry {
	t.Helper()
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(nil), Hooks: h}); err != nil {
		t.Fatal(err)
	}
	return r
}

// An endpoint that does not answer is the RESULT of a ping, not a
// failure to ping. The delivery was sent and recorded; the op says so
// on a 200 and carries what the endpoint said.
//
// It used to be a 500 after the delivery row was written, so a client
// that trusted the status retried and double-delivered (b82291eb,
// G-102). The same shape for redeliver.
func TestAnUnreachableEndpointIsAnAnswerNotAnError(t *testing.T) {
	r := hookRegistry(t, fakeHooks{out: webhook.Outcome{Status: 0, Ms: 31, Err: "dial tcp: no such host"}})
	for _, op := range []string{"hook.ping", "hook.redeliver"} {
		got, err := r.Invoke(context.Background(), op, hookSendIn{Repo: "test", ID: 7})
		if err != nil {
			t.Fatalf("FAIL: %s answered an error for an endpoint that did not answer: %v", op, err)
		}
		out := got.(hookSendOut)
		if out.Sent || out.Status != 0 || out.Err == "" {
			t.Fatalf("FAIL: %s = %+v; want sent=false, status=0, the dial error carried", op, out)
		}
		t.Logf("ok: %s -> sent=%v status=%d err=%q", op, out.Sent, out.Status, out.Err)
	}
}

// An endpoint that answers 2xx is sent; one that answers anything
// else is not, and its status is the answer.
func TestTheEndpointsStatusIsTheAnswer(t *testing.T) {
	for _, c := range []struct {
		status int
		sent   bool
	}{{200, true}, {204, true}, {404, false}, {503, false}} {
		r := hookRegistry(t, fakeHooks{out: webhook.Outcome{Status: c.status, Ms: 5}})
		got, err := r.Invoke(context.Background(), "hook.ping", hookSendIn{Repo: "test", ID: 1})
		if err != nil {
			t.Fatal(err)
		}
		if out := got.(hookSendOut); out.Sent != c.sent || out.Status != c.status {
			t.Errorf("FAIL: endpoint %d -> %+v, want sent=%v", c.status, out, c.sent)
		}
	}
	t.Log("ok: 2xx is sent, anything else is reported as what it was")
}

// The one thing that IS an error: the forge could not attempt the send
// — no such endpoint or delivery. That is not found, by name, never a
// bare 500.
func TestAMissingEndpointIsNotFoundNotInternal(t *testing.T) {
	r := hookRegistry(t, fakeHooks{err: fmt.Errorf("no endpoint 9 on test: %w", ops.ErrNotFound)})
	_, err := r.Invoke(context.Background(), "hook.ping", hookSendIn{Repo: "test", ID: 9})
	if !errors.Is(err, ops.ErrNotFound) {
		t.Fatalf("FAIL: err = %v, want ErrNotFound", err)
	}
	t.Logf("ok: %v", err)
}