Store a target's settings as columns (closes #543)
check / check (push) Canceled after 0s

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
This commit was merged in pull request #554.
This commit is contained in:
2026-10-15 23:02:39 +02:00
parent ded39aa158
commit 8406c77949
71 changed files with 1665 additions and 2168 deletions
+38 -24
View File
@@ -16,8 +16,12 @@ import (
"sneak.berlin/go/webhooker/internal/database"
)
// rotationNone is the archive rotation that keeps one file.
const rotationNone = "none"
// The archive rotations these tests store: none keeps one file.
const (
rotationNone = "none"
rotationDaily = "daily"
rotationHourly = "hourly"
)
// rotationShown returns the archive rotations the webhook page's
// target list shows.
@@ -41,7 +45,9 @@ func TestArchiveRotationChoices(t *testing.T) {
env := setupSourceTest(t)
for _, rotation := range []string{rotationNone, "monthly", "daily", "hourly"} {
for _, rotation := range []string{
rotationNone, "monthly", rotationDaily, rotationHourly,
} {
t.Run(rotation, func(t *testing.T) {
t.Parallel()
@@ -60,10 +66,8 @@ func TestArchiveRotationChoices(t *testing.T) {
targets := targetsForWebhook(t, env.db, webhook.ID)
require.Len(t, targets, 1)
assert.JSONEq(t,
`{"expiry":"720h","rotation":"`+rotation+`"}`,
targets[0].Config,
)
assert.Equal(t, 720*time.Hour, targets[0].ArchiveExpiry)
assert.Equal(t, rotation, targets[0].ArchiveRotation)
assert.Equal(t,
[]string{rotation}, rotationShown(t, env, webhook.ID))
@@ -78,26 +82,37 @@ func TestArchiveRotationChoices(t *testing.T) {
w = submitTargetEdit(env, webhook.ID, targets[0].ID, form)
require.Equal(t, http.StatusSeeOther, w.Code, w.Body.String())
assert.JSONEq(t,
`{"expiry":"720h","rotation":"hourly"}`,
storedTarget(t, env, targets[0].ID).Config,
)
stored := storedTarget(t, env, targets[0].ID)
assert.Equal(t, 720*time.Hour, stored.ArchiveExpiry)
assert.Equal(t, rotationHourly, stored.ArchiveRotation)
})
}
}
// TestArchiveRotationEditStartsOnNone checks the edit form of a
// database target with no rotation stored starts on none.
// TestArchiveRotationEditStartsOnNone checks that a database target
// added with no rotation is stored with none and shown with none, and
// that its edit form starts on none.
func TestArchiveRotationEditStartsOnNone(t *testing.T) {
t.Parallel()
env := setupSourceTest(t)
webhook := seedWebhookWithRetention(t, env.db, 30)
target := seedConfiguredTarget(
t, env.db, webhook.ID, database.TargetTypeDatabase, "",
)
page := targetEditPage(t, env, webhook.ID, target.ID)
form := url.Values{}
form.Set("name", "archive")
form.Set("type", string(database.TargetTypeDatabase))
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, rotationNone, targets[0].ArchiveRotation)
page := targetEditPage(t, env, webhook.ID, targets[0].ID)
assert.Equal(t, []string{rotationNone}, selectedIn(page, "rotation"))
assert.Equal(t, []string{rotationNone}, rotationShown(t, env, webhook.ID))
}
@@ -125,7 +140,7 @@ func TestArchiveRotationRefused(t *testing.T) {
target := seedConfiguredTarget(
t, env.db, webhook.ID, database.TargetTypeDatabase,
`{"rotation":"daily"}`,
database.Target{ArchiveRotation: rotationDaily},
)
form.Del("type")
@@ -133,8 +148,8 @@ func TestArchiveRotationRefused(t *testing.T) {
w = submitTargetEdit(env, webhook.ID, target.ID, form)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, w.Body.String(), "Invalid archive rotation")
assert.JSONEq(t,
`{"rotation":"daily"}`, storedTarget(t, env, target.ID).Config,
assert.Equal(
t, rotationDaily, storedTarget(t, env, target.ID).ArchiveRotation,
)
}
@@ -161,9 +176,8 @@ func TestHandleSourceCreateSubmit_ArchiveRotation(t *testing.T) {
targets := targetsForWebhook(t, env.db, webhook.ID)
require.Len(t, targets, 1)
assert.JSONEq(t,
`{"expiry":"720h","rotation":"daily"}`, targets[0].Config,
)
assert.Equal(t, 720*time.Hour, targets[0].ArchiveExpiry)
assert.Equal(t, rotationDaily, targets[0].ArchiveRotation)
}
// TestArchiveFileView_Rotated describes a daily target's archive files
@@ -178,7 +192,7 @@ func TestArchiveFileView_Rotated(t *testing.T) {
webhook := seedWebhookWithRetention(t, env.db, 30)
target := seedConfiguredTarget(
t, env.db, webhook.ID, database.TargetTypeDatabase,
`{"rotation":"daily"}`,
database.Target{ArchiveRotation: rotationDaily},
)
path := archive.Path(env.dbMgr, &webhook, target)