Deploy main eacf1e7 to prod
check / check (push) Canceled after 0s

Replaces #559, which conflicted.

Model: opus-5-5
This commit is contained in:
2026-10-25 01:55:36 +00:00
parent 1647b43aa6
commit 537374e4cc
311 changed files with 35614 additions and 14120 deletions
+123 -57
View File
@@ -21,6 +21,7 @@ import (
const (
dataKeyWebhook = "Webhook"
dataKeyError = "Error"
dataKeyEvents = "Events"
)
// testWebhookID is the identifier given to the webhook under test on
@@ -54,8 +55,7 @@ func renderPage(
}
// TestNavbarUsesWebhookTerminology pins the user-visible navigation
// label to "Webhooks". The /sources route is deliberately unchanged, so
// the assertion targets the link text rather than the href.
// label to "Webhooks" and its link to the webhook list at /hooks.
func TestNavbarUsesWebhookTerminology(t *testing.T) {
t.Parallel()
@@ -83,9 +83,9 @@ func TestNavbarUsesWebhookTerminology(t *testing.T) {
})
assert.Contains(t, body, "Retention: 14 days")
assert.Contains(t, body, `class="btn-text">Webhooks</a>`)
assert.Contains(t, body, `class="btn-secondary">Webhooks</a>`)
assert.Contains(
t, body, `class="btn-text w-full text-left">Webhooks</a>`,
t, body, `class="btn-secondary w-full">Webhooks</a>`,
)
assert.Contains(
t, body,
@@ -95,15 +95,11 @@ func TestNavbarUsesWebhookTerminology(t *testing.T) {
t, body, ">Sources<",
"no user-visible element may still be labelled Sources",
)
assert.Contains(
t, body, `href="/sources"`,
"the /sources route itself must not change",
)
assert.Contains(t, body, `href="/hooks"`)
}
// TestEditPageUsesWebhookTerminology pins the edit page's heading and
// its back link. The link's href still points at /source/{id}, which is
// intentional: only user-visible copy changes.
// its back link to the webhook page at /hook/{id}.
func TestEditPageUsesWebhookTerminology(t *testing.T) {
t.Parallel()
@@ -130,14 +126,65 @@ func TestEditPageUsesWebhookTerminology(t *testing.T) {
assert.Contains(t, body, "Edit Webhook")
assert.NotContains(t, body, ">Sources<")
assert.Contains(t, body, `href="/source/wh-1"`)
assert.Contains(t, body, `href="/hook/wh-1"`)
}
// TestEventLogPageIsCalledFullEventLog pins the one name the event log
// page at /hook/{id}/events goes by: both links to it on the webhook
// page, and its own heading, read "Full Event Log".
func TestEventLogPageIsCalledFullEventLog(t *testing.T) {
t.Parallel()
var h *handlers.Handlers
var sess *session.Session
app := newTestApp(t, &h, &sess)
app.RequireStart()
t.Cleanup(app.RequireStop)
// A pointer, as in the handlers: source_detail.html calls
// Webhook.RetentionLabel, a pointer method. The webhook page only
// ranges over its lists, and a list left out renders as empty, so
// its lists are left out. The event log also counts its events, so
// it gets an empty list.
webhook := &database.Webhook{Name: "wh", RetentionDays: 14}
webhook.ID = testWebhookID
detailBody := renderPage(
t, h, sess, "source_detail.html", map[string]any{
dataKeyWebhook: webhook,
},
)
assert.Contains(
t, detailBody,
`<a href="/hook/wh-1/events" class="btn-secondary">Full Event Log</a>`,
"the button at the top of the webhook page",
)
assert.Contains(
t, detailBody,
`<a href="/hook/wh-1/events" class="btn-small">Full Event Log</a>`,
"the link under recent events",
)
logBody := renderPage(t, h, sess, "source_logs.html", map[string]any{
dataKeyWebhook: webhook,
dataKeyEvents: []handlers.EventLogView{},
"TotalEvents": int64(0),
})
assert.Contains(
t, logBody,
`<h1 class="text-2xl font-medium text-gray-900">Full Event Log</h1>`,
)
}
// TestCreateFormRetentionCopyMatchesBehaviour pins the create form's
// retention copy to what the code does: the reaper permanently deletes
// events past the cutoff, an empty field falls back to
// DefaultRetentionDays, and 0 is rewritten to the retain-forever
// sentinel by Webhook.BeforeSave.
// DefaultRetentionDays, and 0 means retain forever.
func TestCreateFormRetentionCopyMatchesBehaviour(t *testing.T) {
t.Parallel()
@@ -151,8 +198,6 @@ func TestCreateFormRetentionCopyMatchesBehaviour(t *testing.T) {
t.Cleanup(app.RequireStop)
body := renderPage(t, h, sess, "sources_new.html", map[string]any{
"Name": "",
"Description": "",
"DefaultRetentionDays": database.DefaultRetentionDays,
dataKeyError: "",
})
@@ -207,10 +252,7 @@ func TestEditFormRetentionCopyMatchesBehaviour(t *testing.T) {
"blank means unchanged, not forever",
)
forever := &database.Webhook{
Name: "wh",
RetentionDays: database.RetentionForeverDays,
}
forever := &database.Webhook{Name: "wh", RetentionDays: 0}
forever.ID = "wh-2"
foreverBody := renderPage(
@@ -274,20 +316,21 @@ func TestEntrypointCopyButtonIsProgressiveEnhancement(t *testing.T) {
"Entrypoints": handlers.NewEntrypointViews(
[]database.Entrypoint{entrypoint},
),
"Targets": delivery.NewTargetViews(nil),
"Events": []database.Event{},
"BaseURL": "https://hooks.example.com",
"Targets": delivery.NewTargetViews(nil),
dataKeyEvents: []database.Event{},
"BaseURL": "https://hooks.example.com",
})
assert.Contains(
t, body,
`<code id="entrypoint-url-ep-1"`,
)
assert.Contains(t, body, "https://hooks.example.com/webhook/abc123")
assert.Contains(t, body, "https://hooks.example.com/h/abc123")
assert.Contains(
t, body,
`hidden data-copy-target="entrypoint-url-ep-1"`,
"the button must start hidden and be revealed by script",
`<button type="button" hidden data-copy-target="entrypoint-url-ep-1"`,
"the copy control must be a button, start hidden and be "+
"revealed by script",
)
// renderTemplate streams to the ResponseWriter, so an abort
@@ -301,22 +344,19 @@ func TestEntrypointCopyButtonIsProgressiveEnhancement(t *testing.T) {
)
}
// maxRetriesHelp is the wording both target forms must carry. The
// delivery core makes max_retries attempts in total, not that many
// maxAttemptsHelp is the wording both target forms must carry. The
// delivery core makes max_attempts attempts in total, not that many
// retries on top of a first try (a fresh delivery starts at attempt 1
// and target_http gives up once the attempt number reaches
// max_retries), and 0 is special-cased to a single fire-and-forget
// attempt with no circuit breaker.
const maxRetriesHelp = "This is the total number of delivery attempts, " +
"not retries on top of the first: a value of 3 makes three attempts " +
"in all. 0 means a single attempt with no retries and no circuit " +
"breaker."
// max_attempts).
const maxAttemptsHelp = "How many times each delivery is attempted in " +
"all, the first attempt included."
// TestTargetFormMaxRetriesCopyMatchesBehaviour pins the max_retries
// help text on both the create form (the add-target form on the webhook
// detail page) and the edit form, so the copy cannot drift back to
// calling the number a retry count.
func TestTargetFormMaxRetriesCopyMatchesBehaviour(t *testing.T) {
// TestTargetFormMaxAttemptsCopyMatchesBehaviour pins the max_attempts
// label, "Delivery attempts", and help text on both the create form
// (the add-target form on the webhook detail page) and the edit form,
// so the copy cannot drift back to calling the number a retry count.
func TestTargetFormMaxAttemptsCopyMatchesBehaviour(t *testing.T) {
t.Parallel()
var h *handlers.Handlers
@@ -340,40 +380,66 @@ func TestTargetFormMaxRetriesCopyMatchesBehaviour(t *testing.T) {
"Entrypoints": handlers.NewEntrypointViews(
[]database.Entrypoint{entrypoint},
),
"Targets": delivery.NewTargetViews(nil),
"Events": []database.Event{},
"BaseURL": "https://hooks.example.com",
"Targets": delivery.NewTargetViews(nil),
dataKeyEvents: []database.Event{},
"BaseURL": "https://hooks.example.com",
},
)
assert.Contains(t, createBody, "Delivery attempts:</label>")
assert.Contains(
t, createBody, maxRetriesHelp,
"the add-target form must explain max_retries as total attempts",
t, createBody, maxAttemptsHelp,
"the add-target form must explain max_attempts as total attempts",
)
// A slack target exercises the same max_retries field while needing
// only Config.URL from the edit template, so the test data stays
// minimal. The Target key mirrors the field names the template reads
// off the handler's view value.
// A slack target exercises the same max_attempts field while needing
// only a URL from the edit template, so the test data stays
// minimal. The Target and TargetForm keys mirror the field names
// the template reads off the handler's values.
editBody := renderPage(
t, h, sess, "target_edit.html", map[string]any{
dataKeyWebhook: webhook,
"Target": map[string]any{
"ID": "tg-1",
"Name": "t",
"Type": "slack",
"Active": true,
"MaxRetries": 3,
"Config": map[string]any{
"URL": "https://hooks.slack.com/services/x",
},
"ID": "tg-1",
"Name": "t",
"Type": "slack",
"Active": true,
},
"TargetForm": map[string]any{
"URL": "https://hooks.slack.com/services/x",
"MaxAttempts": "3",
},
dataKeyError: "",
},
)
assert.Contains(t, editBody, `class="label">Delivery attempts</label>`)
assert.Contains(
t, editBody, maxRetriesHelp,
"the target edit form must explain max_retries as total attempts",
t, editBody, maxAttemptsHelp,
"the target edit form must explain max_attempts as total attempts",
)
}
// TestCreateFormCallsTheDatabaseTargetAnArchive pins the names the new
// webhook page gives the database target its Archive checkbox creates,
// and that target's settings, to the ones the target forms use.
func TestCreateFormCallsTheDatabaseTargetAnArchive(t *testing.T) {
t.Parallel()
var h *handlers.Handlers
var sess *session.Session
app := newTestApp(t, &h, &sess)
app.RequireStart()
t.Cleanup(app.RequireStop)
body := renderPage(t, h, sess, "sources_new.html", map[string]any{
dataKeyError: "",
})
assert.Contains(t, body, "created with an archive target")
assert.Contains(t, body, `class="label">Archive expiry</label>`)
assert.Contains(t, body, `class="label">Archive rotation</label>`)
}