Skip to content
Snippets Groups Projects
store_test.go 19.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Christian Pointner's avatar
    Christian Pointner committed
    //
    //  tank
    //
    //  Import and Playlist Daemon for autoradio project
    //
    //
    
    //  Copyright (C) 2017-2018 Christian Pointner <equinox@helsinki.at>
    
    Christian Pointner's avatar
    Christian Pointner committed
    //
    //  This file is part of tank.
    //
    //  tank is free software: you can redistribute it and/or modify
    //  it under the terms of the GNU General Public License as published by
    //  the Free Software Foundation, either version 3 of the License, or
    //  any later version.
    //
    //  tank 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 General Public License for more details.
    //
    //  You should have received a copy of the GNU General Public License
    //  along with tank. If not, see <http://www.gnu.org/licenses/>.
    //
    
    package store
    
    import (
    	"fmt"
    
    Christian Pointner's avatar
    Christian Pointner committed
    	"os"
    	"os/user"
    
    	"path/filepath"
    
    Christian Pointner's avatar
    Christian Pointner committed
    	"testing"
    
    Christian Pointner's avatar
    Christian Pointner committed
    	//"github.com/jinzhu/gorm"
    
    	testBasePath      = "run/aura-tank_testing"
    	testPublicDirPath = filepath.Join(testBasePath, publicGroupName)
    	testDBType        = "mysql"
    	testDBConnection  = "tank:aura@tcp(127.0.0.1:3306)/tank?charset=utf8&parseTime=True&loc=Local"
    
    Christian Pointner's avatar
    Christian Pointner committed
    	// testDBType       = "postgres"
    	// testDBConnection = "host=127.0.0.1 port=5432 user=tank dbname=tank password=aura sslmode=disable"
    
    	testGroup1          = "test1"
    	testGroup2          = "test2"
    	testUser1           = "user1"
    	testUser2           = "user2"
    	testSourceFileName1 = "test1.mp3"
    	testSourceFileName2 = "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"
    
    func newTestStore(t *testing.T) *Store {
    	cfg := Config{}
    	cfg.BasePath = testBasePath
    	cfg.DB.Type = testDBType
    	cfg.DB.Connection = testDBConnection
    
    
    	// TODO: drop database (all tables and constrains)
    
    
    	store, err := NewStore(cfg)
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    
    Christian Pointner's avatar
    Christian Pointner committed
    	}
    
    Christian Pointner's avatar
    Christian Pointner committed
    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)
    
    	os.RemoveAll(testBasePath)
    	testPublicDirPath = filepath.Join(testBasePath, publicGroupName)
    
    	//	testDBConnection = filepath.Join(testBasePath, ".db.sqlite")
    
    Christian Pointner's avatar
    Christian Pointner committed
    	os.Exit(m.Run())
    }
    
    //
    // Testing
    //
    
    
    func TestOpen(t *testing.T) {
    	// base-path is non-existing directory
    
    Christian Pointner's avatar
    Christian Pointner committed
    	cfg := Config{}
    
    	cfg.BasePath = "/nonexistend/"
    	cfg.DB.Type = testDBType
    	cfg.DB.Connection = testDBConnection
    	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)
    	}
    
    	/// exisiting but non-database file (only possible with sqlite...)
    
    	// if f, err := os.Create(testDBConnection); err != nil {
    	// 	t.Fatalf("unexpected error: %v", err)
    	// } else {
    	// 	io.WriteString(f, "this is not a sqlite db.")
    	// 	f.Close()
    	// }
    	// if _, err := NewStore(cfg); err == nil {
    	// 	t.Fatalf("opening store using a invalid database should throw an error")
    	// }
    
    	/// create new db and reopen it
    	// os.Remove(testDBConnection)
    
    	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(testPublicDirPath); err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    	if f, err := os.Create(testPublicDirPath); err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	} else {
    		io.WriteString(f, "this is not a directory")
    		f.Close()
    	}
    	if _, err = NewStore(cfg); err == nil {
    		t.Fatalf("opening store where path to public group dir is not a directory should throw an error")
    	}
    	if err := os.RemoveAll(testPublicDirPath); err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    
    func TestMigrations(t *testing.T) {
    
    Christian Pointner's avatar
    Christian Pointner committed
    	cfg := Config{}
    
    	cfg.BasePath = testBasePath
    	cfg.DB.Type = testDBType
    	cfg.DB.Connection = testDBConnection
    
    	// os.Remove(testDBConnection)
    
    	store, err := NewStore(cfg)
    	if err != nil {
    		t.Fatalf("creating new store failed: %v", err)
    	}
    	// TODO: This is only true for now!!
    
    	if store.GetRevision() != "201806161853" {
    		t.Fatalf("for now new databases should have revision %q: got %q", "201806161853", 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 groups (name) VALUES ($1)", testGroup1)
    ////     mysql:    db.Exec("INSERT INTO groups (name) VALUES (?)", testGroup1)
    ////
    ////  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 groups (name) VALUES (?)", testGroup1)
    // 	if res.Error != nil {
    // 		t.Fatalf("adding group '%s' shouldn't fail: %v", testGroup1, res.Error)
    // 	}
    // 	if res = db.Exec("INSERT INTO groups (name) VALUES (?)", testGroup1); res.Error == nil {
    // 		t.Fatalf("re-adding the same group should fail")
    // 	}
    
    // 	if res = db.Exec("INSERT INTO files (group_name, size) VALUES (?, ?)", testGroup1, 500); res.Error != nil {
    // 		t.Fatalf("re-adding the same group should fail")
    // 	}
    // }
    
    Christian Pointner's avatar
    Christian Pointner committed
    // Groups
    //
    
    func checkGroups(t *testing.T, groups Groups, expected []string) {
    
    	// if len(groups) != len(expected) {
    	// 	t.Fatalf("expected %d groups in store but got %d: %v", len(expected), len(groups), groups)
    	// }
    
    	for _, gname := range expected {
    		found := false
    		for _, g := range groups {
    			if gname == g.Name {
    				found = true
    				break
    			}
    		}
    		if !found {
    			t.Fatalf("expected group '%s' to be in store but got: %v", gname, groups)
    		}
    		if st, err := os.Stat(filepath.Join(testBasePath, gname)); err != nil {
    
    			t.Fatalf("can't open group directory for group '%s': %v", gname, err)
    
    		} else if !st.IsDir() {
    
    			t.Fatalf("path of group '%s' is not a directory", gname)
    
    Christian Pointner's avatar
    Christian Pointner committed
    func TestGroups(t *testing.T) {
    
    	store := newTestStore(t)
    
    Christian Pointner's avatar
    Christian Pointner committed
    	groups, err := store.ListGroups()
    	if err != nil {
    		t.Fatalf("listing groups failed: %v", err)
    	}
    
    	checkGroups(t, groups, []string{publicGroupName})
    
    	if _, err = store.CreateGroup(testGroup1); err != nil {
    
    Christian Pointner's avatar
    Christian Pointner committed
    		t.Fatalf("creating group failed: %v", err)
    	}
    
    	if _, err = store.CreateGroup(testGroup2); err != nil {
    
    Christian Pointner's avatar
    Christian Pointner committed
    		t.Fatalf("creating group failed: %v", err)
    	}
    
    Christian Pointner's avatar
    Christian Pointner committed
    	groups, err = store.ListGroups()
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    
    	checkGroups(t, groups, []string{publicGroupName, testGroup1, testGroup2})
    
    	if err = store.DeleteGroup(testGroup1); err != nil {
    
    		t.Fatalf("deleting group '%s' failed: %v", testGroup1, err)
    
    	groups, err = store.ListGroups()
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    	checkGroups(t, groups, []string{publicGroupName, testGroup2})
    
    	if err = store.DeleteGroup(testGroup2); err != nil {
    
    		t.Fatalf("deleteing group '%s' failed: %v", testGroup2, err)
    
    	}
    	checkGroups(t, groups, []string{publicGroupName})
    
    func TestFilesListCreateDelete(t *testing.T) {
    	store := newTestStore(t)
    
    	files, err := store.ListFiles(publicGroupName)
    	if err != nil {
    		t.Fatalf("listing files of public group failed: %v", err)
    	}
    	if len(files) != 0 {
    		t.Fatalf("a newly created store should contain no files in public group but ListFiles returned: %v", files)
    	}
    
    	files, err = store.ListFiles("notexistend")
    	if err != nil {
    		t.Fatalf("listing files of not existing group shouldn't throw an error but returned: %v", err)
    	}
    	if len(files) != 0 {
    		t.Fatalf("listing files of not existing group should return and empty list but ListFiles returned: %v", files)
    	}
    
    	publicFile, err := store.CreateFile(publicGroupName, File{})
    	if err != nil {
    		t.Fatalf("creating file in public group failed: %v", err)
    	}
    
    	files, err = store.ListFiles(publicGroupName)
    	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(testGroup1, File{Size: 17})
    	if err != nil {
    		t.Fatalf("creating file in not existing group shouldn't throw an error but CreateFile returned: %v", err)
    	}
    	_, err = store.CreateFile(testGroup1, File{Size: 23})
    	if err != nil {
    		t.Fatalf("creating file in not existing group shouldn't throw an error but CreateFile returned: %v", err)
    	}
    
    	groups, err := store.ListGroups()
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    	checkGroups(t, groups, []string{publicGroupName, testGroup1})
    
    	files, err = store.ListFiles(testGroup1)
    	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.DeleteGroup(testGroup1); err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    
    	if err = store.DeleteFile(publicGroupName, publicFile.ID); err != nil {
    		t.Fatalf("deleteing file %d of group '%s' failed: %v", publicFile.ID, publicGroupName, err)
    	}
    	checkGroups(t, groups, []string{publicGroupName})
    }
    
    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(testGroup1, 0); err != ErrNotFound {
    		t.Fatalf("getting file in not-existing group should return ErrNotFound, but GetFile returned: %v", err)
    	}
    
    	file1 := File{Size: 12345}
    	file1.Source.FileName = testSourceFileName1
    	file1.Metadata.Artist = testFileArtist1
    	file1.Metadata.Album = testFileAlbum1
    	file1.Metadata.Title = testFileTitle1
    	in1, err := store.CreateFile(testGroup1, file1)
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    
    	out1, err := store.GetFile(testGroup1, 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(testGroup1, 0); err != ErrNotFound {
    		t.Fatalf("getting not-existing file in existing group should return ErrNotFound, but GetFile returned: %v", err)
    	}
    
    	file2 := File{Size: 54321}
    	file2.Source.FileName = testSourceFileName2
    	file2.Metadata.Artist = testFileArtist2
    	file2.Metadata.Album = testFileAlbum2
    	file2.Metadata.Title = testFileTitle2
    	in2, err := store.CreateFile(testGroup1, file2)
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    
    	out2, err := store.GetFile(testGroup1, 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(testGroup1)
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    	if len(files) != 2 {
    		t.Fatalf("group '%s' should contain 2 files but ListFiles returned: %+v", testGroup1, 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("group '%s' should only contain files %d and %d but ListFiles returned: %+v", testGroup1, 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.DeleteGroup(testGroup1); err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    }
    
    func TestFilesUpdate(t *testing.T) {
    	store := newTestStore(t)
    
    	if _, err := store.UpdateFile(publicGroupName, 0, File{}); err != ErrNotFound {
    		t.Fatalf("updateting not-existing file hould return ErrNotFound, but UpdateFile returned: %v", err)
    	}
    
    	file := File{Size: 12345}
    	file.Source.FileName = testSourceFileName1
    	file.Metadata.Artist = testFileArtist1
    	file.Metadata.Album = testFileAlbum1
    	file.Metadata.Title = testFileTitle1
    	in, err := store.CreateFile(testGroup2, file)
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    
    	out, err := store.GetFile(testGroup2, 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.Source.FileName = testSourceFileName2
    	out.Metadata.Artist = testFileArtist2
    	out.Metadata.Album = testFileAlbum2
    	out.Metadata.Title = testFileTitle2
    	if _, err = store.UpdateFile(testGroup2, in.ID, *out); err != nil {
    		t.Fatalf("updateting an existing file shouldn't fail but UpdateFile returned: %v", err)
    	}
    
    	files, err := store.ListFiles(testGroup2)
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    	if len(files) != 1 {
    		t.Fatalf("group '%s' should contain 1 file but ListFiles returned: %+v", testGroup2, 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.DeleteGroup(testGroup2); err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    }
    
    func TestFilesDelete(t *testing.T) {
    	store := newTestStore(t)
    
    	if err := store.DeleteFile(testGroup1, 0); err != ErrNotFound {
    		t.Fatalf("deleting not-existing file should return ErrNotFound, but DeleteFile returned: %v", err)
    	}
    
    	file, err := store.CreateFile(testGroup1, File{Size: 12345})
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    
    	if err = store.DeleteFile(testGroup1, file.ID); err != nil {
    		t.Fatalf("deleting file failed, DeleteFile returned: %v", err)
    	}
    
    	if err = store.DeleteFile(testGroup1, 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.DeleteGroup(testGroup1); err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    }
    
    
    // Playlists
    //
    
    func generateTestPlaylist(uris ...string) (p Playlist) {
    	for _, u := range uris {
    
    		e := PlaylistEntry{URI: u}
    
    		p.Entries = append(p.Entries, e)
    	}
    	return p
    }
    
    func TestPlaylistsListCreateDelete(t *testing.T) {
    	store := newTestStore(t)
    
    	playlists, err := store.ListPlaylists(publicGroupName)
    	if err != nil {
    		t.Fatalf("listing playlists of public group failed: %v", err)
    	}
    	if len(playlists) != 0 {
    		t.Fatalf("a newly created store should contain no playlists in public group but ListPlaylists returned: %v", playlists)
    	}
    
    	playlists, err = store.ListPlaylists("notexistend")
    	if err != nil {
    		t.Fatalf("listing playlists of not existing group shouldn't throw an error but returned: %v", err)
    	}
    	if len(playlists) != 0 {
    		t.Fatalf("listing playlists of not existing group should return and empty list but ListPlaylists returned: %v", playlists)
    	}
    
    	in := generateTestPlaylist("audioin://1", "http://stream.example.com/live.mp")
    	publicPlaylist, err := store.CreatePlaylist(publicGroupName, in)
    	if err != nil {
    		t.Fatalf("creating playlist in public group failed: %v", err)
    	}
    
    	playlists, err = store.ListPlaylists(publicGroupName)
    	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("audioin://1", "http://stream.example.com/live.mp3")
    
    	_, err = store.CreatePlaylist(testGroup1, in1)
    	if err != nil {
    		t.Fatalf("creating playlist in not existing group shouldn't throw an error but CreatePlaylist returned: %v", err)
    	}
    
    	in2 := generateTestPlaylist("https://stream.example.com/other.ogg", "audioin://2")
    
    	_, err = store.CreatePlaylist(testGroup1, in2)
    	if err != nil {
    		t.Fatalf("creating playlist in not existing group shouldn't throw an error but CreatePlaylist returned: %v", err)
    	}
    
    	groups, err := store.ListGroups()
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    	checkGroups(t, groups, []string{publicGroupName, testGroup1})
    
    	playlists, err = store.ListPlaylists(testGroup1)
    	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.DeleteGroup(testGroup1); err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    
    	if err = store.DeletePlaylist(publicGroupName, publicPlaylist.ID); err != nil {
    		t.Fatalf("deleteing playlist %d of group '%s' failed: %v", publicPlaylist.ID, publicGroupName, err)
    	}
    }
    
    
    func TestPlaylistsCreateAndGet(t *testing.T) {
    	store := newTestStore(t)
    
    	f := File{Size: 12345}
    	f.Source.FileName = testSourceFileName1
    	f.Metadata.Artist = testFileArtist1
    	f.Metadata.Album = testFileAlbum1
    	f.Metadata.Title = testFileTitle1
    	file1, err := store.CreateFile(testGroup1, f)
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    
    	if _, err := store.GetPlaylist(testGroup1, 0); err != ErrNotFound {
    		t.Fatalf("getting playlist in not-existing group should return ErrNotFound, but GetPlaylist returned: %v", err)
    	}
    
    
    	p := generateTestPlaylist("http://stream.example.com/stream.mp3")
    
    	p.Entries = append(p.Entries, PlaylistEntry{File: &File{GroupName: file1.GroupName, ID: file1.ID}})
    	list1, err := store.CreatePlaylist(testGroup1, p)
    	if err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    
    	if _, err := store.GetPlaylist(testGroup1, 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("http://stream.example.com/other.mp3", fmt.Sprintf("file://%s/%d", file1.GroupName, file1.ID))
    	if _, err = store.CreatePlaylist(testGroup1, p); err != nil {
    		t.Fatalf("unexpected error: %v", err)
    	}
    
    	playlists, err := store.ListPlaylists(testGroup1)
    	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