The reverted "missing from master" feature had shipped migration 11, so databases that ran it skipped the new auto_cleanup_empty migration (also 11) and every pairs query failed, showing no libraries. Restore the old 11 as an unused placeholder like 6-10 and add the column as 12; a database that ran it under the old number counts it as applied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
104 lines
3.4 KiB
Go
104 lines
3.4 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestDecodeWarnings(t *testing.T) {
|
|
old := decodeWarnings(`["Only 3 of 9 folders match."]`)
|
|
if len(old) != 1 || old[0].Title != "Only 3 of 9 folders match." {
|
|
t.Fatalf("plain-string warnings from older scans should decode as titles, got %+v", old)
|
|
}
|
|
cur := decodeWarnings(`[{"title":"t","items":[{"slave":"a","master":"b","count":2}],"total":5}]`)
|
|
if len(cur) != 1 || cur[0].Total != 5 || cur[0].Items[0].Count != 2 {
|
|
t.Fatalf("structured warnings should round-trip, got %+v", cur)
|
|
}
|
|
if ws := decodeWarnings(`[]`); ws == nil || len(ws) != 0 {
|
|
t.Fatalf("empty list should stay an empty (non-nil) list, got %#v", ws)
|
|
}
|
|
}
|
|
|
|
func TestMatchLetters(t *testing.T) {
|
|
ctx := context.Background()
|
|
st, err := Open(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { st.DB.Close() })
|
|
p := Pair{Name: "p", MasterRoot: "/m", SlaveRoot: "/s", RetentionDays: 30}
|
|
if err := st.SavePair(ctx, &p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, rel := range []string{"beck/a.flac", "ABBA/a.flac", "Air/a.flac", "2Pac/a.flac", "Björk/a.flac", "abba/b.flac"} {
|
|
m := Match{PairID: p.ID, RelSlave: rel, Status: StatusUnmatched, Kind: KindNone}
|
|
if err := st.UpsertMatch(ctx, &m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
ls, err := st.MatchLetters(ctx, p.ID, MatchFilter{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []LetterStart{{"2", 0}, {"a", 1}, {"b", 4}}
|
|
if !reflect.DeepEqual(ls, want) {
|
|
t.Fatalf("letters = %+v, want %+v", ls, want)
|
|
}
|
|
// The offsets must agree with the listing order, whatever the case.
|
|
ms, _, err := st.Matches(ctx, p.ID, MatchFilter{Limit: 10})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, l := range ls {
|
|
if got := strings.ToLower(ms[l.Offset].RelSlave[:1]); got != l.Letter {
|
|
t.Fatalf("offset %d starts with %q, want %q (listing %v)", l.Offset, got, l.Letter, ms)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Databases reached schema 11 two ways: the removed "missing from master"
|
|
// column, or (briefly) auto_cleanup_empty numbered 11. Both must open with
|
|
// every current column, keeping their pairs.
|
|
func TestMigrateFromEitherSchema11(t *testing.T) {
|
|
for name, eleven := range map[string]string{
|
|
"missing": `ALTER TABLE matches ADD COLUMN missing TEXT NOT NULL DEFAULT '';`,
|
|
"auto cleanup": `ALTER TABLE pairs ADD COLUMN auto_cleanup_empty INTEGER NOT NULL DEFAULT 0;`,
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
db, err := sql.Open("sqlite", "file:"+filepath.Join(dir, "playlink.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
old := append(append([]string{}, migrations[:10]...), eleven,
|
|
`INSERT INTO pairs (name, master_root, slave_root, quarantine_dir, extensions, created_at) VALUES ('Music', '/m', '/s', '/q', 'flac', 1)`)
|
|
stmts := []string{`CREATE TABLE schema_version (v INTEGER NOT NULL)`, `INSERT INTO schema_version (v) VALUES (11)`}
|
|
for _, m := range append(stmts, old...) {
|
|
if _, err := db.Exec(m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
db.Close()
|
|
|
|
st, err := Open(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.DB.Close()
|
|
ps, err := st.Pairs(context.Background())
|
|
if err != nil || len(ps) != 1 || ps[0].Name != "Music" {
|
|
t.Fatalf("pairs = %+v, %v", ps, err)
|
|
}
|
|
var v int
|
|
st.DB.QueryRow(`SELECT MAX(v) FROM schema_version`).Scan(&v)
|
|
if v != SchemaVersion() {
|
|
t.Fatalf("schema %d, want %d", v, SchemaVersion())
|
|
}
|
|
})
|
|
}
|
|
}
|