refactor: update scanner, scanner tests, mockfs
closes #165 closes #163
This commit is contained in:
@@ -1,39 +1,19 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
"gopkg.in/gormigrate.v1"
|
||||
)
|
||||
|
||||
// wrapMigrations wraps a list of migrations to add logging and transactions
|
||||
func wrapMigrations(migrs ...gormigrate.Migration) []*gormigrate.Migration {
|
||||
log := func(i int, mig gormigrate.MigrateFunc, name string) gormigrate.MigrateFunc {
|
||||
return func(db *gorm.DB) error {
|
||||
// print that we're on the ith out of n migrations
|
||||
defer log.Printf("migration (%d/%d) '%s' finished", i+1, len(migrs), name)
|
||||
return db.Transaction(mig)
|
||||
}
|
||||
}
|
||||
ret := make([]*gormigrate.Migration, 0, len(migrs))
|
||||
for i, mig := range migrs {
|
||||
ret = append(ret, &gormigrate.Migration{
|
||||
ID: mig.ID,
|
||||
Rollback: mig.Rollback,
|
||||
Migrate: log(i, mig.Migrate, mig.ID),
|
||||
})
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func defaultOptions() url.Values {
|
||||
func DefaultOptions() url.Values {
|
||||
return url.Values{
|
||||
// with this, multiple connections share a single data and schema cache.
|
||||
// see https://www.sqlite.org/sharedcache.html
|
||||
@@ -46,17 +26,23 @@ func defaultOptions() url.Values {
|
||||
}
|
||||
}
|
||||
|
||||
func mockOptions() url.Values {
|
||||
return url.Values{
|
||||
"_foreign_keys": {"true"},
|
||||
}
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
func New(path string) (*DB, error) {
|
||||
func New(path string, options url.Values) (*DB, error) {
|
||||
// https://github.com/mattn/go-sqlite3#connection-string
|
||||
url := url.URL{
|
||||
Scheme: "file",
|
||||
Opaque: path,
|
||||
}
|
||||
url.RawQuery = defaultOptions().Encode()
|
||||
url.RawQuery = options.Encode()
|
||||
db, err := gorm.Open("sqlite3", url.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("with gorm: %w", err)
|
||||
@@ -91,34 +77,29 @@ func New(path string) (*DB, error) {
|
||||
}
|
||||
|
||||
func NewMock() (*DB, error) {
|
||||
return New(":memory:")
|
||||
return New(":memory:", mockOptions())
|
||||
}
|
||||
|
||||
func (db *DB) GetSetting(key string) string {
|
||||
func (db *DB) GetSetting(key string) (string, error) {
|
||||
setting := &Setting{}
|
||||
db.
|
||||
Where("key=?", key).
|
||||
First(setting)
|
||||
return setting.Value
|
||||
if err := db.Where("key=?", key).First(setting).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "", err
|
||||
}
|
||||
return setting.Value, nil
|
||||
}
|
||||
|
||||
func (db *DB) SetSetting(key, value string) {
|
||||
db.
|
||||
func (db *DB) SetSetting(key, value string) error {
|
||||
return db.
|
||||
Where(Setting{Key: key}).
|
||||
Assign(Setting{Value: value}).
|
||||
FirstOrCreate(&Setting{})
|
||||
}
|
||||
|
||||
func (db *DB) GetOrCreateKey(key string) string {
|
||||
value := db.GetSetting(key)
|
||||
if value == "" {
|
||||
value = string(securecookie.GenerateRandomKey(32))
|
||||
db.SetSetting(key, value)
|
||||
}
|
||||
return value
|
||||
FirstOrCreate(&Setting{}).
|
||||
Error
|
||||
}
|
||||
|
||||
func (db *DB) InsertBulkLeftMany(table string, head []string, left int, col []int) error {
|
||||
if len(col) == 0 {
|
||||
return nil
|
||||
}
|
||||
var rows []string
|
||||
var values []interface{}
|
||||
for _, c := range col {
|
||||
@@ -139,7 +120,7 @@ func (db *DB) GetUserByID(id int) *User {
|
||||
Where("id=?", id).
|
||||
First(user).
|
||||
Error
|
||||
if gorm.IsRecordNotFoundError(err) {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return user
|
||||
@@ -151,7 +132,7 @@ func (db *DB) GetUserByName(name string) *User {
|
||||
Where("name=?", name).
|
||||
First(user).
|
||||
Error
|
||||
if gorm.IsRecordNotFoundError(err) {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return user
|
||||
@@ -164,6 +145,9 @@ func (db *DB) Begin() *DB {
|
||||
type ChunkFunc func(*gorm.DB, []int64) error
|
||||
|
||||
func (db *DB) TransactionChunked(data []int64, cb ChunkFunc) error {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
// https://sqlite.org/limits.html
|
||||
const size = 999
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
var testDB *DB
|
||||
|
||||
func randKey() string {
|
||||
letters := []rune("abcdef0123456789")
|
||||
b := make([]rune, 16)
|
||||
@@ -22,27 +22,31 @@ func randKey() string {
|
||||
|
||||
func TestGetSetting(t *testing.T) {
|
||||
key := randKey()
|
||||
// new key
|
||||
expected := "hello"
|
||||
testDB.SetSetting(key, expected)
|
||||
actual := testDB.GetSetting(key)
|
||||
if actual != expected {
|
||||
t.Errorf("expected %q, got %q", expected, actual)
|
||||
value := "howdy"
|
||||
|
||||
is := is.New(t)
|
||||
|
||||
testDB, err := NewMock()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating db: %v", err)
|
||||
}
|
||||
// existing key
|
||||
expected = "howdy"
|
||||
testDB.SetSetting(key, expected)
|
||||
actual = testDB.GetSetting(key)
|
||||
if actual != expected {
|
||||
t.Errorf("expected %q, got %q", expected, actual)
|
||||
if err := testDB.Migrate(MigrationContext{}); err != nil {
|
||||
t.Fatalf("error migrating db: %v", err)
|
||||
}
|
||||
|
||||
is.NoErr(testDB.SetSetting(key, value))
|
||||
|
||||
actual, err := testDB.GetSetting(key)
|
||||
is.NoErr(err)
|
||||
is.Equal(actual, value)
|
||||
|
||||
is.NoErr(testDB.SetSetting(key, value))
|
||||
actual, err = testDB.GetSetting(key)
|
||||
is.NoErr(err)
|
||||
is.Equal(actual, value)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var err error
|
||||
testDB, err = NewMock()
|
||||
if err != nil {
|
||||
log.Fatalf("error opening database: %v\n", err)
|
||||
}
|
||||
log.SetOutput(ioutil.Discard)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
@@ -31,20 +32,14 @@ func migrateInitSchema() gormigrate.Migration {
|
||||
}
|
||||
}
|
||||
|
||||
func migrateCreateInitUser() gormigrate.Migration {
|
||||
return gormigrate.Migration{
|
||||
ID: "202002192019",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
const (
|
||||
initUsername = "admin"
|
||||
initPassword = "admin"
|
||||
)
|
||||
err := tx.
|
||||
Where("name=?", initUsername).
|
||||
First(&User{}).
|
||||
Error
|
||||
if !gorm.IsRecordNotFoundError(err) {
|
||||
return nil
|
||||
func construct(ctx MigrationContext, id string, f func(*gorm.DB, MigrationContext) error) *gormigrate.Migration {
|
||||
return &gormigrate.Migration{
|
||||
ID: id,
|
||||
Migrate: func(db *gorm.DB) error {
|
||||
tx := db.Begin()
|
||||
defer tx.Commit()
|
||||
if err := f(tx, ctx); err != nil {
|
||||
return fmt.Errorf("%q: %w", id, err)
|
||||
}
|
||||
|
||||
return tx.Create(&User{
|
||||
|
||||
@@ -88,7 +88,7 @@ type Track struct {
|
||||
Artist *Artist
|
||||
ArtistID int `gorm:"not null" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
|
||||
Genres []*Genre `gorm:"many2many:track_genres"`
|
||||
Size int `gorm:"not null" sql:"default: null"`
|
||||
Size int `sql:"default: null"`
|
||||
Length int `sql:"default: null"`
|
||||
Bitrate int `sql:"default: null"`
|
||||
TagTitle string `sql:"default: null"`
|
||||
|
||||
Reference in New Issue
Block a user