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