Keep what was typed when a target or webhook edit is refused (closes #381)
check / check (push) Successful in 3m19s
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:
@@ -565,6 +565,73 @@ func assertEditRejectsTimeout(
|
||||
)
|
||||
}
|
||||
|
||||
// TestHandleTargetEditSubmit_RefusedFormComesBack refuses an edit of
|
||||
// a target of each type and checks that the edit form comes back with
|
||||
// the reason and every value submitted, and that nothing is saved.
|
||||
func TestHandleTargetEditSubmit_RefusedFormComesBack(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
env := setupSourceTest(t)
|
||||
|
||||
// fields is what the operator submitted, as a query string.
|
||||
cases := []struct {
|
||||
targetType database.TargetType
|
||||
fields string
|
||||
reason string
|
||||
}{
|
||||
{
|
||||
database.TargetTypeHTTP,
|
||||
"name=edited&url=" + editBlockedURL +
|
||||
"&headers=X-Edited:+kept&timeout=12&max_retries=3",
|
||||
"Invalid target URL",
|
||||
},
|
||||
{
|
||||
database.TargetTypeSlack,
|
||||
"name=edited&url=" + editOriginalURL + "&max_retries=25",
|
||||
"Invalid max retries",
|
||||
},
|
||||
{
|
||||
database.TargetTypeDatabase, "name=edited&expiry=7d",
|
||||
"Invalid archive expiry",
|
||||
},
|
||||
{database.TargetTypeLog, "name=", "Name is required"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(string(tc.targetType), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
webhook := seedWebhookWithRetention(t, env.db, 30)
|
||||
target := seedTarget(t, env.db, webhook.ID, tc.targetType)
|
||||
|
||||
form, err := url.ParseQuery(tc.fields)
|
||||
require.NoError(t, err)
|
||||
|
||||
w := submitTargetEdit(env, webhook.ID, target.ID, form)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
|
||||
page := w.Body.String()
|
||||
assert.Contains(t, page, `class="alert-error">`+tc.reason)
|
||||
|
||||
// headers is the form's one textarea; every other field is
|
||||
// an input.
|
||||
for field := range form {
|
||||
shown := `name="` + field + `" value="` + form.Get(field) + `"`
|
||||
if field == "headers" {
|
||||
shown = ">" + form.Get(field) + "</textarea>"
|
||||
}
|
||||
|
||||
assert.Contains(t, page, shown)
|
||||
}
|
||||
|
||||
assert.Equal(
|
||||
t, target.Name, storedTarget(t, env, target.ID).Name,
|
||||
"a refused edit must save nothing",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleTargetEdit_Scoping keeps the edit routes scoped the way
|
||||
// the delete and toggle routes are: ownership is decided by the
|
||||
// webhook, and the target is then scoped to it.
|
||||
@@ -700,6 +767,10 @@ func TestHandleTargetEditSubmit_RenamesArchive(t *testing.T) {
|
||||
w = submitTargetEdit(env, wh.ID, archive.ID, again)
|
||||
require.Equal(t, http.StatusConflict, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "archive-taken.db")
|
||||
assert.Contains(
|
||||
t, w.Body.String(), `name="name" value="Again"`,
|
||||
"the form comes back with the name submitted",
|
||||
)
|
||||
assert.Equal(
|
||||
t, renamedTargetName, storedTarget(t, env, archive.ID).Name,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user