Files
go-omni/cmd/go-omni/scaffold/scaffold_test.go

177 lines
6.2 KiB
Go

package scaffold
import (
"os"
"path/filepath"
"runtime/debug"
"strconv"
"strings"
"testing"
)
func TestGenerateCreatesServiceLayout(t *testing.T) {
directory := t.TempDir()
project := filepath.Join(directory, "billing")
if err := Generate(Options{Dir: project, Module: "example.com/billing", FrameworkDir: frameworkRoot(t)}); err != nil {
t.Fatalf("generate project: %v", err)
}
for _, path := range []string{
"go.mod",
"cmd/server/main.go",
"internal/model/user.go",
"internal/repository/memory.go",
"internal/servicecontext/context.go",
"internal/service/user.go",
"internal/handler/user.go",
} {
if _, err := os.Stat(filepath.Join(project, filepath.FromSlash(path))); err != nil {
t.Fatalf("expected generated file %s: %v", path, err)
}
}
content, err := os.ReadFile(filepath.Join(project, "go.mod"))
if err != nil {
t.Fatalf("read generated go.mod: %v", err)
}
if !strings.Contains(string(content), "replace git.yuntuo.hk/k2safe/go-omni =>") {
t.Fatalf("expected local framework replace directive: %s", content)
}
mainContent, err := os.ReadFile(filepath.Join(project, "cmd/server/main.go"))
if err != nil {
t.Fatalf("read generated server: %v", err)
}
for _, expected := range []string{"config.LoadFromEnv(\"\")", "logx.SetDefault(logger)", "omni.WithConfig(settings)", "servicecontext.New(ctx, logger)", "services.Health.ReadinessHandler()", "application.RunContext(ctx, settings.Address)"} {
if !strings.Contains(string(mainContent), expected) {
t.Fatalf("generated server missing %q: %s", expected, mainContent)
}
}
}
func TestGenerateRejectsNonEmptyDirectory(t *testing.T) {
directory := t.TempDir()
if err := os.WriteFile(filepath.Join(directory, "existing.txt"), []byte("keep"), 0o644); err != nil {
t.Fatalf("seed directory: %v", err)
}
if err := Generate(Options{Dir: directory, Module: "example.com/demo"}); err == nil {
t.Fatal("expected non-empty directory error")
}
}
func TestGenerateMySQLLayout(t *testing.T) {
project := filepath.Join(t.TempDir(), "billing")
if err := Generate(Options{Dir: project, Module: "example.com/billing", Database: "mysql", FrameworkDir: frameworkRoot(t)}); err != nil {
t.Fatal(err)
}
for _, path := range []string{"schema.sql", "internal/repository/mysql.go", "internal/repository/users.go", "internal/model/errors.go", "internal/servicecontext/databaseIntegration_test.go"} {
if _, err := os.Stat(filepath.Join(project, path)); err != nil {
t.Fatal(err)
}
}
content, err := os.ReadFile(filepath.Join(project, "go.mod"))
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(content), "github.com/go-sql-driver/mysql v1.9.3") {
t.Fatal("mysql driver version is not pinned")
}
}
func TestGenerateRejectsUnknownDatabaseBeforeWriting(t *testing.T) {
project := filepath.Join(t.TempDir(), "billing")
if err := Generate(Options{Dir: project, Module: "example.com/billing", Database: "unknown"}); err == nil {
t.Fatal("accepted unknown backend")
}
if _, err := os.Stat(project); !os.IsNotExist(err) {
t.Fatalf("invalid backend created files: %v", err)
}
}
func TestFrameworkReplaceUsesCanonicalPaths(t *testing.T) {
root := frameworkRoot(t)
project := filepath.Join(t.TempDir(), "generated")
if err := os.MkdirAll(project, 0o755); err != nil {
t.Fatal(err)
}
replace := frameworkReplace(project, root)
resolvedProject, err := filepath.EvalSymlinks(project)
if err != nil {
t.Fatal(err)
}
resolvedRoot, err := filepath.EvalSymlinks(root)
if err != nil {
t.Fatal(err)
}
expected, err := filepath.Rel(resolvedProject, resolvedRoot)
if err != nil {
t.Fatal(err)
}
if replace != strconv.Quote(filepath.ToSlash(expected)) {
t.Fatalf("expected canonical replace %q, got %q", filepath.ToSlash(expected), replace)
}
}
func TestGenerateRemoteProjectPinsVersion(t *testing.T) {
for _, version := range []string{"v0.1.0", "v1.2.3-rc.1", "v0.0.0-20260920140624-8b19aa79eea0"} {
t.Run(version, func(t *testing.T) {
project := filepath.Join(t.TempDir(), "billing")
if err := Generate(Options{Dir: project, Module: "example.com/billing", FrameworkVersion: version}); err != nil {
t.Fatal(err)
}
content, err := os.ReadFile(filepath.Join(project, "go.mod"))
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(content), "require "+frameworkModule+" "+version+"\n") || strings.Contains(string(content), "replace ") {
t.Fatalf("expected pinned remote dependency without replace: %s", content)
}
})
}
}
func TestGenerateRejectsInvalidVersionBeforeWriting(t *testing.T) {
for _, version := range []string{"main", "latest", "v1.2", "v01.2.3", "v2.0.0", "v0.1.0-01", "v0.1.0\nreplace bad => ./bad"} {
project := filepath.Join(t.TempDir(), "billing")
if err := Generate(Options{Dir: project, Module: "example.com/billing", FrameworkVersion: version}); err == nil {
t.Fatalf("accepted invalid version %q", version)
}
if _, err := os.Stat(project); !os.IsNotExist(err) {
t.Fatalf("invalid version %q created a project: %v", version, err)
}
}
}
func TestFrameworkVersionUsesInstalledModuleMetadata(t *testing.T) {
for _, test := range []struct {
info *debug.BuildInfo
want string
}{
{nil, defaultFrameworkVersion},
{&debug.BuildInfo{Main: debug.Module{Path: frameworkModule, Version: "(devel)"}}, defaultFrameworkVersion},
{&debug.BuildInfo{Main: debug.Module{Path: frameworkModule, Version: "v0.1.0+dirty"}}, defaultFrameworkVersion},
{&debug.BuildInfo{Main: debug.Module{Path: "example.com/unrelated", Version: "v1.2.3"}}, defaultFrameworkVersion},
{&debug.BuildInfo{Main: debug.Module{Path: frameworkModule, Version: "v0.9.2", Sum: "h1:test"}}, "v0.9.2"},
{&debug.BuildInfo{Main: debug.Module{Path: frameworkModule, Version: "v0.0.0-20260920140624-8b19aa79eea0", Sum: "h1:test"}}, "v0.0.0-20260920140624-8b19aa79eea0"},
} {
if got := frameworkVersion(test.info); got != test.want {
t.Fatalf("version = %q, want %q", got, test.want)
}
}
}
func frameworkRoot(t *testing.T) string {
t.Helper()
directory, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
for {
if _, err := os.Stat(filepath.Join(directory, "go.mod")); err == nil {
return directory
}
parent := filepath.Dir(directory)
if parent == directory {
t.Fatal("framework root not found")
}
directory = parent
}
}