feat(04-01): generate model, migration, and command artifacts
Add make:model, make:migration and make:command with plugin-id inference, duplicate rejection, and a deterministic registry refresh that leaves handwritten plugin.go untouched. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -410,6 +410,153 @@ func TestScaffoldPluginSmoke(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestScaffoldCoreArtifacts(t *testing.T) {
|
||||
dir := copyHelloApp(t)
|
||||
ctx := t.Context()
|
||||
pluginDir, err := MakePlugin(ctx, dir, "golem15.demo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pluginBefore, err := os.ReadFile(filepath.Join(pluginDir, "plugin.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
model, err := MakeModel(ctx, dir, "golem15.demo", "Album", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if model.Hint != "" {
|
||||
t.Fatalf("new plugin printed accessor hint: %s", model.Hint)
|
||||
}
|
||||
if len(model.Files) != 2 {
|
||||
t.Fatalf("make:model files = %v", model.Files)
|
||||
}
|
||||
albumSrc, err := os.ReadFile(filepath.Join(pluginDir, "models", "album.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, want := range []string{
|
||||
"type Album struct",
|
||||
"CreatedAt",
|
||||
"UpdatedAt",
|
||||
`return "golem15_demo_albums"`,
|
||||
} {
|
||||
if !bytes.Contains(albumSrc, []byte(want)) {
|
||||
t.Fatalf("album.go missing %s:\n%s", want, albumSrc)
|
||||
}
|
||||
}
|
||||
createAlbum, err := filepath.Glob(filepath.Join(pluginDir, "updates", "*_create_golem15_demo_albums.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(createAlbum) != 1 {
|
||||
t.Fatalf("create-table migration files = %v", createAlbum)
|
||||
}
|
||||
migSrc, err := os.ReadFile(createAlbum[0])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, want := range []string{
|
||||
"func CreateAlbums()",
|
||||
"CREATE TABLE golem15_demo_albums",
|
||||
"DROP TABLE IF EXISTS golem15_demo_albums",
|
||||
} {
|
||||
if !bytes.Contains(migSrc, []byte(want)) {
|
||||
t.Fatalf("model migration missing %s:\n%s", want, migSrc)
|
||||
}
|
||||
}
|
||||
|
||||
widget, err := MakeModel(ctx, pluginDir, "", "Widget", true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(widget.Files) != 1 || !strings.HasSuffix(widget.Files[0], filepath.Join("models", "widget.go")) {
|
||||
t.Fatalf("--no-migration files = %v", widget.Files)
|
||||
}
|
||||
widgetMigs, err := filepath.Glob(filepath.Join(pluginDir, "updates", "*widget*"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(widgetMigs) != 0 {
|
||||
t.Fatalf("--no-migration wrote %v", widgetMigs)
|
||||
}
|
||||
|
||||
if _, err := MakeMigration(ctx, dir, "golem15.demo", "AddWidgetIndex"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := MakeCommand(ctx, dir, "golem15.demo", "Purge"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmdSrc, err := os.ReadFile(filepath.Join(pluginDir, "console", "purge.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Contains(cmdSrc, []byte(`"demo:purge"`)) {
|
||||
t.Fatalf("command name missing:\n%s", cmdSrc)
|
||||
}
|
||||
|
||||
if _, err := MakeModel(ctx, dir, "golem15.demo", "Album", false); err == nil {
|
||||
t.Fatal("duplicate make:model want error")
|
||||
}
|
||||
reg1, err := os.ReadFile(filepath.Join(pluginDir, "registry.gen.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := MakeCommand(ctx, dir, "golem15.demo", "Purge"); err == nil {
|
||||
t.Fatal("duplicate make:command want error")
|
||||
}
|
||||
if err := refreshRegistry(pluginDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reg2, err := os.ReadFile(filepath.Join(pluginDir, "registry.gen.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(reg1, reg2) {
|
||||
t.Fatal("repeated generation changed registry.gen.go")
|
||||
}
|
||||
for _, want := range []string{
|
||||
"&models.Album{}",
|
||||
"&models.Widget{}",
|
||||
"updates.CreateAlbums()",
|
||||
"updates.AddWidgetIndex()",
|
||||
"console.PurgeCommand()",
|
||||
} {
|
||||
if !bytes.Contains(reg1, []byte(want)) {
|
||||
t.Fatalf("registry.gen.go missing %s:\n%s", want, reg1)
|
||||
}
|
||||
}
|
||||
|
||||
pluginAfter, err := os.ReadFile(filepath.Join(pluginDir, "plugin.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(pluginBefore, pluginAfter) {
|
||||
t.Fatal("handwritten plugin.go was rewritten")
|
||||
}
|
||||
|
||||
greeter, err := MakeCommand(ctx, filepath.Join(dir, "plugins", "greeter"), "", "Wave")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if greeter.Hint == "" || !strings.Contains(greeter.Hint, "generatedCommands()") {
|
||||
t.Fatalf("want accessor hint for handwritten plugin, got %q", greeter.Hint)
|
||||
}
|
||||
|
||||
if err := AddPlugin(ctx, dir, filepath.Join("plugins", "demo")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := App(ctx, dir, &buf); err != nil {
|
||||
t.Fatalf("app build: %v\n%s", err, buf.String())
|
||||
}
|
||||
vet := execGo(t, pluginDir, "vet", "./...")
|
||||
if out, err := vet.CombinedOutput(); err != nil {
|
||||
t.Fatalf("plugin go vet: %v\n%s", err, out)
|
||||
}
|
||||
}
|
||||
|
||||
func assertScaffoldFiles(t *testing.T, pluginDir string) {
|
||||
t.Helper()
|
||||
assertWinterPluginShape(t, pluginDir)
|
||||
|
||||
Reference in New Issue
Block a user