Align docs with archive semantics; fail loud on non-positive expiry (#43)
All checks were successful
check / check (push) Successful in 2m39s

- README: rewrite the database-target documentation (target-types
  bullet and the per-webhook databases section) to describe the
  shipped archiving semantics -- separate archive-{webhookID}.db,
  debounced close/reopen for offline archiving, auto-recreate,
  creation-validated optional expiry with prune-on-open, and
  fail-loud delivery on archive write errors -- replacing the
  stale always-successful stub description.
- parseArchiveExpiry now returns an error for set-but-non-positive
  durations ("0s", "-5h") instead of silently defaulting to
  keep-forever, matching ValidateArchiveExpiry at creation time;
  the delivery then fails loudly like any other archive error.
  TestParseArchiveExpiry extended with zero and negative cases.
- databaseTarget type comment: "fire-and-forget" -> "no-retry",
  matching the fail-loud behaviour.
This commit is contained in:
2026-08-07 17:27:00 +00:00
parent 7ca62664d0
commit f5b4aec0bd
4 changed files with 47 additions and 25 deletions

View File

@@ -10,7 +10,7 @@ import (
"sneak.berlin/go/webhooker/internal/database"
)
// databaseTarget is a fire-and-forget target that archives the
// databaseTarget is a no-retry target that archives the
// full inbound event into a per-webhook archive SQLite file,
// separate from the per-webhook event database. The event is
// already persisted in the per-webhook event DB by the time

View File

@@ -77,7 +77,10 @@ type archivedEvent struct {
// parseArchiveExpiry reads the optional expiry from a database
// target's config JSON. An empty config, an empty expiry, or
// the literal "never" all mean keep forever, returned as a zero
// duration. Any other value is parsed as a Go duration.
// duration. Any other value must parse as a positive Go
// duration; a set-but-invalid value (unparseable, zero, or
// negative) is an error rather than a silent default, matching
// ValidateArchiveExpiry at target creation.
func parseArchiveExpiry(
configJSON string,
) (time.Duration, error) {
@@ -106,7 +109,9 @@ func parseArchiveExpiry(
}
if dur <= 0 {
return 0, nil
return 0, fmt.Errorf(
"%w: %q", errArchiveExpiryNotPositive, cfg.Expiry,
)
}
return dur, nil

View File

@@ -250,15 +250,18 @@ func TestParseArchiveExpiry(t *testing.T) {
t.Parallel()
cases := []struct {
name string
in string
want time.Duration
name string
in string
want time.Duration
wantErr bool
}{
{"empty config", "", 0},
{"explicit never", `{"expiry":"never"}`, 0},
{"empty expiry", `{"expiry":""}`, 0},
{"duration", `{"expiry":"1h"}`, time.Hour},
{"zero duration", `{"expiry":"0s"}`, 0},
{"empty config", "", 0, false},
{"explicit never", `{"expiry":"never"}`, 0, false},
{"empty expiry", `{"expiry":""}`, 0, false},
{"duration", `{"expiry":"1h"}`, time.Hour, false},
{"unparseable", `{"expiry":"nonsense"}`, 0, true},
{"zero duration", `{"expiry":"0s"}`, 0, true},
{"negative duration", `{"expiry":"-5h"}`, 0, true},
}
for _, tc := range cases {
@@ -266,15 +269,16 @@ func TestParseArchiveExpiry(t *testing.T) {
t.Parallel()
got, err := delivery.ExportParseArchiveExpiry(tc.in)
if tc.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tc.want, got)
})
}
_, err := delivery.ExportParseArchiveExpiry(
`{"expiry":"nonsense"}`,
)
require.Error(t, err)
}
// seedDatabaseTargetDelivery seeds a pending delivery for a