Keep what was typed when a target or webhook edit is refused (closes #381)
check / check (push) Successful in 3m19s

A refused save on the target edit page now shows the edit form again,
with the reason above it and every value submitted, instead of a bare
text page; the status codes are unchanged. The webhook edit page keeps
the submitted name, description and retention the same way, while the
page still reports the stored retention.

Target edits are validated by setTargetFromForm, which newTarget now
uses too, so the add and edit forms accept and refuse the same things.
An empty max_retries keeps the target's own count.

The browser test also saves both edit pages with refused values.

Model: opus-5-5
This commit is contained in:
2026-10-02 22:11:10 +00:00
parent 9305af4f85
commit 3e36c966ba
11 changed files with 395 additions and 219 deletions
+59
View File
@@ -122,6 +122,7 @@ func TestAlpineRunsUnderTheSecurityPolicy(t *testing.T) {
checkTargetDeliveries(ctx, t, page, target.Name,
"0 in total, 0 in the last 24 hours",
"1 in total, 1 in the last 24 hours")
checkRefusedEdits(ctx, t, page, target.ID)
checkCopy(ctx, t, page)
checkEntrypointEdit(ctx, t, page, page+"/events")
checkRecentEvents(ctx, t, page)
@@ -478,6 +479,64 @@ func checkTargetDeliveries(
"the row of %s does not show %q failed", name, failed)
}
// checkRefusedEdits fills in the target edit page and the webhook edit
// page of a webhook page with values the server refuses, a loopback
// destination and a retention above the longest finite one, which the
// browser lets through. It saves each and checks that the page comes
// back with the reason and every value still in its field. The values
// are keyed by the id of their field.
func checkRefusedEdits(
ctx context.Context, t *testing.T, page, targetID string,
) {
t.Helper()
const reason = `//div[@class="alert-error"]`
edits := []struct {
url string
values map[string]string
}{
{page + "/targets/" + targetID + "/edit", map[string]string{
"#name": "edited-target",
"#url": "http://127.0.0.1/hook",
"#headers": "X-Edited: kept",
"#timeout": "12",
"#max_retries": "3",
}},
{page + "/edit", map[string]string{
"#name": "edited-webhook",
"#description": "kept description",
"#retention_days": "200000",
}},
}
for _, edit := range edits {
require.NoError(t, chromedp.Run(ctx, loadPage(edit.url)))
for field, value := range edit.values {
require.NoError(t, chromedp.Run(
ctx, chromedp.SetValue(field, value, chromedp.ByQuery),
))
}
click(ctx, t, `//button[text()="Save Changes"]`)
assert.Truef(t, shown(ctx, reason),
"%s: a refused save does not show the reason", edit.url)
for field, value := range edit.values {
var kept string
require.NoError(t, chromedp.Run(
ctx, chromedp.Value(field, &kept, chromedp.ByQuery),
))
assert.Equalf(t, value, kept,
"%s: a refused save does not keep the %s entered",
edit.url, field)
}
}
}
// checkCopy loads a webhook page and checks that the Copy control beside
// its entrypoint's URL is a button, and that clicking it copies the URL
// and says so: the button reads "Copied" only once the copy succeeded.