2 Commits

Author SHA1 Message Date
f98f8416e8 codegen: test fixture factories now use randomness to generate unique column values
All checks were successful
CI / build-docker (push) Successful in 2s
CI / build-docker-bootstrap (push) Has been skipped
CI / release-test (push) Successful in 10s
2026-10-04 22:18:38 -07:00
1592c7d1fc codegen: MakeXyz() fixture factories now take args for required foreign key objects 2026-10-04 20:40:19 -07:00

View File

@@ -101,12 +101,55 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps() hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps()
updateTestFields := UpdateTestFields(tbl) updateTestFields := UpdateTestFields(tbl)
uniqueColumns := map[string]bool{}
for _, index := range schema.Indexes {
if index.TableName != tbl.TableName || !index.IsUnique {
continue
}
for _, columnName := range index.Columns {
uniqueColumns[columnName] = true
}
}
hasRandomUniqueField := false
for _, c := range updateTestFields {
if uniqueColumns[c.Name] {
hasRandomUniqueField = true
break
}
}
requiredForeignKeys := []pkgschema.Column{}
for _, c := range tbl.Columns {
// Only ordinary object foreign keys use a distinct TargetObjectID Go type;
// code-table foreign keys retain their underlying SQLite type in the generated model.
if c.IsNotNull && c.IsNonCodeTableForeignKey() {
requiredForeignKeys = append(requiredForeignKeys, c)
}
}
requiredForeignKeyParamNames := paramNamesFor(requiredForeignKeys)
// Calls made by this generated test do not create related objects themselves. Pass a
// zero ID so the generated test remains syntactically valid; callers of MakeXyz provide
// IDs of the related objects they create.
makeItemArgs := func() (ret []ast.Expr) {
for range requiredForeignKeys {
ret = append(ret, &ast.BasicLit{Kind: token.INT, Value: "0"})
}
return
}()
// func MakeItem() Item { return Item{} } // func MakeItem() Item { return Item{} }
makeItemFunc := &ast.FuncDecl{ makeItemFunc := &ast.FuncDecl{
Name: makeHelperName, Name: makeHelperName,
Type: &ast.FuncType{ Type: &ast.FuncType{
Params: &ast.FieldList{}, Params: &ast.FieldList{List: func() (ret []*ast.Field) {
for i, c := range requiredForeignKeys {
ret = append(ret, &ast.Field{
Names: []*ast.Ident{ast.NewIdent(requiredForeignKeyParamNames[i])},
Type: GoTypeForColumn(c),
})
}
return
}()},
Results: &ast.FieldList{ Results: &ast.FieldList{
List: []*ast.Field{ List: []*ast.Field{
{Type: ast.NewIdent(tbl.GoTypeName)}, {Type: ast.NewIdent(tbl.GoTypeName)},
@@ -114,24 +157,71 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
}, },
}, },
Body: &ast.BlockStmt{ Body: &ast.BlockStmt{
List: []ast.Stmt{ List: append(
&ast.ReturnStmt{ func() []ast.Stmt {
Results: []ast.Expr{ if !hasRandomUniqueField {
&ast.CompositeLit{ return nil
Type: ast.NewIdent(tbl.GoTypeName), }
Elts: func() (ret []ast.Expr) { return []ast.Stmt{&ast.AssignStmt{
for _, c := range updateTestFields { Lhs: []ast.Expr{ast.NewIdent("id")},
ret = append(ret, &ast.KeyValueExpr{ Tok: token.DEFINE,
Key: ast.NewIdent(c.GoFieldName()), Rhs: []ast.Expr{&ast.CallExpr{Fun: &ast.SelectorExpr{X: ast.NewIdent("rand"), Sel: ast.NewIdent("Int")}}},
Value: SampleValue(c, 0), }}
}) }(),
} []ast.Stmt{
return &ast.ReturnStmt{
}(), Results: []ast.Expr{
&ast.CompositeLit{
Type: ast.NewIdent(tbl.GoTypeName),
Elts: func() (ret []ast.Expr) {
// `UpdateTestFields()` skips foreign keys, so this will not cause duplicates
for i, c := range requiredForeignKeys {
ret = append(ret, &ast.KeyValueExpr{
Key: ast.NewIdent(c.GoFieldName()),
Value: ast.NewIdent(requiredForeignKeyParamNames[i]),
})
}
for _, c := range updateTestFields {
value := SampleValue(c, 0)
if uniqueColumns[c.Name] {
switch c.Type {
case "integer", "int":
value = ast.NewIdent("id")
case "text":
value = &ast.CallExpr{
Fun: &ast.SelectorExpr{X: ast.NewIdent("fmt"), Sel: ast.NewIdent("Sprintf")},
Args: []ast.Expr{
&ast.BasicLit{Kind: token.STRING, Value: `"%s-%d"`},
SampleValue(c, 0),
ast.NewIdent("id"),
},
}
case "blob":
value = &ast.CallExpr{
Fun: &ast.ArrayType{Elt: ast.NewIdent("byte")},
Args: []ast.Expr{&ast.CallExpr{
Fun: &ast.SelectorExpr{X: ast.NewIdent("fmt"), Sel: ast.NewIdent("Sprintf")},
Args: []ast.Expr{
&ast.BasicLit{Kind: token.STRING, Value: `"%s-%d"`},
&ast.CallExpr{Fun: ast.NewIdent("string"), Args: []ast.Expr{SampleValue(c, 0)}},
ast.NewIdent("id"),
},
}},
}
}
}
ret = append(ret, &ast.KeyValueExpr{
Key: ast.NewIdent(c.GoFieldName()),
Value: value,
})
}
return
}(),
},
}, },
}, },
}, }...,
}, ),
}, },
} }
@@ -242,7 +332,8 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
Tok: map[bool]token.Token{true: token.DEFINE, false: token.ASSIGN}[isFirst], Tok: map[bool]token.Token{true: token.DEFINE, false: token.ASSIGN}[isFirst],
Rhs: []ast.Expr{ Rhs: []ast.Expr{
&ast.CallExpr{ &ast.CallExpr{
Fun: ast.NewIdent("Make" + tbl.GoTypeName), Fun: ast.NewIdent("Make" + tbl.GoTypeName),
Args: makeItemArgs,
}, },
}, },
}, },
@@ -329,7 +420,7 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
&ast.AssignStmt{ &ast.AssignStmt{
Lhs: []ast.Expr{testObj}, Lhs: []ast.Expr{testObj},
Tok: token.DEFINE, Tok: token.DEFINE,
Rhs: []ast.Expr{&ast.CallExpr{Fun: makeHelperName, Args: nil}}, Rhs: []ast.Expr{&ast.CallExpr{Fun: makeHelperName, Args: makeItemArgs}},
}, },
// item.Description = <sample value, offset 1> // item.Description = <sample value, offset 1>
// ...one assignment per updatable field // ...one assignment per updatable field
@@ -560,23 +651,33 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
Decls: append([]ast.Decl{ Decls: append([]ast.Decl{
&ast.GenDecl{ &ast.GenDecl{
Tok: token.IMPORT, Tok: token.IMPORT,
Specs: []ast.Spec{ Specs: func() []ast.Spec {
&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"testing"`}}, specs := []ast.Spec{}
&ast.ImportSpec{ if hasRandomUniqueField {
Path: &ast.BasicLit{Kind: token.STRING, Value: `"git.offline-twitter.com/offline-labs/gas-stack/pkg/db"`}, specs = append(specs,
Name: ast.NewIdent("."), &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"fmt"`}},
}, &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"math/rand"`}},
&ast.ImportSpec{ )
Path: &ast.BasicLit{Kind: token.STRING, Value: `"git.offline-twitter.com/offline-labs/gas-stack/pkg/must"`}, }
}, specs = append(specs, []ast.Spec{
&ast.ImportSpec{ &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"testing"`}},
Path: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(`"%s/pkg/%s"`, gomodName, packageName)}, &ast.ImportSpec{
Name: ast.NewIdent("."), Path: &ast.BasicLit{Kind: token.STRING, Value: `"git.offline-twitter.com/offline-labs/gas-stack/pkg/db"`},
}, Name: ast.NewIdent("."),
&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/go-test/deep"`}}, },
&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/stretchr/testify/assert"`}}, &ast.ImportSpec{
&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/stretchr/testify/require"`}}, Path: &ast.BasicLit{Kind: token.STRING, Value: `"git.offline-twitter.com/offline-labs/gas-stack/pkg/must"`},
}, },
&ast.ImportSpec{
Path: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(`"%s/pkg/%s"`, gomodName, packageName)},
Name: ast.NewIdent("."),
},
&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/go-test/deep"`}},
&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/stretchr/testify/assert"`}},
&ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"github.com/stretchr/testify/require"`}},
}...)
return specs
}(),
}, },
}, testList...), }, testList...),
} }