Something went wrong. Try again.
let's backfill your watched movies on popfeed
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212package main
import ( "context" "fmt" "slices" "testing")
type mockMovieFetcher struct { movies map[int]*Movie}
func (m *mockMovieFetcher) FetchMovieDetails(_ context.Context, id int) (*Movie, error) { if id == 0 { return nil, fmt.Errorf("tmdb error") } movie, ok := m.movies[id] if !ok { return nil, nil } return movie, nil}
type mockStorer struct { entries []Entry skipSet map[int]struct{} skipErr error readErr error writeErr error
WrittenEntries []Entry // tracks calls to WriteEntries}
func (m *mockStorer) ReadEntries(_ string) ([]Entry, error) { return m.entries, m.readErr}
func (m *mockStorer) WriteEntries(_ string, entries []Entry) error { m.WrittenEntries = entries return m.writeErr}
func (m *mockStorer) LoadSkipSet(_ string) (map[int]struct{}, error) { return m.skipSet, m.skipErr}
type mockPopfeeder struct { storeErr error calledMovies []int experiencedIDs []int}
func (m *mockPopfeeder) Review(_ context.Context, mv Movie, _ int) error { m.calledMovies = append(m.calledMovies, mv.ID) return m.storeErr}
func (m *mockPopfeeder) MarkExperienced(_ context.Context, mv Movie) error { m.experiencedIDs = append(m.experiencedIDs, mv.ID) return nil}
func TestDecideAction(t *testing.T) { movies := map[int]*Movie{ 1: {ID: 1, Title: "Alpha", Director: "Alice"}, 2: {ID: 2, Title: "Beta"}, 3: {ID: 3, Title: "Gamma"}, } tests := []struct { name string entry Entry movie *Movie want backfillDecision }{ { name: "nil movie skips", entry: Entry{ID: 1, Seen: true, Rating: 8}, movie: nil, want: backfillDecision{action: actionSkip}, }, { name: "seen with rating reviews", entry: Entry{ID: 1, Seen: true, Rating: 8}, movie: movies[1], want: backfillDecision{action: actionReview, movie: *movies[1], rating: 8}, }, { name: "seen without rating marks experienced", entry: Entry{ID: 2, Seen: true}, movie: movies[2], want: backfillDecision{action: actionMarkExperienced, movie: *movies[2]}, }, { name: "seen with zero rating marks experienced", entry: Entry{ID: 3, Seen: true, Rating: 0}, movie: movies[3], want: backfillDecision{action: actionMarkExperienced, movie: *movies[3]}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := decideAction(tt.entry, tt.movie) if got.action != tt.want.action || got.rating != tt.want.rating { t.Errorf("decideAction() = %+v, want %+v", got, tt.want) } if tt.want.action != actionSkip && got.movie.ID != tt.want.movie.ID { t.Errorf("decideAction() movie = %+v, want %+v", got.movie, tt.want.movie) } }) }}
func TestRunBackfill(t *testing.T) { movies := map[int]*Movie{ 1: {ID: 1, Title: "Alpha", Director: "Alice"}, 2: {ID: 2, Title: "Beta", Director: "Bob", Genres: []string{"Drama"}}, }
tests := []struct { name string entries []Entry storeErr error wantStored []int wantExperienced []int wantErr bool }{ { name: "no entries", entries: nil, wantStored: nil, }, { name: "all unseen", entries: []Entry{{ID: 1, Seen: false}}, wantStored: nil, }, { name: "seen no rating", entries: []Entry{{ID: 1, Seen: true}}, wantExperienced: []int{1}, }, { name: "seen with rating", entries: []Entry{{ID: 1, Seen: true, Rating: 8}}, wantStored: []int{1}, }, { name: "multiple entries", entries: []Entry{{ID: 1, Seen: true, Rating: 8}, {ID: 2, Seen: true, Rating: 5}}, wantStored: []int{1, 2}, }, { name: "tmdb not found", entries: []Entry{{ID: 999, Seen: true}}, wantStored: nil, }, { name: "tmdb error", entries: []Entry{{ID: 0, Seen: true}}, wantStored: nil, }, { name: "conflict skips", entries: []Entry{{ID: 1, Seen: true, Rating: 8}}, storeErr: ErrAlreadyExists, wantStored: []int{1}, }, { name: "review error", entries: []Entry{{ID: 1, Seen: true, Rating: 8}}, storeErr: fmt.Errorf("network error"), wantStored: []int{1}, wantErr: false, // backfill doesn't return error on individual failures }, { name: "mark experienced", entries: []Entry{{ID: 2, Seen: true}}, wantExperienced: []int{2}, }, { name: "mix seen and unseen", entries: []Entry{ {ID: 1, Seen: false}, {ID: 2, Seen: true, Rating: 7}, {ID: 999, Seen: true}, }, wantStored: []int{2}, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { store := &mockStorer{entries: tt.entries} tmdb := &mockMovieFetcher{movies: movies} pf := &mockPopfeeder{storeErr: tt.storeErr}
err := runBackfill(store, tmdb, pf, "test.csv") if (err != nil) != tt.wantErr { t.Fatalf("runBackfill() = %v, wantErr %v", err, tt.wantErr) }
if !slices.Equal(pf.calledMovies, tt.wantStored) { t.Errorf("stored movies = %v, want %v", pf.calledMovies, tt.wantStored) } if !slices.Equal(pf.experiencedIDs, tt.wantExperienced) { t.Errorf("experienced movies = %v, want %v", pf.experiencedIDs, tt.wantExperienced) } }) }}