This commit is contained in:
@@ -2,6 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -450,6 +452,108 @@ func TestRunEmptyDataPathIsNamed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func recordDir(t *testing.T) string {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
content := `{"format":"{first} {last}","first":["Ada","Bo"],"last":["Lovelace","Ek"]}`
|
||||
if err := os.WriteFile(filepath.Join(dir, "users.json"), []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func TestRunRecordJSON(t *testing.T) {
|
||||
code, out, errb := runOut("--seed", "1", "--format", "json", "--data-path", recordDir(t), "users")
|
||||
if code != 0 {
|
||||
t.Fatalf("run = %d, stderr=%q", code, errb)
|
||||
}
|
||||
var m map[string]string
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &m); err != nil {
|
||||
t.Fatalf("json output is not one valid JSON object: %v\n%q", err, out)
|
||||
}
|
||||
if len(m) != 2 || m["first"] == "" || m["last"] == "" {
|
||||
t.Fatalf("json output = %q, want first and last columns", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunRecordCSVRoundTrips(t *testing.T) {
|
||||
code, out, errb := runOut("--seed", "1", "--format", "csv", "--repeat", "3", "--data-path", recordDir(t), "users")
|
||||
if code != 0 {
|
||||
t.Fatalf("run = %d, stderr=%q", code, errb)
|
||||
}
|
||||
lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
|
||||
if len(lines) != 4 {
|
||||
t.Fatalf("csv output = %d lines %q, want a header and 3 rows", len(lines), out)
|
||||
}
|
||||
if lines[0] != "first,last" {
|
||||
t.Fatalf("csv header = %q, want first,last", lines[0])
|
||||
}
|
||||
r, err := csv.NewReader(strings.NewReader(out)).ReadAll()
|
||||
if err != nil {
|
||||
t.Fatalf("csv output is not valid CSV: %v", err)
|
||||
}
|
||||
if len(r) != 4 || len(r[0]) != 2 {
|
||||
t.Fatalf("csv parsed to %v, want 4 records of 2 fields", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunRecordSQL(t *testing.T) {
|
||||
code, out, errb := runOut("--seed", "1", "--format", "sql", "--repeat", "2", "--table", "people", "--data-path", recordDir(t), "users")
|
||||
if code != 0 {
|
||||
t.Fatalf("run = %d, stderr=%q", code, errb)
|
||||
}
|
||||
lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
|
||||
if len(lines) != 2 || !strings.HasPrefix(lines[0], "INSERT INTO people (first, last) VALUES (") {
|
||||
t.Fatalf("sql output = %q, want two INSERT INTO people statements", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunRecordDefaultTable(t *testing.T) {
|
||||
code, out, errb := runOut("--seed", "1", "--format", "sql", "--data-path", recordDir(t), "users")
|
||||
if code != 0 || !strings.HasPrefix(out, "INSERT INTO users (") {
|
||||
t.Fatalf("default table = %d, %q, want the path's last segment; stderr %q", code, out, errb)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunRecordInlineTemplate(t *testing.T) {
|
||||
code, out, errb := runOut("--seed", "1", "--format", "json", `{"format":"{x}","x":["a","b"]}`)
|
||||
if code != 0 {
|
||||
t.Fatalf("inline record = %d, stderr=%q", code, errb)
|
||||
}
|
||||
var m map[string]string
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &m); err != nil || m["x"] != "a" && m["x"] != "b" {
|
||||
t.Fatalf("inline record json = %q, want a column x (err %v)", out, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunRecordMisuse(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{[]string{"--format", "yaml", "users"}, "--format takes text, json, csv or sql"},
|
||||
{[]string{"--format", "json", "--separator", ",", "users"}, "--separator joins text values"},
|
||||
{[]string{"--table", "t", "users"}, "--table names the INSERT target"},
|
||||
{[]string{"--format", "json", "--table", "t", "users"}, "--table names the INSERT target"},
|
||||
} {
|
||||
code, out, errb := runOut(c.args...)
|
||||
if code != 2 || out != "" || !strings.Contains(errb, c.want) {
|
||||
t.Errorf("run(%v) = %d, %q, %q; want misuse naming %q", c.args, code, out, errb, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunRecordOnABareValueIsRuntimeError(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "word.json"), []byte(`["a","b"]`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
code, _, errb := runOut("--format", "json", "--data-path", dir, "word")
|
||||
if code != 1 {
|
||||
t.Fatalf("record on a choice = %d, want exit 1 (runtime error), stderr %q", code, errb)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunNumbersFollowTheShell(t *testing.T) {
|
||||
_, want, _ := runOut("--seed", "7", "--repeat", "3", "sv_SE.word")
|
||||
code, got, errb := runOut("--seed", "007", "--repeat", "+3", "sv_SE.word")
|
||||
|
||||
@@ -43,6 +43,31 @@ func TestReadmeExamplesLoadAndRender(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadmeRecordExample(t *testing.T) {
|
||||
src := readme(t)
|
||||
section := src[strings.Index(src, "### Records"):]
|
||||
section = section[:strings.Index(section, "## Library")]
|
||||
block := jsonBlock.FindStringSubmatch(section)
|
||||
if block == nil {
|
||||
t.Fatal("README lost the Records example")
|
||||
}
|
||||
f, err := New(WithDataPath(writeData(t, map[string]string{"users": block[1]})), WithSeed(1))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, err := f.Record("users")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := r.Fields()
|
||||
if len(got) != 2 || got[0].Name != "first" || got[1].Name != "last" {
|
||||
t.Fatalf("record columns = %v, want first, last", got)
|
||||
}
|
||||
if r.CSVHeader() != "first,last" || !strings.HasPrefix(r.SQLInsert("users"), "INSERT INTO users (first, last) VALUES (") {
|
||||
t.Fatalf("serializers = %q, %q, want first,last and an INSERT", r.CSVHeader(), r.SQLInsert("users"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadmeSQLExampleOutput(t *testing.T) {
|
||||
src := readme(t)
|
||||
src = src[strings.Index(src, "### Your own data"):]
|
||||
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
package fejkdata
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func recordCat(t *testing.T) *Generator {
|
||||
t.Helper()
|
||||
dir := writeData(t, map[string]string{
|
||||
"users": `{
|
||||
"format": "{first} {last}",
|
||||
"first": ["Ada", "Bo"],
|
||||
"last": ["Lovelace", "Ek"]
|
||||
}`,
|
||||
})
|
||||
return newGenerator(t, dir, WithSeed(1))
|
||||
}
|
||||
|
||||
func TestRecordProjectsFieldsAsColumns(t *testing.T) {
|
||||
f := recordCat(t)
|
||||
r, err := f.Record("users")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := r.Fields()
|
||||
if len(got) != 2 || got[0].Name != "first" || got[1].Name != "last" {
|
||||
t.Fatalf("Record.Fields() = %v, want columns first, last in name order", got)
|
||||
}
|
||||
if (got[0].Value != "Ada" && got[0].Value != "Bo") || (got[1].Value != "Lovelace" && got[1].Value != "Ek") {
|
||||
t.Fatalf("columns = %v, want the field values", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordIsDeterministic(t *testing.T) {
|
||||
a, b := recordCat(t), recordCat(t)
|
||||
for i := 0; i < 20; i++ {
|
||||
x, _ := a.Record("users")
|
||||
y, _ := b.Record("users")
|
||||
if x.JSON() != y.JSON() {
|
||||
t.Fatalf("same seed diverged: %s != %s", x.JSON(), y.JSON())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordSkipsReferenceBindings(t *testing.T) {
|
||||
dir := writeData(t, map[string]string{
|
||||
"name": `{"format": "{first} {last}", "first": "Ada", "last": "Lovelace"}`,
|
||||
"user": `{"format": "they are {/name}", "id": "1"}`,
|
||||
})
|
||||
f := newGenerator(t, dir, WithSeed(1))
|
||||
r, err := f.Record("user")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := r.Fields()
|
||||
if len(got) != 1 || got[0].Name != "id" {
|
||||
t.Fatalf("Record.Fields() = %v, want only the id column (a {/path} is not a column)", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordErrors(t *testing.T) {
|
||||
dir := writeData(t, map[string]string{
|
||||
"bare": `"hello"`,
|
||||
"pick": `["a", "b"]`,
|
||||
"group/cat": `"x"`,
|
||||
})
|
||||
f := newGenerator(t, dir, WithSeed(1))
|
||||
for _, c := range []struct {
|
||||
path string
|
||||
want string
|
||||
}{
|
||||
{"bare", "has no fields, so no columns"},
|
||||
{"pick", "does not name a record"},
|
||||
{"group", "names a folder"},
|
||||
{"nope", "no entry"},
|
||||
} {
|
||||
if _, err := f.Record(c.path); err == nil || !strings.Contains(err.Error(), c.want) {
|
||||
t.Errorf("Record(%q) = %v, want an error containing %q", c.path, err, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordJSON(t *testing.T) {
|
||||
f := recordCat(t)
|
||||
r, err := f.Record("users")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m map[string]string
|
||||
if err := json.Unmarshal([]byte(r.JSON()), &m); err != nil {
|
||||
t.Fatalf("Record.JSON() is not valid JSON: %v\n%s", err, r.JSON())
|
||||
}
|
||||
if len(m) != 2 || (m["first"] != "Ada" && m["first"] != "Bo") {
|
||||
t.Fatalf("Record.JSON() = %s, want two addressable columns", r.JSON())
|
||||
}
|
||||
for _, f := range r.Fields() {
|
||||
if m[f.Name] != f.Value {
|
||||
t.Fatalf("JSON column %q = %q, want %q", f.Name, m[f.Name], f.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordCSV(t *testing.T) {
|
||||
dir := writeData(t, map[string]string{
|
||||
"note": `{"format": "{word}, {word}", "word": ["a", "b,c", "d\"e", "f\n"]}`,
|
||||
})
|
||||
f := newGenerator(t, dir, WithSeed(1))
|
||||
r, err := f.Record("note")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := r.CSVHeader(); got != "word" {
|
||||
t.Errorf("CSVHeader() = %q, want word", got)
|
||||
}
|
||||
rec, err := csv.NewReader(strings.NewReader(r.CSVLine() + "\n")).Read()
|
||||
if err != nil {
|
||||
t.Fatalf("CSVLine() is not valid CSV: %v\n%q", err, r.CSVLine())
|
||||
}
|
||||
if len(rec) != 1 || rec[0] != r.Fields()[0].Value {
|
||||
t.Fatalf("CSVLine() = %v, want the field value round-tripped", rec)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordSQLInsert(t *testing.T) {
|
||||
dir := writeData(t, map[string]string{
|
||||
"person": `{"format": "{last}", "last": "O'Brien"}`,
|
||||
})
|
||||
f := newGenerator(t, dir, WithSeed(1))
|
||||
r, err := f.Record("person")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := r.SQLInsert("people")
|
||||
if got != "INSERT INTO people (last) VALUES ('O''Brien');" {
|
||||
t.Fatalf("SQLInsert() = %q, want the single quote doubled", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFakeRecordAndTemplate(t *testing.T) {
|
||||
f := recordCat(t)
|
||||
in := `{"format":"{x} {y}","x":["1","2"],"y":["3","4"]}`
|
||||
want, err := f.FakeRecord(in)
|
||||
if err != nil {
|
||||
t.Fatalf("FakeRecord: %v", err)
|
||||
}
|
||||
if len(want.Fields()) != 2 {
|
||||
t.Fatalf("FakeRecord fields = %v, want two columns", want.Fields())
|
||||
}
|
||||
reusable, err := f.NewRecordTemplate(in)
|
||||
if err != nil {
|
||||
t.Fatalf("NewRecordTemplate: %v", err)
|
||||
}
|
||||
for i := 0; i < 20; i++ {
|
||||
if got := reusable.Fake(); len(got.Fields()) != 2 {
|
||||
t.Fatalf("RecordTemplate.Fake() = %v, want two columns", got.Fields())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInlineRecordErrors(t *testing.T) {
|
||||
f := recordCat(t)
|
||||
for _, c := range []struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{`"hello"`, "needs at least one field"},
|
||||
{`["a","b"]`, "a choice"},
|
||||
} {
|
||||
if _, err := f.FakeRecord(c.input); err == nil || !strings.Contains(err.Error(), c.want) {
|
||||
t.Errorf("FakeRecord(%q) = %v, want an error naming %q", c.input, err, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user