check / check (push) Waiting to run
A target's settings move from the per-type JSON string in targets.config to columns: url, headers, timeout_seconds, forward_query, archive_expiry and archive_rotation. A column a type does not use stays empty. headers holds the header map as JSON text through GORM's JSON serializer, and archive_expiry holds a time.Duration, 0 meaning never. The forms check each setting once, with the same refusals and messages, and the edit form is filled from the columns, so the per-type config structs, their parsers and the "could not be read" branches are gone. A delivery task carries the six settings in place of the JSON string; tests check that each copy into a task, and back out of one, keeps all six. Model: opus-5-5
196 lines
5.1 KiB
Go
196 lines
5.1 KiB
Go
package handlers_test
|
|
|
|
import (
|
|
"html"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
)
|
|
|
|
// TestHandleTargetCreate_EveryType adds a target of each type and
|
|
// checks the settings it stores. Each submission carries a url and an
|
|
// expiry: only the http and slack types store the url, only the
|
|
// database type the expiry, and every setting a type does not use
|
|
// stays empty.
|
|
func TestHandleTargetCreate_EveryType(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
env := setupSourceTest(t)
|
|
|
|
// fields is the rest of each submission, as a query string, and
|
|
// want holds the settings stored.
|
|
cases := []struct {
|
|
targetType database.TargetType
|
|
fields string
|
|
want database.Target
|
|
wantAttempts int
|
|
}{
|
|
{
|
|
database.TargetTypeHTTP,
|
|
"timeout=12&max_attempts=3&forward_query=on",
|
|
database.Target{
|
|
URL: editOriginalURL,
|
|
Headers: map[string]string{},
|
|
TimeoutSeconds: 12,
|
|
ForwardQuery: true,
|
|
}, 3,
|
|
},
|
|
{
|
|
database.TargetTypeSlack, "max_attempts=4",
|
|
database.Target{URL: editOriginalURL}, 4,
|
|
},
|
|
{
|
|
database.TargetTypeDatabase, "rotation=daily",
|
|
database.Target{
|
|
ArchiveExpiry: 720 * time.Hour,
|
|
ArchiveRotation: rotationDaily,
|
|
}, 1,
|
|
},
|
|
{database.TargetTypeLog, "", database.Target{}, 1},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(string(tc.targetType), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, got := addTargetThroughForm(
|
|
t, env, tc.targetType,
|
|
"url="+editOriginalURL+"&expiry=720h&"+tc.fields,
|
|
)
|
|
assert.Equal(t, tc.targetType, got.Type)
|
|
assert.Equal(t, tc.wantAttempts, got.MaxAttempts)
|
|
assert.Equal(t, tc.want, targetSettingsOf(got))
|
|
})
|
|
}
|
|
}
|
|
|
|
// addTargetThroughForm adds a target of targetType named every-type to
|
|
// a new webhook through the add target form, with the rest of the form
|
|
// given as a query string, and returns the webhook and the target the
|
|
// form stored.
|
|
func addTargetThroughForm(
|
|
t *testing.T,
|
|
env *sourceTestEnv,
|
|
targetType database.TargetType,
|
|
fields string,
|
|
) (database.Webhook, database.Target) {
|
|
t.Helper()
|
|
|
|
webhook := seedWebhookWithRetention(t, env.db, 30)
|
|
|
|
form, err := url.ParseQuery(fields)
|
|
require.NoError(t, err)
|
|
form.Set("name", "every-type")
|
|
form.Set("type", string(targetType))
|
|
|
|
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)
|
|
|
|
return webhook, targets[0]
|
|
}
|
|
|
|
// targetSettingsOf returns a target's settings, URL to
|
|
// ArchiveRotation, and nothing else, for comparing with the settings a
|
|
// test expects.
|
|
func targetSettingsOf(target database.Target) database.Target {
|
|
return database.Target{
|
|
URL: target.URL,
|
|
Headers: target.Headers,
|
|
TimeoutSeconds: target.TimeoutSeconds,
|
|
ForwardQuery: target.ForwardQuery,
|
|
ArchiveExpiry: target.ArchiveExpiry,
|
|
ArchiveRotation: target.ArchiveRotation,
|
|
}
|
|
}
|
|
|
|
// 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))
|
|
})
|
|
}
|
|
}
|