package store_test import ( "context" "testing" "time" "github.com/jackc/pgx/v5/pgxpool" "github.com/stretchr/testify/require" ) // seedReportUser inserts a user + its identity mapping (the enumeration source // ActiveWeeks reads). display_name is optional. func seedReportUser(t *testing.T, p *pgxpool.Pool, userID, subject, name string) { t.Helper() ctx := context.Background() _, err := p.Exec(ctx, `INSERT INTO users (id, display_name) VALUES ($1, NULLIF($2, ''))`, userID, name) require.NoError(t, err) _, err = p.Exec(ctx, `INSERT INTO user_identities (dex_subject, user_id) VALUES ($1, $2)`, subject, userID) require.NoError(t, err) } // TestActiveWeeksCountsDistinctWeeksAcrossReadsAndActs is the gate-query proof. // It seeds, with fixed timestamps in known ISO weeks: // - user A: reads in week of Jan 5 and Jan 12, acts in week of Jan 12 (dup) and // Jan 19 → the UNION across both tables collapses the shared week → 3 distinct. // - user B: a single read in the week of Jan 5 → 1 distinct (below the gate). // // It verifies the count is correct, dedups the cross-table shared week, and orders // most-active first. func TestActiveWeeksCountsDistinctWeeksAcrossReadsAndActs(t *testing.T) { ctx := context.Background() s := newStore(t) p := rawPool(t) resetDB(t, p) // TRUNCATE ... users CASCADE also clears user_identities seedReportUser(t, p, userA, "subject-a", "Ada") seedReportUser(t, p, userB, "subject-b", "") // Reads (login_events) — fixed dates in distinct ISO weeks. _, err := p.Exec(ctx, `INSERT INTO login_events (user_id, seen_at) VALUES ($1, '2026-01-05T09:00:00Z'), ($1, '2026-01-12T09:00:00Z'), ($2, '2026-01-05T09:00:00Z')`, userA, userB) require.NoError(t, err) // Acts (summary_actions) — one in A's week-of-Jan-12 (shared with a read, must // dedup) and one in a new week (Jan 19). _, err = p.Exec(ctx, `INSERT INTO summary_actions (user_id, video_id, action, acted_at) VALUES ($1, 'vid-1', 'watched', '2026-01-12T18:00:00Z'), ($1, 'vid-2', 'saved', '2026-01-19T18:00:00Z')`, userA) require.NoError(t, err) got, err := s.ActiveWeeks(ctx, time.Time{}) // zero since = no lower bound require.NoError(t, err) require.Len(t, got, 2, "both identity users must appear") require.Equal(t, userA, got[0].UserID, "most-active user first") require.Equal(t, "Ada", got[0].DisplayName) require.Equal(t, 3, got[0].ActiveWeeks, "3 distinct weeks across reads+acts, shared week deduped") require.Equal(t, userB, got[1].UserID) require.Equal(t, 1, got[1].ActiveWeeks, "single read = 1 distinct week (below gate)") } // TestActiveWeeksEmptyWhenNoUsers: no identities → no rows (not an error). func TestActiveWeeksEmptyWhenNoUsers(t *testing.T) { ctx := context.Background() s := newStore(t) resetDB(t, rawPool(t)) got, err := s.ActiveWeeks(ctx, time.Time{}) require.NoError(t, err) require.Empty(t, got) } // TestActiveWeeksExcludesBeforeGateStart proves the baseline cutoff: activity // before `since` does not count, so pre-launch noise (testing, the pilot's blocked // period) is excluded from the Stage-0 return-usage gate (ADR-016). func TestActiveWeeksExcludesBeforeGateStart(t *testing.T) { ctx := context.Background() s := newStore(t) p := rawPool(t) resetDB(t, p) seedReportUser(t, p, userA, "subject-a", "Ada") // One read well before the baseline, two reads in distinct weeks after it. _, err := p.Exec(ctx, `INSERT INTO login_events (user_id, seen_at) VALUES ($1, '2026-05-01T09:00:00Z'), ($1, '2026-06-12T09:00:00Z'), ($1, '2026-06-19T09:00:00Z')`, userA) require.NoError(t, err) since := time.Date(2026, 6, 11, 0, 0, 0, 0, time.UTC) got, err := s.ActiveWeeks(ctx, since) require.NoError(t, err) require.Len(t, got, 1) require.Equal(t, 2, got[0].ActiveWeeks, "only the two post-baseline weeks count; the May read is excluded") }