173 lines
6.6 KiB
Go
173 lines
6.6 KiB
Go
package apigen
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"git.yuntuo.hk/k2safe/go-omni/cmd/go-omni/projectgen"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestWorkspaceSharedGoCatalog(t *testing.T) {
|
|
root := t.TempDir()
|
|
writeFile(t, filepath.Join(root, "go.mod"), fmt.Sprintf("module example.com/catalogs\n\ngo 1.22.0\nrequire %s v0.15.0-preview\nreplace %s => %s\n", frameworkModule, frameworkModule, strconvQuote(frameworkRoot(t))))
|
|
writeFile(t, filepath.Join(root, "internal/apierrors/catalog.go"), `package apierrors
|
|
import errors "git.yuntuo.hk/k2safe/go-omni/errors"
|
|
func Catalog() errors.Catalog {return errors.Catalog{DefaultStatus:400,Definitions:map[string]errors.Definition{
|
|
"INVALID":{Code:"INVALID",Message:"invalid input"},"MISSING":{Status:404,Code:"MISSING",Message:"missing"},
|
|
}}}
|
|
`)
|
|
if err := os.MkdirAll(filepath.Join(root, "contracts"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, name := range []string{"api", "id"} {
|
|
dir := filepath.Join(root, "services", name)
|
|
if _, err := New(Options{Dir: dir, Module: "example.com/" + name, FrameworkDir: frameworkRoot(t)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
writeFile(t, filepath.Join(dir, "api/omni.imports.json"), `{"roots":{"common":{"path":"../../../contracts"}},"errors":{"source":"@common/errors.json","include":["INVALID"],"routes":{"GET /api/greet/:name":["MISSING"]}}}`)
|
|
}
|
|
if _, err := projectgen.New(projectgen.Options{Dir: filepath.Join(root, "services/program"), Module: "example.com/program", FrameworkDir: frameworkRoot(t)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
workspace := filepath.Join(root, "omni.workspace.json")
|
|
writeFile(t, workspace, `{"errorCatalogs":[{"dir":".","package":"example.com/catalogs/internal/apierrors","function":"Catalog","output":"contracts/errors.json"}],"services":[{"dir":"services/api"},{"dir":"services/id"},{"dir":"services/program"}]}`)
|
|
if err := Workspace(workspace, "generate", io.Discard); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := Workspace(workspace, "format", io.Discard); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := Workspace(workspace, "generate", io.Discard); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
before := snapshot(t, root)
|
|
if err := Workspace(workspace, "check", io.Discard); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !sameFiles(before, snapshot(t, root)) {
|
|
t.Fatal("check wrote project files")
|
|
}
|
|
for _, name := range []string{"api", "id"} {
|
|
entry := filepath.Join(root, "services", name, "api/service.api")
|
|
spec, err := Parse(entry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c, err := Compile(spec)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
op := operation(t, document(t, c), "/api/greet/{name}", "get")
|
|
if op["responses"].(map[string]any)["404"] == nil || len(spec.Errors) != 0 || len(spec.Routes[0].Errors) != 0 {
|
|
t.Fatal("selection changed runtime catalog")
|
|
}
|
|
if _, err = os.Stat(filepath.Join(filepath.Dir(entry), catalogDocumentFile)); !os.IsNotExist(err) {
|
|
t.Fatal("shared catalog was copied")
|
|
}
|
|
}
|
|
shared := filepath.Join(root, "contracts/errors.json")
|
|
data := readFile(t, shared)
|
|
writeFile(t, shared, strings.Replace(data, "invalid input", "stale", 1))
|
|
before = snapshot(t, root)
|
|
if err := Workspace(workspace, "check", io.Discard); err == nil || !strings.Contains(err.Error(), "catalog drift") {
|
|
t.Fatal(err)
|
|
}
|
|
if !sameFiles(before, snapshot(t, root)) {
|
|
t.Fatal("drift check wrote files")
|
|
}
|
|
if err := Workspace(workspace, "generate", io.Discard); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := readFile(t, shared); got != data {
|
|
t.Fatal("catalog not refreshed")
|
|
}
|
|
// Failed provider/preflight may not update even the shared metadata.
|
|
source := filepath.Join(root, "internal/apierrors/catalog.go")
|
|
writeFile(t, source, readFile(t, source)+"\ninvalid go source")
|
|
before = snapshot(t, root)
|
|
if err := Workspace(workspace, "generate", io.Discard); err == nil {
|
|
t.Fatal("provider failure ignored")
|
|
}
|
|
if !sameFiles(before, snapshot(t, root)) {
|
|
t.Fatal("provider failure mutated project")
|
|
}
|
|
}
|
|
|
|
func TestDocumentSelectionBoundaries(t *testing.T) {
|
|
root := t.TempDir()
|
|
local := filepath.Join(root, "api")
|
|
shared := filepath.Join(root, "shared")
|
|
entry := filepath.Join(local, "service.api")
|
|
writeFile(t, entry, "service demo {\n@handler list\nget /list returns ([]string)\n}\n")
|
|
writeFile(t, filepath.Join(shared, "errors.json"), `{"defaultStatus":400,"definitions":{"INVALID":{"code":400002,"message":"invalid"}}}`)
|
|
for _, cfg := range []string{
|
|
`{"source":"@common/errors.json","include":["UNKNOWN"]}`,
|
|
`{"source":"@common/errors.json","routes":{"GET /absent":["INVALID"]}}`,
|
|
`{"source":"@common/../api/service.api"}`,
|
|
`{"source":"../shared/errors.json"}`,
|
|
} {
|
|
writeFile(t, filepath.Join(local, importsFile), `{"roots":{"common":{"path":"../shared"}},"errors":`+cfg+`}`)
|
|
if _, err := Parse(entry); err == nil {
|
|
t.Fatal("bad selection accepted", cfg)
|
|
}
|
|
}
|
|
writeFile(t, filepath.Join(local, importsFile), `{"roots":{"common":{"path":"../shared"}},"errors":{"source":"@common/errors.json","include":[]}}`)
|
|
spec, err := Parse(entry)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(documentRouteCatalog(spec, spec.Routes[0]).Definitions) != 0 {
|
|
t.Fatal("empty include ignored")
|
|
}
|
|
seeded, err := sharedSeeds(spec, filepath.Join(root, "generated"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var cfg sharedConfig
|
|
if err = json.Unmarshal(seeded, &cfg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Errors.Include == nil || !bytes.Contains(seeded, []byte(`"include": []`)) {
|
|
t.Fatal("empty selection lost during relocation")
|
|
}
|
|
}
|
|
|
|
func TestWorkspaceCatalogPathsAndProviderValidation(t *testing.T) {
|
|
root := t.TempDir()
|
|
writeFile(t, filepath.Join(root, "go.mod"), "module example.com/test\ngo 1.24.0\n")
|
|
good := WorkspaceCatalog{Dir: ".", Package: "example.com/test/internal/errors", Function: "Catalog", Output: "errors.json"}
|
|
cases := []WorkspaceCatalog{}
|
|
for _, change := range []func(*WorkspaceCatalog){
|
|
func(c *WorkspaceCatalog) { c.Dir = "../outside" },
|
|
func(c *WorkspaceCatalog) { c.Output = "../outside.json" },
|
|
func(c *WorkspaceCatalog) { c.Package = "example.com/pkg;bad" },
|
|
func(c *WorkspaceCatalog) { c.Function = "Catalog()" },
|
|
func(c *WorkspaceCatalog) { c.Output = "go.mod" },
|
|
} {
|
|
c := good
|
|
change(&c)
|
|
cases = append(cases, c)
|
|
}
|
|
for _, c := range cases {
|
|
if _, err := exportWorkspaceCatalog(root, c); err == nil {
|
|
t.Fatal("bad provider accepted", c)
|
|
}
|
|
}
|
|
outside := filepath.Join(t.TempDir(), "errors.json")
|
|
writeFile(t, outside, `{}`)
|
|
if err := os.Symlink(outside, filepath.Join(root, "errors.json")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := prepareWorkspaceCatalogs(root, []WorkspaceCatalog{good}, false); err == nil {
|
|
t.Fatal("symlink output accepted")
|
|
}
|
|
if readFile(t, outside) != `{}` {
|
|
t.Fatal("outside file modified")
|
|
}
|
|
}
|