package cmd import ( "fmt" "strings" "testing" "gitea.dcglab.co.uk/steve/arrman/radarr" ) type mockRadarr struct { movies []radarr.Movie added *radarr.Movie profiles []radarr.QualityProfile folders []radarr.RootFolder lookupFn func(term string) ([]radarr.Movie, error) lookupIMDB func(id string) (*radarr.Movie, error) addErr error } func (m *mockRadarr) List() ([]radarr.Movie, error) { return m.movies, nil } func (m *mockRadarr) Lookup(term string) ([]radarr.Movie, error) { if m.lookupFn != nil { return m.lookupFn(term) } return m.movies, nil } func (m *mockRadarr) LookupIMDB(imdbID string) (*radarr.Movie, error) { if m.lookupIMDB != nil { return m.lookupIMDB(imdbID) } return nil, fmt.Errorf("not found") } func (m *mockRadarr) Get(id int) (*radarr.Movie, error) { for _, mv := range m.movies { if mv.ID == id { return &mv, nil } } return nil, nil } func (m *mockRadarr) QualityProfiles() ([]radarr.QualityProfile, error) { return m.profiles, nil } func (m *mockRadarr) RootFolders() ([]radarr.RootFolder, error) { return m.folders, nil } func (m *mockRadarr) Add(mv radarr.Movie) (*radarr.Movie, error) { if m.addErr != nil { return nil, m.addErr } m.added = &mv mv.ID = 1 return &mv, nil } func TestFilmListEmpty(t *testing.T) { mock := &mockRadarr{} out := captureOutput(func() { FilmList(mock, "", false, false) }) if !strings.Contains(out, "No films found") { t.Errorf("expected 'No films found', got: %s", out) } } func TestFilmListWithItems(t *testing.T) { mock := &mockRadarr{ movies: []radarr.Movie{ {Title: "Dune", Year: 2021, Status: "released", HasFile: true}, }, } out := captureOutput(func() { FilmList(mock, "", false, false) }) if !strings.Contains(out, "Dune (2021)") { t.Errorf("expected movie in output, got: %s", out) } if !strings.Contains(out, "HasFile: yes") { t.Errorf("expected HasFile: yes, got: %s", out) } } func TestFilmAddByIMDB(t *testing.T) { mock := &mockRadarr{ lookupIMDB: func(id string) (*radarr.Movie, error) { if id == "tt26443616" { return &radarr.Movie{Title: "Test Film", Year: 2024, ImdbID: "tt26443616", TmdbID: 12345}, nil } return nil, fmt.Errorf("not found") }, } out := captureOutput(func() { err := FilmAdd(mock, []string{"tt26443616", "--profile", "1", "--rootfolder", "/movies"}, false) if err != nil { t.Fatal(err) } }) if !strings.Contains(out, "Added: Test Film") { t.Errorf("expected confirmation, got: %s", out) } if mock.added == nil { t.Fatal("expected Add to be called") } if mock.added.MinimumAvailability != "released" { t.Errorf("expected minimumAvailability=released, got %s", mock.added.MinimumAvailability) } } func TestFilmAddMissingFlags(t *testing.T) { mock := &mockRadarr{} err := FilmAdd(mock, []string{"tt26443616"}, false) if err == nil { t.Fatal("expected error for missing flags") } } func TestFilmAddIMDBNotFound(t *testing.T) { mock := &mockRadarr{ lookupIMDB: func(id string) (*radarr.Movie, error) { return nil, fmt.Errorf("not found") }, } err := FilmAdd(mock, []string{"tt0000000", "--profile", "1", "--rootfolder", "/movies"}, false) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), "No film found with IMDB ID tt0000000") { t.Errorf("unexpected error: %v", err) } } func TestFilmAddByTitle(t *testing.T) { mock := &mockRadarr{ lookupFn: func(term string) ([]radarr.Movie, error) { return []radarr.Movie{ {Title: "Dune", Year: 2021, ImdbID: "tt1160419"}, }, nil }, } out := captureOutput(func() { err := FilmAdd(mock, []string{"Dune", "--profile", "1", "--rootfolder", "/movies"}, false) if err != nil { t.Fatal(err) } }) if !strings.Contains(out, "Added: Dune") { t.Errorf("expected confirmation, got: %s", out) } } func TestFilmAddFuzzyMatch(t *testing.T) { mock := &mockRadarr{ lookupFn: func(term string) ([]radarr.Movie, error) { return []radarr.Movie{ {Title: "Dune", Year: 2021, ImdbID: "tt1160419"}, {Title: "Dune: Part Two", Year: 2024, ImdbID: "tt15239678"}, }, nil }, } out := captureOutput(func() { err := FilmAdd(mock, []string{"Dun", "--profile", "1", "--rootfolder", "/movies"}, false) if err != nil { t.Fatal(err) } }) if !strings.Contains(out, "Added: Dune") { t.Errorf("expected fuzzy match to Dune, got: %s", out) } } func TestFilmAddNoMatch(t *testing.T) { mock := &mockRadarr{ lookupFn: func(term string) ([]radarr.Movie, error) { return []radarr.Movie{ {Title: "Something Completely Different", Year: 2020, ImdbID: "tt0000001"}, }, nil }, } err := FilmAdd(mock, []string{"xyz", "--profile", "1", "--rootfolder", "/movies"}, false) if err == nil { t.Fatal("expected error for no match") } } func TestFilmProfiles(t *testing.T) { mock := &mockRadarr{ profiles: []radarr.QualityProfile{ {ID: 1, Name: "HD-1080p"}, }, } out := captureOutput(func() { FilmProfiles(mock, false) }) if !strings.Contains(out, "ID: 1 — Name: HD-1080p") { t.Errorf("expected profile output, got: %s", out) } } func TestFilmRootFolders(t *testing.T) { mock := &mockRadarr{ folders: []radarr.RootFolder{ {ID: 1, Path: "/movies", FreeSpace: 214748364800}, }, } out := captureOutput(func() { FilmRootFolders(mock, false) }) if !strings.Contains(out, "Path: /movies") { t.Errorf("expected folder output, got: %s", out) } }