Files
minstrel/internal/playevents/contextual_likes_test.go
bvandeusen fe1cc46752 feat(api): capture contextual_likes on like during open play_event
Two new helpers in internal/playevents (CaptureContextualLikeIfPlaying,
SoftDeleteContextualLikes) called by both api and subsonic surfaces.
Like inserts a new contextual_likes row when LikeTrack actually
inserted (rows=1) AND there's an open play_event with a vector.
Unlike soft-deletes via deleted_at. Idempotent in both directions.

Subsonic wiring lands in the next commit.
2026-04-27 11:22:35 -04:00

78 lines
2.5 KiB
Go

package playevents
import (
"context"
"encoding/json"
"io"
"log/slog"
"testing"
"time"
"github.com/jackc/pgx/v5/pgtype"
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)
func TestCaptureContextualLikeIfPlaying_NoOpenEvent_NoOp(t *testing.T) {
f := newFixture(t, 200_000)
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
q := dbq.New(f.pool)
if err := CaptureContextualLikeIfPlaying(context.Background(), q, f.user, f.track, logger); err != nil {
t.Fatalf("err: %v", err)
}
var count int
_ = f.pool.QueryRow(context.Background(), "SELECT count(*) FROM contextual_likes WHERE user_id=$1", f.user).Scan(&count)
if count != 0 {
t.Errorf("count = %d, want 0 (no open play_event)", count)
}
}
func TestCaptureContextualLikeIfPlaying_OpenEventWithVector_Inserts(t *testing.T) {
f := newFixture(t, 200_000)
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
// Start a play (creates an open play_event with a populated vector).
at := time.Now().UTC()
_, err := f.w.RecordPlayStarted(context.Background(), f.user, f.track, "c", at)
if err != nil {
t.Fatalf("RecordPlayStarted: %v", err)
}
// Like the same track — captures contextual signal.
q := dbq.New(f.pool)
if err := CaptureContextualLikeIfPlaying(context.Background(), q, f.user, f.track, logger); err != nil {
t.Fatalf("Capture: %v", err)
}
var rawVec []byte
if err := f.pool.QueryRow(context.Background(),
"SELECT session_vector FROM contextual_likes WHERE user_id=$1 AND track_id=$2",
f.user, f.track).Scan(&rawVec); err != nil {
t.Fatalf("query: %v", err)
}
var vec map[string]any
_ = json.Unmarshal(rawVec, &vec)
if vec == nil {
t.Errorf("vector empty: %s", rawVec)
}
}
func TestSoftDeleteContextualLikes_MarksActiveRowsDeleted(t *testing.T) {
f := newFixture(t, 200_000)
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
at := time.Now().UTC()
_, _ = f.w.RecordPlayStarted(context.Background(), f.user, f.track, "c", at)
q := dbq.New(f.pool)
_ = CaptureContextualLikeIfPlaying(context.Background(), q, f.user, f.track, logger)
// Now soft-delete.
if err := SoftDeleteContextualLikes(context.Background(), q, f.user, f.track); err != nil {
t.Fatalf("SoftDelete: %v", err)
}
var deletedAt pgtype.Timestamptz
if err := f.pool.QueryRow(context.Background(),
"SELECT deleted_at FROM contextual_likes WHERE user_id=$1 AND track_id=$2",
f.user, f.track).Scan(&deletedAt); err != nil {
t.Fatalf("query: %v", err)
}
if !deletedAt.Valid {
t.Errorf("deleted_at not set after SoftDelete")
}
}