codegen: make test fixture factory function intelligent about what fields and values it assigns

This commit is contained in:
2026-02-14 18:33:41 -08:00
parent b33127db6c
commit ae036d15f2

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"go/ast"
"go/token"
"strings"
"github.com/jinzhu/inflection"
@@ -11,6 +12,80 @@ import (
"git.offline-twitter.com/offline-labs/gas-stack/pkg/textutils"
)
// SampleValue returns a deterministic value of the appropriate type for the given column.
func SampleValue(c pkgschema.Column, offset int) ast.Expr {
switch c.Type {
case "integer", "int":
if strings.HasPrefix(c.Name, "is_") || strings.HasPrefix(c.Name, "has_") {
// Boolean case
if offset%2 == 0 {
return ast.NewIdent("false")
}
return ast.NewIdent("true")
} else if strings.HasSuffix(c.Name, "_at") {
// Timestamp case
return &ast.CallExpr{
Fun: ast.NewIdent("TimestampFromUnix"),
Args: []ast.Expr{
&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", 10000+offset)},
},
}
} else {
// Regular integer case
return &ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", 10+offset)}
}
case "text":
if offset == 0 {
return &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", "asdf")}
}
return &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", fmt.Sprintf("asdf%d", offset))}
case "real":
return &ast.BasicLit{Kind: token.FLOAT, Value: fmt.Sprintf("%.2f", 1.23+float64(offset))}
case "blob":
return &ast.CompositeLit{
Type: &ast.ArrayType{
Elt: ast.NewIdent("byte"),
},
Elts: func() []ast.Expr {
ret := []ast.Expr{
&ast.BasicLit{Kind: token.INT, Value: "72"}, // 'H'
&ast.BasicLit{Kind: token.INT, Value: "105"}, // 'i'
&ast.BasicLit{Kind: token.INT, Value: "33"}, // '!'
}
for range offset {
ret = append(ret,
&ast.BasicLit{Kind: token.INT, Value: "33"}, // '!'
)
}
return ret
}(),
}
default:
panic("Unrecognized sqlite column type: " + c.Type)
}
}
// UpdateTestFields returns the columns for which the test generator should assign its own
// sample values, in the MakeXyz() factory and in the create/update test: the same columns
// that GenerateSaveItemFunc's "update" branch writes to, minus foreign keys (which can't be
// given plausible values here) and the auto-managed "created_at"/"updated_at" columns.
func UpdateTestFields(tbl pkgschema.Table) (ret []pkgschema.Column) {
hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps()
for _, c := range tbl.Columns {
if c.Name == "rowid" || c.IsPrimaryKey || c.IsForeignKey {
continue
}
if c.Name == "created_at" && hasCreatedAt {
continue
}
if c.Name == "updated_at" && hasUpdatedAt {
continue
}
ret = append(ret, c)
}
return
}
// GenerateModelTestAST produces an AST for a starter test file for a given model.
func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodName string) *ast.File {
packageName := "db"
@@ -18,6 +93,9 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
makeHelperName := ast.NewIdent("Make" + tbl.GoTypeName)
hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps()
updateTestFields := UpdateTestFields(tbl)
// func MakeItem() Item { return Item{} }
makeItemFunc := &ast.FuncDecl{
Name: makeHelperName,
@@ -35,24 +113,15 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
Results: []ast.Expr{
&ast.CompositeLit{
Type: ast.NewIdent(tbl.GoTypeName),
Elts: []ast.Expr{
&ast.KeyValueExpr{
Key: ast.NewIdent("Data"),
Value: &ast.CompositeLit{
Type: &ast.ArrayType{
Elt: ast.NewIdent("byte"),
},
Elts: []ast.Expr{},
},
},
&ast.KeyValueExpr{
Key: ast.NewIdent("Description"),
Value: &ast.BasicLit{
Kind: token.STRING,
Value: `""`,
},
},
},
Elts: func() (ret []ast.Expr) {
for _, c := range updateTestFields {
ret = append(ret, &ast.KeyValueExpr{
Key: ast.NewIdent(c.GoFieldName()),
Value: SampleValue(c, 0),
})
}
return
}(),
},
},
},
@@ -62,13 +131,8 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
testObj := ast.NewIdent(textutils.CamelToPascal(tbl.GoTypeName))
testObj2 := ast.NewIdent(textutils.CamelToPascal(tbl.GoTypeName) + "2")
fieldName := ast.NewIdent("Description") // TODO
description1 := "an item"
description2 := "a big item"
testDB := ast.NewIdent("TestDB")
hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps()
makeDeepEqual := func(obj1 *ast.Ident, obj2 *ast.Ident) *ast.IfStmt {
return &ast.IfStmt{
Init: &ast.AssignStmt{
@@ -236,23 +300,22 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
Tok: token.DEFINE,
Rhs: []ast.Expr{&ast.CallExpr{Fun: makeHelperName, Args: nil}},
},
// item.Description = "an item"
&ast.AssignStmt{
// item.Description = <sample value, offset 1>
// ...one assignment per updatable field
}
for _, c := range updateTestFields {
stmts = append(stmts, &ast.AssignStmt{
Lhs: []ast.Expr{
&ast.SelectorExpr{
X: testObj,
Sel: ast.NewIdent("Description"),
Sel: ast.NewIdent(c.GoFieldName()),
},
},
Tok: token.ASSIGN,
Rhs: []ast.Expr{
&ast.BasicLit{
Kind: token.STRING,
Value: fmt.Sprintf("%q", description1),
},
},
},
Rhs: []ast.Expr{SampleValue(c, 1)},
})
}
stmts = append(stmts,
// TestDB.SaveItem(&item), possibly with error check
&ast.ExprStmt{X: func() *ast.CallExpr {
mainExpr := &ast.CallExpr{
@@ -274,7 +337,7 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
Fun: &ast.SelectorExpr{X: ast.NewIdent("require"), Sel: ast.NewIdent("NotZero")},
Args: []ast.Expr{ast.NewIdent("t"), &ast.SelectorExpr{X: testObj, Sel: ast.NewIdent("ID")}},
}},
}
)
// After create: assert timestamps are set
if hasCreatedAt {
@@ -305,14 +368,19 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
stmts = append(stmts,
BlankLine(),
Comment("Update"),
)
// item.Description = "a big item"
&ast.AssignStmt{
Lhs: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: fieldName}},
// item.Description = <sample value, offset 2>
// ...one assignment per updatable field
for _, c := range updateTestFields {
stmts = append(stmts, &ast.AssignStmt{
Lhs: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: ast.NewIdent(c.GoFieldName())}},
Tok: token.ASSIGN,
Rhs: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", description2)}},
},
Rhs: []ast.Expr{SampleValue(c, 2)},
})
}
stmts = append(stmts,
// TestDB.SaveItem(&item)
&ast.ExprStmt{X: &ast.CallExpr{
Fun: &ast.SelectorExpr{X: testDB, Sel: ast.NewIdent("Save" + tbl.GoTypeName)},