test: expand coverage across readers, writers, cmd, ui, diff and merge
Implements tests/_plans and previously deferred packages; updates plan README with new coverage numbers.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTemplateError(t *testing.T) {
|
||||
cause := errors.New("boom")
|
||||
tests := []struct {
|
||||
name string
|
||||
err *TemplateError
|
||||
phase string
|
||||
}{
|
||||
{"load", NewTemplateLoadError("cannot read", cause), "load"},
|
||||
{"parse", NewTemplateParseError("bad syntax", cause), "parse"},
|
||||
{"execute", NewTemplateExecuteError("failed render", cause), "execute"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.err.Phase != tt.phase {
|
||||
t.Errorf("phase = %q", tt.err.Phase)
|
||||
}
|
||||
msg := tt.err.Error()
|
||||
if !strings.Contains(msg, "template "+tt.phase+" error") || !strings.Contains(msg, "boom") {
|
||||
t.Errorf("message = %q", msg)
|
||||
}
|
||||
if !errors.Is(tt.err, cause) {
|
||||
t.Error("errors.Is must reach cause")
|
||||
}
|
||||
var te *TemplateError
|
||||
if !errors.As(error(tt.err), &te) || te != tt.err {
|
||||
t.Error("errors.As failed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateErrorWithoutCause(t *testing.T) {
|
||||
e := NewTemplateParseError("only message", nil)
|
||||
if got := e.Error(); got != "template parse error: only message" {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
if e.Unwrap() != nil {
|
||||
t.Error("Unwrap must be nil")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
)
|
||||
|
||||
func colNames(cols []*models.Column) []string {
|
||||
out := make([]string, 0, len(cols))
|
||||
for _, c := range cols {
|
||||
out = append(out, c.Name)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func eqStrings(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func testColumns() map[string]*models.Column {
|
||||
return map[string]*models.Column{
|
||||
"id": {Name: "id", Type: "integer", IsPrimaryKey: true, NotNull: true},
|
||||
"user_id": {Name: "user_id", Type: "bigint", NotNull: true},
|
||||
"name": {Name: "name", Type: "varchar(50)"},
|
||||
"email": {Name: "email", Type: "varchar(255)", NotNull: true},
|
||||
"created_at": {Name: "created_at", Type: "timestamp"},
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterTables(t *testing.T) {
|
||||
tables := []*models.Table{{Name: "user_profile"}, {Name: "user_settings"}, {Name: "orders"}}
|
||||
tests := []struct {
|
||||
name string
|
||||
in []*models.Table
|
||||
pattern string
|
||||
want []string
|
||||
}{
|
||||
{"empty pattern returns all", tables, "", []string{"user_profile", "user_settings", "orders"}},
|
||||
{"glob", tables, "user_*", []string{"user_profile", "user_settings"}},
|
||||
{"single char", tables, "order?", []string{"orders"}},
|
||||
{"no match", tables, "zzz*", []string{}},
|
||||
{"nil input", nil, "x*", []string{}},
|
||||
{"invalid pattern falls back to exact", []*models.Table{{Name: "[a"}}, "[a", []string{"[a"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := FilterTables(tt.in, tt.pattern)
|
||||
names := []string{}
|
||||
for _, tbl := range got {
|
||||
names = append(names, tbl.Name)
|
||||
}
|
||||
if !eqStrings(names, tt.want) {
|
||||
t.Errorf("got %v, want %v", names, tt.want)
|
||||
}
|
||||
byPattern := FilterTablesByPattern(tt.in, tt.pattern)
|
||||
if len(byPattern) != len(got) {
|
||||
t.Errorf("FilterTablesByPattern differs from FilterTables")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterColumns(t *testing.T) {
|
||||
cols := testColumns()
|
||||
tests := []struct {
|
||||
pattern string
|
||||
want []string
|
||||
}{
|
||||
{"", []string{"created_at", "email", "id", "name", "user_id"}},
|
||||
{"*_id", []string{"user_id"}},
|
||||
{"*", []string{"created_at", "email", "id", "name", "user_id"}},
|
||||
{"nomatch", []string{}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := colNames(FilterColumns(cols, tt.pattern)); !eqStrings(got, tt.want) {
|
||||
t.Errorf("pattern %q: got %v, want %v", tt.pattern, got, tt.want)
|
||||
}
|
||||
}
|
||||
if got := FilterColumns(nil, "*"); len(got) != 0 {
|
||||
t.Errorf("nil map must yield empty result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterColumnsByType(t *testing.T) {
|
||||
cols := testColumns()
|
||||
if got := colNames(FilterColumnsByType(cols, "varchar")); !eqStrings(got, []string{"email", "name"}) {
|
||||
t.Errorf("varchar: got %v", got)
|
||||
}
|
||||
if got := colNames(FilterColumnsByType(cols, "varchar(10)")); !eqStrings(got, []string{"email", "name"}) {
|
||||
t.Errorf("varchar(10) must match on base type, got %v", got)
|
||||
}
|
||||
if got := FilterColumnsByType(cols, "jsonb"); len(got) != 0 {
|
||||
t.Errorf("jsonb: expected none, got %v", colNames(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterColumnFlags(t *testing.T) {
|
||||
cols := testColumns()
|
||||
if got := colNames(FilterPrimaryKeys(cols)); !eqStrings(got, []string{"id"}) {
|
||||
t.Errorf("pks: %v", got)
|
||||
}
|
||||
if got := colNames(FilterNullable(cols)); !eqStrings(got, []string{"created_at", "name"}) {
|
||||
t.Errorf("nullable: %v", got)
|
||||
}
|
||||
if got := colNames(FilterNotNull(cols)); !eqStrings(got, []string{"email", "id", "user_id"}) {
|
||||
t.Errorf("notnull: %v", got)
|
||||
}
|
||||
for _, f := range []func(map[string]*models.Column) []*models.Column{FilterPrimaryKeys, FilterNullable, FilterNotNull} {
|
||||
if got := f(nil); got == nil || len(got) != 0 {
|
||||
t.Errorf("nil map must give non-nil empty slice")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterConstraints(t *testing.T) {
|
||||
cons := map[string]*models.Constraint{
|
||||
"pk": {Name: "pk", Type: models.PrimaryKeyConstraint},
|
||||
"fk": {Name: "fk", Type: models.ForeignKeyConstraint},
|
||||
"u1": {Name: "u1", Type: models.UniqueConstraint},
|
||||
"u2": {Name: "u2", Type: models.UniqueConstraint},
|
||||
"ck": {Name: "ck", Type: models.CheckConstraint},
|
||||
}
|
||||
count := func(f func(map[string]*models.Constraint) []*models.Constraint) int { return len(f(cons)) }
|
||||
if n := count(FilterForeignKeys); n != 1 {
|
||||
t.Errorf("fk count %d", n)
|
||||
}
|
||||
if n := count(FilterUniqueConstraints); n != 2 {
|
||||
t.Errorf("unique count %d", n)
|
||||
}
|
||||
if n := count(FilterCheckConstraints); n != 1 {
|
||||
t.Errorf("check count %d", n)
|
||||
}
|
||||
for _, f := range []func(map[string]*models.Constraint) []*models.Constraint{FilterForeignKeys, FilterUniqueConstraints, FilterCheckConstraints} {
|
||||
if got := f(nil); got == nil || len(got) != 0 {
|
||||
t.Errorf("nil map must give non-nil empty slice")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchPattern(t *testing.T) {
|
||||
tests := []struct {
|
||||
s, pattern string
|
||||
want bool
|
||||
}{
|
||||
{"user_profile", "user_*", true},
|
||||
{"user", "user_*", false},
|
||||
{"ab", "a?", true},
|
||||
{"abc", "a?", false},
|
||||
{"[a", "[A", true}, // invalid glob: case-insensitive exact
|
||||
{"x", "[a", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := matchPattern(tt.s, tt.pattern); got != tt.want {
|
||||
t.Errorf("matchPattern(%q,%q) = %v, want %v", tt.s, tt.pattern, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestToJSON(t *testing.T) {
|
||||
if got := ToJSON(map[string]int{"a": 1}); got != `{"a":1}` {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
if got := ToJSON(nil); got != "null" {
|
||||
t.Errorf("nil: %q", got)
|
||||
}
|
||||
if got := ToJSON(math.Inf(1)); !strings.HasPrefix(got, `{"error": "failed to marshal`) {
|
||||
t.Errorf("marshal failure: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToJSONPretty(t *testing.T) {
|
||||
got := ToJSONPretty(map[string]int{"a": 1}, " ")
|
||||
if got != "{\n \"a\": 1\n}" {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
if got := ToJSONPretty(make(chan int), " "); !strings.HasPrefix(got, `{"error"`) {
|
||||
t.Errorf("marshal failure: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToYAML(t *testing.T) {
|
||||
if got := ToYAML(map[string]int{"a": 1}); got != "a: 1\n" {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
if got := ToYAML(make(chan int)); !strings.HasPrefix(got, "error: failed to marshal") {
|
||||
// yaml.v3 panics-recovers into an error for unsupported types
|
||||
t.Errorf("marshal failure: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndent(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
spaces int
|
||||
want string
|
||||
}{
|
||||
{"", 4, ""},
|
||||
{"a", 2, " a"},
|
||||
{"a\nb", 2, " a\n b"},
|
||||
{"a\n\nb", 2, " a\n\n b"},
|
||||
{"a", 0, "a"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := Indent(tt.in, tt.spaces); got != tt.want {
|
||||
t.Errorf("Indent(%q,%d) = %q, want %q", tt.in, tt.spaces, got, tt.want)
|
||||
}
|
||||
}
|
||||
if got := IndentWith("", ">"); got != "" {
|
||||
t.Errorf("IndentWith empty: %q", got)
|
||||
}
|
||||
if got := IndentWith("a\n\nb", "> "); got != "> a\n\n> b" {
|
||||
t.Errorf("IndentWith: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscape(t *testing.T) {
|
||||
if got := Escape("a\"b\\c\nd\re\tf"); got != `a\"b\\c\nd\re\tf` {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
if got := Escape(""); got != "" {
|
||||
t.Errorf("empty: %q", got)
|
||||
}
|
||||
if got := EscapeQuotes(`a"b'c`); got != `a\"b\'c` {
|
||||
t.Errorf("EscapeQuotes: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComment(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, in, style, want string
|
||||
}{
|
||||
{"empty", "", "//", ""},
|
||||
{"slashes", "a\nb", "//", "// a\n// b"},
|
||||
{"hash", "a", "#", "# a"},
|
||||
{"sql", "a\nb", "--", "-- a\n-- b"},
|
||||
{"block single", "a", "/* */", "/* a */"},
|
||||
{"block single alt", "a", "/**/", "/* a */"},
|
||||
{"block multi", "a\nb", "/* */", "/*\n * a\n * b\n */"},
|
||||
{"default", "a", "weird", "// a"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := Comment(tt.in, tt.style); got != tt.want {
|
||||
t.Errorf("got %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuoteUnquote(t *testing.T) {
|
||||
if got := QuoteString("a"); got != `"a"` {
|
||||
t.Errorf("QuoteString: %q", got)
|
||||
}
|
||||
tests := []struct{ in, want string }{
|
||||
{`"a"`, "a"},
|
||||
{`'a'`, "a"},
|
||||
{`""`, ""},
|
||||
{`"a'`, `"a'`},
|
||||
{`a`, `a`},
|
||||
{`"`, `"`},
|
||||
{"", ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := UnquoteString(tt.in); got != tt.want {
|
||||
t.Errorf("UnquoteString(%q) = %q, want %q", tt.in, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func TestBuildFuncMapEntriesAreFunctions(t *testing.T) {
|
||||
fm := BuildFuncMap()
|
||||
if len(fm) < 100 {
|
||||
t.Errorf("unexpectedly small func map: %d", len(fm))
|
||||
}
|
||||
for name, fn := range fm {
|
||||
if reflect.TypeOf(fn).Kind() != reflect.Func {
|
||||
t.Errorf("%s is not a function", name)
|
||||
}
|
||||
}
|
||||
for _, name := range []string{"toSnakeCase", "sqlToGo", "filterTables", "toJSON", "enumerate", "get", "sortTablesByName", "dict", "seq"} {
|
||||
if _, ok := fm[name]; !ok {
|
||||
t.Errorf("missing %s", name)
|
||||
}
|
||||
}
|
||||
// Must be accepted by text/template (valid names and signatures).
|
||||
if _, err := template.New("x").Funcs(fm).Parse("ok"); err != nil {
|
||||
t.Fatalf("funcmap rejected by text/template: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFuncMapRender(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, tmpl, want string
|
||||
}{
|
||||
{"add", `{{add 2 3}}`, "5"},
|
||||
{"sub", `{{sub 5 3}}`, "2"},
|
||||
{"mul", `{{mul 2 3}}`, "6"},
|
||||
{"div", `{{div 6 3}}`, "2"},
|
||||
{"div zero", `{{div 6 0}}`, "0"},
|
||||
{"mod", `{{mod 7 3}}`, "1"},
|
||||
{"mod zero", `{{mod 7 0}}`, "0"},
|
||||
{"default nil", `{{default "d" .Missing}}`, "d"},
|
||||
{"default set", `{{default "d" "v"}}`, "v"},
|
||||
{"dict", `{{get (dict "a" 1) "a"}}`, "1"},
|
||||
{"dict odd", `{{if dict "a"}}set{{else}}nil{{end}}`, "nil"},
|
||||
{"dict non-string key", `{{if dict 1 2}}set{{else}}nil{{end}}`, "nil"},
|
||||
{"list", `{{len (list 1 2 3)}}`, "3"},
|
||||
{"seq", `{{range seq 1 3}}{{.}}{{end}}`, "123"},
|
||||
{"seq reversed", `{{len (seq 3 1)}}`, "0"},
|
||||
{"snake", `{{toSnakeCase "UserName"}}`, "user_name"},
|
||||
{"pluralize", `{{pluralize "category"}}`, "categories"},
|
||||
{"sqlToGo", `{{sqlToGo "integer" true}}`, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tpl, err := template.New("t").Funcs(BuildFuncMap()).Parse(tt.tmpl)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := tpl.Execute(&buf, map[string]interface{}{}); err != nil {
|
||||
t.Fatalf("execute: %v", err)
|
||||
}
|
||||
if tt.name == "sqlToGo" {
|
||||
if buf.Len() == 0 {
|
||||
t.Error("sqlToGo rendered nothing")
|
||||
}
|
||||
return
|
||||
}
|
||||
if buf.String() != tt.want {
|
||||
t.Errorf("got %q, want %q", buf.String(), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type loopItem struct {
|
||||
Name string
|
||||
Group string
|
||||
N int
|
||||
}
|
||||
|
||||
func ints(vs ...interface{}) []interface{} { return vs }
|
||||
|
||||
func TestEnumerate(t *testing.T) {
|
||||
got := Enumerate([]string{"a", "b"})
|
||||
want := []EnumeratedItem{{0, "a"}, {1, "b"}}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got %v", got)
|
||||
}
|
||||
if got := Enumerate([2]int{5, 6}); len(got) != 2 || got[1].Value != 6 {
|
||||
t.Errorf("array: %v", got)
|
||||
}
|
||||
if got := Enumerate("nope"); len(got) != 0 {
|
||||
t.Errorf("non-slice: %v", got)
|
||||
}
|
||||
if got := Enumerate(nil); len(got) != 0 {
|
||||
t.Errorf("nil: %v", got)
|
||||
}
|
||||
if got := Enumerate([]int{}); len(got) != 0 {
|
||||
t.Errorf("empty: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchChunk(t *testing.T) {
|
||||
in := []int{1, 2, 3, 4, 5}
|
||||
got := Batch(in, 2)
|
||||
want := [][]interface{}{{1, 2}, {3, 4}, {5}}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got %v", got)
|
||||
}
|
||||
if got := Chunk(in, 10); len(got) != 1 || len(got[0]) != 5 {
|
||||
t.Errorf("size > len: %v", got)
|
||||
}
|
||||
for _, size := range []int{0, -1} {
|
||||
if got := Batch(in, size); len(got) != 0 {
|
||||
t.Errorf("size %d: %v", size, got)
|
||||
}
|
||||
}
|
||||
if got := Batch([]int{}, 2); len(got) != 0 {
|
||||
t.Errorf("empty: %v", got)
|
||||
}
|
||||
if got := Batch("x", 2); len(got) != 0 {
|
||||
t.Errorf("non-slice: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReverseFirstLastSkipTake(t *testing.T) {
|
||||
in := []int{1, 2, 3, 4}
|
||||
tests := []struct {
|
||||
name string
|
||||
got []interface{}
|
||||
want []interface{}
|
||||
}{
|
||||
{"reverse", Reverse(in), ints(4, 3, 2, 1)},
|
||||
{"reverse empty", Reverse([]int{}), ints()},
|
||||
{"reverse non-slice", Reverse(5), ints()},
|
||||
{"first 2", First(in, 2), ints(1, 2)},
|
||||
{"first n>len", First(in, 9), ints(1, 2, 3, 4)},
|
||||
{"first 0", First(in, 0), ints()},
|
||||
{"first non-slice", First(5, 1), ints()},
|
||||
{"last 2", Last(in, 2), ints(3, 4)},
|
||||
{"last n>len", Last(in, 9), ints(1, 2, 3, 4)},
|
||||
{"last neg", Last(in, -1), ints()},
|
||||
{"last non-slice", Last(5, 1), ints()},
|
||||
{"skip 1", Skip(in, 1), ints(2, 3, 4)},
|
||||
{"skip neg", Skip(in, -3), ints(1, 2, 3, 4)},
|
||||
{"skip all", Skip(in, 4), ints()},
|
||||
{"skip n>len", Skip(in, 10), ints()},
|
||||
{"skip non-slice", Skip(5, 1), ints()},
|
||||
{"take", Take(in, 3), ints(1, 2, 3)},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if len(tt.got) != len(tt.want) || (len(tt.want) > 0 && !reflect.DeepEqual(tt.got, tt.want)) {
|
||||
t.Errorf("got %v, want %v", tt.got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcatUnique(t *testing.T) {
|
||||
got := Concat([]int{1, 2}, []string{"a"}, 5, nil, [1]int{9})
|
||||
if !reflect.DeepEqual(got, ints(1, 2, "a", 9)) {
|
||||
t.Errorf("concat: %v", got)
|
||||
}
|
||||
if got := Concat(); len(got) != 0 {
|
||||
t.Errorf("concat none: %v", got)
|
||||
}
|
||||
if got := Unique([]int{1, 2, 1, 3, 2}); !reflect.DeepEqual(got, ints(1, 2, 3)) {
|
||||
t.Errorf("unique: %v", got)
|
||||
}
|
||||
if got := Unique("x"); len(got) != 0 {
|
||||
t.Errorf("unique non-slice: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortByGroupByCountIf(t *testing.T) {
|
||||
items := []loopItem{{"c", "x", 3}, {"a", "y", 1}, {"b", "x", 2}}
|
||||
|
||||
sorted := SortBy(items, "Name")
|
||||
if sorted[0].(loopItem).Name != "a" || sorted[2].(loopItem).Name != "c" {
|
||||
t.Errorf("sortBy Name: %v", sorted)
|
||||
}
|
||||
sorted = SortBy(items, "N")
|
||||
if sorted[0].(loopItem).N != 1 || sorted[2].(loopItem).N != 3 {
|
||||
t.Errorf("sortBy N: %v", sorted)
|
||||
}
|
||||
if items[0].Name != "c" {
|
||||
t.Errorf("SortBy must not mutate input")
|
||||
}
|
||||
if got := SortBy(5, "Name"); len(got) != 0 {
|
||||
t.Errorf("sortBy non-slice")
|
||||
}
|
||||
|
||||
groups := GroupBy(items, "Group")
|
||||
if len(groups) != 2 || len(groups["x"]) != 2 || len(groups["y"]) != 1 {
|
||||
t.Errorf("groupBy: %v", groups)
|
||||
}
|
||||
if got := GroupBy(5, "Group"); len(got) != 0 {
|
||||
t.Errorf("groupBy non-slice")
|
||||
}
|
||||
|
||||
n := CountIf(items, func(v interface{}) bool { return v.(loopItem).Group == "x" })
|
||||
if n != 2 {
|
||||
t.Errorf("countIf: %d", n)
|
||||
}
|
||||
if got := CountIf(5, func(interface{}) bool { return true }); got != 0 {
|
||||
t.Errorf("countIf non-slice: %d", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type accessItem struct {
|
||||
Name string
|
||||
ID int
|
||||
}
|
||||
|
||||
func TestGetAndGetOr(t *testing.T) {
|
||||
m := map[string]interface{}{"a": 1, "nilv": nil}
|
||||
if got := Get(m, "a"); got != 1 {
|
||||
t.Errorf("Get: %v", got)
|
||||
}
|
||||
if got := Get(m, "missing"); got != nil {
|
||||
t.Errorf("Get missing: %v", got)
|
||||
}
|
||||
if got := Get(nil, "a"); got != nil {
|
||||
t.Errorf("Get nil map: %v", got)
|
||||
}
|
||||
if got := GetOr(m, "missing", "def"); got != "def" {
|
||||
t.Errorf("GetOr missing: %v", got)
|
||||
}
|
||||
if got := GetOr(m, "nilv", "def"); got != "def" {
|
||||
t.Errorf("GetOr nil value: %v", got)
|
||||
}
|
||||
if got := GetOr(m, "a", "def"); got != 1 {
|
||||
t.Errorf("GetOr present: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPath(t *testing.T) {
|
||||
cfg := map[string]interface{}{
|
||||
"db": map[string]interface{}{"conn": map[string]interface{}{"host": "h"}},
|
||||
}
|
||||
if got := GetPath(cfg, "db.conn.host"); got != "h" {
|
||||
t.Errorf("GetPath: %v", got)
|
||||
}
|
||||
if got := GetPath(cfg, "db.nope.host"); got != nil {
|
||||
t.Errorf("GetPath missing: %v", got)
|
||||
}
|
||||
if got := GetPathOr(cfg, "db.nope", "dflt"); got != "dflt" {
|
||||
t.Errorf("GetPathOr: %v", got)
|
||||
}
|
||||
if got := GetPathOr(cfg, "db.conn.host", "dflt"); got != "h" {
|
||||
t.Errorf("GetPathOr present: %v", got)
|
||||
}
|
||||
if !HasPath(cfg, "db.conn") || HasPath(cfg, "db.x") || HasPath(nil, "a") {
|
||||
t.Errorf("HasPath mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafeIndex(t *testing.T) {
|
||||
s := []string{"a", "b"}
|
||||
if got := SafeIndex(s, 1); got != "b" {
|
||||
t.Errorf("SafeIndex: %v", got)
|
||||
}
|
||||
for _, i := range []int{-1, 2, 99} {
|
||||
if got := SafeIndex(s, i); got != nil {
|
||||
t.Errorf("SafeIndex(%d) must be nil, got %v", i, got)
|
||||
}
|
||||
}
|
||||
if got := SafeIndex("notslice", 0); got != nil {
|
||||
t.Errorf("non-slice: %v", got)
|
||||
}
|
||||
if got := SafeIndexOr(s, 5, "d"); got != "d" {
|
||||
t.Errorf("SafeIndexOr: %v", got)
|
||||
}
|
||||
if got := SafeIndexOr(s, 0, "d"); got != "a" {
|
||||
t.Errorf("SafeIndexOr present: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHas(t *testing.T) {
|
||||
m := map[string]int{"a": 1}
|
||||
var nilPtr *map[string]int
|
||||
tests := []struct {
|
||||
name string
|
||||
m interface{}
|
||||
key interface{}
|
||||
want bool
|
||||
}{
|
||||
{"present", m, "a", true},
|
||||
{"missing", m, "b", false},
|
||||
{"pointer to map", &m, "a", true},
|
||||
{"nil pointer", nilPtr, "a", false},
|
||||
{"non-map", []int{1}, 0, false},
|
||||
{"nil", nil, "a", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := Has(tt.m, tt.key); got != tt.want {
|
||||
t.Errorf("got %v", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeysValues(t *testing.T) {
|
||||
m := map[string]int{"a": 1, "b": 2}
|
||||
if got := Keys(m); len(got) != 2 {
|
||||
t.Errorf("Keys: %v", got)
|
||||
}
|
||||
if got := Values(m); len(got) != 2 {
|
||||
t.Errorf("Values: %v", got)
|
||||
}
|
||||
if got := Keys(nil); len(got) != 0 {
|
||||
t.Errorf("Keys nil: %v", got)
|
||||
}
|
||||
if got := Values(5); len(got) != 0 {
|
||||
t.Errorf("Values non-map: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
m1 := map[string]int{"a": 1, "b": 2}
|
||||
m2 := map[string]int{"b": 3, "c": 4}
|
||||
var nilPtr *map[string]int
|
||||
got := Merge(m1, &m2, nilPtr, nil, 5)
|
||||
want := map[interface{}]interface{}{"a": 1, "b": 3, "c": 4}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got %v", got)
|
||||
}
|
||||
if got := Merge(); len(got) != 0 {
|
||||
t.Errorf("empty merge: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPickOmit(t *testing.T) {
|
||||
m := map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
var nilPtr *map[string]int
|
||||
|
||||
if got := Pick(m, "a", "z"); !reflect.DeepEqual(got, map[interface{}]interface{}{"a": 1}) {
|
||||
t.Errorf("Pick: %v", got)
|
||||
}
|
||||
if got := Pick(&m, "b"); len(got) != 1 {
|
||||
t.Errorf("Pick ptr: %v", got)
|
||||
}
|
||||
if got := Pick(nilPtr, "a"); len(got) != 0 {
|
||||
t.Errorf("Pick nil ptr: %v", got)
|
||||
}
|
||||
if got := Pick(5, "a"); len(got) != 0 {
|
||||
t.Errorf("Pick non-map: %v", got)
|
||||
}
|
||||
|
||||
if got := Omit(m, "a", "z"); !reflect.DeepEqual(got, map[interface{}]interface{}{"b": 2, "c": 3}) {
|
||||
t.Errorf("Omit: %v", got)
|
||||
}
|
||||
if got := Omit(&m); len(got) != 3 {
|
||||
t.Errorf("Omit ptr: %v", got)
|
||||
}
|
||||
if got := Omit(nilPtr, "a"); len(got) != 0 {
|
||||
t.Errorf("Omit nil ptr: %v", got)
|
||||
}
|
||||
if got := Omit("x", "a"); len(got) != 0 {
|
||||
t.Errorf("Omit non-map: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceContainsIndexOf(t *testing.T) {
|
||||
s := []string{"a", "b", "c"}
|
||||
sp := &s
|
||||
var nilPtr *[]string
|
||||
if !SliceContains(s, "b") || SliceContains(s, "z") {
|
||||
t.Errorf("SliceContains")
|
||||
}
|
||||
if !SliceContains(sp, "c") || !SliceContains([2]int{1, 2}, 2) {
|
||||
t.Errorf("SliceContains ptr/array")
|
||||
}
|
||||
if SliceContains(nilPtr, "a") || SliceContains("str", "s") || SliceContains(nil, 1) {
|
||||
t.Errorf("SliceContains invalid input")
|
||||
}
|
||||
if got := IndexOf(s, "c"); got != 2 {
|
||||
t.Errorf("IndexOf: %d", got)
|
||||
}
|
||||
if got := IndexOf(sp, "a"); got != 0 {
|
||||
t.Errorf("IndexOf ptr: %d", got)
|
||||
}
|
||||
for _, in := range []interface{}{s, nilPtr, "str", nil} {
|
||||
if got := IndexOf(in, "zzz"); got != -1 {
|
||||
t.Errorf("IndexOf miss %v: %d", in, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluck(t *testing.T) {
|
||||
items := []*accessItem{{"a", 1}, nil, {"c", 3}}
|
||||
got := Pluck(items, "Name")
|
||||
if !reflect.DeepEqual(got, []interface{}{"a", nil, "c"}) {
|
||||
t.Errorf("struct ptrs: %v", got)
|
||||
}
|
||||
if got := Pluck([]accessItem{{"a", 1}}, "Missing"); !reflect.DeepEqual(got, []interface{}{nil}) {
|
||||
t.Errorf("missing field: %v", got)
|
||||
}
|
||||
maps := []map[string]int{{"k": 1}, {"x": 2}}
|
||||
if got := Pluck(maps, "k"); !reflect.DeepEqual(got, []interface{}{1, nil}) {
|
||||
t.Errorf("maps: %v", got)
|
||||
}
|
||||
if got := Pluck([]int{1, 2}, "k"); !reflect.DeepEqual(got, []interface{}{nil, nil}) {
|
||||
t.Errorf("scalars: %v", got)
|
||||
}
|
||||
var nilPtr *[]accessItem
|
||||
if got := Pluck(nilPtr, "Name"); len(got) != 0 {
|
||||
t.Errorf("nil ptr: %v", got)
|
||||
}
|
||||
if got := Pluck("str", "Name"); len(got) != 0 {
|
||||
t.Errorf("non-slice: %v", got)
|
||||
}
|
||||
s := []accessItem{{"z", 9}}
|
||||
if got := Pluck(&s, "ID"); !reflect.DeepEqual(got, []interface{}{9}) {
|
||||
t.Errorf("ptr to slice: %v", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCaseConversions(t *testing.T) {
|
||||
tests := []struct {
|
||||
in, camel, pascal, snake, kebab string
|
||||
}{
|
||||
{"", "", "", "", ""},
|
||||
{"user_name", "userName", "UserName", "user_name", "user-name"},
|
||||
{"http_request", "httpRequest", "HTTPRequest", "http_request", "http-request"},
|
||||
{"user_id", "userID", "UserID", "user_id", "user-id"},
|
||||
{"UserName", "username", "UserName", "user_name", "user-name"},
|
||||
{"HTTPRequest", "httprequest", "HTTPRequest", "http_request", "http-request"},
|
||||
{"userID", "userid", "UserID", "user_id", "user-id"},
|
||||
{"name", "name", "Name", "name", "name"},
|
||||
{"ÜberUser", "überuser", "ÜberUser", "über_user", "über-user"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.in, func(t *testing.T) {
|
||||
if got := ToCamelCase(tt.in); got != tt.camel {
|
||||
t.Errorf("ToCamelCase = %q, want %q", got, tt.camel)
|
||||
}
|
||||
if got := ToPascalCase(tt.in); got != tt.pascal {
|
||||
t.Errorf("ToPascalCase = %q, want %q", got, tt.pascal)
|
||||
}
|
||||
if got := ToSnakeCase(tt.in); got != tt.snake {
|
||||
t.Errorf("ToSnakeCase = %q, want %q", got, tt.snake)
|
||||
}
|
||||
if got := ToKebabCase(tt.in); got != tt.kebab {
|
||||
t.Errorf("ToKebabCase = %q, want %q", got, tt.kebab)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluralize(t *testing.T) {
|
||||
tests := []struct{ in, want string }{
|
||||
{"", ""},
|
||||
{"user", "users"},
|
||||
{"person", "people"},
|
||||
{"Person", "people"},
|
||||
{"status", "statuses"},
|
||||
{"cats", "cats"},
|
||||
{"bus", "buses"},
|
||||
{"dress", "dresses"},
|
||||
{"box", "boxes"},
|
||||
{"quiz", "quizes"},
|
||||
{"church", "churches"},
|
||||
{"dish", "dishes"},
|
||||
{"category", "categories"},
|
||||
{"day", "days"},
|
||||
{"leaf", "leaves"},
|
||||
{"knife", "knives"},
|
||||
{"hero", "heroes"},
|
||||
{"video", "videos"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := Pluralize(tt.in); got != tt.want {
|
||||
t.Errorf("Pluralize(%q) = %q, want %q", tt.in, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSingularize(t *testing.T) {
|
||||
tests := []struct{ in, want string }{
|
||||
{"", ""},
|
||||
{"users", "user"},
|
||||
{"people", "person"},
|
||||
{"Children", "child"},
|
||||
{"categories", "category"},
|
||||
{"ies", "ie"},
|
||||
{"leaves", "leaf"},
|
||||
{"buses", "bus"},
|
||||
{"boxes", "box"},
|
||||
{"churches", "church"},
|
||||
{"dishes", "dish"},
|
||||
{"dress", "dress"},
|
||||
{"user", "user"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := Singularize(tt.in); got != tt.want {
|
||||
t.Errorf("Singularize(%q) = %q, want %q", tt.in, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlainStringWrappers(t *testing.T) {
|
||||
if ToUpper("aB") != "AB" || ToLower("aB") != "ab" {
|
||||
t.Error("case")
|
||||
}
|
||||
if Title("hello world") != "Hello World" || Title("") != "" {
|
||||
t.Errorf("Title: %q", Title("hello world"))
|
||||
}
|
||||
if Trim(" a \n") != "a" {
|
||||
t.Error("Trim")
|
||||
}
|
||||
if TrimPrefix("foobar", "foo") != "bar" || TrimPrefix("bar", "foo") != "bar" {
|
||||
t.Error("TrimPrefix")
|
||||
}
|
||||
if TrimSuffix("foobar", "bar") != "foo" || TrimSuffix("foo", "bar") != "foo" {
|
||||
t.Error("TrimSuffix")
|
||||
}
|
||||
if Replace("aaa", "a", "b", 2) != "bba" || Replace("aaa", "a", "b", -1) != "bbb" {
|
||||
t.Error("Replace")
|
||||
}
|
||||
if !StringContains("abc", "b") || StringContains("abc", "z") {
|
||||
t.Error("StringContains")
|
||||
}
|
||||
if !HasPrefix("abc", "ab") || HasPrefix("abc", "bc") {
|
||||
t.Error("HasPrefix")
|
||||
}
|
||||
if !HasSuffix("abc", "bc") || HasSuffix("abc", "ab") {
|
||||
t.Error("HasSuffix")
|
||||
}
|
||||
if got := Split("a,b", ","); !reflect.DeepEqual(got, []string{"a", "b"}) {
|
||||
t.Errorf("Split: %v", got)
|
||||
}
|
||||
if Join([]string{"a", "b"}, "-") != "a-b" || Join(nil, "-") != "" {
|
||||
t.Error("Join")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapitalizeAndIsVowel(t *testing.T) {
|
||||
tests := []struct{ in, want string }{
|
||||
{"", ""},
|
||||
{"id", "ID"},
|
||||
{"Uuid", "UUID"},
|
||||
{"http", "HTTP"},
|
||||
{"name", "Name"},
|
||||
{"élan", "Élan"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := capitalize(tt.in); got != tt.want {
|
||||
t.Errorf("capitalize(%q) = %q, want %q", tt.in, got, tt.want)
|
||||
}
|
||||
}
|
||||
for _, c := range []byte("aeiouAEIOU") {
|
||||
if !isVowel(c) {
|
||||
t.Errorf("%c should be vowel", c)
|
||||
}
|
||||
}
|
||||
for _, c := range []byte("bcxyz") {
|
||||
if isVowel(c) {
|
||||
t.Errorf("%c should not be vowel", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
)
|
||||
|
||||
func sampleDB() (*models.Database, *models.Schema, *models.Table) {
|
||||
db := models.InitDatabase("shop")
|
||||
schema := models.InitSchema("public")
|
||||
table := models.InitTable("users", "public")
|
||||
col := models.InitColumn("id", "users", "public")
|
||||
col.Type = "integer"
|
||||
col.IsPrimaryKey = true
|
||||
table.Columns["id"] = col
|
||||
schema.Tables = append(schema.Tables, table)
|
||||
db.Schemas = append(db.Schemas, schema)
|
||||
return db, schema, table
|
||||
}
|
||||
|
||||
func TestTemplateDataConstructors(t *testing.T) {
|
||||
db, schema, table := sampleDB()
|
||||
meta := map[string]interface{}{"k": "v"}
|
||||
|
||||
dd := NewDatabaseData(db, meta)
|
||||
if dd.Database != db || dd.ParentDatabase != db || dd.Summary == nil || len(dd.FlatColumns) != 1 || len(dd.FlatTables) != 1 || dd.Metadata["k"] != "v" {
|
||||
t.Errorf("database data: %+v", dd)
|
||||
}
|
||||
if dd.Name() != "shop" {
|
||||
t.Errorf("name: %q", dd.Name())
|
||||
}
|
||||
|
||||
sd := NewSchemaData(schema, meta)
|
||||
if sd.Schema != schema || sd.ParentDatabase == nil || sd.ParentDatabase.Name != "public" || len(sd.FlatColumns) != 1 {
|
||||
t.Errorf("schema data: %+v", sd)
|
||||
}
|
||||
if sd.Name() != "public" {
|
||||
t.Errorf("name: %q", sd.Name())
|
||||
}
|
||||
|
||||
td := NewTableData(table, schema, db, meta)
|
||||
if td.Table != table || td.ParentSchema != schema || td.ParentDatabase != db || td.Name() != "users" {
|
||||
t.Errorf("table data: %+v", td)
|
||||
}
|
||||
|
||||
dom := &models.Domain{Name: "billing"}
|
||||
dmd := NewDomainData(dom, db, meta)
|
||||
if dmd.Domain != dom || dmd.ParentDatabase != db || dmd.Name() != "billing" {
|
||||
t.Errorf("domain data: %+v", dmd)
|
||||
}
|
||||
|
||||
sc := &models.Script{Name: "seed"}
|
||||
scd := NewScriptData(sc, schema, db, meta)
|
||||
if scd.Script != sc || scd.ParentSchema != schema || scd.Name() != "seed" {
|
||||
t.Errorf("script data: %+v", scd)
|
||||
}
|
||||
|
||||
if got := (&TemplateData{}).Name(); got != "output" {
|
||||
t.Errorf("empty name: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeMappersDelegate(t *testing.T) {
|
||||
if got := SQLToGo("integer", false); got == "" {
|
||||
t.Error("SQLToGo")
|
||||
}
|
||||
if got := SQLToTypeScript("integer", false); got == "" {
|
||||
t.Error("SQLToTypeScript")
|
||||
}
|
||||
if got := SQLToJava("integer", false); got == "" {
|
||||
t.Error("SQLToJava")
|
||||
}
|
||||
if got := SQLToPython("integer"); got == "" {
|
||||
t.Error("SQLToPython")
|
||||
}
|
||||
if got := SQLToRust("integer", false); got == "" {
|
||||
t.Error("SQLToRust")
|
||||
}
|
||||
if got := SQLToCSharp("integer", false); got == "" {
|
||||
t.Error("SQLToCSharp")
|
||||
}
|
||||
if got := SQLToPhp("integer", false); got == "" {
|
||||
t.Error("SQLToPhp")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
||||
)
|
||||
|
||||
func writeTemplateFile(t *testing.T, body string) string {
|
||||
t.Helper()
|
||||
p := filepath.Join(t.TempDir(), "t.tmpl")
|
||||
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func modeDB() *models.Database {
|
||||
db := models.InitDatabase("shop")
|
||||
for _, sn := range []string{"a", "b"} {
|
||||
s := models.InitSchema(sn)
|
||||
for _, tn := range []string{"t1", "t2"} {
|
||||
s.Tables = append(s.Tables, models.InitTable(tn, sn))
|
||||
}
|
||||
s.Scripts = append(s.Scripts, &models.Script{Name: "seed_" + sn})
|
||||
db.Schemas = append(db.Schemas, s)
|
||||
}
|
||||
db.Domains = append(db.Domains, &models.Domain{Name: "billing"})
|
||||
return db
|
||||
}
|
||||
|
||||
func newTestWriter(t *testing.T, body, mode, pattern, out string) (*Writer, error) {
|
||||
t.Helper()
|
||||
meta := map[string]interface{}{"template_path": writeTemplateFile(t, body)}
|
||||
if mode != "" {
|
||||
meta["mode"] = mode
|
||||
}
|
||||
if pattern != "" {
|
||||
meta["filename_pattern"] = pattern
|
||||
}
|
||||
return NewWriter(&writers.WriterOptions{OutputPath: out, Metadata: meta})
|
||||
}
|
||||
|
||||
func TestNewWriterErrors(t *testing.T) {
|
||||
if _, err := NewWriter(&writers.WriterOptions{}); err == nil {
|
||||
t.Error("expected error for missing template path")
|
||||
}
|
||||
_, err := NewWriter(&writers.WriterOptions{Metadata: map[string]interface{}{"template_path": "/no/such/file"}})
|
||||
var te *TemplateError
|
||||
if !errors.As(err, &te) || te.Phase != "load" {
|
||||
t.Errorf("load error: %v", err)
|
||||
}
|
||||
_, err = newTestWriter(t, "{{ .Unclosed ", "", "", "")
|
||||
if !errors.As(err, &te) || te.Phase != "parse" {
|
||||
t.Errorf("parse error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriterModes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, mode, body, pattern string
|
||||
wantFiles []string
|
||||
}{
|
||||
{"database", "database", "{{.Database.Name}}", "", []string{"out.txt"}},
|
||||
{"schema", "schema", "{{.Schema.Name}}", "{{.Name}}.txt", []string{"a.txt", "b.txt"}},
|
||||
{"table", "table", "{{.Table.Name}}", "{{.ParentSchema.Name}}_{{.Name}}.txt", []string{"a_t1.txt", "a_t2.txt", "b_t1.txt", "b_t2.txt"}},
|
||||
{"script", "script", "{{.Script.Name}}", "{{.Name}}.sql", []string{"seed_a.sql", "seed_b.sql"}},
|
||||
{"domain", "domain", "{{.Domain.Name}}", "{{.Name}}.md", []string{"billing.md"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
outDir := t.TempDir()
|
||||
out := outDir
|
||||
if tt.mode == "database" {
|
||||
out = filepath.Join(outDir, "out.txt")
|
||||
}
|
||||
w, err := newTestWriter(t, tt.body, tt.mode, tt.pattern, out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.WriteDatabase(modeDB()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, f := range tt.wantFiles {
|
||||
if _, err := os.Stat(filepath.Join(outDir, f)); err != nil {
|
||||
t.Errorf("missing %s: %v", f, err)
|
||||
}
|
||||
}
|
||||
entries, _ := os.ReadDir(outDir)
|
||||
if len(entries) != len(tt.wantFiles) {
|
||||
t.Errorf("got %d files, want %d", len(entries), len(tt.wantFiles))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriterDatabaseModeContent(t *testing.T) {
|
||||
out := filepath.Join(t.TempDir(), "sub", "dir", "o.txt")
|
||||
w, err := newTestWriter(t, "{{.Database.Name}}:{{len .Database.Schemas}}", "", "", out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.WriteDatabase(modeDB()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data, err := os.ReadFile(out)
|
||||
if err != nil || string(data) != "shop:2" {
|
||||
t.Errorf("content %q err %v", data, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriterUnknownMode(t *testing.T) {
|
||||
w, err := newTestWriter(t, "x", "bogus", "", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.WriteDatabase(modeDB()); err == nil || !strings.Contains(err.Error(), "unknown entrypoint mode") {
|
||||
t.Errorf("got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriterExecuteErrors(t *testing.T) {
|
||||
// Execution failure: field does not exist on TemplateData.
|
||||
for _, mode := range []string{"database", "schema", "table", "script", "domain"} {
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
w, err := newTestWriter(t, "{{.NoSuchField}}", mode, "", t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = w.WriteDatabase(modeDB())
|
||||
var te *TemplateError
|
||||
if !errors.As(err, &te) || te.Phase != "execute" {
|
||||
t.Errorf("got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriterBadFilenamePattern(t *testing.T) {
|
||||
for _, pattern := range []string{"{{.Unclosed", "{{.NoSuchField}}"} {
|
||||
for _, mode := range []string{"schema", "table", "script", "domain"} {
|
||||
w, err := newTestWriter(t, "x", mode, pattern, t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.WriteDatabase(modeDB()); err == nil {
|
||||
t.Errorf("mode %s pattern %q: expected error", mode, pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriterWriteOutputFailure(t *testing.T) {
|
||||
// Output path whose parent is a regular file cannot be created.
|
||||
blocker := filepath.Join(t.TempDir(), "file")
|
||||
if err := os.WriteFile(blocker, nil, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w, err := newTestWriter(t, "x", "database", "", filepath.Join(blocker, "child", "o.txt"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.WriteDatabase(modeDB()); err == nil {
|
||||
t.Error("expected write failure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriterGenerateFilenameOutputPathForms(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
data := NewTableData(models.InitTable("users", "public"), nil, nil, nil)
|
||||
|
||||
tests := []struct {
|
||||
name, out, want string
|
||||
}{
|
||||
{"no output path", "", "users.txt"},
|
||||
{"existing dir", dir, filepath.Join(dir, "users.txt")},
|
||||
{"trailing separator", filepath.Join(dir, "new") + string(filepath.Separator), filepath.Join(dir, "new", "users.txt")},
|
||||
{"file path uses its dir", filepath.Join(dir, "x.out"), filepath.Join(dir, "users.txt")},
|
||||
{"bare file name", "x.out", "users.txt"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w, err := newTestWriter(t, "x", "table", "{{.Name}}.txt", tt.out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := w.generateFilename(data)
|
||||
if err != nil || got != tt.want {
|
||||
t.Errorf("got %q err %v, want %q", got, err, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriterWriteSchemaAndTable(t *testing.T) {
|
||||
out := filepath.Join(t.TempDir(), "o.txt")
|
||||
w, err := newTestWriter(t, "{{range .Database.Schemas}}{{.Name}}:{{len .Tables}};{{end}}", "", "", out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
db := modeDB()
|
||||
if err := w.WriteSchema(db.Schemas[0]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if data, _ := os.ReadFile(out); string(data) != "a:2;" {
|
||||
t.Errorf("WriteSchema: %q", data)
|
||||
}
|
||||
if err := w.WriteTable(db.Schemas[1].Tables[0]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if data, _ := os.ReadFile(out); string(data) != "b:1;" {
|
||||
t.Errorf("WriteTable: %q", data)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user