- relative links and anchors resolve against the renderer's heading IDs - summer and ./bin/<app> command names come from the real command constructors through docsite.Options.Commands; a nil set is a problem - consuming-application names fail in page sources and built outputs - go fences in docs/ pages need src=, callouts are NOTE, TIP or WARNING, docs/ headings are plain ASCII - gate gains --claude and self-test plants for each new rule
217 lines
6.2 KiB
Go
217 lines
6.2 KiB
Go
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, "docs/examples") || 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"))
|
|
}
|