mirror of
https://github.com/beetbox/beets.git
synced 2026-03-27 15:54:22 +01:00
254 lines
7.5 KiB
Python
254 lines
7.5 KiB
Python
import optparse
|
|
import shutil
|
|
from collections.abc import Generator
|
|
from pathlib import Path
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
from mediafile import MediaFile
|
|
|
|
from beets import ui
|
|
from beets.library import Item, Library
|
|
from beets.library.exceptions import WriteError
|
|
from beets.test import _common
|
|
from beets.test.helper import TestHelper
|
|
from beetsplug.replace import ReplacePlugin
|
|
|
|
replace = ReplacePlugin()
|
|
|
|
|
|
def always(x):
|
|
return lambda *args, **kwargs: x
|
|
|
|
|
|
def always_raise(x):
|
|
def err(*args, **kwargs):
|
|
raise x
|
|
|
|
return err
|
|
|
|
|
|
class TestReplace:
|
|
@pytest.fixture
|
|
def mp3_file(self, tmp_path) -> Path:
|
|
dest = tmp_path / "full.mp3"
|
|
src = Path(_common.RSRC.decode()) / "full.mp3"
|
|
shutil.copyfile(src, dest)
|
|
|
|
mediafile = MediaFile(dest)
|
|
mediafile.title = "AAA"
|
|
mediafile.save()
|
|
|
|
return dest
|
|
|
|
@pytest.fixture
|
|
def opus_file(self, tmp_path) -> Path:
|
|
dest = tmp_path / "full.opus"
|
|
src = Path(_common.RSRC.decode()) / "full.opus"
|
|
shutil.copyfile(src, dest)
|
|
|
|
return dest
|
|
|
|
@pytest.fixture
|
|
def library(self) -> Generator[Library]:
|
|
helper = TestHelper()
|
|
helper.setup_beets()
|
|
|
|
yield helper.lib
|
|
|
|
helper.teardown_beets()
|
|
|
|
def test_run_replace_too_few_args(self):
|
|
with pytest.raises(ui.UserError):
|
|
replace.run(None, optparse.Values(), [])
|
|
|
|
def test_run_replace_no_matches(self, library):
|
|
with pytest.raises(ui.UserError):
|
|
replace.run(library, optparse.Values(), ["BBB", ""])
|
|
|
|
def test_run_replace_no_song_selected(
|
|
self, library, mp3_file, opus_file, monkeypatch
|
|
):
|
|
monkeypatch.setattr(replace, "file_check", always(None))
|
|
monkeypatch.setattr(replace, "select_song", always(None))
|
|
|
|
item = Item.from_path(mp3_file)
|
|
library.add(item)
|
|
|
|
replace.run(library, optparse.Values(), ["AAA", str(opus_file)])
|
|
|
|
assert mp3_file.exists()
|
|
assert opus_file.exists()
|
|
|
|
def test_run_replace_not_confirmed(
|
|
self, library, mp3_file, opus_file, monkeypatch
|
|
):
|
|
monkeypatch.setattr(replace, "file_check", always(None))
|
|
monkeypatch.setattr(replace, "confirm_replacement", always(False))
|
|
|
|
item = Item.from_path(mp3_file)
|
|
library.add(item)
|
|
|
|
monkeypatch.setattr(replace, "select_song", always(item))
|
|
|
|
replace.run(library, optparse.Values(), ["AAA", str(opus_file)])
|
|
|
|
assert mp3_file.exists()
|
|
assert opus_file.exists()
|
|
|
|
def test_run_replace(self, library, mp3_file, opus_file, monkeypatch):
|
|
replace_file = Mock(replace.replace_file, return_value=None)
|
|
monkeypatch.setattr(replace, "replace_file", replace_file)
|
|
|
|
monkeypatch.setattr(replace, "file_check", always(None))
|
|
monkeypatch.setattr(replace, "confirm_replacement", always(True))
|
|
|
|
item = Item.from_path(mp3_file)
|
|
library.add(item)
|
|
|
|
monkeypatch.setattr(replace, "select_song", always(item))
|
|
|
|
replace.run(library, optparse.Values(), ["AAA", str(opus_file)])
|
|
|
|
replace_file.assert_called_once()
|
|
|
|
def test_path_is_dir(self, tmp_path):
|
|
fake_directory = tmp_path / "fakeDir"
|
|
fake_directory.mkdir()
|
|
with pytest.raises(ui.UserError):
|
|
replace.file_check(fake_directory)
|
|
|
|
def test_path_is_unsupported_file(self, tmp_path):
|
|
fake_file = tmp_path / "fakefile.txt"
|
|
fake_file.write_text("test", encoding="utf-8")
|
|
with pytest.raises(ui.UserError):
|
|
replace.file_check(fake_file)
|
|
|
|
def test_path_is_supported_file(self, mp3_file):
|
|
replace.file_check(mp3_file)
|
|
|
|
def test_select_song_valid_choice(self, monkeypatch, capfd):
|
|
songs = ["Song A", "Song B", "Song C"]
|
|
monkeypatch.setattr("builtins.input", lambda _: "2")
|
|
|
|
selected_song = replace.select_song(songs)
|
|
|
|
captured = capfd.readouterr()
|
|
|
|
assert "1. Song A" in captured.out
|
|
assert "2. Song B" in captured.out
|
|
assert "3. Song C" in captured.out
|
|
assert selected_song == "Song B"
|
|
|
|
def test_select_song_cancel(self, monkeypatch):
|
|
songs = ["Song A", "Song B", "Song C"]
|
|
monkeypatch.setattr("builtins.input", lambda _: "0")
|
|
|
|
selected_song = replace.select_song(songs)
|
|
|
|
assert selected_song is None
|
|
|
|
def test_select_song_invalid_then_valid(self, monkeypatch, capfd):
|
|
songs = ["Song A", "Song B", "Song C"]
|
|
inputs = iter(["invalid", "4", "3"])
|
|
monkeypatch.setattr("builtins.input", lambda _: next(inputs))
|
|
|
|
selected_song = replace.select_song(songs)
|
|
|
|
captured = capfd.readouterr()
|
|
|
|
assert "Invalid input. Please type in a number." in captured.out
|
|
assert (
|
|
"Invalid choice. Please enter a number between 1 and 3."
|
|
in captured.out
|
|
)
|
|
assert selected_song == "Song C"
|
|
|
|
def test_confirm_replacement_file_not_exist(self):
|
|
class Song:
|
|
path = b"test123321.txt"
|
|
|
|
song = Song()
|
|
|
|
with pytest.raises(ui.UserError):
|
|
replace.confirm_replacement("test", song)
|
|
|
|
def test_confirm_replacement_yes(self, monkeypatch):
|
|
src = Path(_common.RSRC.decode()) / "full.mp3"
|
|
monkeypatch.setattr("builtins.input", lambda _: "YES ")
|
|
|
|
class Song:
|
|
path = str(src).encode()
|
|
|
|
song = Song()
|
|
|
|
assert replace.confirm_replacement("test", song) is True
|
|
|
|
def test_confirm_replacement_no(self, monkeypatch):
|
|
src = Path(_common.RSRC.decode()) / "full.mp3"
|
|
monkeypatch.setattr("builtins.input", lambda _: "test123")
|
|
|
|
class Song:
|
|
path = str(src).encode()
|
|
|
|
song = Song()
|
|
|
|
assert replace.confirm_replacement("test", song) is False
|
|
|
|
def test_replace_file_move_fails(self, tmp_path):
|
|
item = Item()
|
|
item.path = bytes(tmp_path / "not_a_song.mp3")
|
|
|
|
with pytest.raises(ui.UserError):
|
|
replace.replace_file(tmp_path / "not_a_file.opus", item)
|
|
|
|
def test_replace_file_delete_fails(
|
|
self, library, mp3_file, opus_file, monkeypatch
|
|
):
|
|
monkeypatch.setattr(Path, "unlink", always_raise(OSError))
|
|
|
|
item = Item.from_path(mp3_file)
|
|
library.add(item)
|
|
|
|
with pytest.raises(ui.UserError):
|
|
replace.replace_file(opus_file, item)
|
|
|
|
def test_replace_file_write_fails(
|
|
self, library, mp3_file, opus_file, monkeypatch
|
|
):
|
|
monkeypatch.setattr(
|
|
Item, "write", always_raise(WriteError("path", "reason"))
|
|
)
|
|
|
|
item = Item.from_path(mp3_file)
|
|
library.add(item)
|
|
|
|
with pytest.raises(ui.UserError):
|
|
replace.replace_file(opus_file, item)
|
|
|
|
def test_replace_file(
|
|
self, mp3_file: Path, opus_file: Path, library: Library
|
|
):
|
|
old_mediafile = MediaFile(mp3_file)
|
|
old_mediafile.albumartist = "ABC"
|
|
old_mediafile.disctitle = "DEF"
|
|
old_mediafile.genre = "GHI"
|
|
old_mediafile.save()
|
|
|
|
item = Item.from_path(mp3_file)
|
|
library.add(item)
|
|
|
|
replace.replace_file(opus_file, item)
|
|
|
|
# Check that the file has been replaced.
|
|
assert opus_file.exists()
|
|
assert not mp3_file.exists()
|
|
|
|
# Check that the database path has been updated.
|
|
assert item.path == bytes(opus_file)
|
|
|
|
# Check that the new file has the old file's metadata.
|
|
new_mediafile = MediaFile(opus_file)
|
|
assert new_mediafile.albumartist == "ABC"
|
|
assert new_mediafile.disctitle == "DEF"
|
|
assert new_mediafile.genre == "GHI"
|