package oltp
import (
"path/filepath"
"strings"
"testing"
"craftdesign.group/quarry/internal/uv"
)
// One row per name: a write applies only by fossil's rule, a tombstone
// keeps its row, the catalog is per repository, and a rebuild replaces
// a repository's rows and records the bucket it came from.
func TestUnversionedCatalog(t *testing.T) {
d, err := Open(filepath.Join(t.TempDir(), "oltp.db"))
if err != nil {
t.Fatal(err)
}
defer d.Close()
if files, err := d.UVFiles("w"); err != nil || len(files) != 0 {
t.Fatalf("FAIL: empty catalog = %v %v", files, err)
}
if _, ok, err := d.UVSource("w"); ok || err != nil {
t.Fatalf("FAIL: a source before any build: %v %v", ok, err)
}
put := func(f uv.File) bool {
t.Helper()
applied, err := d.PutUVFile("w", f)
if err != nil {
t.Fatal(err)
}
return applied
}
if !put(uv.File{Name: "a.txt", Mtime: 100, Hash: "bb", Size: 3, Who: "lou"}) {
t.Error("FAIL: the first write did not apply")
}
if put(uv.File{Name: "a.txt", Mtime: 99, Hash: "cc", Size: 4, Who: "mallory"}) {
t.Error("FAIL: an older write applied")
}
if put(uv.File{Name: "gone.txt", Mtime: 5, Deleted: true, Who: "lou"}) {
t.Error("FAIL: a tombstone for a name never held applied")
}
if !put(uv.File{Name: "a.txt", Mtime: 200, Deleted: true, Who: "dan"}) {
t.Error("FAIL: a newer tombstone did not apply")
}
f, ok, err := d.UVFile("w", "a.txt")
if err != nil || !ok || !f.Deleted || f.Hash != "" || f.Size != 0 || f.Mtime != 200 || f.Who != "dan" || f.At == "" {
t.Errorf("FAIL: tombstone row = %+v %v %v", f, ok, err)
}
if !put(uv.File{Name: "a.txt", Mtime: 300, Hash: "dd", Size: 7, Who: "lou"}) {
t.Error("FAIL: a newer write did not revive the name")
}
if _, ok, _ := d.UVFile("other", "a.txt"); ok {
t.Error("FAIL: a row leaked into another repository")
}
rebuilt := []uv.File{
{Name: "z.tgz", Mtime: 10, Hash: "ee", Size: 9, Who: "ci"},
{Name: "old.txt", Mtime: 11, Deleted: true, Who: "dan"},
}
if err := d.ReplaceUVCatalog("w", "https://acct.example/files", rebuilt); err != nil {
t.Fatal(err)
}
files, err := d.UVFiles("w")
if err != nil || len(files) != 2 || files[0].Name != "old.txt" || !files[0].Deleted || files[1].Name != "z.tgz" || files[1].Hash != "ee" || files[1].Who != "ci" {
t.Errorf("FAIL: rebuilt catalog = %+v %v", files, err)
}
if src, ok, err := d.UVSource("w"); !ok || err != nil || src != "https://acct.example/files" {
t.Errorf("FAIL: source = %q %v %v", src, ok, err)
}
if err := d.DropUVCatalog("w"); err != nil {
t.Fatal(err)
}
if files, _ := d.UVFiles("w"); len(files) != 0 {
t.Errorf("FAIL: drop left %v", files)
}
if _, ok, _ := d.UVSource("w"); ok {
t.Error("FAIL: drop left the source, so nothing would rebuild")
}
t.Log("ok: the catalog keeps one row per name by fossil's rule and rebuilds whole")
}
// The releases questions read only names in a folder under releases/,
// live ones, of this repository.
func TestUVReleaseQueries(t *testing.T) {
d, err := Open(filepath.Join(t.TempDir(), "oltp.db"))
if err != nil {
t.Fatal(err)
}
defer d.Close()
check := func(want ...string) {
t.Helper()
has, err := d.UVHasReleases("w")
files, ferr := d.UVReleaseFiles("w")
var names []string
for _, f := range files {
names = append(names, f.Name)
}
if err != nil || ferr != nil || has != (len(want) > 0) || strings.Join(names, " ") != strings.Join(want, " ") {
t.Errorf("FAIL: has %v files %v (%v %v), want %v", has, names, err, ferr, want)
}
}
check()
for _, f := range []uv.File{
{Name: "releases/README", Mtime: 1, Hash: "h"},
{Name: "releasesX/a/b", Mtime: 1, Hash: "h"},
{Name: "releases/1.0/gone", Mtime: 1, Deleted: true},
} {
if _, err := d.PutUVFile("w", f); err != nil {
t.Fatal(err)
}
}
check()
for _, n := range []string{"releases/1.0/a.bin", "releases/0.9/b.bin"} {
if _, err := d.PutUVFile("w", uv.File{Name: n, Mtime: 1, Hash: "h"}); err != nil {
t.Fatal(err)
}
}
check("releases/0.9/b.bin", "releases/1.0/a.bin")
if has, _ := d.UVHasReleases("other"); has {
t.Errorf("FAIL: releases leak across repositories")
}
t.Log("ok: release names by the primary key, live ones only")
}
// The Files box reads one row per release folder, its newest mtime,
// never every file.
func TestUVReleaseFolders(t *testing.T) {
d, err := Open(filepath.Join(t.TempDir(), "oltp.db"))
if err != nil {
t.Fatal(err)
}
defer d.Close()
for _, f := range []uv.File{
{Name: "releases/1.0/a", Mtime: 5, Hash: "h"}, {Name: "releases/1.0/b", Mtime: 9, Hash: "h"},
{Name: "releases/2.0/x/y", Mtime: 3, Hash: "h"}, {Name: "releases/3.0/gone", Mtime: 7, Deleted: true},
{Name: "releases/README", Mtime: 1, Hash: "h"}, {Name: "other", Mtime: 1, Hash: "h"},
} {
if _, err := d.PutUVFile("w", f); err != nil {
t.Fatal(err)
}
}
got, err := d.UVReleaseFolders("w")
if err != nil || len(got) != 2 || got[0] != (UVFolder{Name: "1.0", Mtime: 9}) || got[1] != (UVFolder{Name: "2.0", Mtime: 3}) {
t.Errorf("FAIL: folders = %+v %v", got, err)
}
if ok, err := d.BucketBound("w"); ok || err != nil {
t.Errorf("FAIL: unbound = %v %v", ok, err)
}
if err := d.BindBucket("w", BucketBinding{Endpoint: "https://e", Bucket: "b", Region: "auto", AccessKey: "AK", Secret: "s", LimitBytes: 1}); err != nil {
t.Fatal(err)
}
if ok, err := d.BucketBound("w"); !ok || err != nil {
t.Errorf("FAIL: bound = %v %v", ok, err)
}
t.Log("ok: one row per folder; the binding by existence")
}