Rotate a database target's archive monthly, daily or hourly (closes #379)
check / check (push) Successful in 3m27s
check / check (push) Successful in 3m27s
A database target's rotation setting (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 and both target forms, and shown in the target list. Renames move every one of a target's files and move them all back if one fails. The sweep prunes one file at a time under the target's lock and deletes a rotated file it leaves empty. Download opens one file at a time, oldest period first, finding each again under the target's current name. The target list names the current file and totals all of them. Model: opus-5-5
This commit was merged in pull request #482.
This commit is contained in:
@@ -84,7 +84,7 @@ func TestAlpineRunsUnderTheSecurityPolicy(t *testing.T) {
|
||||
// this order. A new check is one more line here.
|
||||
checkAddEntrypoint(ctx, t, page)
|
||||
checkAddEachTargetType(ctx, t, page)
|
||||
checkArchiveExpiry(ctx, t, page)
|
||||
checkArchiveChoices(ctx, t, page)
|
||||
checkRefusedTarget(ctx, t, page)
|
||||
checkTargetDeliveries(ctx, t, page, target.Name,
|
||||
"0 in total, 0 in the last 24 hours",
|
||||
@@ -334,8 +334,8 @@ func checkAddEachTargetType(ctx context.Context, t *testing.T, url string) {
|
||||
map[string]string{"url": publicTargetURL},
|
||||
},
|
||||
{
|
||||
"database", "csrf_token name type expiry",
|
||||
map[string]string{"expiry": "720h"},
|
||||
"database", "csrf_token name type expiry rotation",
|
||||
map[string]string{"expiry": "720h", "rotation": "daily"},
|
||||
},
|
||||
{"log", "csrf_token name type", nil},
|
||||
}
|
||||
@@ -435,14 +435,18 @@ func chooseTargetType(ctx context.Context, t *testing.T, targetType string) {
|
||||
"%s: Add still shows while the form is open", targetType)
|
||||
}
|
||||
|
||||
// checkArchiveExpiry loads a webhook page and checks that the add
|
||||
// target form's archive expiry starts on never, that the database
|
||||
// target checkAddTarget added with 720h is listed as 30 days, and that
|
||||
// its edit form starts on 720h.
|
||||
func checkArchiveExpiry(ctx context.Context, t *testing.T, url string) {
|
||||
// checkArchiveChoices loads a webhook page and checks that the add
|
||||
// target form's archive expiry starts on never and its archive
|
||||
// rotation on none, that the database target checkAddTarget added with
|
||||
// 720h and daily is listed as 30 days and daily, and that its edit
|
||||
// form starts on 720h and daily.
|
||||
func checkArchiveChoices(ctx context.Context, t *testing.T, url string) {
|
||||
t.Helper()
|
||||
|
||||
const expiry = `form[action$="/targets"] select[name="expiry"]`
|
||||
const (
|
||||
expiry = `form[action$="/targets"] select[name="expiry"]`
|
||||
rotation = `form[action$="/targets"] select[name="rotation"]`
|
||||
)
|
||||
|
||||
row := `//span[text()="added-database"]/ancestor::div[@class="p-4"][1]`
|
||||
|
||||
@@ -450,26 +454,36 @@ func checkArchiveExpiry(ctx context.Context, t *testing.T, url string) {
|
||||
|
||||
chooseTargetType(ctx, t, "database")
|
||||
|
||||
var start, edited string
|
||||
var startExpiry, startRotation, editedExpiry, editedRotation string
|
||||
|
||||
require.NoError(t, chromedp.Run(
|
||||
ctx, chromedp.Value(expiry, &start, chromedp.ByQuery),
|
||||
ctx,
|
||||
chromedp.Value(expiry, &startExpiry, chromedp.ByQuery),
|
||||
chromedp.Value(rotation, &startRotation, chromedp.ByQuery),
|
||||
))
|
||||
assert.Equal(t, "never", start,
|
||||
assert.Equal(t, "never", startExpiry,
|
||||
"the add target form's archive expiry does not start on never")
|
||||
assert.Equal(t, "none", startRotation,
|
||||
"the add target form's archive rotation does not start on none")
|
||||
|
||||
assert.True(t, shown(ctx, row+`//span[text()="Archive Expiry:"]`+
|
||||
`/following-sibling::span[text()="30 days"]`),
|
||||
"a database target added with 720h is not listed as 30 days")
|
||||
assert.True(t, shown(ctx, row+`//span[text()="Archive Rotation:"]`+
|
||||
`/following-sibling::span[text()="daily"]`),
|
||||
"a database target added with daily is not listed as daily")
|
||||
|
||||
click(ctx, t, row+`//a[text()="Edit"]`)
|
||||
require.NoError(t, chromedp.Run(
|
||||
ctx,
|
||||
chromedp.WaitReady("#expiry", chromedp.ByQuery),
|
||||
chromedp.Value("#expiry", &edited, chromedp.ByQuery),
|
||||
chromedp.Value("#expiry", &editedExpiry, chromedp.ByQuery),
|
||||
chromedp.Value("#rotation", &editedRotation, chromedp.ByQuery),
|
||||
))
|
||||
assert.Equal(t, "720h", edited,
|
||||
assert.Equal(t, "720h", editedExpiry,
|
||||
"the edit form does not start on the stored archive expiry")
|
||||
assert.Equal(t, "daily", editedRotation,
|
||||
"the edit form does not start on the stored archive rotation")
|
||||
}
|
||||
|
||||
// checkRefusedTarget submits an http target the server refuses, a
|
||||
@@ -1153,7 +1167,9 @@ func checkNewWebhookTargets(
|
||||
pruningChoice, expiry, chromedp.BySearch,
|
||||
)))
|
||||
|
||||
want[database.TargetTypeDatabase] = `{"expiry":"` + expiry + `"}`
|
||||
// The rotation choice is left on none.
|
||||
want[database.TargetTypeDatabase] = `{"expiry":"` + expiry +
|
||||
`","rotation":"none"}`
|
||||
}
|
||||
|
||||
click(ctx, t, createButton)
|
||||
@@ -1194,8 +1210,8 @@ func targetConfigs(
|
||||
// checkRefusedNewWebhook submits the new webhook page with archive
|
||||
// checked and an HTTP target URL the server refuses, a loopback
|
||||
// destination, and checks that the page comes back with the reason and
|
||||
// every value entered, archive still checked and its pruning choice
|
||||
// showing.
|
||||
// every value entered, archive still checked and its pruning and
|
||||
// rotation choices showing.
|
||||
func checkRefusedNewWebhook(ctx context.Context, t *testing.T, url string) {
|
||||
t.Helper()
|
||||
|
||||
@@ -1212,17 +1228,19 @@ func checkRefusedNewWebhook(ctx context.Context, t *testing.T, url string) {
|
||||
click(ctx, t, archiveBox)
|
||||
require.True(t, shown(ctx, pruningChoice),
|
||||
"checking archive does not show the pruning choice")
|
||||
require.NoError(t, chromedp.Run(ctx, chromedp.SetValue(
|
||||
pruningChoice, "2160h", chromedp.BySearch,
|
||||
)))
|
||||
require.NoError(t, chromedp.Run(
|
||||
ctx,
|
||||
chromedp.SetValue(pruningChoice, "2160h", chromedp.BySearch),
|
||||
chromedp.SetValue("#archive_rotation", "monthly", chromedp.ByQuery),
|
||||
))
|
||||
click(ctx, t, createButton)
|
||||
|
||||
assert.True(t, shown(ctx, `//div[@class="alert-error"]`),
|
||||
"a refused webhook does not show the reason")
|
||||
|
||||
var (
|
||||
name, description, retention, typed, expiry string
|
||||
checked bool
|
||||
name, description, retention, typed, expiry, rotation string
|
||||
checked bool
|
||||
)
|
||||
|
||||
require.NoError(t, chromedp.Run(
|
||||
@@ -1232,6 +1250,7 @@ func checkRefusedNewWebhook(ctx context.Context, t *testing.T, url string) {
|
||||
chromedp.Value("#retention_days", &retention, chromedp.ByQuery),
|
||||
chromedp.Value("#http_url", &typed, chromedp.ByQuery),
|
||||
chromedp.Value("#archive_expiry", &expiry, chromedp.ByQuery),
|
||||
chromedp.Value("#archive_rotation", &rotation, chromedp.ByQuery),
|
||||
chromedp.Evaluate(archiveIsOn, &checked),
|
||||
))
|
||||
|
||||
@@ -1242,6 +1261,7 @@ func checkRefusedNewWebhook(ctx context.Context, t *testing.T, url string) {
|
||||
assert.True(t, checked, "archive is no longer checked")
|
||||
assert.True(t, shown(ctx, pruningChoice), "the pruning choice is hidden")
|
||||
assert.Equal(t, "2160h", expiry, "the pruning chosen is lost")
|
||||
assert.Equal(t, "monthly", rotation, "the rotation chosen is lost")
|
||||
}
|
||||
|
||||
// checkMobileMenu loads a page in a phone-sized window and checks that
|
||||
|
||||
Reference in New Issue
Block a user