138 lines
5.0 KiB
Go
138 lines
5.0 KiB
Go
package scaffold
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func moduleProject(t *testing.T) string {
|
|
t.Helper()
|
|
root := filepath.Join(t.TempDir(), "billing")
|
|
if err := Generate(Options{Dir: root, Module: "example.com/billing", FrameworkDir: frameworkRoot(t)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return root
|
|
}
|
|
|
|
func TestAddModulePreservesProjectAndRejectsOverwrite(t *testing.T) {
|
|
root := moduleProject(t)
|
|
manifest, _ := os.ReadFile(filepath.Join(root, "go.mod"))
|
|
main, _ := os.ReadFile(filepath.Join(root, "cmd/server/main.go"))
|
|
if err := AddModule(root, "orderItems"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, path := range []string{"module.go", "model/model.go", "handler/handler.go", "service/service.go", "repository/repository.go", "README.md"} {
|
|
if _, err := os.Stat(filepath.Join(root, "internal/orderItems", path)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
marker := filepath.Join(root, "internal/orderItems/keep.txt")
|
|
if err := os.WriteFile(marker, []byte("keep"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := AddModule(root, "orderItems"); err == nil {
|
|
t.Fatal("overwrote existing module")
|
|
}
|
|
if got, _ := os.ReadFile(marker); string(got) != "keep" {
|
|
t.Fatal("existing file changed")
|
|
}
|
|
if got, _ := os.ReadFile(filepath.Join(root, "go.mod")); string(got) != string(manifest) {
|
|
t.Fatal("go.mod changed")
|
|
}
|
|
if got, _ := os.ReadFile(filepath.Join(root, "cmd/server/main.go")); string(got) != string(main) {
|
|
t.Fatal("main changed")
|
|
}
|
|
for _, name := range []string{"../escape", "Order", "order_items", "func", "main", "init", "internal", "", strings.Repeat("a", 49)} {
|
|
if err := AddModule(root, name); err == nil {
|
|
t.Fatalf("accepted %q", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAddModuleRejectsForeignProjectAndSymlink(t *testing.T) {
|
|
root := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example.com/foreign\ngo 1.22\n"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := AddModule(root, "orders"); err == nil {
|
|
t.Fatal("accepted foreign project")
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "internal")); !os.IsNotExist(err) {
|
|
t.Fatal("created files before validation")
|
|
}
|
|
root = moduleProject(t)
|
|
if err := os.Rename(filepath.Join(root, "internal"), filepath.Join(root, "original")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Symlink(filepath.Join(root, "original"), filepath.Join(root, "internal")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := AddModule(root, "orders"); err == nil {
|
|
t.Fatal("accepted symlink parent")
|
|
}
|
|
}
|
|
|
|
// Opt-in because this compiles a separate consuming module and may need module
|
|
// downloads. The Linux feature acceptance runner enables it explicitly.
|
|
func TestAddModuleIntegration(t *testing.T) {
|
|
if os.Getenv("GO_OMNI_SCAFFOLD_INTEGRATION") != "1" {
|
|
t.Skip("set GO_OMNI_SCAFFOLD_INTEGRATION=1 for generated-project CRUD")
|
|
}
|
|
root := moduleProject(t)
|
|
if err := AddModule(root, "orderItems"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(root, "internal/orderItems/module_test.go"), []byte(generatedModuleTest), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
|
defer cancel()
|
|
cmd := exec.CommandContext(ctx, "go", "test", "-mod=mod", "-race", "./...")
|
|
cmd.Dir = root
|
|
cmd.Env = append(os.Environ(), "GOWORK=off")
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("generated project: %v\n%s", err, output)
|
|
}
|
|
}
|
|
|
|
const generatedModuleTest = `package orderItems_test
|
|
import (
|
|
"context"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
omni "git.yuntuo.hk/k2safe/go-omni"
|
|
"example.com/billing/internal/orderItems"
|
|
)
|
|
func TestGeneratedCRUD(t *testing.T) {
|
|
app:=omni.New()
|
|
repo:=orderItems.NewMemory()
|
|
orderItems.Register(app,repo,func(next omni.Handler)omni.Handler{return func(c *omni.Context)error{
|
|
if c.Header("Authorization")!="Bearer demo" {return omni.Unauthorized("DENIED","denied")};return next(c)
|
|
}})
|
|
request:=func(method,path,body string,authorized bool)*httptest.ResponseRecorder{
|
|
r:=httptest.NewRequest(method,path,strings.NewReader(body));r.Header.Set("Content-Type","application/json")
|
|
if authorized {r.Header.Set("Authorization","Bearer demo")}
|
|
w:=httptest.NewRecorder();app.Engine().ServeHTTP(w,r);return w
|
|
}
|
|
if w:=request("POST","/orderItems","{\"name\":\"first\"}",false);w.Code!=401 {t.Fatal(w.Code)}
|
|
cases:=[]struct{method,path,body string;status int;contains string}{
|
|
{"POST","/orderItems","{\"name\":\" first \"}",201,"createdAt"},
|
|
{"GET","/orderItems/1","",200,"first"},
|
|
{"PUT","/orderItems/1","{\"name\":\"changed\"}",200,"changed"},
|
|
{"GET","/orderItems","",200,"changed"},
|
|
{"POST","/orderItems","{\"name\":\" \"}",400,"INVALID_NAME"},
|
|
{"DELETE","/orderItems/1","",204,""},
|
|
{"GET","/orderItems/1","",404,"NOT_FOUND"},
|
|
}
|
|
for _,tc:=range cases{w:=request(tc.method,tc.path,tc.body,true);if w.Code!=tc.status||!strings.Contains(w.Body.String(),tc.contains){t.Fatalf("%s %s: %d %s",tc.method,tc.path,w.Code,w.Body.String())}}
|
|
ctx,cancel:=context.WithCancel(context.Background());cancel()
|
|
if _,err:=repo.List(ctx);err!=context.Canceled {t.Fatal(err)}
|
|
}
|
|
`
|