The webhook page's targets section lists only its targets until Add is clicked. Add shows a choice of target type with Next; Next shows only that type's fields, with Save and Cancel. The `database` and `log` types have no URL field, and the `slack` form gains max retries. A refused target now shows the webhook page again with the form open on its type, the values entered and the reason, instead of a bare text page. Target validation returns that message rather than writing the response; newTarget validates a whole new target for reuse by the new-webhook page. The edit page still answers a refusal in plain text. Model: opus-5-5
145 lines
3.5 KiB
Go
145 lines
3.5 KiB
Go
package handlers_test
|
|
|
|
import (
|
|
"html"
|
|
"net/http"
|
|
"net/url"
|
|
"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))
|
|
|
|
for field := range typed {
|
|
assert.Contains(
|
|
t, page, `name="`+field+`" value="`+
|
|
html.EscapeString(typed.Get(field))+`"`,
|
|
)
|
|
}
|
|
|
|
assert.Empty(t, targetsForWebhook(t, env.db, webhook.ID))
|
|
})
|
|
}
|
|
}
|