# Copyright 2022 (c) Anna Schumaker. """Tests our track Gio.ListModel.""" import datetime import pathlib import unittest import emmental.db.tracks import tests.util import unittest.mock from gi.repository import GObject from gi.repository import Gio from gi.repository import Gtk class TestTrackObject(tests.util.TestCase): """Tests our track object.""" def setUp(self): """Set up common variables.""" super().setUp() self.table = Gio.ListStore() self.table.sql = self.sql self.table.update = unittest.mock.Mock() self.library = self.sql.libraries.create(pathlib.Path("/a/b")) self.album = self.sql.albums.create("Test Album", "Test Artist", release="1988-06") self.medium = self.sql.media.create(self.album, "", number=1) self.year = self.sql.years.create(1988) self.track = emmental.db.tracks.Track(trackid=12345, table=self.table, libraryid=self.library.libraryid, mediumid=self.medium.mediumid, year=self.year.year, path=pathlib.Path("/a/b/c.ogg")) def test_constants(self): """Test constant values.""" self.assertEqual(emmental.db.tracks.PLAYED_THRESHOLD, (2 / 3)) def test_init(self): """Test that the Track is set up properly.""" self.assertIsInstance(self.track, emmental.db.table.Row) self.assertEqual(self.track.table, self.table) self.assertEqual(self.track.trackid, 12345) self.assertEqual(self.track.primary_key, 12345) self.assertEqual(self.track.libraryid, self.library.libraryid) self.assertEqual(self.track.mediumid, self.medium.mediumid) self.assertEqual(self.track.year, self.year.year) self.assertFalse(self.track.active) self.assertFalse(self.track.favorite) self.assertEqual(self.track.path, pathlib.Path("/a/b/c.ogg")) self.assertEqual(self.track.mbid, "") self.assertEqual(self.track.title, "") self.assertEqual(self.track.artist, "") self.assertEqual(self.track.number, 0) self.assertEqual(self.track.length, 0.0) self.assertEqual(self.track.mtime, 0.0) self.assertEqual(self.track.playcount, 0) self.assertIsNone(self.track.added) self.assertIsNone(self.track.laststarted) self.assertIsNone(self.track.lastplayed) self.assertIsNone(self.track.restarted) def test_get_artists(self): """Test getting the Artist list for a Track.""" self.table.get_artists = unittest.mock.Mock(return_value=[1, 2, 3]) self.assertListEqual(self.track.get_artists(), [1, 2, 3]) self.table.get_artists.assert_called_with(self.track) def test_get_genres(self): """Test getting the Genre list for a Track.""" self.table.get_genres = unittest.mock.Mock(return_value=[1, 2, 3]) self.assertListEqual(self.track.get_genres(), [1, 2, 3]) self.table.get_genres.assert_called_with(self.track) def test_get_library(self): """Test getting the Library associated with a Track.""" self.assertEqual(self.track.get_library(), self.library) def test_get_medium(self): """Test getting a Medium playlist.""" self.assertEqual(self.track.get_medium(), self.medium) def test_get_year(self): """Test getting a Year playlist.""" self.assertEqual(self.track.get_year(), self.year) def test_restart(self): """Test the Track.restart() function.""" self.table.restart_track = unittest.mock.Mock() self.track.restart() self.table.restart_track.assert_called_with(self.track) def test_start(self): """Test the Track.start() function.""" self.table.start_track = unittest.mock.Mock() self.track.start() self.table.start_track.assert_called_with(self.track) def test_stop(self): """Test the Track.stop() function.""" self.table.stop_track = unittest.mock.Mock() self.track.length = 3 self.track.stop(0) self.table.stop_track.assert_called_with(self.track, False) self.track.stop(2.5) self.table.stop_track.assert_called_with(self.track, True) def test_update_properties(self): """Test updating track properties.""" now = datetime.datetime.now() self.track.update_properties(trackid=1, libraryid=1, active=True, path=pathlib.Path("/a/b/c.ogg"), playcount=1, laststarted=now, lastplayed=now, restarted=now) self.table.update.assert_not_called() self.track.update_properties(mediumid=2, favorite=True, year=1985, mbid="ab-cd-ef", title="New Title", artist="New Artist", number=2, length=12.345, mtime=67.890) self.table.update.assert_has_calls( [unittest.mock.call(self.track, "mediumid", 2), unittest.mock.call(self.track, "favorite", True), unittest.mock.call(self.track, "year", 1985), unittest.mock.call(self.track, "mbid", "ab-cd-ef"), unittest.mock.call(self.track, "title", "New Title"), unittest.mock.call(self.track, "artist", "New Artist"), unittest.mock.call(self.track, "number", 2), unittest.mock.call(self.track, "length", 12.345), unittest.mock.call(self.track, "mtime", 67.890)]) self.table.update.reset_mock() self.track.update_properties(mediumid=2, favorite=True, year=1985, mbid="ab-cd-ef", title="New Title", artist="New Artist", number=2, length=12.345, mtime=67.890) self.table.update.assert_not_called() class TestTrackTable(tests.util.TestCase): """Tests our track table.""" def setUp(self): """Set up common variables.""" super().setUp() self.sql.playlists.load(now=True) self.sql.playlists.favorites.add_track = unittest.mock.Mock() self.sql.playlists.favorites.remove_track = unittest.mock.Mock() self.sql.playlists.most_played.reload_tracks = unittest.mock.Mock() self.sql.playlists.previous.add_track = unittest.mock.Mock() self.sql.playlists.previous.remove_track = unittest.mock.Mock() self.sql.playlists.queued.remove_track = unittest.mock.Mock() self.sql.playlists.unplayed.remove_track = unittest.mock.Mock() self.playlists = self.sql.playlists self.library = self.sql.libraries.create(pathlib.Path("/a/b/")) self.album = self.sql.albums.create("Test Album", "Album Artist", release="2022-10") self.medium = self.sql.media.create(self.album, "Test Medium", number=1) self.year = self.sql.years.create(1988) self.tracks = self.sql.tracks def test_track_filter(self): """Test the tracks.Filter object.""" filter = emmental.db.tracks.Filter() self.assertEqual(filter.get_strictness(), Gtk.FilterMatch.SOME) filter.keys = {1, 2, 3} self.assertEqual(filter.get_strictness(), Gtk.FilterMatch.SOME) filter.keys = set() self.assertEqual(filter.get_strictness(), Gtk.FilterMatch.NONE) def test_init(self): """Test that the Track table is initialized properly.""" self.assertIsInstance(self.tracks, emmental.db.table.Table) self.assertIsInstance(self.tracks.get_filter(), emmental.db.tracks.Filter) self.assertIsNone(self.tracks.get_model()) def test_construct(self): """Test constructing a new Track.""" now = datetime.datetime.now() track = self.tracks.construct(trackid=1, year=1988, libraryid=self.library.libraryid, mediumid=self.medium.mediumid, path=pathlib.Path("/a/b/c.ogg"), mbid="ab-cd-ef", title="Title", number=1, length=1.0, artist="Artist", mtime=1.0, playcount=1, lastplayed=now) self.assertIsInstance(track, emmental.db.tracks.Track) self.assertEqual(track.table, self.tracks) self.assertEqual(track.trackid, 1) self.assertEqual(track.libraryid, self.library.libraryid) self.assertEqual(track.mediumid, self.medium.mediumid) self.assertEqual(track.year, 1988) self.assertEqual(track.path, pathlib.Path("/a/b/c.ogg")) self.assertEqual(track.mbid, "ab-cd-ef") self.assertEqual(track.title, "Title") self.assertEqual(track.number, 1) self.assertEqual(track.length, 1.0) self.assertEqual(track.artist, "Artist") self.assertEqual(track.mtime, 1.0) self.assertEqual(track.playcount, 1) self.assertEqual(track.lastplayed, now) self.assertFalse(track.active) self.assertFalse(track.favorite) self.assertIsNone(self.tracks.current_track) def test_create(self): """Test creating a new Track.""" track = self.tracks.create(self.library, pathlib.Path("/a/b/c.ogg"), self.medium, self.year) self.assertIsInstance(track, emmental.db.tracks.Track) self.assertEqual(track.libraryid, self.library.libraryid) self.assertEqual(track.mediumid, self.medium.mediumid) self.assertEqual(track.year, 1988) self.assertEqual(track.path, pathlib.Path("/a/b/c.ogg")) self.assertEqual(track.added, datetime.datetime.utcnow().date()) track2 = self.tracks.create(self.library, pathlib.Path("/a/b/d.ogg"), self.medium, self.year, title="Test Track", number=1, length=1.23, artist="Artist", mbid="ab-cd-ef", mtime=4.56) self.assertEqual(track2.trackid, 2) self.assertEqual(track2.libraryid, self.library.libraryid) self.assertEqual(track2.mediumid, self.medium.mediumid) self.assertEqual(track2.path, pathlib.Path("/a/b/d.ogg")) self.assertEqual(track2.title, "Test Track") self.assertEqual(track2.number, 1) self.assertEqual(track2.length, 1.23) self.assertEqual(track2.artist, "Artist") self.assertEqual(track2.mbid, "ab-cd-ef") self.assertEqual(track2.mtime, 4.56) track3 = self.tracks.create(self.library, pathlib.Path("/a/b/c.ogg"), self.medium, self.year) self.assertIsNone(track3) cur = self.sql("SELECT COUNT(*) FROM tracks") self.assertEqual(cur.fetchone()["COUNT(*)"], 2) def test_create_restore(self): """Test restoring saved track data.""" now = datetime.datetime.utcnow() today = now.date() yesterday = today - datetime.timedelta(days=1) self.sql("""INSERT INTO saved_track_data (mbid, favorite, playcount, lastplayed, laststarted, added) VALUES (?, ?, ?, ? , ?, ?)""", "ab-cd-ef", True, 42, now, now, yesterday) track1 = self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year) self.assertFalse(track1.favorite) self.assertEqual(track1.playcount, 0) self.assertIsNone(track1.lastplayed) self.assertIsNone(track1.laststarted) self.assertEqual(track1.added, today) row = self.sql("SELECT COUNT(*) FROM saved_track_data").fetchone() self.assertEqual(row["COUNT(*)"], 1) track2 = self.tracks.create(self.library, pathlib.Path("/a/b/2.ogg"), self.medium, self.year, mbid="ab-cd-ef") self.assertTrue(track2.favorite) self.assertEqual(track2.playcount, 42) self.assertEqual(track2.lastplayed, now) self.assertEqual(track2.laststarted, now) self.assertEqual(track2.added, yesterday) row = self.sql("SELECT COUNT(*) FROM saved_track_data").fetchone() self.assertEqual(row["COUNT(*)"], 0) def test_delete(self): """Test deleting a Track.""" track = self.tracks.create(self.library, pathlib.Path("/a/b/c.ogg"), self.medium, self.year) self.assertTrue(track.delete()) self.assertIsNone(self.tracks.index(track)) cur = self.sql("SELECT COUNT(path) FROM tracks") self.assertEqual(cur.fetchone()["COUNT(path)"], 0) self.assertEqual(len(self.tracks), 0) self.assertFalse(track.delete()) def test_delete_listens(self): """Test deleting listens from the listenbrainz_queue.""" track1 = self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year, length=10) track2 = self.tracks.create(self.library, pathlib.Path("/a/b/2.ogg"), self.medium, self.year, length=10) for track in [track1, track2]: track.start() track.stop(9) self.tracks.delete_listens([1, 2]) self.assertListEqual(self.tracks.get_n_listens(5), []) def test_delete_save(self): """Test saving track data when a track is deleted.""" now = datetime.datetime.now() track1 = self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year, mbid="ab-cd-ef") track2 = self.tracks.create(self.library, pathlib.Path("/a/b/2.ogg"), self.medium, self.year) self.sql("""UPDATE tracks SET favorite=?, laststarted=?, lastplayed=?, playcount=? WHERE trackid=?""", True, now, now, 42, track1.trackid) track1.delete() track2.delete() rows = self.sql("SELECT * FROM saved_track_data").fetchall() self.assertEqual(len(rows), 1) self.assertEqual(rows[0]["mbid"], "ab-cd-ef") self.assertEqual(rows[0]["favorite"], True) self.assertEqual(rows[0]["laststarted"], now) self.assertEqual(rows[0]["lastplayed"], now) self.assertEqual(rows[0]["playcount"], 42) self.assertEqual(rows[0]["added"], datetime.datetime.utcnow().date()) def test_filter(self): """Test filtering the Track table.""" self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year, title="Title 1", artist="Test Artist") self.tracks.create(self.library, pathlib.Path("/a/b/2.ogg"), self.medium, self.year, title="Title 2", artist="Test Artist") self.tracks.filter("*1", now=True) self.assertSetEqual(self.tracks.get_filter().keys, {1}) self.tracks.filter("*artist", now=True) self.assertSetEqual(self.tracks.get_filter().keys, {1, 2}) self.tracks.filter("*medium", now=True) self.assertSetEqual(self.tracks.get_filter().keys, {1, 2}) self.tracks.filter("*album", now=True) self.assertSetEqual(self.tracks.get_filter().keys, {1, 2}) self.tracks.filter("*album artist", now=True) self.assertSetEqual(self.tracks.get_filter().keys, {1, 2}) self.tracks.filter("2022-*", now=True) self.assertSetEqual(self.tracks.get_filter().keys, {1, 2}) def test_load(self): """Test loading tracks from the database.""" now = datetime.datetime.now() self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year) self.tracks.create(self.library, pathlib.Path("/a/b/2.ogg"), self.medium, self.year, title="Track 2", number=2, length=2, artist="Test Artist", mbid="ab-cd-ef", mtime=123.45) self.sql("""UPDATE tracks SET active=TRUE, favorite=TRUE, playcount=3, lastplayed=?, laststarted=? WHERE trackid=2""", now, now) table2 = emmental.db.tracks.Table(sql=self.sql) self.assertEqual(len(table2), 0) table2.load(now=True) self.assertEqual(len(table2.store), 2) self.assertEqual(table2.current_track, table2.store[1]) for i in [0, 1]: with self.subTest(i=i): self.assertEqual(table2.store[i].trackid, i + 1) self.assertEqual(table2.store[i].libraryid, self.library.libraryid) self.assertEqual(table2.store[i].mediumid, self.medium.mediumid) self.assertEqual(table2.store[i].year, self.year.year) self.assertEqual(table2.store[i].active, bool(i)) self.assertEqual(table2.store[i].favorite, bool(i)) self.assertEqual(table2.store[i].path, pathlib.Path(f"/a/b/{i+1}.ogg")) self.assertEqual(table2.store[i].mbid, "ab-cd-ef" if i else "") self.assertEqual(table2.store[i].title, "Track 2" if i else "") self.assertEqual(table2.store[i].artist, "Test Artist" if i else "") self.assertEqual(table2.store[i].number, 2 if i else 0) self.assertEqual(table2.store[i].length, 2 if i else 0) self.assertEqual(table2.store[i].mtime, 123.45 if i else 0) self.assertEqual(table2.store[i].playcount, 3 if i else 0) self.assertEqual(table2.store[i].laststarted, now if i else None) self.assertEqual(table2.store[i].lastplayed, now if i else None) self.assertIsNone(table2.store[i].restarted) def test_lookup(self): """Test looking up tracks in the database.""" path1 = pathlib.Path("/a/b/1.ogg") path2 = pathlib.Path("/a/b/2.ogg") track1 = self.tracks.create(self.library, path1, self.medium, self.year) track2 = self.tracks.create(self.library, path2, self.medium, self.year, mbid="ab-cd-ef") library2 = self.sql.libraries.create(pathlib.Path("/a/b/d")) self.assertEqual(self.tracks.lookup(self.library, path=path1), track1) self.assertEqual(self.tracks.lookup(path=path1), track1) self.assertEqual(self.tracks.lookup(path=path2), track2) self.assertIsNone(self.tracks.lookup(path="/no/such/track")) self.assertIsNone(self.tracks.lookup(library2, path=path1)) self.assertEqual(self.tracks.lookup(self.library, mbid="ab-cd-ef"), track2) self.assertEqual(self.tracks.lookup(mbid="ab-cd-ef"), track2) self.assertIsNone(self.tracks.lookup(mbid="gh-ij-kl")) with self.assertRaises(KeyError) as error: self.tracks.lookup(self.library) self.assertEqual(error.value, "Either 'path' or 'mbid' are required") def test_map_sort_order(self): """Test getting a lookup table for Track sort keys.""" tracks = [self.tracks.create(self.library, pathlib.Path(f"/a/b/{n}.ogg"), self.medium, self.year, number=n) for n in range(10)] self.assertDictEqual(self.tracks.map_sort_order(""), {t.trackid: t.trackid - 1 for t in tracks}) self.assertDictEqual(self.tracks.map_sort_order("number DESC"), {t.trackid: 10 - t.trackid for t in tracks}) def test_update(self): """Test updating tracks in the database.""" track = self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year, length=10) medium2 = self.sql.media.create(self.album, "", number=2) y2022 = self.sql.years.create(2022) track.update_properties(mediumid=medium2.mediumid, year=y2022.year, favorite=True, mbid="ab-cd-ef", title="New Title", artist="New Artist", number=1, length=42, mtime=123.45) self.playlists.favorites.add_track.assert_called_with(track) cur = self.sql("""SELECT mediumid, year, favorite, mbid, title, artist, number, length, mtime FROM tracks WHERE trackid = ?""", track.trackid) row = cur.fetchone() self.assertEqual(row["mediumid"], medium2.mediumid) self.assertEqual(row["year"], 2022) self.assertTrue(row["favorite"]) self.assertEqual(row["mbid"], "ab-cd-ef") self.assertEqual(row["title"], "New Title") self.assertEqual(row["artist"], "New Artist") self.assertEqual(row["number"], 1) self.assertEqual(row["length"], 42) self.assertEqual(row["mtime"], 123.45) track.update_properties(favorite=False) self.playlists.favorites.remove_track.assert_called_with(track) track2 = self.tracks.create(self.library, pathlib.Path("/a/b/2.ogg"), self.medium, self.year, length=10) track2.active = True row = self.sql("SELECT active FROM tracks WHERE trackid=?", track.trackid).fetchone() self.assertFalse(row["active"]) def test_get_artists(self): """Test finding the artists for a track.""" track = self.tracks.create(self.library, pathlib.Path("a/b/1.ogg"), self.medium, self.year) artist1 = self.sql.artists.create("Artist 1") artist2 = self.sql.artists.create("Artist 2") artist1.add_track(track) artist2.add_track(track) self.assertListEqual(self.tracks.get_artists(track), [artist1, artist2]) def test_get_genres(self): """Test finding the genres for a track.""" track = self.tracks.create(self.library, pathlib.Path("a/b/1.ogg"), self.medium, self.year) genre1 = self.sql.genres.create("Genre 1") genre2 = self.sql.genres.create("Genre 2") genre1.add_track(track) genre2.add_track(track) self.assertListEqual(self.tracks.get_genres(track), [genre1, genre2]) def test_get_n_listens(self): """Test the get_n_listens() function.""" track1 = self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year, length=10) track2 = self.tracks.create(self.library, pathlib.Path("/a/b/2.ogg"), self.medium, self.year, length=12) self.assertListEqual(self.tracks.get_n_listens(2), []) track1.start() track1.stop(8) ts1 = track1.lastplayed self.assertListEqual(self.tracks.get_n_listens(2), [(1, track1, ts1)]) track2.start() track2.stop(11) ts2 = track2.lastplayed self.assertListEqual(self.tracks.get_n_listens(2), [(2, track2, ts2), (1, track1, ts1)]) track1.start() track1.stop(9) ts3 = track1.lastplayed self.assertListEqual(self.tracks.get_n_listens(2), [(3, track1, ts3), (2, track2, ts2)]) self.assertListEqual(self.tracks.get_n_listens(4), [(3, track1, ts3), (2, track2, ts2), (1, track1, ts1)]) def test_mark_path_active(self): """Test marking a path as active.""" self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year) self.tracks.mark_path_active(pathlib.Path("/a/b/1.ogg")) cur = self.sql("SELECT trackid FROM tracks WHERE active=TRUE") self.assertEqual(cur.fetchone()["trackid"], 1) self.tracks.mark_path_active(pathlib.Path("/a/b/4.ogg")) cur = self.sql("SELECT trackid FROM tracks WHERE active=TRUE") self.assertIsNone(cur.fetchone()) def test_restart_track(self): """Test marking that a Track has restarted.""" track = self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year) track.restart() self.assertTrue(track.active) self.assertGreater(datetime.datetime.utcnow(), track.restarted) self.assertEqual(self.tracks.current_track, track) self.playlists.previous.remove_track.assert_not_called() def test_start_track(self): """Test marking that a Track has started playback.""" track = self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year) with unittest.mock.patch.object(self.sql, "commit", wraps=self.sql.commit) as mock_commit: track.start() mock_commit.assert_called() row = self.sql("SELECT laststarted FROM tracks WHERE trackid=?", track.trackid).fetchone() self.assertTrue(track.active) self.assertIsNotNone(track.laststarted) self.assertEqual(track.laststarted, row["laststarted"]) self.assertEqual(self.tracks.current_track, track) self.playlists.previous.remove_track.assert_called_with(track) self.playlists.previous.add_track.assert_called_with(track) def test_stop_started_track(self): """Test marking that a Track has stopped playback.""" track = self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year, length=10) track_played = unittest.mock.Mock() self.tracks.connect("track-played", track_played) track.start() with unittest.mock.patch.object(self.sql, "commit", wraps=self.sql.commit) as mock_commit: track.stop(3) mock_commit.assert_called() row = self.sql("SELECT lastplayed FROM tracks WHERE trackid=?", track.trackid).fetchone() self.assertFalse(track.active) self.assertEqual(track.playcount, 0) self.assertIsNone(row["lastplayed"]) self.assertIsNone(track.lastplayed) self.assertIsNone(self.tracks.current_track) cur = self.sql("SELECT trackid, timestamp FROM listenbrainz_queue") self.assertListEqual(cur.fetchall(), []) self.playlists.most_played.reload_tracks.assert_not_called() self.playlists.queued.remove_track.assert_not_called() self.playlists.unplayed.remove_track.assert_not_called() track_played.assert_not_called() track.start() with unittest.mock.patch.object(self.sql, "commit", wraps=self.sql.commit) as mock_commit: track.stop(8) mock_commit.assert_called() row = self.sql("""SELECT lastplayed, playcount FROM tracks WHERE trackid=?""", track.trackid).fetchone() self.assertEqual(row["playcount"], 1) self.assertEqual(track.playcount, 1) self.assertEqual(row["lastplayed"], track.laststarted) self.assertEqual(track.lastplayed, track.laststarted) cur = self.sql("SELECT trackid, timestamp FROM listenbrainz_queue") row = cur.fetchall()[0] self.assertEqual(row["trackid"], track.trackid) self.assertEqual(row["timestamp"], track.lastplayed) self.playlists.most_played.reload_tracks.assert_called() self.playlists.queued.remove_track.assert_called_with(track) self.playlists.unplayed.remove_track.assert_called_with(track) track_played.assert_called_with(self.tracks, track) def test_stop_restarted_track(self): """Test marking that a restarted Track has stopped playback.""" track = self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year, length=10) track_played = unittest.mock.Mock() self.tracks.connect("track-played", track_played) track.restart() track.stop(3) row = self.sql("""SELECT lastplayed, laststarted FROM tracks WHERE trackid=?""", track.trackid).fetchone() self.assertFalse(track.active) self.assertEqual(track.playcount, 0) self.assertIsNone(row["lastplayed"]) self.assertIsNone(track.lastplayed) self.assertIsNone(row["laststarted"]) self.assertIsNone(track.laststarted) self.assertIsNone(track.restarted) self.assertIsNone(self.tracks.current_track) cur = self.sql("SELECT trackid, timestamp FROM listenbrainz_queue") self.assertListEqual(cur.fetchall(), []) self.playlists.most_played.reload_tracks.assert_not_called() self.playlists.queued.remove_track.assert_not_called() self.playlists.unplayed.remove_track.assert_not_called() track_played.assert_not_called() track.restart() restarted = track.restarted track.stop(8) row = self.sql("""SELECT lastplayed, laststarted, playcount FROM tracks WHERE trackid=?""", track.trackid).fetchone() self.assertEqual(row["playcount"], 1) self.assertEqual(track.playcount, 1) self.assertEqual(row["lastplayed"], restarted) self.assertEqual(track.lastplayed, restarted) self.assertEqual(row["laststarted"], restarted) self.assertEqual(track.laststarted, restarted) cur = self.sql("SELECT trackid, timestamp FROM listenbrainz_queue") row = cur.fetchall()[0] self.assertEqual(row["trackid"], track.trackid) self.assertEqual(row["timestamp"], track.lastplayed) self.playlists.most_played.reload_tracks.assert_called_with(idle=True) self.playlists.queued.remove_track.assert_called_with(track) self.playlists.unplayed.remove_track.assert_called_with(track) track_played.assert_called_with(self.tracks, track) def test_current_track(self): """Test the current-track and have-current-track properties.""" self.assertIsNone(self.tracks.current_track) self.assertFalse(self.tracks.have_current_track) track = self.tracks.construct(trackid=2, active=True) self.assertEqual(self.tracks.current_track, track) self.assertTrue(self.tracks.have_current_track) self.playlists.previous.add_track.assert_called_with(track) track2 = self.tracks.construct(trackid=2, active=True) self.assertEqual(self.tracks.current_track, track2) self.assertTrue(self.tracks.have_current_track) self.playlists.previous.add_track.assert_called_with(track2) self.playlists.previous.add_track.reset_mock() self.tracks.current_track = None self.assertFalse(self.tracks.have_current_track) self.playlists.previous.add_track.assert_not_called() def test_current_favorite(self): """Test the current-favorite property.""" self.assertFalse(self.tracks.current_favorite) self.tracks.current_favorite = True self.assertFalse(self.tracks.current_favorite) track = self.tracks.construct(trackid=2, active=True, favorite=True) self.assertEqual(self.tracks.current_track, track) self.assertTrue(self.tracks.current_favorite) track.favorite = False self.assertFalse(self.tracks.current_favorite) track.favorite = True self.assertTrue(self.tracks.current_favorite) class TestTrackIdSet(tests.util.TestCase): """Test our custom TrackIdSet object.""" def setUp(self): """Set up common variables.""" super().setUp() self.library = self.sql.libraries.create(pathlib.Path("/a/b")) self.album = self.sql.albums.create("Album", "Artist", "2023-03") self.medium = self.sql.media.create(self.album, "", number=1) self.year = self.sql.years.create(1988) self.tracks = self.sql.tracks self.track1 = self.tracks.create(self.library, pathlib.Path("/a/b/1.ogg"), self.medium, self.year) self.track2 = self.tracks.create(self.library, pathlib.Path("/a/b/2.ogg"), self.medium, self.year) self.trackids = emmental.db.tracks.TrackidSet() def test_init(self): """Test that the TrackIdSet was initialized properly.""" self.assertIsInstance(self.trackids, GObject.GObject) self.assertIsInstance(self.trackids._TrackidSet__trackids, set) self.assertEqual(self.trackids.n_trackids, 0) trackids2 = emmental.db.tracks.TrackidSet({1, 2, 3}) self.assertSetEqual(trackids2._TrackidSet__trackids, {1, 2, 3}) self.assertEqual(trackids2.n_trackids, 3) def test_contains(self): """Test the __contains__() function.""" self.trackids.add_track(self.track1) self.assertTrue(self.track1 in self.trackids) self.assertFalse(self.track2 in self.trackids) def test_len(self): """Test the __len__() function.""" self.assertEqual(len(self.trackids), 0) self.trackids.add_track(self.track1) self.assertEqual(len(self.trackids), 1) def test_sub(self): """Test the __sub__() function.""" self.trackids.trackids = {1, 2, 3, 4, 5} trackidset2 = emmental.db.tracks.TrackidSet({3, 4, 5, 6, 7}) res = self.trackids - trackidset2 self.assertIsInstance(res, emmental.db.tracks.TrackidSet) self.assertSetEqual(res.trackids, {1, 2}) def test_add_track(self): """Test adding a Track to the set.""" added = unittest.mock.Mock() self.trackids.connect("trackid-added", added) self.trackids.add_track(self.track1) self.assertSetEqual(self.trackids.trackids, {self.track1.trackid}) self.assertEqual(self.trackids.n_trackids, 1) added.assert_called_with(self.trackids, self.track1.trackid) self.trackids.add_track(self.track2) self.assertSetEqual(self.trackids.trackids, {self.track1.trackid, self.track2.trackid}) self.assertEqual(self.trackids.n_trackids, 2) added.assert_called_with(self.trackids, self.track2.trackid) added.reset_mock() self.trackids.add_track(self.track2) self.assertSetEqual(self.trackids.trackids, {self.track1.trackid, self.track2.trackid}) self.assertEqual(self.trackids.n_trackids, 2) added.assert_not_called() @unittest.mock.patch("random.choice") def test_random_trackid(self, mock_choice: unittest.mock.Mock): """Test getting a random trackid from the set.""" self.assertIsNone(self.trackids.random_trackid()) mock_choice.assert_not_called() self.trackids.trackids = {1, 2, 3} mock_choice.return_value = 2 self.assertEqual(self.trackids.random_trackid(), 2) mock_choice.assert_called_with([1, 2, 3]) def test_remove_track(self): """Test removing a Track from the set.""" removed = unittest.mock.Mock() self.trackids.trackids = {self.track1.trackid, self.track2.trackid} self.trackids.connect("trackid-removed", removed) self.trackids.remove_track(self.track1) self.assertSetEqual(self.trackids.trackids, {self.track2.trackid}) self.assertEqual(self.trackids.n_trackids, 1) removed.assert_called_with(self.trackids, self.track1.trackid) removed.reset_mock() self.trackids.remove_track(self.track1) self.assertSetEqual(self.trackids.trackids, {self.track2.trackid}) self.assertEqual(self.trackids.n_trackids, 1) removed.assert_not_called() def test_trackids(self): """Test setting the Trackids property.""" added = unittest.mock.Mock() removed = unittest.mock.Mock() reset = unittest.mock.Mock() self.trackids.connect("trackid-added", added) self.trackids.connect("trackid-removed", removed) self.trackids.connect("trackids-reset", reset) self.trackids.trackids = {1, 2, 3, 4, 5} self.assertSetEqual(self.trackids.trackids, {1, 2, 3, 4, 5}) self.assertEqual(self.trackids.n_trackids, 5) added.assert_not_called() removed.assert_not_called() reset.assert_called_with(self.trackids) reset.reset_mock() self.trackids.trackids = {1, 2, 3, 4, 5} self.assertSetEqual(self.trackids.trackids, {1, 2, 3, 4, 5}) added.assert_not_called() removed.assert_not_called() reset.assert_not_called() self.trackids.trackids = {3, 4, 5, 6, 7} self.assertSetEqual(self.trackids.trackids, {3, 4, 5, 6, 7}) added.assert_has_calls([unittest.mock.call(self.trackids, 6), unittest.mock.call(self.trackids, 7)]) removed.assert_has_calls([unittest.mock.call(self.trackids, 1), unittest.mock.call(self.trackids, 2)]) reset.assert_not_called()