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:
@@ -1,6 +1,7 @@
|
||||
package delivery_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"sneak.berlin/go/webhooker/internal/archive"
|
||||
"sneak.berlin/go/webhooker/internal/database"
|
||||
"sneak.berlin/go/webhooker/internal/database/databasetest"
|
||||
"sneak.berlin/go/webhooker/internal/db/migrations"
|
||||
"sneak.berlin/go/webhooker/internal/delivery"
|
||||
"sneak.berlin/go/webhooker/internal/gormlog"
|
||||
)
|
||||
@@ -78,10 +80,11 @@ func (env *archiveEnv) archivePath(tgt *database.Target) string {
|
||||
)
|
||||
}
|
||||
|
||||
// seedDatabaseTarget creates a webhook with one database target
|
||||
// carrying the given target config JSON, and returns the target.
|
||||
// seedDatabaseTarget creates a webhook with one database target,
|
||||
// which keeps its archive forever in one file, and returns the
|
||||
// target.
|
||||
func (env *archiveEnv) seedDatabaseTarget(
|
||||
t *testing.T, configJSON string,
|
||||
t *testing.T,
|
||||
) *database.Target {
|
||||
t.Helper()
|
||||
|
||||
@@ -97,12 +100,12 @@ func (env *archiveEnv) seedDatabaseTarget(
|
||||
)
|
||||
|
||||
tgt := &database.Target{
|
||||
WebhookID: wh.ID,
|
||||
Name: "Archive",
|
||||
Type: database.TargetTypeDatabase,
|
||||
Active: true,
|
||||
Config: configJSON,
|
||||
MaxAttempts: 1,
|
||||
WebhookID: wh.ID,
|
||||
Name: "Archive",
|
||||
Type: database.TargetTypeDatabase,
|
||||
Active: true,
|
||||
ArchiveRotation: "none",
|
||||
MaxAttempts: 1,
|
||||
}
|
||||
require.NoError(
|
||||
t,
|
||||
@@ -136,9 +139,10 @@ func seedDatabaseTargetDelivery(
|
||||
Status: database.DeliveryStatusPending,
|
||||
Event: event,
|
||||
Target: database.Target{
|
||||
Name: tgt.Name,
|
||||
Type: database.TargetTypeDatabase,
|
||||
Config: tgt.Config,
|
||||
Name: tgt.Name,
|
||||
Type: database.TargetTypeDatabase,
|
||||
ArchiveExpiry: tgt.ArchiveExpiry,
|
||||
ArchiveRotation: tgt.ArchiveRotation,
|
||||
},
|
||||
}
|
||||
d.ID = dlv.ID
|
||||
@@ -218,7 +222,7 @@ func TestDeliverDatabase_ArchivesEvent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
env := setupArchiveTest(t)
|
||||
tgt := env.seedDatabaseTarget(t, "")
|
||||
tgt := env.seedDatabaseTarget(t)
|
||||
|
||||
webhookDB := testWebhookDB(t)
|
||||
event := seedEvent(t, webhookDB, `{"archived":true}`)
|
||||
@@ -256,9 +260,9 @@ func TestDeliverDatabase_ArchivesEvent(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestDeliverDatabase_ArchiveFailureFailsDelivery verifies that
|
||||
// an archive error (here: an unparseable expiry in the target
|
||||
// config) fails the delivery loudly: the attempt is recorded as
|
||||
// failed with the error and the delivery is marked failed, not
|
||||
// an archive error (here: an event with no webhook id to record)
|
||||
// fails the delivery loudly: the attempt is recorded as failed
|
||||
// with the error and the delivery is marked failed, not
|
||||
// delivered.
|
||||
func TestDeliverDatabase_ArchiveFailureFailsDelivery(
|
||||
t *testing.T,
|
||||
@@ -266,11 +270,12 @@ func TestDeliverDatabase_ArchiveFailureFailsDelivery(
|
||||
t.Parallel()
|
||||
|
||||
env := setupArchiveTest(t)
|
||||
tgt := env.seedDatabaseTarget(t, `{"expiry":"nonsense"}`)
|
||||
tgt := env.seedDatabaseTarget(t)
|
||||
|
||||
webhookDB := testWebhookDB(t)
|
||||
event := seedEvent(t, webhookDB, `{"archived":false}`)
|
||||
d := seedDatabaseTargetDelivery(t, webhookDB, event, tgt)
|
||||
d.Event.WebhookID = ""
|
||||
|
||||
env.eng.ExportDeliverDatabase(webhookDB, d)
|
||||
|
||||
@@ -295,12 +300,96 @@ func TestDeliverDatabase_ArchiveFailureFailsDelivery(
|
||||
"the attempt must be recorded as failed",
|
||||
)
|
||||
assert.Contains(t,
|
||||
results[0].Error, "nonsense",
|
||||
results[0].Error, "without a webhook id",
|
||||
"the archive error must be recorded on the attempt",
|
||||
)
|
||||
|
||||
assert.NoFileExists(t,
|
||||
env.archivePath(tgt),
|
||||
"no archive file should exist for a failed config",
|
||||
"no archive file should exist for a failed write",
|
||||
)
|
||||
}
|
||||
|
||||
// seedArchivedRow creates the archive file at path holding one row, for
|
||||
// eventID, archived at archivedAt.
|
||||
func seedArchivedRow(
|
||||
t *testing.T, path, eventID string, archivedAt time.Time,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
sqlDB, err := sql.Open(
|
||||
"sqlite", fmt.Sprintf("file:%s?mode=rwc", path),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() { _ = sqlDB.Close() }()
|
||||
|
||||
gdb, err := gorm.Open(
|
||||
sqlite.Dialector{Conn: sqlDB},
|
||||
&gorm.Config{Logger: gormlog.New(slog.New(slog.DiscardHandler))},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, migrations.Apply(gdb, migrations.Archive))
|
||||
require.NoError(t, gdb.Exec(
|
||||
"INSERT INTO archived_events (event_id, archived_at) VALUES (?, ?)",
|
||||
eventID, archivedAt,
|
||||
).Error)
|
||||
}
|
||||
|
||||
// TestDeliverDatabase_TaskArchiveSettings proves a task's ArchiveExpiry
|
||||
// and ArchiveRotation reach the archive through the engine. The
|
||||
// target's row has neither, so only the task can bring them: with daily
|
||||
// rotation the event goes to the file for its day, and opening that
|
||||
// file with a one-hour expiry prunes the row it already held, archived
|
||||
// two hours ago.
|
||||
func TestDeliverDatabase_TaskArchiveSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := newISetup(t)
|
||||
|
||||
webhook := database.Webhook{UserID: uuid.NewString(), Name: "settings"}
|
||||
webhook.ID = s.WebhookID
|
||||
require.NoError(t, s.MainDB.Create(&webhook).Error)
|
||||
|
||||
target := database.Target{
|
||||
WebhookID: s.WebhookID,
|
||||
Name: "archive",
|
||||
Type: database.TargetTypeDatabase,
|
||||
Active: true,
|
||||
MaxAttempts: 1,
|
||||
}
|
||||
require.NoError(t, s.MainDB.Create(&target).Error)
|
||||
|
||||
event := iSeedEvent(t, s.WebhookDB, s.WebhookID, `{"archived":true}`)
|
||||
d := iSeedDelivery(
|
||||
t, s.WebhookDB, event.ID, target.ID,
|
||||
database.DeliveryStatusPending,
|
||||
)
|
||||
|
||||
task := iTask(
|
||||
d, event, s.WebhookID, target.ID, target.Name, "", 1, 1,
|
||||
&event.Body,
|
||||
)
|
||||
task.TargetType = database.TargetTypeDatabase
|
||||
task.ArchiveExpiry = time.Hour
|
||||
task.ArchiveRotation = "daily"
|
||||
|
||||
daily := target
|
||||
daily.ArchiveRotation = task.ArchiveRotation
|
||||
dailyFile := archive.PathAt(s.DBMgr, &webhook, &daily, event.CreatedAt)
|
||||
seedArchivedRow(t, dailyFile, "expired", time.Now().Add(-2*time.Hour))
|
||||
|
||||
s.Engine.ExportProcessNewTask(context.TODO(), &task)
|
||||
|
||||
iAssertStatus(t, s.WebhookDB, d.ID, database.DeliveryStatusDelivered)
|
||||
|
||||
var rows []archivedRow
|
||||
|
||||
require.NoError(t, openArchiveDBForRead(t, dailyFile).
|
||||
Table("archived_events").Find(&rows).Error)
|
||||
require.Len(t, rows, 1)
|
||||
assert.Equal(t, event.ID, rows[0].EventID,
|
||||
"the event goes to its day's file and the expired row is pruned",
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user