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
189 lines
4.8 KiB
Go
189 lines
4.8 KiB
Go
package delivery_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/gorm"
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
)
|
|
|
|
// targetTotals reads one target's totals from a webhook database, all
|
|
// zero when it has no row.
|
|
func targetTotals(
|
|
t *testing.T, db *gorm.DB, targetID string,
|
|
) database.TargetTotals {
|
|
t.Helper()
|
|
|
|
var rows []database.TargetTotals
|
|
|
|
require.NoError(t, db.Where("target_id = ?", targetID).
|
|
Find(&rows).Error)
|
|
|
|
if len(rows) == 0 {
|
|
return database.TargetTotals{TargetID: targetID}
|
|
}
|
|
|
|
return rows[0]
|
|
}
|
|
|
|
// TestUpdateDeliveryStatus_FinishTimeAndTargetTotals pins what a status
|
|
// write records for the webhook page's statistics: the time a delivery
|
|
// finished, set only when it becomes delivered or failed and stored in
|
|
// the same form as created_at, and one more on its target's delivered
|
|
// or failed total.
|
|
func TestUpdateDeliveryStatus_FinishTimeAndTargetTotals(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
status database.DeliveryStatus
|
|
finished bool
|
|
delivered int64
|
|
failed int64
|
|
}{
|
|
{database.DeliveryStatusRetrying, false, 0, 0},
|
|
{database.DeliveryStatusDelivered, true, 1, 0},
|
|
{database.DeliveryStatusFailed, true, 0, 1},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(string(tt.status), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := testWebhookDB(t)
|
|
e := testEngine(t, 1)
|
|
event := seedEvent(t, db, `{}`)
|
|
targetID := uuid.New().String()
|
|
d := seedDelivery(
|
|
t, db, event.ID, targetID,
|
|
database.DeliveryStatusPending,
|
|
)
|
|
|
|
before := time.Now()
|
|
|
|
require.NoError(t, e.ExportUpdateDeliveryStatus(
|
|
db, &d, tt.status,
|
|
))
|
|
|
|
var stored database.Delivery
|
|
|
|
require.NoError(t, db.First(&stored, "id = ?", d.ID).Error)
|
|
assert.Equal(t, tt.status, stored.Status)
|
|
|
|
if tt.finished {
|
|
require.NotNil(t, stored.FinishedAt)
|
|
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 {
|
|
assert.Nil(t, stored.FinishedAt)
|
|
}
|
|
|
|
assert.Equal(t, database.TargetTotals{
|
|
TargetID: targetID,
|
|
Delivered: tt.delivered,
|
|
Failed: tt.failed,
|
|
}, targetTotals(t, db, targetID))
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestUpdateDeliveryStatus_DeletedDeliveryIsNotCounted covers a
|
|
// delivery retention deleted while the engine still held it. Failing
|
|
// it afterwards writes no row, so it adds no failure either: retention
|
|
// has already counted what it removed.
|
|
func TestUpdateDeliveryStatus_DeletedDeliveryIsNotCounted(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := testWebhookDB(t)
|
|
e := testEngine(t, 1)
|
|
event := seedEvent(t, db, `{}`)
|
|
targetID := uuid.New().String()
|
|
d := seedDelivery(
|
|
t, db, event.ID, targetID,
|
|
database.DeliveryStatusRetrying,
|
|
)
|
|
|
|
require.NoError(t, db.Unscoped().
|
|
Delete(&database.Delivery{}, "id = ?", d.ID).Error)
|
|
|
|
require.NoError(t, e.ExportUpdateDeliveryStatus(
|
|
db, &d, database.DeliveryStatusFailed,
|
|
))
|
|
|
|
assert.Equal(t, database.TargetTotals{TargetID: targetID},
|
|
targetTotals(t, db, targetID))
|
|
}
|
|
|
|
// TestUpdateDeliveryStatus_FinishedDeliveryIsNotSettledAgain covers a
|
|
// delivery settled a second time, as recovery can do when a worker has
|
|
// settled it since recovery read it. Neither status writes over the
|
|
// first, and the totals do not move.
|
|
func TestUpdateDeliveryStatus_FinishedDeliveryIsNotSettledAgain(
|
|
t *testing.T,
|
|
) {
|
|
t.Parallel()
|
|
|
|
finished := []database.DeliveryStatus{
|
|
database.DeliveryStatusDelivered,
|
|
database.DeliveryStatusFailed,
|
|
}
|
|
|
|
for _, first := range finished {
|
|
t.Run(string(first), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := testWebhookDB(t)
|
|
e := testEngine(t, 1)
|
|
event := seedEvent(t, db, `{}`)
|
|
targetID := uuid.New().String()
|
|
d := seedDelivery(
|
|
t, db, event.ID, targetID,
|
|
database.DeliveryStatusRetrying,
|
|
)
|
|
|
|
// The delivery as recovery read it, before the worker
|
|
// settled it.
|
|
readBefore := d
|
|
|
|
require.NoError(t, e.ExportUpdateDeliveryStatus(
|
|
db, &d, first,
|
|
))
|
|
|
|
var settled database.Delivery
|
|
|
|
require.NoError(t, db.First(&settled, "id = ?", d.ID).Error)
|
|
require.NotNil(t, settled.FinishedAt)
|
|
|
|
totals := targetTotals(t, db, targetID)
|
|
|
|
for _, again := range finished {
|
|
stale := readBefore
|
|
|
|
require.NoError(t, e.ExportUpdateDeliveryStatus(
|
|
db, &stale, again,
|
|
))
|
|
}
|
|
|
|
var stored database.Delivery
|
|
|
|
require.NoError(t, db.First(&stored, "id = ?", d.ID).Error)
|
|
assert.Equal(t, first, stored.Status)
|
|
require.NotNil(t, stored.FinishedAt)
|
|
assert.True(t, settled.FinishedAt.Equal(*stored.FinishedAt))
|
|
assert.Equal(t, totals, targetTotals(t, db, targetID))
|
|
})
|
|
}
|
|
}
|