7 from sima.lib.simadb import SimaDB
8 from sima.lib.track import Track
9 from sima.lib.meta import Album, Artist, MetaContainer
14 'albumartist': 'Devolt',
15 'albumartistsort': 'Devolt',
20 'file': 'music/Devolt/2011-Grey/03-Devolt - Crazy.mp3',
21 'last-modified': '2012-04-02T20:48:59Z',
22 'musicbrainz_albumartistid': 'd8e7e3e2-49ab-4f7c-b148-fc946d521f99',
23 'musicbrainz_albumid': 'ea2ef2cf-59e1-443a-817e-9066e3e0be4b',
24 'musicbrainz_artistid': 'd8e7e3e2-49ab-4f7c-b148-fc946d521f99',
25 'musicbrainz_trackid': 'fabf8fc9-2ae5-49c9-8214-a839c958d872',
27 'duration': '220.000',
31 DB_FILE = 'file::memory:?cache=shared'
32 KEEP_FILE = True # File db in file to ease debug
34 DB_FILE = '/dev/shm/unittest.sqlite'
35 CURRENT = datetime.datetime.utcnow()
36 IN_THE_PAST = CURRENT - datetime.timedelta(hours=1)
39 class Main(unittest.TestCase):
40 """Deal with database creation and purge between tests"""
44 self.db = SimaDB(db_path=DB_FILE)
47 # Maintain a connection to keep the database (when stored in memory)
48 self.conn = self.db.get_database_connection()
58 def tearDownClass(self):
61 if os.path.isfile(DB_FILE):
65 class Test_00DB(Main):
67 def test_00_recreation(self):
70 def test_01_add_track(self):
72 trk_id = self.db.get_track(trk)
73 self.assertEqual(trk_id, self.db.get_track(trk, add=False),
74 'Same track, same record')
76 def test_02_history(self):
77 # set records in the past to ease purging then
78 last = CURRENT - datetime.timedelta(seconds=5)
80 self.db.add_history(trk, date=last)
81 self.db.add_history(trk, date=last)
82 hist = self.db.fetch_history()
83 self.assertEqual(len(hist), 1, 'same track results in a single record')
85 trk_foo = Track(file="/foo/bar/baz.flac")
86 self.db.add_history(trk_foo, date=last)
87 hist = self.db.fetch_history()
88 self.assertEqual(len(hist), 2)
90 self.db.add_history(trk, date=last)
91 hist = self.db.fetch_history()
92 self.assertEqual(len(hist), 2)
93 self.db.purge_history(duration=0)
94 hist = self.db.fetch_history()
95 self.assertEqual(len(hist), 0)
97 # Controls we got history in the right order
98 # recent first, oldest last
100 for i in range(1, 5): # starts at 1 to ensure records are in the past
101 trk = Track(file=f'/foo/bar.{i}', name=f'{i}-baz',
102 album='foolbum', genre=f'{i}')
104 last = CURRENT - datetime.timedelta(minutes=i)
105 self.db.add_history(trk, date=last)
106 hist_records = self.db.fetch_history()
107 self.assertEqual(hist, hist_records)
108 self.db.purge_history(duration=0)
110 def test_history_to_tracks(self):
113 trk01 = Track(file='01', **tr)
114 self.db.add_history(trk01, CURRENT-datetime.timedelta(minutes=1))
116 tr.pop('musicbrainz_artistid')
117 trk02 = Track(file='02', **tr)
118 self.db.add_history(trk02, CURRENT-datetime.timedelta(minutes=2))
120 tr.pop('musicbrainz_albumid')
121 trk03 = Track(file='03', **tr)
122 self.db.add_history(trk03, CURRENT-datetime.timedelta(minutes=3))
124 tr.pop('musicbrainz_albumartistid')
125 trk04 = Track(file='04', **tr)
126 self.db.add_history(trk04, CURRENT-datetime.timedelta(minutes=4))
128 tr.pop('musicbrainz_trackid')
129 trk05 = Track(file='05', **tr)
130 self.db.add_history(trk05, CURRENT-datetime.timedelta(minutes=5))
131 history = self.db.fetch_history()
132 self.assertEqual(len(history), 5)
133 # Controls history ordering, recent first
134 self.assertEqual(history, [trk01, trk02, trk03, trk04, trk05])
136 def test_history_to_artists(self):
139 tr.pop('musicbrainz_artistid')
141 trk01 = Track(file='01', **tr)
142 self.db.add_history(trk01, CURRENT-datetime.timedelta(hours=1))
144 trk02 = Track(file='02', **tr)
145 self.db.add_history(trk02, CURRENT-datetime.timedelta(hours=1))
146 self.db.add_history(trk02, CURRENT-datetime.timedelta(hours=1))
148 trk03 = Track(file='03', **tr)
149 self.db.add_history(trk03, CURRENT-datetime.timedelta(hours=1))
150 # got multiple tracks, same artist/album, got artist/album history len = 1
151 art_history = self.db.fetch_artists_history()
152 alb_history = self.db.fetch_albums_history()
153 self.assertEqual(len(art_history), 1)
154 self.assertEqual(len(alb_history), 1)
155 self.assertEqual(art_history, [trk01.Artist])
157 # Now add new artist to history
158 trk04 = Track(file='04', artist='New Art')
159 trk05 = Track(file='05', artist='New² Art')
160 self.db.add_history(trk04, CURRENT-datetime.timedelta(minutes=3))
161 self.db.add_history(trk03, CURRENT-datetime.timedelta(minutes=2))
162 self.db.add_history(trk05, CURRENT-datetime.timedelta(minutes=1))
163 art_history = self.db.fetch_artists_history()
164 # Now we should have 4 artists in history
165 self.assertEqual(len(art_history), 4)
166 # Controling order, recent first
167 self.assertEqual([trk05.artist, trk03.artist,
168 trk04.artist, trk03.artist],
171 def test_albums_history(self):
172 # set records in the past to ease purging then
173 last = CURRENT - datetime.timedelta(seconds=5)
175 'album': 'album', 'title': 'title',
176 'albumartist': 'albumartist',
178 'file': 'foo/bar.flac',
179 'musicbrainz_albumartistid': 'd8e7e3e2-49ab-4f7c-b148-fc946d521f99',
180 'musicbrainz_albumid': 'ea2ef2cf-59e1-443a-817e-9066e3e0be4b',
181 'musicbrainz_artistid': 'd8e7e3e2-49ab-4f7c-b148-fc946d521f99',}
182 self.db.purge_history(duration=0)
183 self.db.add_history(Track(**track), last)
184 alb_history = self.db.fetch_albums_history()
186 self.assertEqual(alb.Artist.name, track.get('albumartist'))
187 # Fetching Album history for "artist" should return "album"
188 artist = Artist(track.get('artist'), mbid=track.get('musicbrainz_artistid'))
189 alb_history = self.db.fetch_albums_history(artist)
190 self.assertTrue(alb_history)
191 # Falls back to album and MBID when albumartist and
192 # musicbrainz_albumartistid are not set
193 self.db.purge_history(duration=0)
195 'album': 'no albumartist set', 'title': 'title',
196 'artist': 'no album artist set',
197 'file': 'bar/foo.flac',
198 'musicbrainz_artistid': 'dddddddd-49ab-4f7c-b148-dddddddddddd',}
199 self.db.add_history(Track(**track), last)
200 alb_history = self.db.fetch_albums_history()
201 album = alb_history[0]
202 self.assertEqual(album.albumartist, track['artist'])
203 self.assertEqual(album.Artist.mbid, track['musicbrainz_artistid'])
205 def test_04_filtering_history(self):
206 # Controls artist history filtering
207 for i in range(0, 5):
208 trk = Track(file=f'/foo/bar.{i}', name=f'{i}-baz',
209 artist=f'{i}-art', album=f'{i}-lbum')
210 last = CURRENT - datetime.timedelta(minutes=i)
211 self.db.add_history(trk, date=last)
212 if i == 5: # bounce latest record
213 self.db.add_history(trk, date=last)
214 art_history = self.db.fetch_artists_history()
215 # Already checked but to be sure, we should have 5 artists in history
216 self.assertEqual(len(art_history), 5)
217 for needle in ['4-art', Artist(name='4-art')]:
218 art_history = self.db.fetch_artists_history(needle=needle)
219 self.assertEqual(art_history, [needle])
220 needle = Artist(name='not-art')
221 art_history = self.db.fetch_artists_history(needle=needle)
222 self.assertEqual(art_history, [])
223 # Controls artists history filtering
224 # for a list of Artist objects
225 needle_list = [Artist(name='3-art'), Artist(name='4-art')]
226 art_history = self.db.fetch_artists_history(needle=needle_list)
227 self.assertEqual(art_history, needle_list)
228 # for a MetaContainer
229 needle_meta = MetaContainer(needle_list)
230 art_history = self.db.fetch_artists_history(needle=needle_meta)
231 self.assertEqual(art_history, needle_list)
232 # for a list of string (Meta object handles Artist/str comparison)
233 art_history = self.db.fetch_artists_history(['3-art', '4-art'])
234 self.assertEqual(art_history, needle_list)
236 # Controls album history filtering
237 needle = Artist(name='4-art')
238 alb_history = self.db.fetch_albums_history(needle=needle)
239 self.assertEqual(alb_history, [Album(name='4-lbum')])
241 def test_05_triggers(self):
242 self.db.purge_history(duration=0)
245 track = Track(file='/baz/bar.baz', name='baz', artist='fooart',
246 albumartist='not-same', album='not-same',)
247 self.db.get_track(track)
248 # Set 6 more records from same artist but not same album
249 for i in range(1, 6): # starts at 1 to ensure records are in the past
250 trk = Track(file=f'/foo/{i}', name=f'{i}', artist='fooart',
251 albumartist='fooalbart', album='foolbum',)
252 # Add track, save its DB id
253 tracks_ids.append(self.db.get_track(trk))
254 # set records in the past to ease purging then
255 last = CURRENT - datetime.timedelta(minutes=i)
256 self.db.add_history(trk, date=last) # Add to history
257 conn = self.db.get_database_connection()
258 # for tid in tracks_ids:
259 for tid in tracks_ids[:-1]:
260 # Delete lastest record
261 conn.execute('DELETE FROM history WHERE history.track = ?',
263 c = conn.execute('SELECT albums.name FROM albums;')
264 # There are still albums records (still a history using it)
265 self.assertIn((trk.album,), c.fetchall())
266 # purging last entry in history for album == trk.album
267 conn.execute('DELETE FROM history WHERE history.track = ?',
269 # triggers purge other tables if possible
270 conn.execute('SELECT albums.name FROM albums;')
271 albums = c.fetchall()
272 # No more "foolbum" in the table albums
273 self.assertNotIn(('foolbum',), albums)
274 # There is still "fooart" though
275 c = conn.execute('SELECT artists.name FROM artists;')
276 artists = c.fetchall()
277 # No more "foolbum" in the table albums
278 self.assertIn(('fooart',), artists)
281 def test_06_add_album(self):
284 class Test_01BlockList(Main):
286 def test_blocklist_addition(self):
287 trk = Track(file='/foo/bar/baz', name='title')
288 self.assertIs(self.db.get_bl_track(trk, add=False), None)
290 # Set 6 records, same album
291 for i in range(1, 6): # starts at 1 to ensure records are in the past
292 trk = Track(file=f'/foo/{i}', name=f'{i}', artist='fooart',
293 albumartist='fooalbart', album='foolbum',)
294 # Add track, save its DB id
295 tracks_ids.append(self.db.get_track(trk))
297 self.db.get_bl_track(trk)
298 self.assertIsNot(self.db.get_bl_track(trk, add=False), None)
300 self.db.get_bl_album(Album(name=trk.album))
302 self.db.get_bl_artist(trk.Artist)
304 self.assertIs(self.db.get_bl_track(trk, add=False), None)
307 def test_blocklist_triggers_00(self):
308 trk01 = Track(file='01', name='01', artist='artist A', album='album A')
309 blart01_id = self.db.get_bl_artist(trk01.Artist)
310 blalb01_id = self.db.get_bl_album(Album(name=trk01.album, mbid=trk01.musicbrainz_albumid))
311 conn = self.db.get_database_connection()
312 self.db._remove_blocklist_id(blart01_id, with_connection=conn)
313 self.db._remove_blocklist_id(blalb01_id, with_connection=conn)
314 albums = conn.execute('SELECT albums.name FROM albums;').fetchall()
315 artists = conn.execute('SELECT artists.name FROM artists;').fetchall()
317 self.assertNotIn((trk01.album,), albums)
318 self.assertNotIn((trk01.artist,), artists)
320 def test_blocklist_triggers_01(self):
321 trk01 = Track(file='01', name='01', artist='artist A', album='album A')
322 trk02 = Track(file='02', name='01', artist='artist A', album='album B')
323 trk01_id = self.db.get_bl_track(trk01)
324 trk02_id = self.db.get_bl_track(trk02)
325 self.db.add_history(trk01, IN_THE_PAST)
326 self.db._remove_blocklist_id(trk01_id)
328 # albums/artists table not affected since trk01_id still in history
329 conn = self.db.get_database_connection()
330 albums = conn.execute('SELECT albums.name FROM albums;').fetchall()
331 artists = conn.execute('SELECT artists.name FROM artists;').fetchall()
332 self.assertIn(('album A',), albums)
333 self.assertIn(('artist A',), artists)
334 self.db.purge_history(0)
335 # remove last reference to trk01
336 albums = conn.execute('SELECT albums.name FROM albums;').fetchall()
337 self.assertNotIn(('album A',), albums)
338 self.assertIn(('artist A',), artists)
340 self.db._remove_blocklist_id(trk02_id)
341 albums = conn.execute('SELECT albums.name FROM albums;').fetchall()
342 artists = conn.execute('SELECT artists.name FROM artists;').fetchall()
343 self.assertNotIn(('album B',), albums)
344 self.assertNotIn(('artist A'), artists)
348 class Test_02Genre(Main):
350 def test_genre(self):
351 conn = self.db.get_database_connection()
352 self.db.get_genre('Post-Rock', with_connection=conn)
354 for i in range(1, 15): # starts at 1 to ensure records are in the past
355 trk = Track(file=f'/foo/bar.{i}', name=f'{i}-baz',
356 album='foolbum', artist=f'{i}-art', genre=f'{i}')
357 genres.append(f'{i}')
358 last = CURRENT - datetime.timedelta(minutes=i)
359 self.db.add_history(trk, date=last)
360 genre_hist = self.db.fetch_genres_history(limit=10)
361 self.assertEqual([g[0] for g in genre_hist], genres[:10])
363 def test_null_genres(self):
364 conn = self.db.get_database_connection()
366 for i in range(1, 2): # starts at 1 to ensure records are in the past
367 trk = Track(file=f'/foo/bar.{i}', name=f'{i}-baz',
368 album='foolbum', artist=f'{i}-art')
369 last = CURRENT - datetime.timedelta(minutes=i)
370 self.db.add_history(trk, date=last)
371 genre_hist = self.db.fetch_genres_history(limit=10)
372 self.assertEqual(genre_hist, [])
375 # vim: ai ts=4 sw=4 sts=4 expandtab fileencoding=utf8