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)) }) } }