package store_test import ( "context" "testing" "github.com/stretchr/testify/require" ) func TestSetActionThenActionsFor(t *testing.T) { ctx := context.Background() s := newStore(t) resetDB(t, rawPool(t)) require.NoError(t, s.SetAction(ctx, userA, videoX, "watched")) got, err := s.ActionsFor(ctx, userA, []string{videoX}) require.NoError(t, err) require.Equal(t, map[string][]string{videoX: {"watched"}}, got) } func TestSetActionWatchedSkippedMutuallyExclusive(t *testing.T) { ctx := context.Background() s := newStore(t) resetDB(t, rawPool(t)) require.NoError(t, s.SetAction(ctx, userA, videoX, "watched")) // Switching to skipped must clear watched (and vice versa). require.NoError(t, s.SetAction(ctx, userA, videoX, "skipped")) got, err := s.ActionsFor(ctx, userA, []string{videoX}) require.NoError(t, err) require.Equal(t, map[string][]string{videoX: {"skipped"}}, got, "skipped replaces watched") require.NoError(t, s.SetAction(ctx, userA, videoX, "watched")) got, err = s.ActionsFor(ctx, userA, []string{videoX}) require.NoError(t, err) require.Equal(t, map[string][]string{videoX: {"watched"}}, got, "watched replaces skipped") } func TestSetActionSavedCoexists(t *testing.T) { ctx := context.Background() s := newStore(t) resetDB(t, rawPool(t)) require.NoError(t, s.SetAction(ctx, userA, videoX, "watched")) require.NoError(t, s.SetAction(ctx, userA, videoX, "saved")) got, err := s.ActionsFor(ctx, userA, []string{videoX}) require.NoError(t, err) // ActionsFor orders by action: saved, watched. require.Equal(t, map[string][]string{videoX: {"saved", "watched"}}, got, "saved is independent and coexists with watched") } func TestClearAction(t *testing.T) { ctx := context.Background() s := newStore(t) resetDB(t, rawPool(t)) require.NoError(t, s.SetAction(ctx, userA, videoX, "saved")) require.NoError(t, s.ClearAction(ctx, userA, videoX, "saved")) got, err := s.ActionsFor(ctx, userA, []string{videoX}) require.NoError(t, err) require.Empty(t, got, "cleared action no longer active") // Clearing an action that was never set is a harmless no-op. require.NoError(t, s.ClearAction(ctx, userA, videoX, "watched")) } func TestSetActionIsIdempotent(t *testing.T) { ctx := context.Background() s := newStore(t) p := rawPool(t) resetDB(t, p) require.NoError(t, s.SetAction(ctx, userA, videoX, "watched")) require.NoError(t, s.SetAction(ctx, userA, videoX, "watched")) var count int require.NoError(t, p.QueryRow(ctx, `SELECT count(*) FROM summary_actions WHERE user_id = $1 AND video_id = $2 AND action = 'watched'`, userA, videoX).Scan(&count)) require.Equal(t, 1, count, "re-setting must refresh, not duplicate") } func TestSetActionRejectsInvalid(t *testing.T) { ctx := context.Background() s := newStore(t) resetDB(t, rawPool(t)) require.Error(t, s.SetAction(ctx, userA, videoX, "bookmarked")) require.Error(t, s.ClearAction(ctx, userA, videoX, "")) } func TestActionsAreUserScoped(t *testing.T) { ctx := context.Background() s := newStore(t) resetDB(t, rawPool(t)) require.NoError(t, s.SetAction(ctx, userA, videoX, "watched")) got, err := s.ActionsFor(ctx, userB, []string{videoX}) require.NoError(t, err) require.Empty(t, got, "user B must not see user A's actions") } func TestActionsForMultipleVideos(t *testing.T) { ctx := context.Background() s := newStore(t) resetDB(t, rawPool(t)) require.NoError(t, s.SetAction(ctx, userA, videoX, "watched")) require.NoError(t, s.SetAction(ctx, userA, videoY, "saved")) got, err := s.ActionsFor(ctx, userA, []string{videoX, videoY}) require.NoError(t, err) require.Equal(t, map[string][]string{ videoX: {"watched"}, videoY: {"saved"}, }, got) // Empty input -> empty (non-nil) map, no query. empty, err := s.ActionsFor(ctx, userA, nil) require.NoError(t, err) require.Empty(t, empty) } func TestReadsSurfaceActions(t *testing.T) { ctx := context.Background() s := newStore(t) resetDB(t, rawPool(t)) require.NoError(t, s.Deliver(ctx, summary(userA, videoX, "body"))) require.NoError(t, s.SetAction(ctx, userA, videoX, "watched")) require.NoError(t, s.SetAction(ctx, userA, videoX, "saved")) list, err := s.ListSummaries(ctx, userA, 50) require.NoError(t, err) require.Len(t, list, 1) require.Equal(t, []string{"saved", "watched"}, list[0].Actions, "list view carries current actions") detail, err := s.GetSummaryByVideo(ctx, userA, videoX) require.NoError(t, err) require.Equal(t, []string{"saved", "watched"}, detail.Actions, "detail view carries current actions") } func TestReadsSurfaceNoActionsAsNil(t *testing.T) { ctx := context.Background() s := newStore(t) resetDB(t, rawPool(t)) require.NoError(t, s.Deliver(ctx, summary(userA, videoX, "body"))) list, err := s.ListSummaries(ctx, userA, 50) require.NoError(t, err) require.Len(t, list, 1) require.Nil(t, list[0].Actions, "no actions -> nil slice") }