mirror of
https://github.com/coder/coder.git
synced 2026-10-31 19:36:41 +08:00
The usage generator writes `hb_agent_runtime_v1` rows with `created_at` at the UTC hourly bucket start and exactly one row per bucket, but nothing in the schema enforced either invariant. A duplicate bucket row under a different id would be double-counted by any consumer summing `runtime_ms`, and a misaligned `created_at` would skew which usage period a bucket is attributed to. This replaces the non-unique partial index `idx_usage_events_agent_runtime` (from migration 000561) with a unique index of the same shape and adds an hour-alignment `CHECK` constraint. Both statements validate existing rows: every supported writer has always produced conforming data, so a pre-existing violator is anomalous and failing the migration loudly beats silently rewriting usage rows. `generateBucket` treats a unique violation on the bucket index as another replica having won the race, mirroring the existing `ON CONFLICT (id)` no-op for committed rows. The `coderd/notifications` sync commit and its revert cancel out (the drift they addressed was fixed on main by #27979); the PR's net diff is only the usage-event changes. Part 1 of a 3-PR stack splitting up #27796 (see there for review history). Stack: this PR → #27984 → #27985.
44 lines
1.3 KiB
Go
44 lines
1.3 KiB
Go
package usage
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/lib/pq"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/mock/gomock"
|
|
|
|
"cdr.dev/slog/v3/sloggers/slogtest"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/dbmock"
|
|
"github.com/coder/coder/v2/testutil"
|
|
"github.com/coder/quartz"
|
|
)
|
|
|
|
// TestGenerateBucketUniqueViolation pins that a unique violation on the
|
|
// bucket index resolves the bucket as complete: another writer already
|
|
// recorded it. TestGeneratorConcurrentReplicas also reaches this path, but
|
|
// only when its goroutines actually interleave; this case cannot pass by
|
|
// scheduling accident.
|
|
func TestGenerateBucketUniqueViolation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
ctrl := gomock.NewController(t)
|
|
mDB := dbmock.NewMockStore(ctrl)
|
|
gen := NewGenerator(quartz.NewMock(t), slogtest.Make(t, nil), mDB, NewDBInserter())
|
|
|
|
mDB.EXPECT().
|
|
GetTotalChatMessageRuntimeMsInRange(gomock.Any(), gomock.Any()).
|
|
Return(int64(1000), nil)
|
|
mDB.EXPECT().
|
|
InsertUsageEvent(gomock.Any(), gomock.Any()).
|
|
Return(&pq.Error{
|
|
Code: "23505", // unique_violation
|
|
Constraint: string(database.UniqueIndexUsageEventsAgentRuntime),
|
|
})
|
|
|
|
bucket := time.Date(2025, 3, 10, 10, 0, 0, 0, time.UTC)
|
|
require.NoError(t, gen.generateBucket(ctx, bucket))
|
|
}
|