check / check (push) Waiting to run
Target.MaxRetries (max_retries) becomes Target.MaxAttempts (max_attempts), and the form field, parser and ceiling are renamed to match. 1 is a single attempt with no circuit breaker; above 1 retries behind the breaker up to that many attempts. Both target forms refuse 0 and less; a blank field on the add target form gives 1, and on the edit form keeps the stored value. The target page shows the plain number, and the README's two paragraphs on 0 against 1 become one sentence. Model: opus-5-5
155 lines
3.9 KiB
Go
155 lines
3.9 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
|
|
wantAttempts int
|
|
}{
|
|
{
|
|
database.TargetTypeHTTP, "timeout=12&max_attempts=3",
|
|
`{"url":"` + editOriginalURL + `","timeout":12}`, 3,
|
|
},
|
|
{
|
|
database.TargetTypeSlack, "max_attempts=4",
|
|
`{"webhookUrl":"` + editOriginalURL + `"}`, 4,
|
|
},
|
|
{
|
|
database.TargetTypeDatabase, "expiry=720h",
|
|
`{"expiry":"720h"}`, 1,
|
|
},
|
|
{database.TargetTypeLog, "", "", 1},
|
|
}
|
|
|
|
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.wantAttempts, targets[0].MaxAttempts)
|
|
|
|
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_attempts=3",
|
|
"Invalid target URL",
|
|
},
|
|
{
|
|
database.TargetTypeSlack, "name=no-url&max_attempts=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_attempts as
|
|
// data-max-attempts), 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))
|
|
})
|
|
}
|
|
}
|