Store delivery and archive times without the monotonic reading (closes #529)
check / check (push) Waiting to run
check / check (push) Waiting to run
finished_at, archived_at and the updated_at stamp a re-dispatch
writes came from a bare time.Now(). The SQLite driver stores a time
as time.Time.String(), which appends the monotonic clock reading
(" m=+…") to a time read from time.Now(). All three now come from
GORM's own clock, NowFunc(), which stamps created_at, so they are
stored in the same form. The tests for each write read the stored
text back.
Model: opus-5-5
This commit was merged in pull request #533.
This commit is contained in:
@@ -32,8 +32,9 @@ func targetTotals(
|
|||||||
|
|
||||||
// TestUpdateDeliveryStatus_FinishTimeAndTargetTotals pins what a status
|
// TestUpdateDeliveryStatus_FinishTimeAndTargetTotals pins what a status
|
||||||
// write records for the webhook page's statistics: the time a delivery
|
// write records for the webhook page's statistics: the time a delivery
|
||||||
// finished, set only when it becomes delivered or failed, and one more
|
// finished, set only when it becomes delivered or failed and stored in
|
||||||
// on its target's delivered or failed total.
|
// the same form as created_at, and one more on its target's delivered
|
||||||
|
// or failed total.
|
||||||
func TestUpdateDeliveryStatus_FinishTimeAndTargetTotals(t *testing.T) {
|
func TestUpdateDeliveryStatus_FinishTimeAndTargetTotals(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -75,6 +76,16 @@ func TestUpdateDeliveryStatus_FinishTimeAndTargetTotals(t *testing.T) {
|
|||||||
if tt.finished {
|
if tt.finished {
|
||||||
require.NotNil(t, stored.FinishedAt)
|
require.NotNil(t, stored.FinishedAt)
|
||||||
assert.False(t, stored.FinishedAt.Before(before))
|
assert.False(t, stored.FinishedAt.Before(before))
|
||||||
|
|
||||||
|
var createdAt, finishedAt string
|
||||||
|
|
||||||
|
require.NoError(t, db.Raw(
|
||||||
|
`SELECT CAST(created_at AS TEXT),
|
||||||
|
CAST(finished_at AS TEXT)
|
||||||
|
FROM deliveries WHERE id = ?`, d.ID,
|
||||||
|
).Row().Scan(&createdAt, &finishedAt))
|
||||||
|
assertStoredTime(t, createdAt)
|
||||||
|
assertStoredTime(t, finishedAt)
|
||||||
} else {
|
} else {
|
||||||
assert.Nil(t, stored.FinishedAt)
|
assert.Nil(t, stored.FinishedAt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1702,7 +1702,10 @@ func writeDeliveryStatus(
|
|||||||
}).
|
}).
|
||||||
Updates(map[string]any{
|
Updates(map[string]any{
|
||||||
"status": status,
|
"status": status,
|
||||||
"finished_at": time.Now(),
|
// GORM's clock, the one that stamps created_at. A bare
|
||||||
|
// time.Now() carries a monotonic clock reading, which the
|
||||||
|
// driver would store as an " m=+…" suffix.
|
||||||
|
"finished_at": tx.NowFunc(),
|
||||||
})
|
})
|
||||||
if res.Error != nil || res.RowsAffected == 0 {
|
if res.Error != nil || res.RowsAffected == 0 {
|
||||||
return res.Error
|
return res.Error
|
||||||
@@ -1906,12 +1909,13 @@ func (e *Engine) takeForRedispatch(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GORM's clock, as writeDeliveryStatus explains.
|
||||||
res := webhookDB.
|
res := webhookDB.
|
||||||
Model(&database.Delivery{}).
|
Model(&database.Delivery{}).
|
||||||
Where(
|
Where(
|
||||||
"id = ? AND status = ?", deliveryID, eligible,
|
"id = ? AND status = ?", deliveryID, eligible,
|
||||||
).
|
).
|
||||||
UpdateColumn("updated_at", time.Now())
|
UpdateColumn("updated_at", webhookDB.NowFunc())
|
||||||
|
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
e.log.Error(
|
e.log.Error(
|
||||||
|
|||||||
@@ -117,6 +117,18 @@ func seedDelivery(
|
|||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assertStoredTime asserts that text, a timestamp as SQLite holds it,
|
||||||
|
// is in the form GORM stores created_at in: the layout of
|
||||||
|
// time.Time.String, with no " m=+…" monotonic clock reading after it.
|
||||||
|
func assertStoredTime(t *testing.T, text string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, err := time.Parse(
|
||||||
|
"2006-01-02 15:04:05.999999999 -0700 MST", text,
|
||||||
|
)
|
||||||
|
assert.NoError(t, err, "stored as %q", text)
|
||||||
|
}
|
||||||
|
|
||||||
// httpDeliveryFixture holds the task and delivery needed
|
// httpDeliveryFixture holds the task and delivery needed
|
||||||
// for HTTP delivery tests.
|
// for HTTP delivery tests.
|
||||||
type httpDeliveryFixture struct {
|
type httpDeliveryFixture struct {
|
||||||
|
|||||||
@@ -462,7 +462,8 @@ func TestScheduledRetryIsNotSweptDuringBackoff(t *testing.T) {
|
|||||||
|
|
||||||
// TestRedispatchStampsTheRow pins the cadence control: a stranded
|
// TestRedispatchStampsTheRow pins the cadence control: a stranded
|
||||||
// delivery that has just been handed out is not selected again by the
|
// delivery that has just been handed out is not selected again by the
|
||||||
// next tick a minute later.
|
// next tick a minute later. The stamp is stored in the same form as
|
||||||
|
// created_at.
|
||||||
func TestRedispatchStampsTheRow(t *testing.T) {
|
func TestRedispatchStampsTheRow(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -493,4 +494,13 @@ func TestRedispatchStampsTheRow(t *testing.T) {
|
|||||||
"a re-dispatched delivery must be stamped so the "+
|
"a re-dispatched delivery must be stamped so the "+
|
||||||
"next tick does not select it again",
|
"next tick does not select it again",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var createdAt, updatedAt string
|
||||||
|
|
||||||
|
require.NoError(t, s.WebhookDB.Raw(
|
||||||
|
`SELECT CAST(created_at AS TEXT), CAST(updated_at AS TEXT)
|
||||||
|
FROM deliveries WHERE id = ?`, d.ID,
|
||||||
|
).Row().Scan(&createdAt, &updatedAt))
|
||||||
|
assertStoredTime(t, createdAt)
|
||||||
|
assertStoredTime(t, updatedAt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,7 +277,11 @@ func (w *archiveWriter) write(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
row.ArchivedAt = time.Now()
|
// GORM's clock, the one that stamps created_at in the other
|
||||||
|
// databases, so this is stored in the same form. A bare time.Now()
|
||||||
|
// carries a monotonic clock reading, which the driver would store
|
||||||
|
// as an " m=+…" suffix.
|
||||||
|
row.ArchivedAt = w.db.NowFunc()
|
||||||
|
|
||||||
err := w.db.Create(&row).Error
|
err := w.db.Create(&row).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -150,6 +150,13 @@ func TestArchiveWriter_WritesRow(t *testing.T) {
|
|||||||
assert.Equal(t, row.ContentType, got[0].ContentType)
|
assert.Equal(t, row.ContentType, got[0].ContentType)
|
||||||
assert.JSONEq(t, `{"hello":"world"}`, got[0].Body)
|
assert.JSONEq(t, `{"hello":"world"}`, got[0].Body)
|
||||||
assert.False(t, got[0].ArchivedAt.IsZero())
|
assert.False(t, got[0].ArchivedAt.IsZero())
|
||||||
|
|
||||||
|
var archivedAt string
|
||||||
|
|
||||||
|
require.NoError(t, w.DB().Raw(
|
||||||
|
"SELECT CAST(archived_at AS TEXT) FROM archived_events",
|
||||||
|
).Row().Scan(&archivedAt))
|
||||||
|
assertStoredTime(t, archivedAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArchiveWriter_RecreatesAfterRemoval(
|
func TestArchiveWriter_RecreatesAfterRemoval(
|
||||||
|
|||||||
Reference in New Issue
Block a user