Newer
Older
//
// tank
//
// Import and Playlist Daemon for autoradio project
//
//
// Copyright (C) 2017-2018 Christian Pointner <equinox@helsinki.at>
//
// 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"
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"
// 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)
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")
os.Exit(m.Run())
}
//
// Testing
//
func TestOpen(t *testing.T) {
// base-path is non-existing directory
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)
}
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")
// }
// }
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)
t.Fatalf("path of group '%s' is not a directory", gname)
store := newTestStore(t)
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 {
t.Fatalf("creating group failed: %v", err)
}
if _, err = store.CreateGroup(testGroup2); err != nil {
t.Fatalf("creating group failed: %v", err)
}
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)
}
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
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 {
498
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
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