149 lines
5.2 KiB
Go
149 lines
5.2 KiB
Go
package scaffold
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type moduleProfile struct{ database, storage, output, format string }
|
|
|
|
var moduleProfiles = []moduleProfile{
|
|
{"memory", "none", "stdout", "json"},
|
|
{"memory", "local", "file", "json"},
|
|
{"memory", "s3", "stdout", "text"},
|
|
{"memory", "r2", "stderr", "json"},
|
|
{"memory", "oss", "stderr", "text"},
|
|
{"mysql", "none", "file", "text"},
|
|
{"mysql", "local", "stdout", "json"},
|
|
{"mysql", "s3", "file", "text"},
|
|
{"mysql", "r2", "stderr", "text"},
|
|
{"mysql", "oss", "file", "json"},
|
|
}
|
|
|
|
func generateProfile(t *testing.T, p moduleProfile) string {
|
|
t.Helper()
|
|
project := filepath.Join(t.TempDir(), "project")
|
|
if err := Generate(Options{Dir: project, Module: "example.com/profile", FrameworkDir: frameworkRoot(t), Database: p.database, Storage: p.storage, LogOutput: p.output, LogFormat: p.format}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return project
|
|
}
|
|
func TestGenerateModuleProfiles(t *testing.T) {
|
|
for _, p := range moduleProfiles {
|
|
t.Run(p.database+"/"+p.storage, func(t *testing.T) {
|
|
project := generateProfile(t, p)
|
|
for _, name := range []string{"etc/config.yaml", "internal/settings/config.go", "MODULES.md", ".gitignore"} {
|
|
if _, err := os.Stat(filepath.Join(project, name)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
_, err := os.Stat(filepath.Join(project, "internal/handler/files.go"))
|
|
if p.storage == "none" && !os.IsNotExist(err) {
|
|
t.Fatal("unselected storage was emitted")
|
|
}
|
|
if p.storage != "none" && err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
module, err := os.ReadFile(filepath.Join(project, "go.mod"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pieces := strings.Split(string(module), "=> ")
|
|
if len(pieces) != 2 {
|
|
t.Fatal("local replace missing")
|
|
}
|
|
relative, err := strconv.Unquote(strings.TrimSpace(pieces[1]))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err = os.Stat(filepath.Join(project, relative, "go.mod")); err != nil {
|
|
t.Fatal("replacement does not resolve", err)
|
|
}
|
|
cfg, _ := os.ReadFile(filepath.Join(project, "etc/config.yaml"))
|
|
for _, line := range strings.Split(string(cfg), "\n") {
|
|
key, value, ok := strings.Cut(strings.TrimSpace(line), ":")
|
|
if ok && (key == "secretKey" || key == "filesToken" || key == "signingKey") && !strings.Contains(value, "${GO_OMNI_") {
|
|
t.Fatal("secret must be an explicit reference")
|
|
}
|
|
}
|
|
|
|
})
|
|
}
|
|
}
|
|
func TestModuleOptionsRejectBeforeWriting(t *testing.T) {
|
|
for _, options := range []Options{
|
|
{LogOutput: "invalid"}, {LogFormat: "xml"}, {Storage: "ftp"},
|
|
{Storage: "local", FrameworkVersion: "v0.6.0-preview"},
|
|
{FrameworkVersion: "v0.21.0-preview"},
|
|
{Module: "example.com/x\nrequire malicious v1.0.0"},
|
|
} {
|
|
options.Dir = filepath.Join(t.TempDir(), "project")
|
|
if options.Module == "" {
|
|
options.Module = "example.com/profile"
|
|
}
|
|
if err := Generate(options); err == nil {
|
|
t.Fatalf("invalid options accepted: %+v", options)
|
|
}
|
|
if _, err := os.Stat(options.Dir); !os.IsNotExist(err) {
|
|
t.Fatal("invalid options touched target")
|
|
}
|
|
}
|
|
}
|
|
func TestCanonicalPathBeforeDirectoryExists(t *testing.T) {
|
|
parent := t.TempDir()
|
|
alias := filepath.Join(t.TempDir(), "alias")
|
|
if err := os.Symlink(parent, alias); err != nil {
|
|
t.Skip(err)
|
|
}
|
|
path := filepath.Join(alias, "new", "nested")
|
|
if got, want := canonicalPath(path), filepath.Join(canonicalPath(parent), "new", "nested"); got != want {
|
|
t.Fatalf("%s != %s", got, want)
|
|
}
|
|
}
|
|
|
|
// Compile consuming modules, then run their generated tests. No provider account
|
|
// is used: cloud constructors are checked offline, file workflows use Local.
|
|
func TestModuleProfilesIntegration(t *testing.T) {
|
|
if os.Getenv("GO_OMNI_SCAFFOLD_INTEGRATION") != "1" {
|
|
t.Skip("set GO_OMNI_SCAFFOLD_INTEGRATION=1 for generated-profile tests")
|
|
}
|
|
for _, p := range moduleProfiles {
|
|
t.Run(p.database+"/"+p.storage, func(t *testing.T) {
|
|
project := generateProfile(t, p)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
|
defer cancel()
|
|
cmd := exec.CommandContext(ctx, "go", "test", "-mod=mod", "-race", "-p=2", "-count=1", "-timeout=60s", "./...")
|
|
cmd.Dir = project
|
|
cmd.Env = append(os.Environ(), "GOWORK=off", "GOMAXPROCS=2", "GO_OMNI_TEST_MYSQL_DSN=")
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("generated %s/%s: %v\n%s", p.database, p.storage, err, output)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestForceRejectsRemovedDependenciesWithoutTouchingCode(t *testing.T) {
|
|
project := generateProfile(t, moduleProfile{"mysql", "local", "file", "json"})
|
|
path := filepath.Join(project, "cmd/server/main.go")
|
|
before, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = Generate(Options{Dir: project, Module: "example.com/profile", FrameworkDir: frameworkRoot(t), Force: true, Storage: "none"}); err == nil {
|
|
t.Fatal("incompatible force overwrite accepted")
|
|
}
|
|
after, _ := os.ReadFile(path)
|
|
if string(after) != string(before) {
|
|
t.Fatal("failed profile switch changed code")
|
|
}
|
|
if err = Generate(Options{Dir: project, Module: "example.com/profile", FrameworkDir: frameworkRoot(t), Force: true, Database: "mysql", Storage: "local", LogOutput: "stdout"}); err != nil {
|
|
t.Fatal("compatible force overwrite rejected", err)
|
|
}
|
|
}
|