Files
nilsandClaude Opus 5.5 ca8c71fa52 Disk cache: version the poster folder
Posters stored by the previous build (the downloaded bytes, for real
images Radarr's 333x500 copy) were reused as they were for 30 days.
Posters now live in posters-v2/; older folders are deleted at startup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 11:41:11 +02:00

124 lines
3.9 KiB
Go

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")
}
}