package main import ( "bytes" "go/parser" "go/token" "os" "path/filepath" "slices" "strconv" "strings" "testing" "git.golem15.com/golem15/summercms/modules/bonfire" ) func TestToolCommandNames(t *testing.T) { var names []string for _, c := range toolCommands() { names = append(names, c.Name) } for _, want := range []string{"build", "make:plugin", "make:model", "make:migration", "make:command", "make:job", "make:admin-controller", "plugin:add", "dev", "migrate", "migrate:rollback", "migrate:status", "serve", "queue:work", "queue:clear", "schedule:run", "parity:broadcasts", "docs:build", "docs:sync"} { if !slices.Contains(names, want) { t.Fatalf("missing %s in %v", want, names) } } helpWants := map[string][]string{ "make:plugin": {"vendor.plugin"}, "make:model": {"[plugin] [name]", "no-migration"}, "make:migration": {"[plugin] [name]"}, "make:command": {"[plugin] [name]"}, "make:job": {"[plugin] [name]"}, "make:admin-controller": {"[plugin] [name]"}, "schedule:run": {"--once"}, "parity:broadcasts": {"--flow", "--step", "--ids", "127.0.0.1:8424"}, "docs:build": {"--out", "--src", "--root", "--base-url", "--check"}, "docs:sync": {"--src", "--root"}, } for cmd, wants := range helpWants { var buf bytes.Buffer root, err := bonfire.NewRoot("summer", toolCommands(), &buf) if err != nil { t.Fatal(err) } root.SetArgs([]string{cmd, "--help"}) if err := root.Execute(); err != nil { t.Fatalf("%s --help: %v", cmd, err) } out := buf.String() for _, want := range wants { if !strings.Contains(out, want) { t.Fatalf("%s help missing %q:\n%s", cmd, want, out) } } } var buf bytes.Buffer root, err := bonfire.NewRoot("summer", toolCommands(), &buf) if err != nil { t.Fatal(err) } root.SetArgs([]string{"make:model"}) err = root.Execute() if err == nil || !strings.Contains(err.Error(), "vendor.plugin") || !strings.Contains(err.Error(), "Name") { t.Fatalf("make:model missing args error = %v", err) } } func TestToolDoesNotImportExamplePlugins(t *testing.T) { roots := []string{".", filepath.Join("..", "..", "internal", "build")} fset := token.NewFileSet() for _, root := range roots { entries, err := os.ReadDir(root) if err != nil { t.Fatal(err) } for _, entry := range entries { name := entry.Name() if entry.IsDir() || !strings.HasSuffix(name, ".go") { continue } file, err := parser.ParseFile(fset, filepath.Join(root, name), nil, parser.ImportsOnly) if err != nil { t.Fatal(err) } for _, spec := range file.Imports { path, err := strconv.Unquote(spec.Path.Value) if err != nil { t.Fatal(err) } if strings.Contains(path, "examples/hello") || strings.Contains(path, "golem15/fonoteka") { t.Fatalf("%s imports %s", name, path) } } } } } func TestMakeCommandsViaCLI(t *testing.T) { dir := copyHelloApp(t) t.Chdir(dir) runSummer := func(args ...string) string { t.Helper() var buf bytes.Buffer root, err := bonfire.NewRoot("summer", toolCommands(), &buf) if err != nil { t.Fatal(err) } root.SetArgs(args) if err := root.Execute(); err != nil { t.Fatalf("%v: %v\n%s", args, err, buf.String()) } return buf.String() } out := runSummer("make:plugin", "golem15.demo") if !strings.Contains(out, filepath.Join("plugins", "demo")) { t.Fatalf("make:plugin output missing plugin dir:\n%s", out) } pluginDir := filepath.Join(dir, "plugins", "demo") if _, err := os.Stat(filepath.Join(pluginDir, "plugin.go")); err != nil { t.Fatal(err) } out = runSummer("make:model", "golem15.demo", "Album", "--no-migration") if !strings.Contains(out, filepath.Join("models", "album.go")) { t.Fatalf("make:model output missing album.go:\n%s", out) } if strings.Contains(out, "create_golem15_demo_albums") { t.Fatalf("--no-migration still printed a migration:\n%s", out) } out = runSummer("make:job", "golem15.demo", "Reindex") if !strings.Contains(out, filepath.Join("jobs", "reindex.go")) { t.Fatalf("make:job output missing reindex.go:\n%s", out) } t.Chdir(pluginDir) out = runSummer("make:command", "Purge") if !strings.Contains(out, filepath.Join("console", "purge.go")) { t.Fatalf("one-arg make:command output missing purge.go:\n%s", out) } pluginGo, err := os.ReadFile(filepath.Join(pluginDir, "plugin.go")) if err != nil { t.Fatal(err) } if !bytes.Contains(pluginGo, []byte("generatedJobs()")) { t.Fatal("scaffold plugin.go should already call generated accessors") } } func copyHelloApp(t *testing.T) string { t.Helper() src, err := filepath.Abs(filepath.Join("..", "..", "examples", "hello")) if err != nil { t.Fatal(err) } framework, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatal(err) } dst := t.TempDir() if err := filepath.WalkDir(src, func(path string, d os.DirEntry, err error) error { if err != nil { return err } rel, err := filepath.Rel(src, path) if err != nil { return err } if rel == "bin" || strings.HasPrefix(rel, "bin"+string(os.PathSeparator)) { if d.IsDir() { return filepath.SkipDir } return nil } target := filepath.Join(dst, rel) if d.IsDir() { return os.MkdirAll(target, 0o755) } data, err := os.ReadFile(path) if err != nil { return err } if filepath.Base(path) == "go.mod" { data = rewriteFrameworkReplace(data, framework) } if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { return err } return os.WriteFile(target, data, 0o644) }); err != nil { t.Fatal(err) } return dst } func rewriteFrameworkReplace(data []byte, framework string) []byte { const module = "git.golem15.com/golem15/summercms" lines := strings.Split(string(data), "\n") for i, line := range lines { trimmed := strings.TrimSpace(line) switch { case strings.HasPrefix(trimmed, "replace "+module+" =>"): indent := line[:len(line)-len(strings.TrimLeft(line, " \t"))] lines[i] = indent + "replace " + module + " => " + framework case strings.HasPrefix(trimmed, module+" =>"): indent := line[:len(line)-len(strings.TrimLeft(line, " \t"))] lines[i] = indent + module + " => " + framework } } return []byte(strings.Join(lines, "\n")) }