Store a target's settings as columns (closes #543)
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
This commit is contained in:
2026-10-15 20:40:25 +00:00
parent ded39aa158
commit aa12ba19ca
71 changed files with 1665 additions and 2168 deletions
+39 -19
View File
@@ -1183,16 +1183,26 @@ func checkNewWebhookTargets(
) {
t.Helper()
// Each value the pruning choice submits, after an empty one that
// stands for archive left off.
expiries := []string{
"", "never", "1h", "12h", "24h", "720h", "2160h", "8760h",
// Each value the pruning choice submits and the expiry it stores,
// after an empty one that stands for archive left off.
expiries := []struct {
value string
stored time.Duration
}{
{"", 0},
{"never", 0},
{"1h", time.Hour},
{"12h", 12 * time.Hour},
{"24h", 24 * time.Hour},
{"720h", 720 * time.Hour},
{"2160h", 2160 * time.Hour},
{"8760h", 8760 * time.Hour},
}
for _, httpURL := range []string{"", publicTargetURL} {
for _, expiry := range expiries {
name := "url=" + httpURL + " archive=" + expiry
want := map[database.TargetType]string{}
name := "url=" + httpURL + " archive=" + expiry.value
want := map[database.TargetType]database.Target{}
require.NoError(t, chromedp.Run(
ctx,
@@ -1205,10 +1215,12 @@ func checkNewWebhookTargets(
"#http_url", httpURL, chromedp.ByQuery,
)))
want[database.TargetTypeHTTP] = `{"url":"` + httpURL + `"}`
want[database.TargetTypeHTTP] = database.Target{
URL: httpURL, Headers: map[string]string{},
}
}
if expiry != "" {
if expiry.value != "" {
// The choice showing moves Create down, so it is
// waited for before Create is clicked.
click(ctx, t, archiveBox)
@@ -1216,29 +1228,30 @@ func checkNewWebhookTargets(
"%s: checking archive does not show the pruning choice",
name)
require.NoError(t, chromedp.Run(ctx, chromedp.SetValue(
pruningChoice, expiry, chromedp.BySearch,
pruningChoice, expiry.value, chromedp.BySearch,
)))
// The rotation choice is left on none.
want[database.TargetTypeDatabase] = `{"expiry":"` + expiry +
`","rotation":"none"}`
want[database.TargetTypeDatabase] = database.Target{
ArchiveExpiry: expiry.stored, ArchiveRotation: "none",
}
}
clickAndLoad(ctx, t, createButton)
require.Truef(t, shown(ctx, `//h1[text()="`+name+`"]`),
"%s: the new webhook's page does not open", name)
assert.Equalf(t, want, targetConfigs(t, env, name),
assert.Equalf(t, want, targetSettings(t, env, name),
"%s: the webhook does not have the targets asked for", name)
}
}
}
// targetConfigs reads the targets of the webhook named name, and
// returns each one's stored configuration by its type.
func targetConfigs(
// targetSettings reads the targets of the webhook named name, and
// returns each one's stored settings by its type.
func targetSettings(
t *testing.T, env *testEnv, name string,
) map[database.TargetType]string {
) map[database.TargetType]database.Target {
t.Helper()
var (
@@ -1251,12 +1264,19 @@ func targetConfigs(
require.NoError(t, env.db.DB().
Where("webhook_id = ?", webhook.ID).Find(&targets).Error)
configs := map[database.TargetType]string{}
settings := map[database.TargetType]database.Target{}
for _, target := range targets {
configs[target.Type] = target.Config
settings[target.Type] = database.Target{
URL: target.URL,
Headers: target.Headers,
TimeoutSeconds: target.TimeoutSeconds,
ForwardQuery: target.ForwardQuery,
ArchiveExpiry: target.ArchiveExpiry,
ArchiveRotation: target.ArchiveRotation,
}
}
return configs
return settings
}
// checkRefusedNewWebhook submits the new webhook page with archive
+1 -1
View File
@@ -311,7 +311,7 @@ func (s *Server) setupSourceRoutes() {
r.Post("/targets", s.h.HandleTargetCreate())
// The edit form is the one page that renders a target's
// destination URL and header values in full; see
// delivery.TargetConfigForm. It belongs to this group for
// Handlers.HandleTargetEdit. It belongs to this group for
// its RequireAuth and NoCache, which are what keep that
// exception from reaching an unauthenticated request or a
// shared cache.
+1 -1
View File
@@ -414,7 +414,7 @@ func (e *testEnv) seedTarget(
Name: "routed-target",
Type: database.TargetTypeHTTP,
Active: true,
Config: `{"url":"http://93.184.216.34/hook"}`,
URL: "http://93.184.216.34/hook",
MaxAttempts: 1,
}