164 lines
5.8 KiB
Go
164 lines
5.8 KiB
Go
package apigen
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestFieldComposition(t *testing.T) {
|
|
source := readFile(t, "testdata/composition/service.api")
|
|
spec, err := parseText(t, source)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
first, err := Compile(spec)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := Compile(spec)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(first.Document, second.Document) || len(spec.Types[1].Fields) != 1 {
|
|
t.Fatal("composition mutates input")
|
|
}
|
|
for _, name := range []string{"EnterpriseRequest", "TenantRequest", "AdminRequest"} {
|
|
schema := first.Bindings[name].BodySchema()
|
|
raw, _ := json.Marshal(schema)
|
|
if !bytes.Contains(raw, []byte("Employee display name.")) || bytes.Contains(raw, []byte("SalaryFields")) {
|
|
t.Fatalf("flattened schema/comment missing: %s", raw)
|
|
}
|
|
}
|
|
cases := []struct{ name, decl, want string }{
|
|
{"unknown", "type Req {\n Missing\n}", "unknown composed"},
|
|
{"cycle", "type Req {\n Other\n}\ntype Other {\n Req\n}", "cycle"},
|
|
{"conflict", "type Base {\n Name string `json:\"name\"`\n}\ntype Req {\n Base\n Name string `json:\"other\"`\n}", "conflicting"},
|
|
{"wireConflict", "type Base {\n Name string `json:\"name\"`\n}\ntype Req {\n Base\n Other string `json:\"name\"`\n}", "conflicting"},
|
|
{"tagged", "type Req {\n Other `json:\"other\"`\n}", "cannot carry"},
|
|
{"diamond", "type Base {\n Name string `json:\"name\"`\n}\ntype Left {\n Base\n}\ntype Right {\n Base\n}\ntype Req {\n Left\n Right\n}", "conflicting"},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
s, e := parseText(t, tc.decl+"\nservice demo{\n@handler post\npost / (Req)\n}")
|
|
if e == nil {
|
|
_, e = Compile(s)
|
|
}
|
|
if e == nil || !strings.Contains(e.Error(), tc.want) {
|
|
t.Fatal(e)
|
|
}
|
|
})
|
|
}
|
|
// Imports can contain reusable definitions and retain the existing root policy.
|
|
dir := t.TempDir()
|
|
writeFile(t, filepath.Join(dir, "base.api"), "type Base {\n Name string `json:\"name\"`\n}\n")
|
|
writeFile(t, filepath.Join(dir, "service.api"), "import \"base.api\"\ntype Req {\n Base\n}\nservice demo{\n@handler post\npost / (Req)\n}")
|
|
if _, err := FormatProject(filepath.Join(dir, "service.api"), false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := FormatProject(filepath.Join(dir, "service.api"), true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryGenerationOptions(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "app")
|
|
if _, err := New(Options{Dir: dir, Module: "example.com/repository", FrameworkDir: frameworkRoot(t)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "internal/repository")); !os.IsNotExist(err) {
|
|
t.Fatal("default creates repository")
|
|
}
|
|
o := Options{Dir: dir, API: filepath.Join(dir, "api/service.api")}
|
|
writeFile(t, filepath.Join(dir, generatorConfigFile), `{"repository":"group","middleware":"factories"}`)
|
|
if _, err := Generate(o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repo := filepath.Join(dir, "internal/repository/greet/repository.go")
|
|
original := readFile(t, repo) + "\n// business SQL belongs here\n"
|
|
writeFile(t, repo, original)
|
|
writeFile(t, filepath.Join(dir, generatorConfigFile), `{"repository":"none","middleware":"factories"}`)
|
|
if _, err := Generate(o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
o.Check = true
|
|
if _, err := Generate(o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if readFile(t, repo) != original {
|
|
t.Fatal("repository removed or overwritten")
|
|
}
|
|
for _, raw := range []string{`{"repository":"oops"}`, `{"middleware":"oops"}`, `{"unknown":true}`, `{"repository":"none","repository":"group"}`} {
|
|
writeFile(t, filepath.Join(dir, generatorConfigFile), raw)
|
|
before := snapshot(t, dir)
|
|
if _, err := Generate(o); err == nil {
|
|
t.Fatal("accepted", raw)
|
|
}
|
|
if !sameFiles(before, snapshot(t, dir)) {
|
|
t.Fatal("invalid config wrote files")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCompositionExpansionBound(t *testing.T) {
|
|
var source strings.Builder
|
|
source.WriteString("type Base {\n Value string `json:\"value\"`\n}\n")
|
|
last := "Base"
|
|
for i := 0; i < 35; i++ {
|
|
name := fmt.Sprintf("Level%d", i)
|
|
fmt.Fprintf(&source, "type %s {\n %s\n}\n", name, last)
|
|
last = name
|
|
}
|
|
// Reverse order forces traversal instead of hitting pre-expanded definitions.
|
|
spec, err := parseText(t, source.String()+"service demo{\n@handler post\npost / ("+last+")\n}")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i, j := 0, len(spec.Types)-1; i < j; i, j = i+1, j-1 {
|
|
spec.Types[i], spec.Types[j] = spec.Types[j], spec.Types[i]
|
|
}
|
|
if _, err = Compile(spec); err == nil {
|
|
t.Fatal("unbounded composition accepted")
|
|
}
|
|
}
|
|
|
|
func TestGenerationConfigMissingAndLegacy(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "app")
|
|
if _, err := New(Options{Dir: dir, Module: "example.com/config", FrameworkDir: frameworkRoot(t)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
o := Options{Dir: dir, API: filepath.Join(dir, "api/service.api")}
|
|
if err := os.Remove(filepath.Join(dir, generatorConfigFile)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
before := snapshot(t, dir)
|
|
if _, err := Generate(o); err == nil || !strings.Contains(err.Error(), "restore") {
|
|
t.Fatal(err)
|
|
}
|
|
if !sameFiles(before, snapshot(t, dir)) {
|
|
t.Fatal("missing config changed project")
|
|
}
|
|
// A genuine old manifest has never tracked a generator config.
|
|
var old manifest
|
|
if err := json.Unmarshal([]byte(readFile(t, filepath.Join(dir, manifestPath))), &old); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
old.GeneratorConfig = false
|
|
raw, _ := json.Marshal(old)
|
|
writeFile(t, filepath.Join(dir, manifestPath), string(raw))
|
|
if _, err := Generate(o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg, exists, err := readGeneratorConfig(dir)
|
|
if err != nil || !exists || cfg.Middleware != "handlers" || cfg.Repository != "none" {
|
|
t.Fatal(cfg, exists, err)
|
|
}
|
|
if !strings.Contains(readFile(t, filepath.Join(dir, "internal/svc/runtimeGen.go")), "middleware.Defaults()") {
|
|
t.Fatal("legacy registration replaced")
|
|
}
|
|
}
|