Implements tests/_plans and previously deferred packages; updates plan README with new coverage numbers.
220 lines
6.4 KiB
Go
220 lines
6.4 KiB
Go
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)
|
|
}
|
|
}
|