Skip to content
quarry Artifact [b8aeb52486]
Login

Artifact [b8aeb52486]

Artifact b8aeb524860c7a39d9d9cba62a672c4258baf948d64d74c45cc7dd20e212fce4:


package notify

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"
)

// An endpoint already carrying the forge's URL and signing is left
// alone; nothing is created or rotated across restarts.
func TestEnsureReplyEndpointKeepsASignedOne(t *testing.T) {
	posts := 0
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method + " " + r.URL.Path {
		case "GET /v1/webhooks/endpoints":
			w.Write([]byte(`{"data":[{"id":"ep_7","url":"https://forge/api/v1/notify/events","active":true,"signed":true}]}`))
		default:
			posts++
			w.WriteHeader(http.StatusCreated)
		}
	}))
	t.Cleanup(srv.Close)
	ep, err := New(srv.URL, "k").EnsureReplyEndpoint(context.Background(), "https://forge/api/v1/notify/events")
	if err != nil || posts != 0 || ep.ID != "ep_7" || ep.Secret != "" {
		t.Fatalf("FAIL: posts=%d ep=%+v err=%v", posts, ep, err)
	}
	t.Log("ok: the signed endpoint is reused and no secret is shown")
}

// An endpoint that exists unsigned is given a secret, and that secret
// comes back for the forge to keep.
func TestEnsureReplyEndpointSignsAnUnsignedOne(t *testing.T) {
	var hit []string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hit = append(hit, r.Method+" "+r.URL.Path)
		switch r.Method + " " + r.URL.Path {
		case "GET /v1/webhooks/endpoints":
			w.Write([]byte(`{"data":[{"id":"ep_7","url":"https://forge/api/v1/notify/events","active":true,"signed":false}]}`))
		case "POST /v1/webhooks/endpoints/ep_7/secret":
			w.Write([]byte(`{"id":"ep_7","signed":true,"secret":"whsec_new"}`))
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	}))
	t.Cleanup(srv.Close)
	ep, err := New(srv.URL, "k").EnsureReplyEndpoint(context.Background(), "https://forge/api/v1/notify/events")
	if err != nil || ep.ID != "ep_7" || ep.Secret != "whsec_new" || !ep.Signed {
		t.Fatalf("FAIL: ep=%+v err=%v hits=%v", ep, err, hit)
	}
	t.Log("ok: the unsigned endpoint is rotated onto a secret")
}

// A forge that has none registers one, active, and keeps the secret
// notify shows once.
func TestEnsureReplyEndpointCreatesAndKeepsTheSecret(t *testing.T) {
	var got map[string]any
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method + " " + r.URL.Path {
		case "GET /v1/webhooks/endpoints":
			w.Write([]byte(`{"data":[]}`))
		case "POST /v1/webhooks/endpoints":
			b, _ := io.ReadAll(r.Body)
			json.Unmarshal(b, &got)
			w.WriteHeader(http.StatusCreated)
			w.Write([]byte(`{"id":"ep_1","url":"https://forge/api/v1/notify/events","active":true,"signed":true,"secret":"whsec_1"}`))
		}
	}))
	t.Cleanup(srv.Close)
	ep, err := New(srv.URL, "k").EnsureReplyEndpoint(context.Background(), "https://forge/api/v1/notify/events")
	if err != nil || ep.ID != "ep_1" || ep.Secret != "whsec_1" {
		t.Fatalf("FAIL: ep=%+v err=%v", ep, err)
	}
	if got["url"] != "https://forge/api/v1/notify/events" || got["active"] != true {
		t.Fatalf("FAIL: create request was %+v", got)
	}
	t.Log("ok: the endpoint is created active and its secret kept")
}

// Moving the endpoint to a new address deletes the old registration of
// the same route, and leaves unrelated endpoints alone.
func TestEnsureReplyEndpointRetiresTheOldAddress(t *testing.T) {
	var deleted []string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == "GET" && r.URL.Path == "/v1/webhooks/endpoints":
			w.Write([]byte(`{"data":[` +
				`{"id":"ep_old","url":"https://forge/api/v1/notify/events","active":true,"signed":true},` +
				`{"id":"ep_other","url":"https://elsewhere/hooks","active":true,"signed":true}]}`))
		case r.Method == "DELETE":
			deleted = append(deleted, r.URL.Path)
			w.WriteHeader(http.StatusNoContent)
		case r.Method == "POST" && r.URL.Path == "/v1/webhooks/endpoints":
			w.WriteHeader(http.StatusCreated)
			w.Write([]byte(`{"id":"ep_new","signed":true,"secret":"whsec_2"}`))
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	}))
	t.Cleanup(srv.Close)
	ep, err := New(srv.URL, "k").EnsureReplyEndpoint(context.Background(), "http://127.0.0.1:8950/api/v1/notify/events")
	if err != nil || ep.ID != "ep_new" || ep.Secret != "whsec_2" {
		t.Fatalf("FAIL: ep=%+v err=%v", ep, err)
	}
	if len(deleted) != 1 || deleted[0] != "/v1/webhooks/endpoints/ep_old" {
		t.Fatalf("FAIL: deleted %v, want only ep_old", deleted)
	}
	t.Log("ok: the old address is retired and the new endpoint keeps its secret")
}

// A disabled client registers nothing and says so quietly.
func TestEnsureReplyEndpointNilClient(t *testing.T) {
	var c *Client
	if ep, err := c.EnsureReplyEndpoint(context.Background(), "https://forge/x"); err != nil || ep.ID != "" {
		t.Fatalf("FAIL: %+v %v", ep, err)
	}
	t.Log("ok: no notify, no endpoint")
}