Compare commits
6 Commits
aec994bafd
...
324dd770df
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
324dd770df | ||
|
|
11400536b5 | ||
|
|
d596792375 | ||
|
|
a8791f925d | ||
|
|
5317ecdf18 | ||
|
|
a2baf489c3 |
32
app/ds.py
32
app/ds.py
@ -459,16 +459,17 @@ def track_update(
|
||||
|
||||
with db.Session() as session:
|
||||
track = session.get(Tracks, track_id)
|
||||
|
||||
if not track:
|
||||
raise ApplicationError(f"Can't retrieve Track ({track_id=})")
|
||||
track.path = str(metadata["path"])
|
||||
track.title = str(metadata["title"]),
|
||||
track.artist = str(metadata["artist"]),
|
||||
track.duration = int(metadata["duration"]),
|
||||
track.start_gap = int(metadata["start_gap"]),
|
||||
track.fade_at = int(metadata["fade_at"]),
|
||||
track.silence_at = int(metadata["silence_at"]),
|
||||
track.bitrate = int(metadata["bitrate"]),
|
||||
track.title = str(metadata["title"])
|
||||
track.artist = str(metadata["artist"])
|
||||
track.duration = int(metadata["duration"])
|
||||
track.start_gap = int(metadata["start_gap"])
|
||||
track.fade_at = int(metadata["fade_at"])
|
||||
track.silence_at = int(metadata["silence_at"])
|
||||
track.bitrate = int(metadata["bitrate"])
|
||||
|
||||
session.commit()
|
||||
|
||||
@ -837,7 +838,7 @@ def playlist_save_as_template(playlist_id: int, template_name: str) -> None:
|
||||
|
||||
new_template = playlist_create(template_name, 0, as_template=True)
|
||||
|
||||
playlist_copy(playlist_id, new_template.id)
|
||||
playlist_copy(playlist_id, new_template.playlist_id)
|
||||
|
||||
|
||||
def playlists_templates_all() -> list[PlaylistDTO]:
|
||||
@ -853,14 +854,17 @@ def playlists_template_by_id(playlist_id: int) -> PlaylistDTO | None:
|
||||
Return a list of closed playlists
|
||||
"""
|
||||
|
||||
playlist_list = _playlists_where(
|
||||
Playlists.playlist_id == playlist_id, Playlists.is_template.is_(True)
|
||||
)
|
||||
playlist_list = _playlists_where(Playlists.playlist_id == playlist_id)
|
||||
|
||||
if not playlist_list:
|
||||
return None
|
||||
if len(playlist_list) > 1:
|
||||
raise ApplicationError(f"Duplicate {playlist_id=}")
|
||||
return playlist_list[0]
|
||||
template = playlist_list[0]
|
||||
if template.is_template is False:
|
||||
raise ApplicationError(f"Playlist {playlist_id=} is not a template")
|
||||
|
||||
return template
|
||||
|
||||
|
||||
# @log_call
|
||||
@ -1051,13 +1055,13 @@ def playdates_get_last(track_id: int, limit: int = 5) -> str:
|
||||
)
|
||||
|
||||
|
||||
def playdates_update(track_id: int) -> None:
|
||||
def playdates_update(track_id: int, when: dt.datetime | None = None) -> None:
|
||||
"""
|
||||
Update playdates for passed track
|
||||
"""
|
||||
|
||||
with db.Session() as session:
|
||||
_ = Playdates(session, track_id)
|
||||
_ = Playdates(session, track_id, when)
|
||||
|
||||
|
||||
def playdates_between_dates(
|
||||
|
||||
@ -142,7 +142,7 @@ class FileImporter:
|
||||
|
||||
# Refresh list of existing tracks as they may have been updated
|
||||
# by previous imports
|
||||
self.existing_tracks = ds.get_all_tracks()
|
||||
self.existing_tracks = ds.tracks_all()
|
||||
|
||||
for infile in [
|
||||
os.path.join(Config.REPLACE_FILES_DEFAULT_SOURCE, f)
|
||||
@ -638,7 +638,7 @@ class DoTrackImport(QThread):
|
||||
# Update databse
|
||||
metadata = get_all_track_metadata(self.destination_track_path)
|
||||
if self.track_id == 0:
|
||||
track_dto = ds.create_track(self.destination_track_path, metadata)
|
||||
track_dto = ds.track_create(metadata)
|
||||
else:
|
||||
track_dto = ds.track_update(
|
||||
self.destination_track_path, self.track_id, metadata
|
||||
|
||||
@ -153,7 +153,7 @@ class NoteColours(dbtables.NoteColoursTable):
|
||||
|
||||
class Playdates(dbtables.PlaydatesTable):
|
||||
def __init__(
|
||||
self, session: Session, track_id: int, when: Optional[dt.datetime] = None
|
||||
self, session: Session, track_id: int, when: dt.datetime | None = None
|
||||
) -> None:
|
||||
"""Record that track was played"""
|
||||
|
||||
|
||||
@ -76,7 +76,6 @@ from dialogs import TrackInsertDialog
|
||||
from file_importer import FileImporter
|
||||
from helpers import ask_yes_no, file_is_unreadable, get_name
|
||||
from log import log, log_call
|
||||
from models import db, Playdates, PlaylistRows, Playlists, Queries, Settings, Tracks
|
||||
from playlistrow import PlaylistRow, TrackSequence
|
||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||
from playlists import PlaylistTab
|
||||
@ -899,7 +898,7 @@ class QueryDialog(QDialog):
|
||||
querylist_y=self.y(),
|
||||
)
|
||||
for name, value in attributes_to_save.items():
|
||||
ds.set_setting(name, value)
|
||||
ds.setting_set(name, value)
|
||||
|
||||
header = self.table_view.horizontalHeader()
|
||||
if header is None:
|
||||
@ -909,7 +908,7 @@ class QueryDialog(QDialog):
|
||||
return
|
||||
for column_number in range(column_count - 1):
|
||||
attr_name = f"querylist_col_{column_number}_width"
|
||||
ds.set_setting(
|
||||
ds.setting_set(
|
||||
attr_name, self.table_view.columnWidth(column_number)
|
||||
)
|
||||
|
||||
@ -962,10 +961,10 @@ class QueryDialog(QDialog):
|
||||
def set_window_size(self) -> None:
|
||||
"""Set window sizes"""
|
||||
|
||||
x = ds.get_setting("querylist_x") or 100
|
||||
y = ds.get_setting("querylist_y") or 100
|
||||
width = ds.get_setting("querylist_width") or 100
|
||||
height = ds.get_setting("querylist_height") or 100
|
||||
x = ds.setting_get("querylist_x") or 100
|
||||
y = ds.setting_get("querylist_y") or 100
|
||||
width = ds.setting_get("querylist_width") or 100
|
||||
height = ds.setting_get("querylist_height") or 100
|
||||
self.setGeometry(x, y, width, height)
|
||||
|
||||
def set_column_sizes(self) -> None:
|
||||
@ -981,7 +980,7 @@ class QueryDialog(QDialog):
|
||||
# Last column is set to stretch so ignore it here
|
||||
for column_number in range(column_count - 1):
|
||||
attr_name = f"querylist_col_{column_number}_width"
|
||||
width = ds.get_setting(attr_name) or Config.DEFAULT_COLUMN_WIDTH
|
||||
width = ds.setting_get(attr_name) or Config.DEFAULT_COLUMN_WIDTH
|
||||
self.table_view.setColumnWidth(column_number, width)
|
||||
|
||||
|
||||
@ -998,8 +997,8 @@ class SelectPlaylistDialog(QDialog):
|
||||
self.ui.buttonBox.rejected.connect(self.close)
|
||||
self.playlist = None
|
||||
|
||||
width = ds.get_setting("select_playlist_dialog_width") or 800
|
||||
height = ds.get_setting("select_playlist_dialog_height") or 800
|
||||
width = ds.setting_get("select_playlist_dialog_width") or 800
|
||||
height = ds.setting_get("select_playlist_dialog_height") or 800
|
||||
self.resize(width, height)
|
||||
|
||||
for playlist in playlists:
|
||||
@ -1009,8 +1008,8 @@ class SelectPlaylistDialog(QDialog):
|
||||
self.ui.lstPlaylists.addItem(p)
|
||||
|
||||
def __del__(self): # review
|
||||
ds.set_setting("select_playlist_dialog_height", self.height())
|
||||
ds.set_setting("select_playlist_dialog_width", self.width())
|
||||
ds.setting_set("select_playlist_dialog_height", self.height())
|
||||
ds.setting_set("select_playlist_dialog_width", self.width())
|
||||
|
||||
def list_doubleclick(self, entry): # review
|
||||
self.playlist = entry.data(Qt.ItemDataRole.UserRole)
|
||||
@ -1207,7 +1206,7 @@ class Window(QMainWindow):
|
||||
active_tab=self.playlist_section.tabPlaylist.currentIndex(),
|
||||
)
|
||||
for name, value in attributes_to_save.items():
|
||||
ds.set_setting(name, value)
|
||||
ds.setting_set(name, value)
|
||||
|
||||
event.accept()
|
||||
|
||||
@ -1404,13 +1403,13 @@ class Window(QMainWindow):
|
||||
# # # # # # # # # # Playlist management functions # # # # # # # # # #
|
||||
|
||||
# @log_call
|
||||
def _create_playlist(self, name: str, template_id: int) -> Playlists:
|
||||
def _create_playlist(self, name: str, template_id: int) -> PlaylistDTO:
|
||||
"""
|
||||
Create a playlist in the database, populate it from the template
|
||||
if template_id > 0, and return the Playlists object.
|
||||
"""
|
||||
|
||||
return ds.create_playlist(name, template_id)
|
||||
return ds.playlist_create(name, template_id)
|
||||
|
||||
# @log_call
|
||||
def _open_playlist(self, playlist: PlaylistDTO, is_template: bool = False) -> int:
|
||||
@ -1916,7 +1915,7 @@ class Window(QMainWindow):
|
||||
playlist_ids.append(self._open_playlist(playlist))
|
||||
|
||||
# Set active tab
|
||||
value = ds.get_setting("active_tab")
|
||||
value = ds.setting_get("active_tab")
|
||||
if value is not None and value >= 0:
|
||||
self.playlist_section.tabPlaylist.setCurrentIndex(value)
|
||||
|
||||
@ -2416,10 +2415,10 @@ class Window(QMainWindow):
|
||||
def set_main_window_size(self) -> None:
|
||||
"""Set size of window from database"""
|
||||
|
||||
x = ds.get_setting("mainwindow_x") or 100
|
||||
y = ds.get_setting("mainwindow_y") or 100
|
||||
width = ds.get_setting("mainwindow_width") or 100
|
||||
height = ds.get_setting("mainwindow_height") or 100
|
||||
x = ds.setting_get("mainwindow_x") or 100
|
||||
y = ds.setting_get("mainwindow_y") or 100
|
||||
width = ds.setting_get("mainwindow_width") or 100
|
||||
height = ds.setting_get("mainwindow_height") or 100
|
||||
self.setGeometry(x, y, width, height)
|
||||
|
||||
# @log_call
|
||||
|
||||
@ -196,7 +196,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
if self.is_header_row(row):
|
||||
# Check for specific header colouring
|
||||
if plr.row_bg is None:
|
||||
plr.row_bg = ds.get_colour(plr.note)
|
||||
plr.row_bg = ds.notecolours_get_colour(plr.note)
|
||||
if plr.row_bg:
|
||||
return QBrush(QColor(plr.row_bg))
|
||||
else:
|
||||
@ -233,7 +233,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
if column == Col.NOTE.value:
|
||||
if plr.note:
|
||||
if plr.note_bg is None:
|
||||
plr.row_bg = ds.get_colour(plr.note)
|
||||
plr.row_bg = ds.notecolours_get_colour(plr.note)
|
||||
if plr.note_bg:
|
||||
return QBrush(QColor(plr.note_bg))
|
||||
|
||||
@ -395,7 +395,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
# Signal that rows will be removed
|
||||
super().beginRemoveRows(QModelIndex(), min(row_group), max(row_group))
|
||||
# Remove rows from data store
|
||||
ds.remove_rows(self.playlist_id, row_group)
|
||||
ds.playlist_remove_rows(self.playlist_id, row_group)
|
||||
# Signal that data store has been updated
|
||||
super().endRemoveRows()
|
||||
|
||||
@ -500,7 +500,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
def _foreground_role(self, row: int, column: int, plr: PlaylistRow) -> QBrush:
|
||||
"""Return header foreground colour or QBrush() if none"""
|
||||
|
||||
plr.row_fg = ds.get_colour(plr.note, foreground=True)
|
||||
plr.row_fg = ds.notecolours_get_colour(plr.note, foreground=True)
|
||||
if plr.row_fg:
|
||||
return QBrush(QColor(plr.row_fg))
|
||||
return QBrush()
|
||||
@ -838,7 +838,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
# Notify model going to change
|
||||
self.beginResetModel()
|
||||
# Update database
|
||||
ds.move_rows(from_rows, self.playlist_id, to_row_number)
|
||||
ds.playlist_move_rows(from_rows, self.playlist_id, to_row_number)
|
||||
# Notify model changed
|
||||
self.endResetModel()
|
||||
|
||||
@ -857,7 +857,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
# @log_call
|
||||
def move_rows_between_playlists(
|
||||
self,
|
||||
from_rows: list[PlaylistRow],
|
||||
from_rows: list[int],
|
||||
to_row_number: int,
|
||||
to_playlist_id: int,
|
||||
) -> None:
|
||||
@ -878,7 +878,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
# and the row range must be contiguous. Process the highest rows
|
||||
# first so the lower row numbers are unchanged
|
||||
|
||||
row_groups = self._reversed_contiguous_row_groups([a.row_number for a in from_rows])
|
||||
row_groups = self._reversed_contiguous_row_groups(from_rows)
|
||||
|
||||
# Handle the moves in row_group chunks
|
||||
|
||||
@ -891,10 +891,10 @@ class PlaylistModel(QAbstractTableModel):
|
||||
to_row_number + len(row_group)
|
||||
)
|
||||
self.signals.signal_begin_insert_rows.emit(insert_rows)
|
||||
ds.move_rows(from_rows=row_group,
|
||||
from_playlist_id=self.playlist_id,
|
||||
to_row=to_row_number,
|
||||
to_playlist_id=to_playlist_id)
|
||||
ds.playlist_move_rows(from_rows=row_group,
|
||||
from_playlist_id=self.playlist_id,
|
||||
to_row=to_row_number,
|
||||
to_playlist_id=to_playlist_id)
|
||||
self.signals.signal_end_insert_rows.emit(to_playlist_id)
|
||||
super().endRemoveRows()
|
||||
|
||||
@ -921,6 +921,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
return
|
||||
|
||||
super().endInsertRows()
|
||||
self.refresh_data()
|
||||
|
||||
# @log_call
|
||||
def move_track_add_note(
|
||||
@ -1035,7 +1036,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
"""Populate dict for one row from database"""
|
||||
|
||||
plrid = self.playlist_rows[row_number].playlistrow_id
|
||||
refreshed_row = ds.get_playlist_row(plrid)
|
||||
refreshed_row = ds.playlistrow_by_id(plrid)
|
||||
if not refreshed_row:
|
||||
raise ApplicationError(f"Failed to retrieve row {self.playlist_id=}, {row_number=}")
|
||||
|
||||
@ -1062,7 +1063,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
|
||||
track = self.playlist_rows[row_number]
|
||||
metadata = get_all_track_metadata(track.path)
|
||||
_ = ds.update_track(track.path, track.track_id, metadata)
|
||||
_ = ds.track_update(track.path, track.track_id, metadata)
|
||||
|
||||
roles = [
|
||||
Qt.ItemDataRole.BackgroundRole,
|
||||
@ -1102,7 +1103,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
):
|
||||
return
|
||||
|
||||
ds.remove_comments(self.playlist_id, row_numbers)
|
||||
ds.playlist_remove_comments(self.playlist_id, row_numbers)
|
||||
|
||||
# only invalidate required roles
|
||||
roles = [
|
||||
@ -1161,7 +1162,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
header_text = header_text[0:-1]
|
||||
|
||||
# Parse passed header text and remove the first colour match string
|
||||
return ds.remove_colour_substring(header_text)
|
||||
return ds.notecolours_remove_colour_substring(header_text)
|
||||
|
||||
def rowCount(self, index: QModelIndex = QModelIndex()) -> int:
|
||||
"""Standard function for view"""
|
||||
@ -1445,7 +1446,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
if not track_id:
|
||||
return ""
|
||||
|
||||
return ds.get_last_played_dates(track_id)
|
||||
return ds.playdates_get_last(track_id)
|
||||
|
||||
# @log_call
|
||||
def update_or_insert(self, track_id: int, row_number: int) -> None:
|
||||
|
||||
@ -161,7 +161,7 @@ class PlaylistRow:
|
||||
if self.track_id > 0:
|
||||
raise ApplicationError("Attempting to add track to row with existing track ({self=}")
|
||||
|
||||
ds.add_track_to_header(playlistrow_id=self.playlistrow_id, track_id=track_id)
|
||||
ds.track_add_to_header(playlistrow_id=self.playlistrow_id, track_id=track_id)
|
||||
|
||||
# Need to update with track information
|
||||
track = ds.track_by_id(track_id)
|
||||
|
||||
@ -1073,7 +1073,7 @@ class PlaylistTab(QTableView):
|
||||
# Last column is set to stretch so ignore it here
|
||||
for column_number in range(header.count() - 1):
|
||||
attr_name = f"playlist_col_{column_number}_width"
|
||||
value = ds.get_setting(attr_name)
|
||||
value = ds.setting_get(attr_name)
|
||||
if value is not None:
|
||||
self.setColumnWidth(column_number, value)
|
||||
else:
|
||||
|
||||
@ -1,480 +0,0 @@
|
||||
"""
|
||||
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 MagicMock, patch
|
||||
|
||||
# PyQt imports
|
||||
from PyQt6.QtWidgets import QDialog, QFileDialog
|
||||
|
||||
# Third party imports
|
||||
from mutagen.mp3 import MP3 # type: ignore
|
||||
import pytest
|
||||
from pytestqt.plugin import QtBot # type: ignore
|
||||
|
||||
# App imports
|
||||
from app import ds, musicmuster
|
||||
from app.models import (
|
||||
db,
|
||||
Tracks,
|
||||
)
|
||||
from config import Config
|
||||
from file_importer import FileImporter
|
||||
|
||||
|
||||
# Custom fixture to adapt qtbot for use with unittest.TestCase
|
||||
@pytest.fixture(scope="class")
|
||||
def qtbot_adapter(qapp, request):
|
||||
"""Adapt qtbot fixture for usefixtures and unittest.TestCase"""
|
||||
request.cls.qtbot = QtBot(request)
|
||||
|
||||
|
||||
# Fixture for tmp_path to be available in the class
|
||||
@pytest.fixture(scope="class")
|
||||
def class_tmp_path(request, tmp_path_factory):
|
||||
"""Provide a class-wide tmp_path"""
|
||||
request.cls.tmp_path = tmp_path_factory.mktemp("pytest_tmp")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("qtbot_adapter", "class_tmp_path")
|
||||
class MyTestCase(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Runs once before any test in this class"""
|
||||
|
||||
db.create_all()
|
||||
|
||||
cls.widget = musicmuster.Window()
|
||||
|
||||
# Create a playlist for all tests
|
||||
playlist_name = "file importer playlist"
|
||||
playlist = ds.playlist_create(name=playlist_name, template_id=0)
|
||||
cls.widget._open_playlist(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"""
|
||||
|
||||
db.drop_all()
|
||||
shutil.rmtree(cls.musicstore)
|
||||
shutil.rmtree(cls.import_source)
|
||||
|
||||
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 wait_for_workers(self, timeout: int = 10000):
|
||||
"""
|
||||
Let import threads workers run to completion
|
||||
"""
|
||||
|
||||
def workers_empty():
|
||||
assert FileImporter.workers == {}
|
||||
|
||||
self.qtbot.waitUntil(workers_empty, timeout=timeout)
|
||||
|
||||
def test_001_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,
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
self.wait_for_workers()
|
||||
|
||||
# Check track was imported
|
||||
tracks = ds.get_all_tracks()
|
||||
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
|
||||
|
||||
self.wait_for_workers()
|
||||
|
||||
# Check track was imported
|
||||
tracks = ds.get_all_tracks()
|
||||
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 and update existing track"""
|
||||
|
||||
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 = 2 # 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()
|
||||
|
||||
self.wait_for_workers()
|
||||
|
||||
# Check track was imported
|
||||
tracks = ds.get_all_tracks()
|
||||
assert len(tracks) == 2
|
||||
track = tracks[1]
|
||||
assert track.title == "The Lovecats"
|
||||
assert track.artist == "The Cure"
|
||||
assert track.track_id == 2
|
||||
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_006_import_file_no_tags(self) -> None:
|
||||
"""Try to import untagged file"""
|
||||
|
||||
test_track_path = "testdata/lovecats.mp3"
|
||||
test_filename = os.path.basename(test_track_path)
|
||||
|
||||
shutil.copy(test_track_path, self.import_source)
|
||||
import_file = os.path.join(self.import_source, test_filename)
|
||||
assert os.path.exists(import_file)
|
||||
|
||||
# Remove tags
|
||||
src = MP3(import_file)
|
||||
src.delete()
|
||||
src.save()
|
||||
|
||||
with patch("file_importer.show_OK") as mock_show_ok:
|
||||
self.widget.import_files_wrapper()
|
||||
mock_show_ok.assert_called_once_with(
|
||||
"File not imported",
|
||||
f"{test_filename} will not be imported because of tag errors "
|
||||
f"(Missing tags in {import_file})",
|
||||
)
|
||||
|
||||
def test_007_import_unreadable_file(self) -> None:
|
||||
"""Import unreadable file"""
|
||||
|
||||
test_track_path = "testdata/lovecats.mp3"
|
||||
test_filename = os.path.basename(test_track_path)
|
||||
|
||||
shutil.copy(test_track_path, self.import_source)
|
||||
import_file = os.path.join(self.import_source, test_filename)
|
||||
assert os.path.exists(import_file)
|
||||
|
||||
# Make undreadable
|
||||
os.chmod(import_file, 0)
|
||||
|
||||
with patch("file_importer.show_OK") as mock_show_ok:
|
||||
self.widget.import_files_wrapper()
|
||||
mock_show_ok.assert_called_once_with(
|
||||
"File not imported",
|
||||
f"{test_filename} will not be imported because {import_file} is unreadable",
|
||||
)
|
||||
|
||||
# clean up
|
||||
os.chmod(import_file, 0o777)
|
||||
os.unlink(import_file)
|
||||
|
||||
def test_008_import_new_file_existing_destination(self) -> None:
|
||||
"""Import duplicate file"""
|
||||
|
||||
test_track_path = "testdata/lovecats.mp3"
|
||||
test_filename = os.path.basename(test_track_path)
|
||||
new_destination = os.path.join(self.musicstore, "lc2.mp3")
|
||||
|
||||
shutil.copy(test_track_path, self.import_source)
|
||||
import_file = os.path.join(self.import_source, test_filename)
|
||||
assert os.path.exists(import_file)
|
||||
|
||||
with (
|
||||
patch("file_importer.PickMatch") as MockPickMatch,
|
||||
patch.object(
|
||||
QFileDialog, "getSaveFileName", return_value=(new_destination, "")
|
||||
) as mock_file_dialog,
|
||||
patch("file_importer.show_OK") as mock_show_ok,
|
||||
):
|
||||
mock_file_dialog.return_value = (
|
||||
new_destination,
|
||||
"",
|
||||
) # Ensure mock correctly returns expected value
|
||||
|
||||
# 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, ""),
|
||||
(
|
||||
"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()
|
||||
|
||||
destination = os.path.join(self.musicstore, test_filename)
|
||||
mock_show_ok.assert_called_once_with(
|
||||
title="Desintation path exists",
|
||||
msg=f"New import requested but default destination path ({destination}) "
|
||||
"already exists. Click OK and choose where to save this track",
|
||||
parent=None,
|
||||
)
|
||||
|
||||
self.wait_for_workers()
|
||||
|
||||
# Ensure QFileDialog was called and returned expected value
|
||||
assert mock_file_dialog.called # Ensure the mock was used
|
||||
result = mock_file_dialog()
|
||||
assert result[0] == new_destination # Validate return value
|
||||
|
||||
# Check track was imported
|
||||
tracks = ds.get_all_tracks()
|
||||
track = tracks[2]
|
||||
assert track.title == "The Lovecats"
|
||||
assert track.artist == "The Cure"
|
||||
assert track.track_id == 3
|
||||
assert track.path == new_destination
|
||||
assert os.path.exists(new_destination)
|
||||
assert os.listdir(self.import_source) == []
|
||||
|
||||
# Remove file so as not to interfere with later tests
|
||||
ds.delete(track)
|
||||
tracks = ds.get_all_tracks()
|
||||
assert len(tracks) == 2
|
||||
|
||||
os.unlink(new_destination)
|
||||
assert not os.path.exists(new_destination)
|
||||
|
||||
def test_009_import_similar_file(self) -> None:
|
||||
"""Import file with similar, but different, title"""
|
||||
|
||||
test_track_path = "testdata/lovecats.mp3"
|
||||
test_filename = os.path.basename(test_track_path)
|
||||
|
||||
shutil.copy(test_track_path, self.import_source)
|
||||
import_file = os.path.join(self.import_source, test_filename)
|
||||
assert os.path.exists(import_file)
|
||||
|
||||
# Change title tag
|
||||
src = MP3(import_file)
|
||||
src["TIT2"].text[0] += " xyz"
|
||||
src.save()
|
||||
|
||||
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 = 2 # Simulated return value
|
||||
|
||||
self.widget.import_files_wrapper()
|
||||
|
||||
# Ensure PickMatch was instantiated correctly
|
||||
MockPickMatch.assert_called_once_with(
|
||||
new_track_description="The Lovecats xyz (The Cure)",
|
||||
choices=[
|
||||
("Do not import", -1, ""),
|
||||
("Import as new track", 0, ""),
|
||||
(
|
||||
"The Lovecats (The Cure) (93%)",
|
||||
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()
|
||||
|
||||
self.wait_for_workers()
|
||||
|
||||
# Check track was imported
|
||||
tracks = ds.get_all_tracks()
|
||||
assert len(tracks) == 2
|
||||
track = tracks[1]
|
||||
assert track.title == "The Lovecats xyz"
|
||||
assert track.artist == "The Cure"
|
||||
assert track.track_id == 2
|
||||
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) == []
|
||||
@ -79,7 +79,8 @@ class MyTestCase(unittest.TestCase):
|
||||
"""Runs after each test"""
|
||||
self.widget.close() # Close UI to prevent side effects
|
||||
|
||||
def wait_for_workers(self, timeout: int = 10000):
|
||||
# def wait_for_workers(self, timeout: int = 10000):
|
||||
def wait_for_workers(self, timeout: int = 1000000):
|
||||
"""
|
||||
Let import threads workers run to completion
|
||||
"""
|
||||
@ -171,7 +172,7 @@ class MyTestCase(unittest.TestCase):
|
||||
self.wait_for_workers()
|
||||
|
||||
# Check track was imported
|
||||
tracks = ds.get_all_tracks()
|
||||
tracks = ds.tracks_all()
|
||||
assert len(tracks) == 1
|
||||
track = tracks[0]
|
||||
assert track.title == "I'm So Afraid"
|
||||
@ -216,7 +217,7 @@ class MyTestCase(unittest.TestCase):
|
||||
self.wait_for_workers()
|
||||
|
||||
# Check track was imported
|
||||
tracks = ds.get_all_tracks()
|
||||
tracks = ds.tracks_all()
|
||||
assert len(tracks) == 2
|
||||
track = tracks[1]
|
||||
assert track.title == "The Lovecats"
|
||||
@ -268,7 +269,7 @@ class MyTestCase(unittest.TestCase):
|
||||
self.wait_for_workers()
|
||||
|
||||
# Check track was imported
|
||||
tracks = ds.get_all_tracks()
|
||||
tracks = ds.tracks_all()
|
||||
assert len(tracks) == 2
|
||||
track = tracks[1]
|
||||
assert track.title == "The Lovecats"
|
||||
@ -397,7 +398,7 @@ class MyTestCase(unittest.TestCase):
|
||||
assert result[0] == new_destination # Validate return value
|
||||
|
||||
# Check track was imported
|
||||
tracks = ds.get_all_tracks()
|
||||
tracks = ds.tracks_all()
|
||||
track = tracks[2]
|
||||
assert track.title == "The Lovecats"
|
||||
assert track.artist == "The Cure"
|
||||
@ -407,8 +408,8 @@ class MyTestCase(unittest.TestCase):
|
||||
assert os.listdir(self.import_source) == []
|
||||
|
||||
# Remove file so as not to interfere with later tests
|
||||
ds.delete(track)
|
||||
tracks = ds.get_all_tracks()
|
||||
ds.track_delete(track.track_id)
|
||||
tracks = ds.tracks_all()
|
||||
assert len(tracks) == 2
|
||||
|
||||
os.unlink(new_destination)
|
||||
@ -463,7 +464,7 @@ class MyTestCase(unittest.TestCase):
|
||||
self.wait_for_workers()
|
||||
|
||||
# Check track was imported
|
||||
tracks = ds.get_all_tracks()
|
||||
tracks = ds.tracks_all()
|
||||
assert len(tracks) == 2
|
||||
track = tracks[1]
|
||||
assert track.title == "The Lovecats xyz"
|
||||
|
||||
@ -7,7 +7,8 @@ import unittest
|
||||
import pytest
|
||||
|
||||
# App imports
|
||||
from app.models import db, Settings
|
||||
from app.models import db
|
||||
import ds
|
||||
|
||||
|
||||
class TestMMMisc(unittest.TestCase):
|
||||
@ -28,13 +29,9 @@ class TestMMMisc(unittest.TestCase):
|
||||
NO_SUCH_SETTING = "abc"
|
||||
VALUE = 3
|
||||
|
||||
with db.Session() as session:
|
||||
setting = Settings(session, SETTING_NAME)
|
||||
# test repr
|
||||
_ = str(setting)
|
||||
setting.f_int = VALUE
|
||||
test = Settings.get_setting(session, SETTING_NAME)
|
||||
assert test.name == SETTING_NAME
|
||||
assert test.f_int == VALUE
|
||||
test_new = Settings.get_setting(session, NO_SUCH_SETTING)
|
||||
assert test_new.name == NO_SUCH_SETTING
|
||||
test_non_existant = ds.setting_get(SETTING_NAME)
|
||||
assert test_non_existant is None
|
||||
|
||||
ds.setting_set(SETTING_NAME, VALUE)
|
||||
test_ok = ds.setting_get(SETTING_NAME)
|
||||
assert test_ok == VALUE
|
||||
|
||||
@ -9,11 +9,7 @@ from PyQt6.QtCore import Qt, QModelIndex
|
||||
# App imports
|
||||
from app.helpers import get_all_track_metadata
|
||||
from app import ds, playlistmodel
|
||||
from app.models import (
|
||||
db,
|
||||
Playlists,
|
||||
Tracks,
|
||||
)
|
||||
from app.models import db
|
||||
from classes import (
|
||||
TrackAndPlaylist,
|
||||
)
|
||||
@ -41,8 +37,9 @@ class TestMMMiscTracks(unittest.TestCase):
|
||||
|
||||
for row in range(len(self.test_tracks)):
|
||||
track_path = self.test_tracks[row % len(self.test_tracks)]
|
||||
track = ds.track_create(**get_all_track_metadata(track_path))
|
||||
self.model.insert_row(track_id=track.id, note=f"{row=}")
|
||||
metadata = get_all_track_metadata(track_path)
|
||||
track = ds.track_create(metadata)
|
||||
self.model.insert_row(track_id=track.track_id, note=f"{row=}")
|
||||
|
||||
def tearDown(self):
|
||||
db.drop_all()
|
||||
@ -95,14 +92,14 @@ class TestMMMiscNoPlaylist(unittest.TestCase):
|
||||
# insert a track into a new playlist
|
||||
playlist = ds.playlist_create(self.PLAYLIST_NAME, template_id=0)
|
||||
# Create a model
|
||||
model = playlistmodel.PlaylistModel(playlist.id, is_template=False)
|
||||
model = playlistmodel.PlaylistModel(playlist.playlist_id, is_template=False)
|
||||
# test repr
|
||||
_ = str(model)
|
||||
|
||||
track_path = self.test_tracks[0]
|
||||
metadata = get_all_track_metadata(track_path)
|
||||
track = ds.track_create(metadata)
|
||||
model.insert_row(track_id=track.id)
|
||||
model.insert_row(track_id=track.track_id)
|
||||
|
||||
prd = model.playlist_rows[model.rowCount() - 1]
|
||||
# test repr
|
||||
@ -122,7 +119,7 @@ class TestMMMiscRowMove(unittest.TestCase):
|
||||
db.create_all()
|
||||
|
||||
self.playlist = ds.playlist_create(self.PLAYLIST_NAME, template_id=0)
|
||||
self.model = playlistmodel.PlaylistModel(self.playlist.id, is_template=False)
|
||||
self.model = playlistmodel.PlaylistModel(self.playlist.playlist_id, is_template=False)
|
||||
for row in range(self.ROWS_TO_CREATE):
|
||||
self.model.insert_row(note=str(row))
|
||||
|
||||
@ -211,12 +208,10 @@ class TestMMMiscRowMove(unittest.TestCase):
|
||||
for row in range(self.ROWS_TO_CREATE):
|
||||
model_dst.insert_row(note=str(row))
|
||||
|
||||
ds.playlist_move_rows(
|
||||
from_rows, self.playlist.playlist_id, to_row, playlist_dst.playlist_id
|
||||
)
|
||||
model_src.move_rows_between_playlists(from_rows, to_row, playlist_dst.playlist_id)
|
||||
|
||||
assert len(model_src.playlist_rows) == self.ROWS_TO_CREATE - len(from_rows)
|
||||
assert len(model_dst.playlist_rows) == self.ROWS_TO_CREATE + len(from_rows)
|
||||
assert model_src.rowCount() == self.ROWS_TO_CREATE - len(from_rows)
|
||||
assert model_dst.rowCount() == self.ROWS_TO_CREATE + len(from_rows)
|
||||
assert sorted([a.row_number for a in model_src.playlist_rows.values()]) == list(
|
||||
range(len(model_src.playlist_rows))
|
||||
)
|
||||
@ -234,9 +229,7 @@ class TestMMMiscRowMove(unittest.TestCase):
|
||||
for row in range(self.ROWS_TO_CREATE):
|
||||
model_dst.insert_row(note=str(row))
|
||||
|
||||
ds.playlist_move_rows(
|
||||
from_rows, self.playlist.playlist_id, to_row, playlist_dst.playlist_id
|
||||
)
|
||||
model_src.move_rows_between_playlists(from_rows, to_row, playlist_dst.playlist_id)
|
||||
|
||||
# Check the rows of the destination model
|
||||
row_notes = []
|
||||
@ -246,8 +239,8 @@ class TestMMMiscRowMove(unittest.TestCase):
|
||||
)
|
||||
row_notes.append(model_dst.data(index, Qt.ItemDataRole.EditRole))
|
||||
|
||||
assert len(model_src.playlist_rows) == self.ROWS_TO_CREATE - len(from_rows)
|
||||
assert len(model_dst.playlist_rows) == self.ROWS_TO_CREATE + len(from_rows)
|
||||
assert model_src.rowCount() == self.ROWS_TO_CREATE - len(from_rows)
|
||||
assert model_dst.rowCount() == self.ROWS_TO_CREATE + len(from_rows)
|
||||
assert [int(a) for a in row_notes] == [0, 1, 3, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
|
||||
def test_move_multiple_rows_between_playlists_to_end(self):
|
||||
@ -264,9 +257,7 @@ class TestMMMiscRowMove(unittest.TestCase):
|
||||
for row in range(self.ROWS_TO_CREATE):
|
||||
model_dst.insert_row(note=str(row))
|
||||
|
||||
ds.playlist_move_rows(
|
||||
from_rows, self.playlist.id, playlist_dst.playlist_id, to_row
|
||||
)
|
||||
model_src.move_rows_between_playlists(from_rows, to_row, playlist_dst.playlist_id)
|
||||
|
||||
# Check the rows of the destination model
|
||||
row_notes = []
|
||||
@ -276,8 +267,8 @@ class TestMMMiscRowMove(unittest.TestCase):
|
||||
)
|
||||
row_notes.append(model_dst.data(index, Qt.ItemDataRole.EditRole))
|
||||
|
||||
assert len(model_src.playlist_rows) == self.ROWS_TO_CREATE - len(from_rows)
|
||||
assert len(model_dst.playlist_rows) == self.ROWS_TO_CREATE + len(from_rows)
|
||||
assert model_src.rowCount() == self.ROWS_TO_CREATE - len(from_rows)
|
||||
assert model_dst.rowCount() == self.ROWS_TO_CREATE + len(from_rows)
|
||||
assert [int(a) for a in row_notes] == [
|
||||
0,
|
||||
1,
|
||||
@ -294,22 +285,3 @@ class TestMMMiscRowMove(unittest.TestCase):
|
||||
9,
|
||||
10,
|
||||
]
|
||||
|
||||
|
||||
# # def test_edit_header(monkeypatch, session): # edit header row in middle of playlist
|
||||
|
||||
# # monkeypatch.setattr(playlistmodel, "Session", session)
|
||||
# # note_text = "test text"
|
||||
# # initial_row_count = 11
|
||||
# # insert_row = 6
|
||||
|
||||
# # model = create_model_with_playlist_rows(session, initial_row_count)
|
||||
# # model.insert_header_row(insert_row, note_text)
|
||||
# # assert model.rowCount() == initial_row_count + 1
|
||||
# # prd = model.playlist_rows[insert_row]
|
||||
# # # Test against edit_role because display_role for headers is
|
||||
# # # handled differently (sets up row span)
|
||||
# # assert (
|
||||
# # model.edit_role(model.rowCount(), playlistmodel.Col.NOTE.value, prd)
|
||||
# # == note_text
|
||||
# # )
|
||||
|
||||
@ -10,12 +10,12 @@ import unittest
|
||||
# App imports
|
||||
from app.models import (
|
||||
db,
|
||||
Playdates,
|
||||
Tracks,
|
||||
)
|
||||
from classes import (
|
||||
Filter,
|
||||
)
|
||||
import ds
|
||||
|
||||
|
||||
class MyTestCase(unittest.TestCase):
|
||||
@ -25,35 +25,34 @@ class MyTestCase(unittest.TestCase):
|
||||
|
||||
db.create_all()
|
||||
|
||||
with db.Session() as session:
|
||||
# Create some track entries
|
||||
_ = Tracks(**dict(
|
||||
session=session,
|
||||
artist="a",
|
||||
bitrate=0,
|
||||
duration=100,
|
||||
fade_at=0,
|
||||
path="/alpha/bravo/charlie",
|
||||
silence_at=0,
|
||||
start_gap=0,
|
||||
title="abc"
|
||||
))
|
||||
track2 = Tracks(**dict(
|
||||
session=session,
|
||||
artist="a",
|
||||
bitrate=0,
|
||||
duration=100,
|
||||
fade_at=0,
|
||||
path="/xray/yankee/zulu",
|
||||
silence_at=0,
|
||||
start_gap=0,
|
||||
title="xyz"
|
||||
))
|
||||
track2_id = track2.id
|
||||
# Add playdates
|
||||
# Track 2 played just over a year ago
|
||||
just_over_a_year_ago = dt.datetime.now() - dt.timedelta(days=367)
|
||||
_ = Playdates(session, track2_id, when=just_over_a_year_ago)
|
||||
# Create some track entries
|
||||
track1_meta = dict(
|
||||
artist="a",
|
||||
bitrate=0,
|
||||
duration=100,
|
||||
fade_at=0,
|
||||
path="/alpha/bravo/charlie",
|
||||
silence_at=0,
|
||||
start_gap=0,
|
||||
title="abc"
|
||||
)
|
||||
_ = ds.track_create(track1_meta)
|
||||
track2_meta = dict(
|
||||
artist="a",
|
||||
bitrate=0,
|
||||
duration=100,
|
||||
fade_at=0,
|
||||
path="/xray/yankee/zulu",
|
||||
silence_at=0,
|
||||
start_gap=0,
|
||||
title="xyz"
|
||||
)
|
||||
track2 = ds.track_create(track2_meta)
|
||||
|
||||
# Add playdates
|
||||
# Track 2 played just over a year ago
|
||||
just_over_a_year_ago = dt.datetime.now() - dt.timedelta(days=367)
|
||||
ds.playdates_update(track2.track_id, when=just_over_a_year_ago)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
@ -76,55 +75,49 @@ class MyTestCase(unittest.TestCase):
|
||||
|
||||
filter = Filter(path="alpha", last_played_comparator="never")
|
||||
|
||||
with db.Session() as session:
|
||||
results = Tracks.get_filtered_tracks(session, filter)
|
||||
assert len(results) == 1
|
||||
assert 'alpha' in results[0].path
|
||||
results = ds.tracks_filtered(filter)
|
||||
assert len(results) == 1
|
||||
assert 'alpha' in results[0].path
|
||||
|
||||
def test_search_path_2(self):
|
||||
"""Search for unplayed track that doesn't exist"""
|
||||
|
||||
filter = Filter(path="xray", last_played_comparator="never")
|
||||
|
||||
with db.Session() as session:
|
||||
results = Tracks.get_filtered_tracks(session, filter)
|
||||
assert len(results) == 0
|
||||
results = ds.tracks_filtered(filter)
|
||||
assert len(results) == 0
|
||||
|
||||
def test_played_over_a_year_ago(self):
|
||||
"""Search for tracks played over a year ago"""
|
||||
|
||||
filter = Filter(last_played_unit="years", last_played_number=1)
|
||||
|
||||
with db.Session() as session:
|
||||
results = Tracks.get_filtered_tracks(session, filter)
|
||||
assert len(results) == 1
|
||||
assert 'zulu' in results[0].path
|
||||
results = ds.tracks_filtered(filter)
|
||||
assert len(results) == 1
|
||||
assert 'zulu' in results[0].path
|
||||
|
||||
def test_played_over_two_years_ago(self):
|
||||
"""Search for tracks played over 2 years ago"""
|
||||
|
||||
filter = Filter(last_played_unit="years", last_played_number=2)
|
||||
|
||||
with db.Session() as session:
|
||||
results = Tracks.get_filtered_tracks(session, filter)
|
||||
assert len(results) == 0
|
||||
results = ds.tracks_filtered(filter)
|
||||
assert len(results) == 0
|
||||
|
||||
def test_never_played(self):
|
||||
"""Search for tracks never played"""
|
||||
|
||||
filter = Filter(last_played_comparator="never")
|
||||
|
||||
with db.Session() as session:
|
||||
results = Tracks.get_filtered_tracks(session, filter)
|
||||
assert len(results) == 1
|
||||
assert 'alpha' in results[0].path
|
||||
results = ds.tracks_filtered(filter)
|
||||
assert len(results) == 1
|
||||
assert 'alpha' in results[0].path
|
||||
|
||||
def test_played_anytime(self):
|
||||
"""Search for tracks played over a year ago"""
|
||||
|
||||
filter = Filter(last_played_comparator="Any time")
|
||||
|
||||
with db.Session() as session:
|
||||
results = Tracks.get_filtered_tracks(session, filter)
|
||||
assert len(results) == 1
|
||||
assert 'zulu' in results[0].path
|
||||
results = ds.tracks_filtered(filter)
|
||||
assert len(results) == 1
|
||||
assert 'zulu' in results[0].path
|
||||
|
||||
277
tests/test_ui.py
277
tests/test_ui.py
@ -15,7 +15,7 @@ from app.models import (
|
||||
Playlists,
|
||||
Tracks,
|
||||
)
|
||||
from app import musicmuster
|
||||
from app import ds, musicmuster
|
||||
|
||||
|
||||
# Custom fixture to adapt qtbot for use with unittest.TestCase
|
||||
@ -49,8 +49,8 @@ class MyTestCase(unittest.TestCase):
|
||||
# self.widget.show()
|
||||
|
||||
# Add two tracks to database
|
||||
self.tracks = {
|
||||
1: {
|
||||
self.track1 = ds.track_create(
|
||||
{
|
||||
"path": "testdata/isa.mp3",
|
||||
"title": "I'm so afraid",
|
||||
"artist": "Fleetwood Mac",
|
||||
@ -59,8 +59,10 @@ class MyTestCase(unittest.TestCase):
|
||||
"start_gap": 60,
|
||||
"fade_at": 236263,
|
||||
"silence_at": 260343,
|
||||
},
|
||||
2: {
|
||||
}
|
||||
)
|
||||
self.track2 = ds.track_create(
|
||||
{
|
||||
"path": "testdata/mom.mp3",
|
||||
"title": "Man of Mystery",
|
||||
"artist": "The Shadows",
|
||||
@ -69,16 +71,8 @@ class MyTestCase(unittest.TestCase):
|
||||
"start_gap": 70,
|
||||
"fade_at": 115000,
|
||||
"silence_at": 118000,
|
||||
},
|
||||
}
|
||||
|
||||
with db.Session() as session:
|
||||
for track in self.tracks.values():
|
||||
db_track = Tracks(session=session, **track)
|
||||
session.add(db_track)
|
||||
track["id"] = db_track.id
|
||||
|
||||
session.commit()
|
||||
}
|
||||
)
|
||||
|
||||
def down(self):
|
||||
db.drop_all()
|
||||
@ -89,11 +83,10 @@ class MyTestCase(unittest.TestCase):
|
||||
|
||||
playlist_name = "test_init playlist"
|
||||
|
||||
with db.Session() as session:
|
||||
playlist = Playlists(session, playlist_name, template_id=0)
|
||||
self.widget._open_playlist(playlist, is_template=False)
|
||||
with self.qtbot.waitExposed(self.widget):
|
||||
self.widget.show()
|
||||
playlist = ds.playlist_create(playlist_name, template_id=0)
|
||||
self.widget._open_playlist(playlist, is_template=False)
|
||||
with self.qtbot.waitExposed(self.widget):
|
||||
self.widget.show()
|
||||
|
||||
@with_updown
|
||||
def test_save_and_restore(self):
|
||||
@ -102,27 +95,22 @@ class MyTestCase(unittest.TestCase):
|
||||
note_text = "my note"
|
||||
playlist_name = "test_save_and_restore playlist"
|
||||
|
||||
with db.Session() as session:
|
||||
playlist = Playlists(session, playlist_name, template_id=0)
|
||||
model = playlistmodel.PlaylistModel(playlist.id, is_template=False)
|
||||
playlist = ds.playlist_create(playlist_name, template_id=0)
|
||||
model = playlistmodel.PlaylistModel(playlist.playlist_id, is_template=False)
|
||||
|
||||
# Add a track with a note
|
||||
model.insert_row(
|
||||
proposed_row_number=0, track_id=self.tracks[1]["id"], note=note_text
|
||||
)
|
||||
# Add a track with a note
|
||||
model.insert_row(track_id=self.track1.track_id, note=note_text)
|
||||
|
||||
# We need to commit the session before re-querying
|
||||
session.commit()
|
||||
|
||||
# Retrieve playlist
|
||||
all_playlists = Playlists.get_all(session)
|
||||
assert len(all_playlists) == 1
|
||||
retrieved_playlist = all_playlists[0]
|
||||
assert len(retrieved_playlist.rows) == 1
|
||||
paths = [a.track.path for a in retrieved_playlist.rows]
|
||||
assert self.tracks[1]["path"] in paths
|
||||
notes = [a.note for a in retrieved_playlist.rows]
|
||||
assert note_text in notes
|
||||
# Retrieve playlist
|
||||
all_playlists = ds.playlists_all()
|
||||
assert len(all_playlists) == 1
|
||||
retrieved_playlist = all_playlists[0]
|
||||
playlist_rows = ds.playlistrows_by_playlist(retrieved_playlist.playlist_id)
|
||||
assert len(playlist_rows) == 1
|
||||
paths = [a.track.path for a in playlist_rows]
|
||||
assert self.track1.path in paths
|
||||
notes = [a.note for a in playlist_rows]
|
||||
assert note_text in notes
|
||||
|
||||
@with_updown
|
||||
def test_utilities(self):
|
||||
@ -134,214 +122,3 @@ class MyTestCase(unittest.TestCase):
|
||||
|
||||
utilities.check_db()
|
||||
utilities.update_bitrates()
|
||||
|
||||
|
||||
# def test_meta_all_clear(qtbot, session):
|
||||
# # Create playlist
|
||||
# playlist = models.Playlists(session, "my playlist", template_id=0)
|
||||
# playlist_tab = playlists.PlaylistTab(None, session, playlist.id)
|
||||
|
||||
# # Add some tracks
|
||||
# # Need to commit session after each one so that new row is found
|
||||
# # for subsequent inserts
|
||||
# track1_path = "/a/b/c"
|
||||
# track1 = models.Tracks(session, track1_path)
|
||||
# playlist_tab.insert_track(session, track1)
|
||||
# session.commit()
|
||||
# track2_path = "/d/e/f"
|
||||
# track2 = models.Tracks(session, track2_path)
|
||||
# playlist_tab.insert_track(session, track2)
|
||||
# session.commit()
|
||||
# track3_path = "/h/i/j"
|
||||
# track3 = models.Tracks(session, track3_path)
|
||||
# playlist_tab.insert_track(session, track3)
|
||||
# session.commit()
|
||||
|
||||
# assert playlist_tab._get_current_track_row() is None
|
||||
# assert playlist_tab._get_next_track_row() is None
|
||||
# assert playlist_tab._get_notes_rows() == []
|
||||
# assert playlist_tab._get_played_track_rows() == []
|
||||
# assert len(playlist_tab._get_unreadable_track_rows()) == 3
|
||||
|
||||
|
||||
# def test_meta(qtbot, session):
|
||||
# # Create playlist
|
||||
# playlist = playlists.Playlists(session, "my playlist",
|
||||
# template_id=0)
|
||||
# playlist_tab = playlists.PlaylistTab(None, session, playlist.id)
|
||||
|
||||
# # Add some tracks
|
||||
# track1_path = "/a/b/c"
|
||||
# track1 = models.Tracks(session, track1_path)
|
||||
# playlist_tab.insert_track(session, track1)
|
||||
# session.commit()
|
||||
# track2_path = "/d/e/f"
|
||||
# track2 = models.Tracks(session, track2_path)
|
||||
# playlist_tab.insert_track(session, track2)
|
||||
# session.commit()
|
||||
# track3_path = "/h/i/j"
|
||||
# track3 = models.Tracks(session, track3_path)
|
||||
# playlist_tab.insert_track(session, track3)
|
||||
# session.commit()
|
||||
|
||||
# assert len(playlist_tab._get_unreadable_track_rows()) == 3
|
||||
|
||||
# assert playlist_tab._get_played_track_rows() == []
|
||||
# assert playlist_tab._get_current_track_row() is None
|
||||
# assert playlist_tab._get_next_track_row() is None
|
||||
# assert playlist_tab._get_notes_rows() == []
|
||||
|
||||
# playlist_tab._set_played_row(0)
|
||||
# assert playlist_tab._get_played_track_rows() == [0]
|
||||
# assert playlist_tab._get_current_track_row() is None
|
||||
# assert playlist_tab._get_next_track_row() is None
|
||||
# assert playlist_tab._get_notes_rows() == []
|
||||
|
||||
# # Add a note
|
||||
# note_text = "my note"
|
||||
# note_row = 7 # will be added as row 3
|
||||
# note = models.Notes(session, playlist.id, note_row, note_text)
|
||||
# playlist_tab._insert_note(session, note)
|
||||
|
||||
# assert playlist_tab._get_played_track_rows() == [0]
|
||||
# assert playlist_tab._get_current_track_row() is None
|
||||
# assert playlist_tab._get_next_track_row() is None
|
||||
# assert playlist_tab._get_notes_rows() == [3]
|
||||
|
||||
# playlist_tab._set_next_track_row(1)
|
||||
# assert playlist_tab._get_played_track_rows() == [0]
|
||||
# assert playlist_tab._get_current_track_row() is None
|
||||
# assert playlist_tab._get_next_track_row() == 1
|
||||
# assert playlist_tab._get_notes_rows() == [3]
|
||||
|
||||
# playlist_tab._set_current_track_row(2)
|
||||
# assert playlist_tab._get_played_track_rows() == [0]
|
||||
# assert playlist_tab._get_current_track_row() == 2
|
||||
# assert playlist_tab._get_next_track_row() == 1
|
||||
# assert playlist_tab._get_notes_rows() == [3]
|
||||
|
||||
# playlist_tab._clear_played_row_status(0)
|
||||
# assert playlist_tab._get_played_track_rows() == []
|
||||
# assert playlist_tab._get_current_track_row() == 2
|
||||
# assert playlist_tab._get_next_track_row() == 1
|
||||
# assert playlist_tab._get_notes_rows() == [3]
|
||||
|
||||
# playlist_tab._meta_clear_next()
|
||||
# assert playlist_tab._get_played_track_rows() == []
|
||||
# assert playlist_tab._get_current_track_row() == 2
|
||||
# assert playlist_tab._get_next_track_row() is None
|
||||
# assert playlist_tab._get_notes_rows() == [3]
|
||||
|
||||
# playlist_tab._clear_current_track_row()
|
||||
# assert playlist_tab._get_played_track_rows() == []
|
||||
# assert playlist_tab._get_current_track_row() is None
|
||||
# assert playlist_tab._get_next_track_row() is None
|
||||
# assert playlist_tab._get_notes_rows() == [3]
|
||||
|
||||
# # Test clearing again has no effect
|
||||
# playlist_tab._clear_current_track_row()
|
||||
# assert playlist_tab._get_played_track_rows() == []
|
||||
# assert playlist_tab._get_current_track_row() is None
|
||||
# assert playlist_tab._get_next_track_row() is None
|
||||
# assert playlist_tab._get_notes_rows() == [3]
|
||||
|
||||
|
||||
# def test_clear_next(qtbot, session):
|
||||
# # Create playlist
|
||||
# playlist = models.Playlists(session, "my playlist", template_id=0)
|
||||
# playlist_tab = playlists.PlaylistTab(None, session, playlist.id)
|
||||
|
||||
# # Add some tracks
|
||||
# track1_path = "/a/b/c"
|
||||
# track1 = models.Tracks(session, track1_path)
|
||||
# playlist_tab.insert_track(session, track1)
|
||||
# session.commit()
|
||||
# track2_path = "/d/e/f"
|
||||
# track2 = models.Tracks(session, track2_path)
|
||||
# playlist_tab.insert_track(session, track2)
|
||||
# session.commit()
|
||||
|
||||
# playlist_tab._set_next_track_row(1)
|
||||
# assert playlist_tab._get_next_track_row() == 1
|
||||
|
||||
# playlist_tab.clear_next(session)
|
||||
# assert playlist_tab._get_next_track_row() is None
|
||||
|
||||
|
||||
# def test_get_selected_row(qtbot, monkeypatch, session):
|
||||
# monkeypatch.setattr(musicmuster, "Session", session)
|
||||
# monkeypatch.setattr(playlists, "Session", session)
|
||||
|
||||
# # Create playlist and playlist_tab
|
||||
# window = musicmuster.Window()
|
||||
# playlist = models.Playlists(session, "test playlist", template_id=0)
|
||||
# playlist_tab = playlists.PlaylistTab(window, session, playlist.id)
|
||||
|
||||
# # Add some tracks
|
||||
# track1_path = "/a/b/c"
|
||||
# track1 = models.Tracks(session, track1_path)
|
||||
# playlist_tab.insert_track(session, track1)
|
||||
# session.commit()
|
||||
# track2_path = "/d/e/f"
|
||||
# track2 = models.Tracks(session, track2_path)
|
||||
# playlist_tab.insert_track(session, track2)
|
||||
# session.commit()
|
||||
|
||||
# qtbot.addWidget(playlist_tab)
|
||||
# with qtbot.waitExposed(window):
|
||||
# window.show()
|
||||
# row0_item0 = playlist_tab.item(0, 0)
|
||||
# assert row0_item0 is not None
|
||||
# rect = playlist_tab.visualItemRect(row0_item0)
|
||||
# qtbot.mouseClick(playlist_tab.viewport(), Qt.LeftButton, pos=rect.center())
|
||||
# row_number = playlist_tab.get_selected_row()
|
||||
# assert row_number == 0
|
||||
|
||||
|
||||
# def test_set_next(qtbot, monkeypatch, session):
|
||||
# monkeypatch.setattr(musicmuster, "Session", session)
|
||||
# monkeypatch.setattr(playlists, "Session", session)
|
||||
# seed2tracks(session)
|
||||
|
||||
# playlist_name = "test playlist"
|
||||
# # Create testing playlist
|
||||
# window = musicmuster.Window()
|
||||
# playlist = models.Playlists(session, playlist_name, template_id=0)
|
||||
# playlist_tab = playlists.PlaylistTab(window, session, playlist.id)
|
||||
# idx = window.tabPlaylist.addTab(playlist_tab, playlist_name)
|
||||
# window.tabPlaylist.setCurrentIndex(idx)
|
||||
# qtbot.addWidget(playlist_tab)
|
||||
|
||||
# # Add some tracks
|
||||
# track1 = models.Tracks.get_by_filename(session, "isa.mp3")
|
||||
# track1_title = track1.title
|
||||
# assert track1_title
|
||||
|
||||
# playlist_tab.insert_track(session, track1)
|
||||
# session.commit()
|
||||
# track2 = models.Tracks.get_by_filename(session, "mom.mp3")
|
||||
# playlist_tab.insert_track(session, track2)
|
||||
|
||||
# with qtbot.waitExposed(window):
|
||||
# window.show()
|
||||
|
||||
# row0_item2 = playlist_tab.item(0, 2)
|
||||
# assert row0_item2 is not None
|
||||
# rect = playlist_tab.visualItemRect(row0_item2)
|
||||
# qtbot.mouseClick(playlist_tab.viewport(), Qt.LeftButton, pos=rect.center())
|
||||
# selected_title = playlist_tab.get_selected_title()
|
||||
# assert selected_title == track1_title
|
||||
|
||||
# qtbot.keyPress(playlist_tab.viewport(), "N", modifier=Qt.ControlModifier)
|
||||
# qtbot.wait(1000)
|
||||
|
||||
|
||||
# def test_kae(monkeypatch, session):
|
||||
# # monkeypatch.setattr(dbconfig, "Session", session)
|
||||
# monkeypatch.setattr(musicmuster, "Session", session)
|
||||
|
||||
# musicmuster.Window.kae()
|
||||
# # monkeypatch.setattr(musicmuster, "Session", session)
|
||||
# # monkeypatch.setattr(dbconfig, "Session", session)
|
||||
# # monkeypatch.setattr(models, "Session", session)
|
||||
# # monkeypatch.setattr(playlists, "Session", session)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user