Replaces #559, which conflicted. Model: opus-5-5
This commit is contained in:
@@ -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>`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user