Tweaks to FileImporter and tests
This commit is contained in:
parent
1f4e7cb054
commit
24787578bc
@ -119,9 +119,6 @@ class FileImporter:
|
||||
# Data structure to track files to import
|
||||
self.import_files_data: list[TrackFileData] = []
|
||||
|
||||
# Dictionary of exsting tracks indexed by track.id
|
||||
self.existing_tracks = self._get_existing_tracks()
|
||||
|
||||
# Get signals
|
||||
self.signals = MusicMusterSignals()
|
||||
|
||||
@ -149,6 +146,10 @@ class FileImporter:
|
||||
)
|
||||
return
|
||||
|
||||
# Refresh list of existing tracks as they may have been updated
|
||||
# by previous imports
|
||||
self.existing_tracks = self._get_existing_tracks()
|
||||
|
||||
for infile in [
|
||||
os.path.join(Config.REPLACE_FILES_DEFAULT_SOURCE, f)
|
||||
for f in os.listdir(Config.REPLACE_FILES_DEFAULT_SOURCE)
|
||||
|
||||
@ -1,20 +1,32 @@
|
||||
"""
|
||||
Tests are named 'test_nnn_xxxx' where 'nn n' is a number. This is used to ensure that
|
||||
the tests run in order as we rely (in some cases) upon the results of an earlier test.
|
||||
Yes, we shouldn't do that.
|
||||
"""
|
||||
|
||||
# Standard library imports
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
# PyQt imports
|
||||
from PyQt6.QtWidgets import QDialog
|
||||
|
||||
# Third party imports
|
||||
import pytest
|
||||
from pytestqt.plugin import QtBot # type: ignore
|
||||
|
||||
# App imports
|
||||
from config import Config
|
||||
from app import musicmuster
|
||||
from app.models import (
|
||||
db,
|
||||
Playlists,
|
||||
Tracks,
|
||||
)
|
||||
from app import musicmuster
|
||||
from config import Config
|
||||
from file_importer import FileImporter
|
||||
|
||||
|
||||
# Custom fixture to adapt qtbot for use with unittest.TestCase
|
||||
@ -24,59 +36,248 @@ def qtbot_adapter(qapp, request):
|
||||
request.cls.qtbot = QtBot(request)
|
||||
|
||||
|
||||
# Wrapper to handle setup/teardown operations
|
||||
def with_updown(function):
|
||||
def test_wrapper(self, *args, **kwargs):
|
||||
if callable(getattr(self, "up", None)):
|
||||
self.up()
|
||||
try:
|
||||
function(self, *args, **kwargs)
|
||||
finally:
|
||||
if callable(getattr(self, "down", None)):
|
||||
self.down()
|
||||
|
||||
test_wrapper.__doc__ = function.__doc__
|
||||
return test_wrapper
|
||||
|
||||
|
||||
# Apply the custom fixture to the test class
|
||||
@pytest.mark.usefixtures("qtbot_adapter")
|
||||
class MyTestCase(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Runs once before any test in this class"""
|
||||
|
||||
def up(self):
|
||||
db.create_all()
|
||||
self.widget = musicmuster.Window()
|
||||
|
||||
cls.widget = musicmuster.Window()
|
||||
|
||||
# Create a playlist for all tests
|
||||
playlist_name = "file importer playlist"
|
||||
|
||||
with db.Session() as session:
|
||||
playlist = Playlists(session, playlist_name)
|
||||
self.widget.create_playlist_tab(playlist)
|
||||
with self.qtbot.waitExposed(self.widget):
|
||||
self.widget.show()
|
||||
cls.widget.create_playlist_tab(playlist)
|
||||
|
||||
# Create our musicstore
|
||||
cls.import_source = tempfile.mkdtemp(suffix="_MMsource_pytest", dir="/tmp")
|
||||
Config.REPLACE_FILES_DEFAULT_SOURCE = cls.import_source
|
||||
cls.musicstore = tempfile.mkdtemp(suffix="_MMstore_pytest", dir="/tmp")
|
||||
Config.IMPORT_DESTINATION = cls.musicstore
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""Runs once after all tests"""
|
||||
|
||||
def down(self):
|
||||
db.drop_all()
|
||||
shutil.rmtree(cls.musicstore)
|
||||
shutil.rmtree(cls.import_source)
|
||||
|
||||
@with_updown
|
||||
@patch("file_importer.show_OK")
|
||||
def test_import_no_files(self, mock_show_ok):
|
||||
def setUp(self):
|
||||
"""Runs before each test"""
|
||||
|
||||
with self.qtbot.waitExposed(self.widget):
|
||||
self.widget.show()
|
||||
|
||||
def tearDown(self):
|
||||
"""Runs after each test"""
|
||||
self.widget.close() # Close UI to prevent side effects
|
||||
|
||||
def test_001_import_no_files(self):
|
||||
"""Try importing with no files to import"""
|
||||
|
||||
self.widget.import_files_wrapper()
|
||||
mock_show_ok.assert_called_once_with(
|
||||
"File import",
|
||||
f"No files in {Config.REPLACE_FILES_DEFAULT_SOURCE} to import",
|
||||
None,
|
||||
)
|
||||
# @with_updown
|
||||
# def test_import_no_files(self):
|
||||
# """Try importing with no files to import"""
|
||||
with patch("file_importer.show_OK") as mock_show_ok:
|
||||
self.widget.import_files_wrapper()
|
||||
mock_show_ok.assert_called_once_with(
|
||||
"File import",
|
||||
f"No files in {Config.REPLACE_FILES_DEFAULT_SOURCE} to import",
|
||||
None,
|
||||
)
|
||||
|
||||
# with patch("file_importer.show_OK") as mock_show_ok:
|
||||
# self.widget.import_files_wrapper()
|
||||
# mock_show_ok.assert_called_once_with(
|
||||
# "File import",
|
||||
# f"No files in {Config.REPLACE_FILES_DEFAULT_SOURCE} to import",
|
||||
# None,
|
||||
# )
|
||||
def test_002_import_file_and_cancel(self):
|
||||
"""Cancel file import"""
|
||||
|
||||
test_track_path = "testdata/isa.mp3"
|
||||
shutil.copy(test_track_path, self.import_source)
|
||||
|
||||
with (
|
||||
patch("file_importer.PickMatch") as MockPickMatch,
|
||||
patch("file_importer.show_OK") as mock_show_ok,
|
||||
):
|
||||
# Create a mock instance of PickMatch
|
||||
mock_dialog_instance = MagicMock()
|
||||
MockPickMatch.return_value = mock_dialog_instance
|
||||
|
||||
# Simulate the user clicking OK in the dialog
|
||||
mock_dialog_instance.exec.return_value = QDialog.DialogCode.Rejected
|
||||
mock_dialog_instance.selected_track_id = -1 # Simulated return value
|
||||
|
||||
self.widget.import_files_wrapper()
|
||||
|
||||
# Ensure PickMatch was instantiated correctly
|
||||
MockPickMatch.assert_called_once_with(
|
||||
new_track_description="I'm So Afraid (Fleetwood Mac)",
|
||||
choices=[("Do not import", -1, ""), ("Import as new track", 0, "")],
|
||||
default=1,
|
||||
)
|
||||
|
||||
# Verify exec() was called
|
||||
mock_dialog_instance.exec.assert_called_once()
|
||||
|
||||
# Ensure selected_track_id was accessed after dialog.exec()
|
||||
assert mock_dialog_instance.selected_track_id < 0
|
||||
|
||||
mock_show_ok.assert_called_once_with(
|
||||
"File not imported",
|
||||
"isa.mp3 will not be imported because you asked not to import this file",
|
||||
)
|
||||
|
||||
def test_003_import_first_file(self):
|
||||
"""Import file into empty directory"""
|
||||
|
||||
test_track_path = "testdata/isa.mp3"
|
||||
shutil.copy(test_track_path, self.import_source)
|
||||
|
||||
with patch("file_importer.PickMatch") as MockPickMatch:
|
||||
# Create a mock instance of PickMatch
|
||||
mock_dialog_instance = MagicMock()
|
||||
MockPickMatch.return_value = mock_dialog_instance
|
||||
|
||||
# Simulate the user clicking OK in the dialog
|
||||
mock_dialog_instance.exec.return_value = QDialog.DialogCode.Accepted
|
||||
mock_dialog_instance.selected_track_id = 0 # Simulated return value
|
||||
|
||||
self.widget.import_files_wrapper()
|
||||
|
||||
# Ensure PickMatch was instantiated correctly
|
||||
MockPickMatch.assert_called_once_with(
|
||||
new_track_description="I'm So Afraid (Fleetwood Mac)",
|
||||
choices=[("Do not import", -1, ""), ("Import as new track", 0, "")],
|
||||
default=1,
|
||||
)
|
||||
|
||||
# Verify exec() was called
|
||||
mock_dialog_instance.exec.assert_called_once()
|
||||
|
||||
# Ensure selected_track_id was accessed after dialog.exec()
|
||||
assert mock_dialog_instance.selected_track_id == 0
|
||||
|
||||
# Wait until workers have run to completion
|
||||
# self.qtbot.wait(3000)
|
||||
def workers_empty():
|
||||
assert FileImporter.workers == {}
|
||||
|
||||
self.qtbot.waitUntil(workers_empty, timeout=10000)
|
||||
|
||||
# Check track was imported
|
||||
with db.Session() as session:
|
||||
tracks = Tracks.get_all(session)
|
||||
assert len(tracks) == 1
|
||||
track = tracks[0]
|
||||
assert track.title == "I'm So Afraid"
|
||||
assert track.artist == "Fleetwood Mac"
|
||||
track_file = os.path.join(
|
||||
self.musicstore, os.path.basename(test_track_path)
|
||||
)
|
||||
assert track.path == track_file
|
||||
assert os.path.exists(track_file)
|
||||
assert os.listdir(self.import_source) == []
|
||||
|
||||
def test_004_import_second_file(self):
|
||||
"""Import a second file"""
|
||||
|
||||
test_track_path = "testdata/lovecats.mp3"
|
||||
shutil.copy(test_track_path, self.import_source)
|
||||
|
||||
with patch("file_importer.PickMatch") as MockPickMatch:
|
||||
# Create a mock instance of PickMatch
|
||||
mock_dialog_instance = MagicMock()
|
||||
MockPickMatch.return_value = mock_dialog_instance
|
||||
|
||||
# Simulate the user clicking OK in the dialog
|
||||
mock_dialog_instance.exec.return_value = QDialog.DialogCode.Accepted
|
||||
mock_dialog_instance.selected_track_id = 0 # Simulated return value
|
||||
|
||||
self.widget.import_files_wrapper()
|
||||
|
||||
# Ensure PickMatch was instantiated correctly
|
||||
MockPickMatch.assert_called_once_with(
|
||||
new_track_description="The Lovecats (The Cure)",
|
||||
choices=[("Do not import", -1, ""), ("Import as new track", 0, "")],
|
||||
default=1,
|
||||
)
|
||||
|
||||
# Verify exec() was called
|
||||
mock_dialog_instance.exec.assert_called_once()
|
||||
|
||||
# Ensure selected_track_id was accessed after dialog.exec()
|
||||
assert mock_dialog_instance.selected_track_id == 0
|
||||
|
||||
# Allow time for import thread to run
|
||||
self.qtbot.wait(3000)
|
||||
|
||||
# Check track was imported
|
||||
with db.Session() as session:
|
||||
tracks = Tracks.get_all(session)
|
||||
assert len(tracks) == 2
|
||||
track = tracks[1]
|
||||
assert track.title == "The Lovecats"
|
||||
assert track.artist == "The Cure"
|
||||
track_file = os.path.join(
|
||||
self.musicstore, os.path.basename(test_track_path)
|
||||
)
|
||||
assert track.path == track_file
|
||||
assert os.path.exists(track_file)
|
||||
assert os.listdir(self.import_source) == []
|
||||
|
||||
def test_005_replace_file(self):
|
||||
"""Import the same file again"""
|
||||
|
||||
test_track_path = "testdata/lovecats.mp3"
|
||||
shutil.copy(test_track_path, self.import_source)
|
||||
|
||||
with patch("file_importer.PickMatch") as MockPickMatch:
|
||||
# Create a mock instance of PickMatch
|
||||
mock_dialog_instance = MagicMock()
|
||||
MockPickMatch.return_value = mock_dialog_instance
|
||||
|
||||
# Simulate the user clicking OK in the dialog
|
||||
mock_dialog_instance.exec.return_value = QDialog.DialogCode.Accepted
|
||||
mock_dialog_instance.selected_track_id = 1 # Simulated return value
|
||||
|
||||
self.widget.import_files_wrapper()
|
||||
|
||||
# Ensure PickMatch was instantiated correctly
|
||||
MockPickMatch.assert_called_once_with(
|
||||
new_track_description="The Lovecats (The Cure)",
|
||||
choices=[("Do not import", -1, ""),
|
||||
("Import as new track", 0, ""),
|
||||
("The Lovecats (The Cure) (100%)", 2,
|
||||
os.path.join(self.musicstore, os.path.basename(test_track_path))
|
||||
),
|
||||
],
|
||||
default=2,
|
||||
)
|
||||
|
||||
# Verify exec() was called
|
||||
mock_dialog_instance.exec.assert_called_once()
|
||||
|
||||
# Ensure selected_track_id was accessed after dialog.exec()
|
||||
assert mock_dialog_instance.selected_track_id == 1
|
||||
|
||||
# Allow time for import thread to run
|
||||
self.qtbot.wait(3000)
|
||||
|
||||
# Check track was imported
|
||||
with db.Session() as session:
|
||||
tracks = Tracks.get_all(session)
|
||||
assert len(tracks) == 2
|
||||
track = tracks[1]
|
||||
assert track.title == "The Lovecats"
|
||||
assert track.artist == "The Cure"
|
||||
track_file = os.path.join(
|
||||
self.musicstore, os.path.basename(test_track_path)
|
||||
)
|
||||
assert track.path == track_file
|
||||
assert os.path.exists(track_file)
|
||||
assert os.listdir(self.import_source) == []
|
||||
|
||||
# def test_import_replace_file
|
||||
# def test_import_similar_file
|
||||
# def test_import_new_file_existing_destination
|
||||
# def test_import_file_and_cancel
|
||||
# def test_import_file_no_tags
|
||||
# def test_import_unreadable_file
|
||||
|
||||
Loading…
Reference in New Issue
Block a user