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