Files
fejkdata/loading_test.go
T

184 lines
6.9 KiB
Go

package fejkdata
import (
"os"
"path/filepath"
"reflect"
"slices"
"strings"
"testing"
)
// TestList pins the discoverable paths: every category, its dotted fields, and
// folder segments — descending transparently through single-variant choices.
func TestList(t *testing.T) {
dir := writeData(t, map[string]string{
"person": `{"format":"{first} {last}","first":["A"],"last":["B"]}`,
"word": `["x", "y"]`,
"geo/city": `["Z"]`,
// A bound {..path} reference is a render edge, not an addressable field.
"greeting": `{"format":"hej {..person.first} and {own}","own":["x"]}`,
// Only the fields every variant carries are addressable, so "extra" is not.
"coin": `[{"format":"{code}","code":["A"],"name":["Aa"]},{"format":"{code}","code":["B"],"name":["Bb"],"extra":["x"]}]`,
})
got := newGenerator(t, dir, WithSeed(1)).List()
want := []string{"coin", "coin.code", "coin.name", "geo.city", "greeting", "greeting.own", "person", "person.first", "person.last", "word"}
if !reflect.DeepEqual(got, want) {
t.Fatalf("List() = %v, want %v", got, want)
}
}
// writeData builds a temp data directory from a map of relative path (without
// ".json") -> file content, creating parent folders as needed. The directory's
// name carries no meaning anymore, so callers pick any layout they like —
// including nested folders, which become dot-path segments.
func writeData(t *testing.T, files map[string]string) string {
t.Helper()
dir := t.TempDir()
for name, content := range files {
p := filepath.Join(dir, name+".json")
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}
return dir
}
func TestNewLoadsAnyDirName(t *testing.T) {
// No locale tag required: a directory named anything loads fine.
dir := writeData(t, map[string]string{"greeting": `["hej", "hallå"]`})
f := newGenerator(t, dir, WithSeed(1))
if got := fake(t, f, "greeting"); got != "hej" && got != "hallå" {
t.Fatalf("greeting = %q, want hej or hallå", got)
}
}
func TestNewEmptyDirErrors(t *testing.T) {
if _, err := New(WithoutShippedData(), WithDataPath(writeData(t, nil))); err == nil {
t.Fatal("New(empty dir) = nil error")
}
}
// TestFoldersBecomeDotPaths checks the core of the new model: a subfolder is a
// namespace, so data/<loc>/person.json is reachable as "<loc>.person".
func TestFoldersBecomeDotPaths(t *testing.T) {
dir := writeData(t, map[string]string{
"sv_SE/greeting": `["hej"]`,
"en_US/greeting": `["hi"]`,
})
f := newGenerator(t, dir, WithSeed(1))
if got := fake(t, f, "sv_SE.greeting"); got != "hej" {
t.Fatalf("sv_SE.greeting = %q, want hej", got)
}
if got := fake(t, f, "en_US.greeting"); got != "hi" {
t.Fatalf("en_US.greeting = %q, want hi", got)
}
}
// TestNestedFoldersAndJSON crosses both nesting kinds in one dot path: folders
// a/b/c, then deep JSON fields inside the file at the end of that path.
func TestNestedFoldersAndJSON(t *testing.T) {
dir := writeData(t, map[string]string{
"a/b/c/thing": `{"format":"{x}","x":{"format":"{y}","y":{"format":"{z}","z":["leaf"]}}}`,
})
f := newGenerator(t, dir, WithSeed(1))
// Folders a.b.c, file thing, then JSON fields x.y.z — one continuous path.
if got := fake(t, f, "a.b.c.thing.x.y.z"); got != "leaf" {
t.Fatalf("a.b.c.thing.x.y.z = %q, want leaf", got)
}
// Rendering the file resolves the same chain top-down.
if got := fake(t, f, "a.b.c.thing"); got != "leaf" {
t.Fatalf("a.b.c.thing = %q, want leaf", got)
}
}
func TestRenderingAFolderErrors(t *testing.T) {
dir := writeData(t, map[string]string{"sv_SE/greeting": `["hej"]`})
f := newGenerator(t, dir, WithSeed(1))
if _, err := f.Fake("sv_SE"); err == nil {
t.Fatal("Fake(folder) = nil error, want a not-a-value error")
}
}
// TestMultiPathLastWins loads two dirs; on a name clash the later dir wins, and
// non-clashing entries from both are reachable (data combines).
func TestMultiPathLastWins(t *testing.T) {
a := writeData(t, map[string]string{"greeting": `["from-a"]`, "only-a": `["a"]`})
b := writeData(t, map[string]string{"greeting": `["from-b"]`, "only-b": `["b"]`})
f := newGeneratorN(t, []string{a, b}, WithSeed(1))
if got := fake(t, f, "greeting"); got != "from-b" {
t.Fatalf("greeting = %q, want from-b (last loaded wins)", got)
}
if got := fake(t, f, "only-a"); got != "a" {
t.Fatalf("only-a = %q, want a", got)
}
if got := fake(t, f, "only-b"); got != "b" {
t.Fatalf("only-b = %q, want b", got)
}
}
// TestMultiPathMergesFolders checks that clashing folders merge by their
// children rather than replacing wholesale: each dir adds a file to sv_SE, and
// a per-file clash inside still resolves last-wins.
func TestMultiPathMergesFolders(t *testing.T) {
a := writeData(t, map[string]string{"sv_SE/person": `["from-a"]`, "sv_SE/shared": `["a"]`})
b := writeData(t, map[string]string{"sv_SE/company": `["from-b"]`, "sv_SE/shared": `["b"]`})
f := newGeneratorN(t, []string{a, b}, WithSeed(1))
if got := fake(t, f, "sv_SE.person"); got != "from-a" {
t.Fatalf("sv_SE.person = %q, want from-a (folder merged, not replaced)", got)
}
if got := fake(t, f, "sv_SE.company"); got != "from-b" {
t.Fatalf("sv_SE.company = %q, want from-b", got)
}
if got := fake(t, f, "sv_SE.shared"); got != "b" {
t.Fatalf("sv_SE.shared = %q, want b (last loaded wins)", got)
}
}
// TestListedPathsAllRender is the contract between List and Fake over the shipped
// tree: everything List advertises renders, every time, and the sub-fields the
// README advertises are discoverable.
func TestListedPathsAllRender(t *testing.T) {
f := newGenerator(t, "data", WithSeed(4))
paths := f.List()
for _, p := range paths {
for i := 0; i < 20; i++ {
if _, err := f.Fake(p); err != nil {
t.Fatalf("Fake(%q) = %v, but List() advertises it", p, err)
}
}
}
for _, p := range []string{"misc.car.maker", "misc.country.alpha2", "misc.currency.symbol", "misc.httpstatus.code", "misc.mimetype.ext"} {
if !slices.Contains(paths, p) {
t.Errorf("List() omits %q, which the README advertises and Fake renders", p)
}
}
}
// TestHiddenEntriesAreSkipped keeps a data directory usable when it is also a
// checkout or an editor workspace: a dot-prefixed entry is not data, and a folder
// carrying no JSON never contributed a namespace, so neither may fail the load.
func TestHiddenEntriesAreSkipped(t *testing.T) {
dir := writeData(t, map[string]string{
"cat": `["V"]`,
".git/HEAD": `["ignored"]`,
".hidden": `["ignored"]`,
"empty.folder/doc": `["ignored"]`,
})
if err := os.Rename(filepath.Join(dir, "empty.folder", "doc.json"), filepath.Join(dir, "empty.folder", "doc.txt")); err != nil {
t.Fatal(err)
}
f := newGenerator(t, dir, WithSeed(1))
if got := fake(t, f, "cat"); got != "V" {
t.Fatalf("cat = %q, want V", got)
}
for _, p := range f.List() {
if strings.HasPrefix(p, ".") || strings.Contains(p, "empty.folder") {
t.Errorf("List() advertises %q, which is not data", p)
}
}
}