check / check (push) Waiting to run
The targets section of the webhook page showed its add form open with every field, a URL included for types that use none. It now lists only its targets until "+ Add" is clicked; "+ Add" shows a choice of target type with Next and Cancel on one row, and Next shows the name and only that type's fields. The database and log types show no URL field and the server stores none for them; the slack form gains its retry field. A refused target brings the page back with the form open on its type, the values entered and the reason, and Cancel empties it. An encoding failure stays a logged 500. Target validation returns its message, so the new-webhook page can reuse it. Model: opus-5-5
155 lines
3.8 KiB
Go
155 lines
3.8 KiB
Go
package handlers_test
|
|
|
|
import (
|
|
"html"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
)
|
|
|
|
// TestHandleTargetCreate_EveryType adds a target of each type. Each
|
|
// submission carries a url: only the http and slack types store one.
|
|
func TestHandleTargetCreate_EveryType(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
env := setupSourceTest(t)
|
|
|
|
// fields is the rest of each submission, as a query string.
|
|
cases := []struct {
|
|
targetType database.TargetType
|
|
fields string
|
|
wantConfig string
|
|
wantRetries int
|
|
}{
|
|
{
|
|
database.TargetTypeHTTP, "timeout=12&max_retries=3",
|
|
`{"url":"` + editOriginalURL + `","timeout":12}`, 3,
|
|
},
|
|
{
|
|
database.TargetTypeSlack, "max_retries=4",
|
|
`{"webhookUrl":"` + editOriginalURL + `"}`, 4,
|
|
},
|
|
{
|
|
database.TargetTypeDatabase, "expiry=720h",
|
|
`{"expiry":"720h"}`, 0,
|
|
},
|
|
{database.TargetTypeLog, "", "", 0},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(string(tc.targetType), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
webhook := seedWebhookWithRetention(t, env.db, 30)
|
|
|
|
form, err := url.ParseQuery(tc.fields)
|
|
require.NoError(t, err)
|
|
form.Set("name", "every-type")
|
|
form.Set("type", string(tc.targetType))
|
|
form.Set("url", editOriginalURL)
|
|
|
|
w := serveTarget(
|
|
env, http.MethodPost,
|
|
"/hook/"+webhook.ID+"/targets", form,
|
|
)
|
|
require.Equal(t, http.StatusSeeOther, w.Code, w.Body.String())
|
|
|
|
targets := targetsForWebhook(t, env.db, webhook.ID)
|
|
require.Len(t, targets, 1)
|
|
assert.Equal(t, tc.targetType, targets[0].Type)
|
|
assert.Equal(t, tc.wantRetries, targets[0].MaxRetries)
|
|
|
|
if tc.wantConfig == "" {
|
|
assert.Empty(t, targets[0].Config)
|
|
} else {
|
|
assert.JSONEq(t, tc.wantConfig, targets[0].Config)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestHandleTargetCreate_RefusedFormComesBack refuses a target of each
|
|
// type and checks that the webhook page comes back with the add target
|
|
// form open on that type, the values entered, and the reason.
|
|
func TestHandleTargetCreate_RefusedFormComesBack(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
env := setupSourceTest(t)
|
|
|
|
// fields is what the operator typed, as a query string.
|
|
cases := []struct {
|
|
targetType database.TargetType
|
|
fields string
|
|
reason string
|
|
}{
|
|
{
|
|
database.TargetTypeHTTP,
|
|
"name=private&url=" + editBlockedURL +
|
|
"&timeout=12&max_retries=3",
|
|
"Invalid target URL",
|
|
},
|
|
{
|
|
database.TargetTypeSlack, "name=no-url&max_retries=4",
|
|
"Webhook URL is required for Slack targets",
|
|
},
|
|
{
|
|
database.TargetTypeDatabase, "name=archive&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)
|
|
|
|
typed, err := url.ParseQuery(tc.fields)
|
|
require.NoError(t, err)
|
|
|
|
form := url.Values{}
|
|
form.Set("type", string(tc.targetType))
|
|
|
|
for field := range typed {
|
|
form.Set(field, typed.Get(field))
|
|
}
|
|
|
|
w := serveTarget(
|
|
env, http.MethodPost,
|
|
"/hook/"+webhook.ID+"/targets", form,
|
|
)
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
|
|
page := w.Body.String()
|
|
assert.Contains(
|
|
t, page, `data-type="`+string(tc.targetType)+`"`,
|
|
)
|
|
assert.Contains(t, page, html.EscapeString(tc.reason))
|
|
|
|
// Each value comes back in a data attribute of the targets
|
|
// section named after its field (max_retries as
|
|
// data-max-retries), except url, which comes back in
|
|
// data-destination; templates/source_detail.html says why.
|
|
for field := range typed {
|
|
attr := "data-" + strings.ReplaceAll(field, "_", "-")
|
|
if field == "url" {
|
|
attr = "data-destination"
|
|
}
|
|
|
|
assert.Contains(
|
|
t, page, attr+`="`+
|
|
html.EscapeString(typed.Get(field))+`"`,
|
|
)
|
|
}
|
|
|
|
assert.Empty(t, targetsForWebhook(t, env.db, webhook.ID))
|
|
})
|
|
}
|
|
}
|