package ui import ( "os" "path/filepath" "testing" "time" "git.stinnesbeck.com/nils/arr-tui/internal/trakt" ) func TestPostersOnDisk(t *testing.T) { d := &diskCache{dir: t.TempDir()} if _, ok := d.readPoster("k", false); ok { t.Fatal("empty cache returned a poster") } d.writePoster("k", false, []byte("SMALL")) d.writePoster("k", true, []byte("LARGE")) if data, ok := d.readPoster("k", false); !ok || string(data) != "SMALL" { t.Errorf("small: %q %v", data, ok) } if data, ok := d.readPoster("k", true); !ok || string(data) != "LARGE" { t.Errorf("large copies are kept apart: %q %v", data, ok) } if info, _ := os.Stat(filepath.Dir(d.posterPath("k", false))); info.Mode().Perm() != 0o700 { t.Errorf("directory mode %v", info.Mode().Perm()) } // Too old: not used, and pruned. old := time.Now().Add(-posterTTL - time.Hour) _ = os.Chtimes(d.posterPath("k", false), old, old) if _, ok := d.readPoster("k", false); ok { t.Error("an expired poster was used") } d.prunePosters() if _, err := os.Stat(d.posterPath("k", false)); !os.IsNotExist(err) { t.Error("an expired poster was not pruned") } if _, ok := d.readPoster("k", true); !ok { t.Error("a fresh poster was pruned") } // Off: nothing is read or written. off := &diskCache{} off.writePoster("k", false, []byte("x")) if _, ok := off.readPoster("k", false); ok { t.Error("cache without a directory returned a poster") } } func TestPrunePostersKeepsTheNewestWithinTheCap(t *testing.T) { d := &diskCache{dir: t.TempDir()} big := make([]byte, posterCacheCap/2+1) // two of these do not fit for i, key := range []string{"oldest", "middle", "newest"} { d.writePoster(key, false, big) at := time.Now().Add(time.Duration(i-3) * time.Hour) _ = os.Chtimes(d.posterPath(key, false), at, at) } d.prunePosters() for key, want := range map[string]bool{"oldest": false, "middle": false, "newest": true} { if _, ok := d.readPoster(key, false); ok != want { t.Errorf("%s kept: %v, want %v", key, ok, want) } } } func TestPeopleOnDisk(t *testing.T) { d := &diskCache{dir: t.TempDir()} all := map[string]savedPeople{ "movie:tmdb:1": {Saved: time.Now(), Cast: []trakt.Person{{Name: "A", Role: "B"}}, Leaders: []trakt.Person{{Name: "C"}}}, "movie:tmdb:2": {Saved: time.Now().Add(-peopleTTL - time.Hour), Cast: []trakt.Person{{Name: "Old"}}}, "show:tvdb:3": {Saved: time.Now()}, // Trakt had no cast: remembered as such } if err := d.savePeople(all); err != nil { t.Fatal(err) } got := d.loadPeople() if len(got) != 2 || got["movie:tmdb:1"].Cast[0].Role != "B" || got["movie:tmdb:1"].Leaders[0].Name != "C" { t.Errorf("loaded %+v", got) } if _, ok := got["show:tvdb:3"]; !ok { t.Error("an empty cast was not kept") } // A damaged file is ignored. _ = os.WriteFile(d.peoplePath(), []byte("{not json"), 0o600) if len(d.loadPeople()) != 0 { t.Error("damaged file") } } func TestPeopleCacheKeepsTheNewest(t *testing.T) { a := newTestApp(t) for i := 0; i < maxPeopleSaved+5; i++ { a.remember(trakt.Ref{Kind: trakt.Movies, TMDB: i + 1}.Key(), trakt.People{}) a.people.saved[trakt.Ref{Kind: trakt.Movies, TMDB: i + 1}.Key()] = time.Unix(int64(i), 0) } a.people.timer.Stop() if len(a.people.done) != maxPeopleSaved { t.Errorf("%d casts kept", len(a.people.done)) } if _, ok := a.people.done["movie:tmdb:1"]; ok { t.Error("the oldest cast was kept") } if !a.people.dirty || len(a.peopleSnapshot()) != maxPeopleSaved { t.Error("new casts must be written") } } func TestPostersOfOlderVersionsAreDropped(t *testing.T) { d := &diskCache{dir: t.TempDir()} old := filepath.Join(d.dir, "posters", "abc.img") if err := writeAtomic(old, []byte("v1")); err != nil { t.Fatal(err) } d.writePoster("k", true, []byte("v2")) d.prunePosters() if _, err := os.Stat(old); !os.IsNotExist(err) { t.Error("a poster of the old format was kept") } if data, ok := d.readPoster("k", true); !ok || string(data) != "v2" { t.Error("the current poster was dropped") } }