package handlers_test import ( "errors" "html" "net/http" "net/http/httptest" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" "sneak.berlin/go/webhooker/internal/database" ) // submitCreateForm posts the new webhook form and returns the // recorder. func submitCreateForm( env *sourceTestEnv, form url.Values, ) *httptest.ResponseRecorder { req := formRequest("/hooks/new", env.cookies, form, nil) w := httptest.NewRecorder() env.handlers.HandleSourceCreateSubmit().ServeHTTP(w, req) return w } // assertNothingCreated checks that the main database holds no webhook, // entrypoint or target. func assertNothingCreated(t *testing.T, db *database.Database) { t.Helper() for _, model := range []any{ &database.Webhook{}, &database.Entrypoint{}, &database.Target{}, } { var count int64 require.NoError(t, db.DB().Model(model).Count(&count).Error) assert.Zerof(t, count, "%T rows were created", model) } } // TestHandleSourceCreateSubmit_CreatesRequestedTargets submits the new // webhook form with the HTTP target URL filled in or empty, and with // the archive checkbox off or on with each pruning choice. The webhook // gets an HTTP target only for a URL and a database target only for a // checked archive. The pruning choice is always submitted, as the // browser submits it while it is hidden, and is ignored when archive // is off. func TestHandleSourceCreateSubmit_CreatesRequestedTargets(t *testing.T) { t.Parallel() env := setupSourceTest(t) // Each value the archive pruning choice submits, after an empty // one that stands for the archive checkbox left off. expiries := []string{ "", "never", "1h", "12h", "24h", "720h", "2160h", "8760h", } for _, httpURL := range []string{"", editOriginalURL} { for _, expiry := range expiries { name := "url=" + httpURL + " archive=" + expiry t.Run(name, func(t *testing.T) { t.Parallel() form := url.Values{} form.Set("name", name) form.Set("http_url", httpURL) form.Set("archive_expiry", "720h") if expiry != "" { form.Set("archive", "on") form.Set("archive_expiry", expiry) } w := submitCreateForm(env, form) require.Equal(t, http.StatusSeeOther, w.Code, w.Body.String()) var webhook database.Webhook require.NoError(t, env.db.DB(). Where("name = ?", name).First(&webhook).Error) byType := map[database.TargetType]database.Target{} for _, target := range targetsForWebhook(t, env.db, webhook.ID) { byType[target.Type] = target } wantCount := 0 if httpURL != "" { wantCount++ assert.Equal(t, "HTTP", byType[database.TargetTypeHTTP].Name) assert.JSONEq(t, `{"url":"`+httpURL+`"}`, byType[database.TargetTypeHTTP].Config) } if expiry != "" { wantCount++ assert.Equal(t, "Archive", byType[database.TargetTypeDatabase].Name) assert.JSONEq(t, `{"expiry":"`+expiry+`"}`, byType[database.TargetTypeDatabase].Config) } assert.Len(t, byType, wantCount) }) } } } // TestHandleSourceCreateSubmit_RefusedFormKeepsEveryValue refuses the // new webhook form for an invalid HTTP target URL and for an invalid // retention, each with archive on. Nothing is created, and the form // comes back with the reason and every value entered: name, // description, retention, URL, the checked archive box and the pruning // choice. func TestHandleSourceCreateSubmit_RefusedFormKeepsEveryValue( t *testing.T, ) { t.Parallel() const badURL = "Invalid target URL" cases := []struct { name string retention string httpURL string reason string }{ {"blocked url", "7", editBlockedURL, badURL}, {"unsupported scheme", "7", "ftp://93.184.216.34/hook", badURL}, {"bad retention", "-5", editOriginalURL, "Retention must be"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { t.Parallel() env := setupSourceTest(t) form := url.Values{} form.Set("name", "kept name") form.Set("description", "kept description") form.Set("retention_days", tc.retention) form.Set("http_url", tc.httpURL) form.Set("archive", "on") form.Set("archive_expiry", "2160h") w := submitCreateForm(env, form) require.Equal(t, http.StatusBadRequest, w.Code) page := w.Body.String() assert.Contains(t, page, tc.reason) assert.Contains(t, page, `value="kept name"`) assert.Contains(t, page, `>kept description`) assert.Contains(t, page, `value="`+tc.retention+`"`) assert.Contains(t, page, `value="`+html.EscapeString(tc.httpURL)+`"`) assert.Contains(t, page, `name="archive" value="on" checked`) assert.Contains(t, page, `x-data="collapsible" data-open`) assert.Contains(t, page, `