204 lines
6.8 KiB
Go
204 lines
6.8 KiB
Go
package datafiles
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
)
|
|
|
|
type handed struct {
|
|
dir []string
|
|
name string
|
|
json any
|
|
}
|
|
|
|
func walk(t *testing.T, src Source) ([]handed, error) {
|
|
t.Helper()
|
|
var got []handed
|
|
err := src.Walk(func(c Category) error {
|
|
got = append(got, handed{c.Folders, c.Name, c.JSON.Any()})
|
|
return nil
|
|
})
|
|
return got, err
|
|
}
|
|
|
|
func TestWalkHandsEachCategoryInOrder(t *testing.T) {
|
|
got, err := walk(t, Source{fsys: fstest.MapFS{
|
|
"a.json": {Data: []byte(`{"x":"1"}`)},
|
|
"sub/b.json": {Data: []byte(`"y"`)},
|
|
"sub/notes.txt": {Data: []byte("not data")},
|
|
"empty/x.txt": {Data: []byte("not data")},
|
|
".git/c.json": {Data: []byte(`"hidden"`)},
|
|
"sub/.d.json": {Data: []byte(`"hidden"`)},
|
|
"sub/deep/e.json": {Data: []byte(`"z"`)},
|
|
}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []handed{
|
|
{nil, "a", map[string]any{"x": "1"}},
|
|
{[]string{"sub"}, "b", "y"},
|
|
{[]string{"sub", "deep"}, "e", "z"},
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("Walk handed %#v, want %#v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestReadRowsReadsOnlyAFileBesideTheCategory(t *testing.T) {
|
|
src := Source{fsys: fstest.MapFS{
|
|
"t.json": {Data: []byte(`{"rows":"t.tsv"}`)},
|
|
"t.tsv": {Data: []byte("key\nA\n")},
|
|
"sub/u.tsv": {Data: []byte("key\nB\n")},
|
|
"sub/u.json": {Data: []byte(`{"rows":"u.tsv"}`)},
|
|
}, label: "lbl"}
|
|
rows := map[string]string{}
|
|
err := src.Walk(func(c Category) error {
|
|
data, err := c.ReadRows(c.Name + ".tsv")
|
|
rows[c.Name] = data
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := c.ReadRows("x.tsv"); err == nil || err.Error() != "rows names x.tsv, which is not beside it in "+map[string]string{"t": "lbl", "u": "lbl/sub"}[c.Name] {
|
|
t.Errorf("ReadRows(x.tsv) = %v, want an error saying x.tsv is not beside %s", err, c.Name)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil || rows["t"] != "key\nA\n" || rows["u"] != "key\nB\n" {
|
|
t.Errorf("Walk = %v, read %q", err, rows)
|
|
}
|
|
}
|
|
|
|
func TestWalkIgnoresARowsFileNoCategoryNames(t *testing.T) {
|
|
if _, err := walk(t, Source{fsys: fstest.MapFS{"a.json": {Data: []byte(`"x"`)}, "a.tsv": {Data: []byte("key\n")}}, label: "lbl"}); err != nil {
|
|
t.Errorf("Walk = %v, want a.tsv ignored", err)
|
|
}
|
|
}
|
|
|
|
func TestARowsFileMayBeHidden(t *testing.T) {
|
|
var rows string
|
|
err := Source{fsys: fstest.MapFS{"a.json": {Data: []byte(`"x"`)}, ".a.tsv": {Data: []byte("key\nA\n")}}}.Walk(func(c Category) error {
|
|
var err error
|
|
rows, err = c.ReadRows(".a.tsv")
|
|
return err
|
|
})
|
|
if err != nil || rows != "key\nA\n" {
|
|
t.Errorf("Walk = %v, read %q, want the hidden rows file read", err, rows)
|
|
}
|
|
}
|
|
|
|
func TestWalkLabelsWhatACategoryFileGetsWrong(t *testing.T) {
|
|
for _, c := range []struct {
|
|
name string
|
|
fs fstest.MapFS
|
|
compile error
|
|
want string
|
|
}{
|
|
{"its compile", fstest.MapFS{"a.json": {Data: []byte(`"x"`)}}, errors.New("bad"), "lbl/a.json: bad"},
|
|
{"its JSON", fstest.MapFS{"a.json": {Data: []byte(`{`)}}, nil, "lbl/a.json: unexpected end of JSON input"},
|
|
{"its name", fstest.MapFS{"a.b.json": {Data: []byte(`"x"`)}}, nil, "lbl/a.b.json: category "},
|
|
{"its folder's name", fstest.MapFS{"a.b/c.json": {Data: []byte(`"x"`)}}, nil, "lbl/a.b: folder "},
|
|
} {
|
|
err := Source{fsys: c.fs, label: "lbl"}.Walk(func(Category) error { return c.compile })
|
|
if err == nil || !strings.HasPrefix(err.Error(), c.want) {
|
|
t.Errorf("%s: Walk = %v, want it to start %q", c.name, err, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWalkChecksAFolderNameOnlyWhenItHoldsData(t *testing.T) {
|
|
if _, err := walk(t, Source{fsys: fstest.MapFS{"a.b/notes.txt": {Data: []byte("x")}, "c.json": {Data: []byte(`"x"`)}}}); err != nil {
|
|
t.Errorf("Walk = %v, want a folder holding no data skipped whatever its name", err)
|
|
}
|
|
}
|
|
|
|
func TestWalkRefusesADiskPathThatIsNoDirectory(t *testing.T) {
|
|
dir := t.TempDir()
|
|
for _, c := range []struct{ path, want string }{
|
|
{"", "a data path is empty"},
|
|
{filepath.Join(dir, "none"), "data path " + filepath.Join(dir, "none") + ": "},
|
|
} {
|
|
_, err := walk(t, Dir(c.path))
|
|
if err == nil || !strings.HasPrefix(err.Error(), c.want) {
|
|
t.Errorf("Walk(%q) = %v, want it to start %q", c.path, err, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadHandsOneCategoryAndItsRows(t *testing.T) {
|
|
src := Source{fsys: fstest.MapFS{
|
|
"sub/t.json": {Data: []byte(`{"rows":"t.tsv"}`)},
|
|
"sub/t.tsv": {Data: []byte("key\nA\n")},
|
|
"sub/u.json": {Data: []byte(`{`)},
|
|
}}
|
|
var got handed
|
|
var rows string
|
|
err := src.Load([]string{"sub"}, "t", func(c Category) error {
|
|
got = handed{c.Folders, c.Name, c.JSON.Any()}
|
|
var err error
|
|
rows, err = c.ReadRows("t.tsv")
|
|
return err
|
|
})
|
|
if err != nil || got.name != "t" || !reflect.DeepEqual(got.dir, []string{"sub"}) || rows != "key\nA\n" {
|
|
t.Errorf("Load = %v, handed %#v with rows %q", err, got, rows)
|
|
}
|
|
}
|
|
|
|
func TestDirReadsTheDirectoryAndLabelsItsErrorsWithIt(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(dir, "a.json"), []byte(`{`), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err := walk(t, Dir(dir))
|
|
if err == nil || !strings.HasPrefix(err.Error(), filepath.ToSlash(filepath.Join(dir, "a.json"))+": ") {
|
|
t.Errorf("Walk = %v, want the error labelled with the directory", err)
|
|
}
|
|
if _, err := walk(t, Dir(filepath.Join(dir, "a.json"))); err == nil || err.Error() != filepath.Join(dir, "a.json")+" is not a directory" {
|
|
t.Errorf("Walk = %v, want a file refused as no directory", err)
|
|
}
|
|
}
|
|
|
|
func TestAMountedDirNamesItsFilesWhereTheyAreOnDisk(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := os.MkdirAll(filepath.Join(dir, "sub"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "sub", "a.json"), []byte(`{`), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err := walk(t, Dir(dir).Mounted("my.data"))
|
|
if err == nil || !strings.HasPrefix(err.Error(), filepath.ToSlash(filepath.Join(dir, "sub", "a.json"))+": ") {
|
|
t.Errorf("Walk = %v, want the error to name the file on disk", err)
|
|
}
|
|
}
|
|
|
|
func TestAMountedTreeIsAValidFS(t *testing.T) {
|
|
m := mounted{fsys: fstest.MapFS{"a.json": {Data: []byte(`"x"`)}, "sub/b.json": {Data: []byte(`"y"`)}}, at: "my/data"}
|
|
if err := fstest.TestFS(m, "my/data/a.json", "my/data/sub/b.json"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestAMountedSourceNamesItsManifestBesideItsFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
for name, c := range map[string]struct {
|
|
src Source
|
|
want string
|
|
}{
|
|
"an fs.FS": {FS(fstest.MapFS{}).Mounted("my.data"), "my/data/.fejkdata.json"},
|
|
"a data path": {Dir(dir).Mounted("my.data"), filepath.ToSlash(filepath.Join(dir, ".fejkdata.json"))},
|
|
} {
|
|
if got := c.src.ManifestPath(); got != c.want {
|
|
t.Errorf("%s: ManifestPath() = %q, want %q", name, got, c.want)
|
|
}
|
|
}
|
|
m, err := FS(fstest.MapFS{ManifestFile: {Data: []byte(`{"namespace": "my.data"}`)}}).Mounted("my.data").Manifest()
|
|
if err != nil || m.Namespace != "my.data" {
|
|
t.Errorf("Manifest() after Mounted = %+v, %v, want the manifest read from beside the files", m, err)
|
|
}
|
|
}
|