check / check (push) Successful in 3m28s
A database target's rotation (none, monthly, daily or hourly) puts the UTC period of each event's receive time in its archive file name, so each file holds exactly its period's events. It is on the new webhook page, the add target form and the target edit form, and shown in the target list. Renames move every one of a target's files and move them back if one fails. The sweep prunes every file, one at a time under the target's lock, and deletes a rotated file it leaves empty. Download lists the files, then opens one at a time, oldest first, finding each again under the target's current names, and gives each row its period. The target list names the current file and totals the size of all of them. Model: opus-5-5
210 lines
5.8 KiB
Go
210 lines
5.8 KiB
Go
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
|
|
// and rotation choices.
|
|
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")
|
|
form.Set("archive_rotation", "hourly")
|
|
|
|
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</textarea>`)
|
|
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, `<option value="2160h" selected>`)
|
|
assert.Contains(t, page, `<option value="hourly" selected>`)
|
|
|
|
assertNothingCreated(t, env.db)
|
|
})
|
|
}
|
|
}
|
|
|
|
// errInjectedTargetCreate is the failure a test makes the insert of a
|
|
// target report.
|
|
var errInjectedTargetCreate = errors.New("injected target create failure")
|
|
|
|
// TestHandleSourceCreateSubmit_FailedTargetInsertCreatesNothing makes
|
|
// inserting a target fail after the webhook and its entrypoint were
|
|
// inserted, and checks that neither is left behind.
|
|
func TestHandleSourceCreateSubmit_FailedTargetInsertCreatesNothing(
|
|
t *testing.T,
|
|
) {
|
|
t.Parallel()
|
|
|
|
env := setupSourceTest(t)
|
|
|
|
require.NoError(t, env.db.DB().Callback().Create().
|
|
Before("gorm:create").
|
|
Register("test:fail_target_create", func(tx *gorm.DB) {
|
|
if tx.Statement.Table == "targets" {
|
|
_ = tx.AddError(errInjectedTargetCreate)
|
|
}
|
|
}),
|
|
)
|
|
|
|
form := url.Values{}
|
|
form.Set("name", "rolled back")
|
|
form.Set("archive", "on")
|
|
form.Set("archive_expiry", "never")
|
|
|
|
w := submitCreateForm(env, form)
|
|
require.Equal(t, http.StatusInternalServerError, w.Code)
|
|
|
|
assertNothingCreated(t, env.db)
|
|
}
|