Files
tapir/internal/adapters/store/summarize_mode_test.go
T
mathiasandClaude Sonnet 4.6 38f222c931
CI / Lint / Test / Vet (push) Successful in 11s
CI / Build & Import (push) Successful in 12s
chore: rename Go module path gitea.d-ma.be → git.d-ma.be
Infra ADR-0004 renamed the Gitea host. Bulk replace across go.mod and
all .go import paths. Build and tests pass unchanged.

Closes #20

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dt6aHEDWRjkK14Voi6HnGh
2026-07-02 14:37:33 +02:00

182 lines
6.3 KiB
Go

package store_test
import (
"context"
"testing"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/require"
"git.d-ma.be/mathias/tapir/internal/adapters/store"
)
// seedBareVideo inserts a videos row with no summary, so the all-videos read and
// the manual-queue flag can be exercised without a delivered summary.
func seedBareVideo(t *testing.T, p *pgxpool.Pool, userID, videoID, title string) {
t.Helper()
_, err := p.Exec(context.Background(),
`INSERT INTO users (id) VALUES ($1) ON CONFLICT (id) DO NOTHING`, userID)
require.NoError(t, err)
_, err = p.Exec(context.Background(),
`INSERT INTO videos (id, user_id, provider, provider_video_id, title)
VALUES ($1, $2, 'youtube', $3, $4)`,
videoID, userID, "pv-"+videoID[:8], title)
require.NoError(t, err)
}
func TestAutoSummarizeRoundTripDefaultsFalse(t *testing.T) {
ctx := context.Background()
s := newStore(t)
resetDB(t, rawPool(t))
// Unknown / fresh user defaults to manual (false).
got, err := s.GetAutoSummarize(ctx, userA)
require.NoError(t, err)
require.False(t, got, "default mode is manual")
require.NoError(t, s.SetAutoSummarize(ctx, userA, true))
got, err = s.GetAutoSummarize(ctx, userA)
require.NoError(t, err)
require.True(t, got, "set to automatic round-trips")
require.NoError(t, s.SetAutoSummarize(ctx, userA, false))
got, err = s.GetAutoSummarize(ctx, userA)
require.NoError(t, err)
require.False(t, got, "set back to manual round-trips")
}
func TestRegisteredUserDefaultsAutoSummarizeOn(t *testing.T) {
ctx := context.Background()
s := newStore(t)
resetDB(t, rawPool(t))
// A real registration creates the users row, so the column default (migration
// 011: TRUE) drives the mode — onboarded friends get auto out of the box.
id, err := s.RegisterUser(ctx, subjectA, "Alice")
require.NoError(t, err)
got, err := s.GetAutoSummarize(ctx, id)
require.NoError(t, err)
require.True(t, got, "new registrations default to auto-summarize (ADR-018)")
// The account-page toggle still works: a user can switch to manual.
require.NoError(t, s.SetAutoSummarize(ctx, id, false))
got, err = s.GetAutoSummarize(ctx, id)
require.NoError(t, err)
require.False(t, got, "the manual toggle still flips it off")
}
func TestRequestSummarizeSetsFlag(t *testing.T) {
ctx := context.Background()
s := newStore(t)
p := rawPool(t)
resetDB(t, p)
seedBareVideo(t, p, userA, videoX, "X Title")
require.NoError(t, s.RequestSummarize(ctx, userA, videoX))
requested, err := s.RequestedVideoIDs(ctx, userA)
require.NoError(t, err)
require.Equal(t, map[string]bool{videoX: true}, requested)
// Clearing drops it from the requested set.
require.NoError(t, s.ClearSummarizeRequested(ctx, userA, videoX))
requested, err = s.RequestedVideoIDs(ctx, userA)
require.NoError(t, err)
require.Empty(t, requested)
}
func TestRequestSummarizeMissingVideo(t *testing.T) {
ctx := context.Background()
s := newStore(t)
resetDB(t, rawPool(t))
err := s.RequestSummarize(ctx, userA, videoX)
require.ErrorIs(t, err, store.ErrNotFound, "queuing a non-existent video reports not found")
}
func TestListVideosReturnsSummarizedAndUnsummarized(t *testing.T) {
ctx := context.Background()
s := newStore(t)
p := rawPool(t)
resetDB(t, p)
// videoX: discovered AND summarized. videoY: discovered, not yet summarized.
seedBareVideo(t, p, userA, videoX, "Summarized One")
seedBareVideo(t, p, userA, videoY, "Pending One")
require.NoError(t, s.Deliver(ctx, summary(userA, videoX, "body x")))
require.NoError(t, s.RequestSummarize(ctx, userA, videoY))
rows, err := s.ListVideos(ctx, userA, 50)
require.NoError(t, err)
require.Len(t, rows, 2, "both summarized and unsummarized videos are listed")
byID := map[string]store.SummaryRow{}
for _, r := range rows {
byID[r.VideoID] = r
}
require.True(t, byID[videoX].Summarized)
require.Equal(t, "body x", byID[videoX].Summary)
require.False(t, byID[videoX].SummarizeRequested)
require.False(t, byID[videoY].Summarized, "no summary -> Summarized false")
require.Empty(t, byID[videoY].Summary, "unsummarized row has empty summary")
require.True(t, byID[videoY].SummarizeRequested, "queued video carries the flag")
}
// TestListVideosOrderedByPublishedDescNullsLast: summarized videos sort first
// (regardless of their date), then unsummarized by published_at DESC with
// undated (NULL) videos last — the recency-aligned list order (UX review B2).
func TestListVideosOrderedByPublishedDescNullsLast(t *testing.T) {
ctx := context.Background()
s := newStore(t)
p := rawPool(t)
resetDB(t, p)
const (
vSummOld = "cccccccc-cccc-cccc-cccc-cccccccccccc" // summarized, oldest date
vNewer = "dddddddd-dddd-dddd-dddd-dddddddddddd" // unsummarized, newest
vOlder = "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee" // unsummarized, older
vUndated = "ffffffff-ffff-ffff-ffff-ffffffffffff" // unsummarized, no date
)
// Deliver first so the userA row exists (the videos FK needs it); the
// summarized video carries the OLDEST date yet must still sort first because
// it is summarized, proving summarized-first dominates the date sort.
require.NoError(t, s.Deliver(ctx, summary(userA, vSummOld, "body")))
seedVideo(t, p, userA, vSummOld, "Summarized Old", "youtube", "https://s", time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC))
seedVideo(t, p, userA, vNewer, "Newer", "youtube", "https://n", time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC))
seedVideo(t, p, userA, vOlder, "Older", "youtube", "https://o", time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC))
seedVideo(t, p, userA, vUndated, "Undated", "youtube", "https://u", time.Time{})
rows, err := s.ListVideos(ctx, userA, 50)
require.NoError(t, err)
require.Len(t, rows, 4)
got := []string{rows[0].VideoID, rows[1].VideoID, rows[2].VideoID, rows[3].VideoID}
require.Equal(t, []string{vSummOld, vNewer, vOlder, vUndated}, got,
"summarized first, then published_at DESC, NULL dates last")
}
func TestListVideosIsUserScoped(t *testing.T) {
ctx := context.Background()
s := newStore(t)
p := rawPool(t)
resetDB(t, p)
seedBareVideo(t, p, userA, videoX, "A only")
rows, err := s.ListVideos(ctx, userB, 50)
require.NoError(t, err)
require.Empty(t, rows, "user B must not see user A's videos")
}
func TestGetVideoRowNotFound(t *testing.T) {
ctx := context.Background()
s := newStore(t)
resetDB(t, rawPool(t))
_, err := s.GetVideoRow(ctx, userA, videoX)
require.ErrorIs(t, err, store.ErrNotFound)
}