Newer
Older
// tank, Import and Playlist Daemon for Aura project
// Copyright (C) 2017-2020 Christian Pointner <equinox@helsinki.at>
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
testBasePath = "run/aura-tank_testing"
testShowName = "foo"
testTestDirPath = filepath.Join(testBasePath, testShowName)
testDBPort = uint16(0)
testDBUsername = "tank"
testDBPassword = "aura"
testDBDB = "tank"
testShow1 = "test1"
testShow2 = "test2"
testSourceURI1 = "upload://test1.mp3"
testSourceURI2 = "upload://test2.ogg"
testFileArtist1 = "solo artist"
testFileArtist2 = "band of 2"
testFileAlbum1 = "first album"
testFileAlbum2 = "second album"
testFileTitle1 = "this is one title"
testFileTitle2 = "this is not two title's"
cfg.Type = os.Getenv("AURA_TANK_TEST_DB_TYPE")
if cfg.Type == "" {
cfg.Type = "mysql"
}
cfg.Host = os.Getenv("AURA_TANK_TEST_DB_HOST")
if cfg.Host == "" {
cfg.Host = "127.0.0.1"
}
switch cfg.Type {
case "mysql":
cfg.TLS = "false"
case "postgres":
cfg.TLS = "disable"
}
cfg.Port = testDBPort
cfg.Username = testDBUsername
cfg.Password = testDBPassword
cfg.DB = testDBDB
return
}
func newTestStore(t *testing.T) *Store {
cfg := Config{}
cfg.BasePath = testBasePath
// TODO: drop database (all tables and constrains)
store, err := NewStore(cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
func TestMain(m *testing.M) {
u, err := user.Current()
if err != nil {
os.Exit(-1)
}
testBasePath = fmt.Sprintf("/run/user/%s/aura-tank_testing", u.Uid)
testTestDirPath = filepath.Join(testBasePath, testShowName)
os.Exit(m.Run())
}
//
// Testing
//
func TestOpen(t *testing.T) {
// base-path is non-existing directory
if _, err := NewStore(cfg); err == nil {
t.Fatalf("opening store in nonexisting directory should throw an error")
}
cfg.BasePath = testBasePath
if err := os.MkdirAll(testBasePath, 0700); err != nil {
t.Fatalf("unexpected error: %v", err)
}
store, err := NewStore(cfg)
if err != nil {
t.Fatalf("creating new store failed: %v", err)
}
store.Close()
if store, err = NewStore(cfg); err != nil {
t.Fatalf("re-opening existing store failed: %v", err)
}
store.Close()
if err := os.RemoveAll(testTestDirPath); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// os.Remove(testDBConnection)
store, err := NewStore(cfg)
if err != nil {
t.Fatalf("creating new store failed: %v", err)
}
expected := dbMigrations[len(dbMigrations)-1].ID
if store.GetRevision() != expected {
t.Fatalf("for now new databases should have revision %q: got %q", expected, store.GetRevision())
}
store.Close()
// TODO: needs more testing!!!!
}
//// Arrrrgh!!!!!
//// both database/sql and gorm are incredibly stupid!!!!
//// using database/sql alone it is needlessly hard to write portable sql queries:
//// postgres: db.Exec("INSERT INTO shows (name) VALUES ($1)", testShow1)
//// mysql: db.Exec("INSERT INTO shows (name) VALUES (?)", testShow1)
////
//// using gorm db.Exec() will deal with that but i couldn't find a good way to get
//// the last inserted id without using the model code of gorm and we specifically
//// don't want to use the model code since it is not clear whether the constraints
//// are actually enforced by the DBMS or by some gorm callbacks...
////
// func TestDBConstraints(t *testing.T) {
// // we don't want to use the model but hand written SQL commands here since we want to check
// // whether the constraints are really enforced by the DBMS and not by some magic bullshit in gorm!
// // This is even worse since gorm.AutoUpdate will not deal with foreign key constraints and we
// // need do it ourselves at the migration scripts. It would be really nice to be able to check
// // whether the migrations do the right thing!!!
// db, err := gorm.Open(testDBType, testDBConnection)
// if err != nil {
// t.Fatalf("unexpected error: %v", err)
// }
// if err = db.DB().Ping(); err != nil {
// t.Fatalf("unexpected error: %v", err)
// }
// res := db.Exec("INSERT INTO shows (name) VALUES (?)", testShow1)
// if res.Error != nil {
// t.Fatalf("adding show '%s' shouldn't fail: %v", testShow1, res.Error)
// if res = db.Exec("INSERT INTO shows (name) VALUES (?)", testShow1); res.Error == nil {
// t.Fatalf("re-adding the same show should fail")
// if res = db.Exec("INSERT INTO files (show_name, size) VALUES (?, ?)", testShow1, 500); res.Error != nil {
// t.Fatalf("re-adding the same show should fail")
// Shows
func checkShows(t *testing.T, shows []Show, expected []string) {
// if len(shows) != len(expected) {
// t.Fatalf("expected %d shows in store but got %d: %v", len(expected), len(shows), shows)
for _, sname := range expected {
for _, g := range shows {
found = true
break
}
}
if !found {
t.Fatalf("expected show '%s' to be in store but got: %v", sname, shows)
if st, err := os.Stat(filepath.Join(testBasePath, sname)); err != nil {
t.Fatalf("can't open show directory for show '%s': %v", sname, err)
t.Fatalf("path of show '%s' is not a directory", sname)
func TestShows(t *testing.T) {
store := newTestStore(t)
shows, err := store.ListShows()
t.Fatalf("listing shows failed: %v", err)
checkShows(t, shows, []string{})
if f, err := os.Create(testTestDirPath); err != nil {
t.Fatalf("unexpected error: %v", err)
} else {
io.WriteString(f, "this is not a directory")
f.Close()
}
if _, err = store.CreateShow(testShowName); err == nil {
t.Fatalf("creating a show where dir exists but is not a directory should throw an error")
}
if err := os.RemoveAll(testTestDirPath); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err = store.CreateShow(testShow1); err != nil {
t.Fatalf("creating show failed: %v", err)
if _, err = store.CreateShow(testShow2); err != nil {
t.Fatalf("creating show failed: %v", err)
shows, err = store.ListShows()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
checkShows(t, shows, []string{testShow1, testShow2})
if err = store.DeleteShow(testShow1); err != nil {
t.Fatalf("deleting show '%s' failed: %v", testShow1, err)
shows, err = store.ListShows()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
checkShows(t, shows, []string{testShow2})
if err = store.DeleteShow(testShow2); err != nil {
t.Fatalf("deleteing show '%s' failed: %v", testShow2, err)
checkShows(t, shows, []string{})
func TestFilesListCreateDelete(t *testing.T) {
store := newTestStore(t)
files, err := store.ListFiles(testShowName, -1, -1)
t.Fatalf("listing files of test show failed: %v", err)
}
if len(files) != 0 {
t.Fatalf("a newly created store should contain no files in test show but ListFiles returned: %v", files)
files, err = store.ListFiles("notexistend", -1, -1)
t.Fatalf("listing files of not existing show shouldn't throw an error but returned: %v", err)
}
if len(files) != 0 {
t.Fatalf("listing files of not existing show should return and empty list but ListFiles returned: %v", files)
testFile, err := store.CreateFile(testShowName, File{})
t.Fatalf("creating file in test show failed: %v", err)
files, err = store.ListFiles(testShowName, -1, -1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(files) != 1 {
t.Fatalf("ListFiles should return a single file but returned: %v", files)
}
_, err = store.CreateFile(testShow1, File{Size: 17})
t.Fatalf("creating file in not existing show shouldn't throw an error but CreateFile returned: %v", err)
_, err = store.CreateFile(testShow1, File{Size: 23})
t.Fatalf("creating file in not existing show shouldn't throw an error but CreateFile returned: %v", err)
shows, err := store.ListShows()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
checkShows(t, shows, []string{testShowName, testShow1})
files, err = store.ListFiles(testShow1, -1, -1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(files) != 2 {
t.Fatalf("ListFiles should return a two files but returned: %v", files)
}
// clean up so next test can run with a clean DB, TODO: remove as soon as newTestStore() can re-init the DB
if err = store.DeleteShow(testShow1); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err = store.DeleteFile(testShowName, testFile.ID); err != nil {
t.Fatalf("deleteing file %d of show '%s' failed: %v", testFile.ID, testShowName, err)
checkShows(t, shows, []string{testShowName})
func fileEqual(a, b *File) bool {
// TODO: comparing the whole file struct using DeepEqual does not work because it does not use time.Equal when comparing timestamps...
return (a.Size == b.Size || reflect.DeepEqual(a.Source, b.Source) || reflect.DeepEqual(a.Metadata, b.Metadata))
}
func TestFilesCreateAndGet(t *testing.T) {
store := newTestStore(t)
if _, err := store.GetFile(testShow1, 0); err != ErrNotFound {
t.Fatalf("getting file in not-existing show should return ErrNotFound, but GetFile returned: %v", err)
file1.Metadata.Artist = testFileArtist1
file1.Metadata.Album = testFileAlbum1
file1.Metadata.Title = testFileTitle1
in1, err := store.CreateFile(testShow1, file1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out1, err := store.GetFile(testShow1, in1.ID)
if err != nil {
t.Fatalf("getting existing file from store shouldn't return an error, but GetFile returned: %v", err)
}
if !fileEqual(in1, out1) {
t.Fatalf("GetFile returned different file than expected. Got %+v, expected %+v", out1, in1)
}
if _, err = store.GetFile(testShow1, 0); err != ErrNotFound {
t.Fatalf("getting not-existing file in existing show should return ErrNotFound, but GetFile returned: %v", err)
file2.Metadata.Artist = testFileArtist2
file2.Metadata.Album = testFileAlbum2
file2.Metadata.Title = testFileTitle2
in2, err := store.CreateFile(testShow1, file2)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out2, err := store.GetFile(testShow1, in2.ID)
if err != nil {
t.Fatalf("getting existing file from store shouldn't return an error, but GetFile returned: %v", err)
}
if !fileEqual(in2, out2) {
t.Fatalf("GetFile returned different file than expected. Got %+v, expected %+v", out2, in2)
}
files, err := store.ListFiles(testShow1, -1, -1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(files) != 2 {
t.Fatalf("show '%s' should contain 2 files but ListFiles returned: %+v", testShow1, files)
}
for _, file := range files {
if file.ID == in1.ID {
if !fileEqual(in1, out1) {
t.Fatalf("ListFile returned different file than expected. Got %+v, expected %+v", out1, in1)
}
} else if file.ID == in2.ID {
if !fileEqual(in2, out2) {
t.Fatalf("ListFile returned different file than expected. Got %+v, expected %+v", out2, in2)
}
} else {
t.Fatalf("show '%s' should only contain files %d and %d but ListFiles returned: %+v", testShow1, in1.ID, in2.ID, files)
// clean up so next test can run with a clean DB, TODO: remove as soon as newTestStore() can re-init the DB
if err = store.DeleteShow(testShow1); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
// TODO: add test for pagination in store.ListeFiles()
func TestFilesUpdate(t *testing.T) {
store := newTestStore(t)
if _, err := store.UpdateFile(testShowName, 0, File{}); err != ErrNotFound {
t.Fatalf("updateting not-existing file hould return ErrNotFound, but UpdateFile returned: %v", err)
}
file.Metadata.Artist = testFileArtist1
file.Metadata.Album = testFileAlbum1
file.Metadata.Title = testFileTitle1
in, err := store.CreateFile(testShow2, file)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out, err := store.GetFile(testShow2, in.ID)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !fileEqual(in, out) {
t.Fatalf("GetFile returned different file than expected. Got %+v, expected %+v", out, in)
}
out.Size = 54321
out.Metadata.Artist = testFileArtist2
out.Metadata.Album = testFileAlbum2
out.Metadata.Title = testFileTitle2
if _, err = store.UpdateFile(testShow2, in.ID, *out); err != nil {
t.Fatalf("updateting an existing file shouldn't fail but UpdateFile returned: %v", err)
}
files, err := store.ListFiles(testShow2, -1, -1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(files) != 1 {
t.Fatalf("show '%s' should contain 1 file but ListFiles returned: %+v", testShow2, files)
}
if files[0].ID != in.ID || !fileEqual(out, &(files[0])) {
t.Fatalf("ListFile returned different file than expected. Got %+v, expected %+v", out, files[0])
}
// clean up so next test can run with a clean DB, TODO: remove as soon as newTestStore() can re-init the DB
if err = store.DeleteShow(testShow2); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestFilesDelete(t *testing.T) {
store := newTestStore(t)
if err := store.DeleteFile(testShow1, 0); err != ErrNotFound {
t.Fatalf("deleting not-existing file should return ErrNotFound, but DeleteFile returned: %v", err)
}
file, err := store.CreateFile(testShow1, File{Size: 12345})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err = store.DeleteFile(testShow1, file.ID); err != nil {
t.Fatalf("deleting file failed, DeleteFile returned: %v", err)
}
if err = store.DeleteFile(testShow1, file.ID); err != ErrNotFound {
t.Fatalf("repeated deletion of file should return ErrNotFound, but DeleteFile returned: %v", err)
}
// clean up so next test can run with a clean DB, TODO: remove as soon as newTestStore() can re-init the DB
if err = store.DeleteShow(testShow1); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
func TestFilesImportState(t *testing.T) {
store := newTestStore(t)
file, err := store.CreateFile(testShow1, File{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if file.Source.Import.State != ImportNew {
t.Fatalf("newly created file should have state %q but has %q", ImportNew, file.Source.Import.State)
}
if _, err = store.SetFileImportStateInitializing(file.ShowName, file.ID); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err = store.SetFileImportStateInitializing(file.ShowName, file.ID); err != ErrFileNotNew {
t.Fatalf("setting file import state to %q for file that is not new must fail with ErrFileNotNew but got: %v", ImportInitializing, err)
}
if file, err = store.SetFileImportStatePending(file.ShowName, file.ID); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if file.Source.Import.State != ImportPending {
t.Fatalf("file should now have state %q but has %q", ImportPending, file.Source.Import.State)
}
if file, err = store.SetFileImportStateRunning(file.ShowName, file.ID); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if file.Source.Import.State != ImportRunning {
t.Fatalf("file should now have state %q but has %q", ImportRunning, file.Source.Import.State)
}
// TODO: testing done needs some extra work since the store will try to read the metadata from the file
if file, err = store.SetFileImportStateAborted(file.ShowName, file.ID, "computer says noooo..."); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if file.Source.Import.State != ImportAborted {
t.Fatalf("file should now have state %q but has %q", ImportAborted, file.Source.Import.State)
}
if file.Source.Import.Error != "computer says noooo..." {
t.Fatalf("file import error is wrong: %q", file.Source.Import.Error)
}
}
func TestFilesSourceHash(t *testing.T) {
store := newTestStore(t)
file, err := store.CreateFile(testShow1, File{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if file.Source.Hash != "" {
t.Fatalf("new file shoud have no hash set but has: %q", file.Source.Hash)
}
hash := "this-is-the-output-of-a-hash-function"
if file, err = store.UpdateFileSourceHash(file.ShowName, file.ID, hash); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if file.Source.Hash != hash {
t.Fatalf("file should now have hash set to %q but has %q", hash, file.Source.Hash)
}
}
type playlistTestEntry struct {
uri string
duration *time.Duration
}
func generateTestPlaylist(entries ...playlistTestEntry) (p Playlist) {
for _, entry := range entries {
e := PlaylistEntry{URI: entry.uri, Duration: entry.duration}
p.Entries = append(p.Entries, e)
}
return p
}
func TestPlaylistsListCreateDelete(t *testing.T) {
store := newTestStore(t)
playlists, err := store.ListPlaylists(testShowName, -1, -1)
t.Fatalf("listing playlists of test show failed: %v", err)
t.Fatalf("a newly created store should contain no playlists in test show but ListPlaylists returned: %v", playlists)
playlists, err = store.ListPlaylists("notexistend", -1, -1)
t.Fatalf("listing playlists of not existing show shouldn't throw an error but returned: %v", err)
t.Fatalf("listing playlists of not existing show should return and empty list but ListPlaylists returned: %v", playlists)
in := generateTestPlaylist(playlistTestEntry{"audioin://1", nil}, playlistTestEntry{"http://stream.example.com/live.mp", nil})
testPlaylist, err := store.CreatePlaylist(testShowName, in)
if err != ErrPlaylistHasMultipleNullDurationEntries {
t.Fatalf("creating playlist with more than one non-file entry without duration should fail with specific error but returned: %v", err)
}
testDuration := time.Second
in = generateTestPlaylist(playlistTestEntry{"audioin://1", &testDuration}, playlistTestEntry{"http://stream.example.com/live.mp", nil})
testPlaylist, err = store.CreatePlaylist(testShowName, in)
t.Fatalf("creating playlist in test show failed: %v", err)
playlists, err = store.ListPlaylists(testShowName, -1, -1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(playlists) != 1 {
t.Fatalf("ListPlaylists should return a single playlist but returned: %v", playlists)
}
in1 := generateTestPlaylist(playlistTestEntry{"audioin://1", &testDuration}, playlistTestEntry{"http://stream.example.com/live.mp3", nil})
_, err = store.CreatePlaylist(testShow1, in1)
t.Fatalf("creating playlist in not existing show shouldn't throw an error but CreatePlaylist returned: %v", err)
in2 := generateTestPlaylist(playlistTestEntry{"https://stream.example.com/other.ogg", &testDuration}, playlistTestEntry{"audioin://2", nil})
_, err = store.CreatePlaylist(testShow1, in2)
t.Fatalf("creating playlist in not existing show shouldn't throw an error but CreatePlaylist returned: %v", err)
shows, err := store.ListShows()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
checkShows(t, shows, []string{testShowName, testShow1})
playlists, err = store.ListPlaylists(testShow1, -1, -1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(playlists) != 2 {
t.Fatalf("ListPlaylists should return two playlists but returned: %v", playlists)
}
// clean up so next test can run with a clean DB, TODO: remove as soon as newTestStore() can re-init the DB
if err = store.DeleteShow(testShow1); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err = store.DeletePlaylist(testShowName, testPlaylist.ID); err != nil {
t.Fatalf("deleteing playlist %d of show '%s' failed: %v", testPlaylist.ID, testShowName, err)
func TestPlaylistsCreateAndGet(t *testing.T) {
store := newTestStore(t)
f := File{Size: 12345}
f.Metadata.Artist = testFileArtist1
f.Metadata.Album = testFileAlbum1
f.Metadata.Title = testFileTitle1
file1, err := store.CreateFile(testShow1, f)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := store.GetPlaylist(testShow1, 0); err != ErrNotFound {
t.Fatalf("getting playlist in not-existing show should return ErrNotFound, but GetPlaylist returned: %v", err)
p := generateTestPlaylist(playlistTestEntry{"http://stream.example.com/stream.mp3", nil})
p.Entries = append(p.Entries, PlaylistEntry{File: &File{ShowName: file1.ShowName, ID: file1.ID}})
list1, err := store.CreatePlaylist(testShow1, p)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := store.GetPlaylist(testShow1, list1.ID); err != nil {
t.Fatalf("getting existing playlist from store shouldn't return an error, but GetPlaylist returned: %v", err)
}
// TODO: check if playlists are equal
p = generateTestPlaylist(playlistTestEntry{"http://stream.example.com/other.mp3", nil}, playlistTestEntry{fmt.Sprintf("file://%s/%d", file1.ShowName, file1.ID), nil})
if _, err = store.CreatePlaylist(testShow1, p); err != nil {
t.Fatalf("unexpected error: %v", err)
}
playlists, err := store.ListPlaylists(testShow1, -1, -1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(playlists) != 2 {
t.Fatalf("ListPlaylists should return two playlists but returned: %v", playlists)
}
// TODO: check playlists contains both lists
// TODO: add test for pagination in store.ListePlaylists()
// File usage
//
func TestFileUsage(t *testing.T) {
store := newTestStore(t)
file := &File{Size: 12345}
file.Source.URI = testSourceURI1
file.Metadata.Artist = testFileArtist1
file.Metadata.Album = testFileAlbum1
file.Metadata.Title = testFileTitle1
file, err := store.CreateFile(testShow1, *file)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
lists, err := store.GetFileUsage(file.ShowName, file.ID)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(lists) != 0 {
t.Fatalf("file should be in use by any playlist but got %d entries in usage list", len(lists))
}
p := generateTestPlaylist(playlistTestEntry{"http://stream.example.com/stream.mp3", nil})
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
p.Entries = append(p.Entries, PlaylistEntry{File: &File{ShowName: file.ShowName, ID: file.ID}})
list, err := store.CreatePlaylist(testShow1, p)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
lists, err = store.GetFileUsage(file.ShowName, file.ID)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(lists) != 1 {
t.Fatalf("file should be used by exactly one playlist but got %d entries in usage list", len(lists))
}
if lists[0].ID != list.ID {
t.Fatalf("file should be in use by playlist %d but entries of usage list only contains: %d", list.ID, lists[0].ID)
}
err = store.DeleteFile(file.ShowName, file.ID)
errInUse, ok := err.(*ErrFileInUse)
if !ok {
t.Fatalf("deleting file that is in use should return an error of type ErrFileInUs, but returned: %v (type: %t)", err, err)
}
if len(errInUse.Playlists) != 1 || errInUse.Playlists[0].ID != list.ID {
t.Fatalf("usage reports invalid playlist-list: %+v", errInUse.Playlists)
}
if err = store.DeletePlaylist(list.ShowName, list.ID); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err = store.DeleteFile(file.ShowName, file.ID); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}