feat: add HTTP migration bridges and extensible API binding
This commit is contained in:
161
cmd/go-omni/apigen/compatibility_test.go
Normal file
161
cmd/go-omni/apigen/compatibility_test.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package apigen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCompatibilityContract(t *testing.T) {
|
||||
s, err := Parse(filepath.Join(frameworkRoot(t), "examples/contracts/compatibility.api"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, err := Compile(s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(s.Scalars) != 1 || len(s.Routes) != 6 {
|
||||
t.Fatal("incomplete contract")
|
||||
}
|
||||
if !strings.Contains(string(c.Document), `"anyOf"`) || !strings.Contains(string(c.Document), `"multipart/form-data"`) {
|
||||
t.Fatal("missing schemas")
|
||||
}
|
||||
for _, change := range [][2]string{{"jsonInput: string | int64", "jsonInput: mystery"}, {"text: true", "text: false"}, {"mode: multipart", "mode: invalid"}, {"maxBytes: 128", "maxBytes: 0"}, {"goType: ID", "goType: id"}, {"Data any", "Data interface{ Method() }"}} {
|
||||
source := readFile(t, filepath.Join(frameworkRoot(t), "examples/contracts/compatibility.api"))
|
||||
spec, err := parseText(t, strings.Replace(source, change[0], change[1], 1))
|
||||
if err == nil {
|
||||
_, err = Compile(spec)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("accepted %s", change[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestCompatibilityConsumer(t *testing.T) {
|
||||
if os.Getenv("GO_OMNI_SCAFFOLD_INTEGRATION") == "" {
|
||||
t.Skip("consumer integration")
|
||||
}
|
||||
root := frameworkRoot(t)
|
||||
dir := filepath.Join(t.TempDir(), "consumer")
|
||||
o := Options{API: filepath.Join(root, "examples/contracts/compatibility.api"), Dir: dir, Module: "example.com/compatibility", FrameworkDir: root}
|
||||
releaseCLI := os.Getenv("GO_OMNI_RELEASE_CLI")
|
||||
generate := func(check bool) {
|
||||
t.Helper()
|
||||
if releaseCLI == "" {
|
||||
options := o
|
||||
options.Check = check
|
||||
if _, err := Generate(options); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
args := []string{"api", "generate", "--api", o.API, "--dir", dir, "--module", o.Module}
|
||||
if check {
|
||||
args = append(args, "--check")
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, releaseCLI, args...)
|
||||
cmd.Dir = filepath.Dir(dir)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("released CLI: %v\n%s", err, output)
|
||||
}
|
||||
}
|
||||
generate(false)
|
||||
files := map[string]string{
|
||||
"internal/service/demo/echo.go": "echo.go",
|
||||
"internal/handler/demo/customBind.go": "hooks.go",
|
||||
"internal/handler/files/downloadManual.go": "download.go",
|
||||
"consumer_test.go": "consumer_test.go",
|
||||
}
|
||||
// The fixture implements related methods together; remove only its generated
|
||||
// one-time stubs, then fill every stub so regeneration can preserve all files.
|
||||
for _, name := range []string{"internal/service/demo/item.go", "internal/service/demo/custom.go"} {
|
||||
writeFile(t, filepath.Join(dir, name), "package demo\n")
|
||||
}
|
||||
for _, name := range []string{"internal/handler/demo/uploadManual.go", "internal/handler/demo/callbackManual.go"} {
|
||||
writeFile(t, filepath.Join(dir, name), "package demo\n")
|
||||
}
|
||||
for target, source := range files {
|
||||
writeFile(t, filepath.Join(dir, target), readFile(t, filepath.Join("testdata/compatibility", source)))
|
||||
}
|
||||
before := map[string]string{}
|
||||
for target := range files {
|
||||
before[target] = readFile(t, filepath.Join(dir, target))
|
||||
}
|
||||
generate(false)
|
||||
for target, content := range before {
|
||||
if readFile(t, filepath.Join(dir, target)) != content {
|
||||
t.Fatalf("overwrote %s", target)
|
||||
}
|
||||
}
|
||||
generate(true)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, "go", "test", "-mod=mod", "-race", "-count=1", "./...")
|
||||
cmd.Dir = dir
|
||||
cmd.Env = append(os.Environ(), "GOWORK=off")
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("consumer: %v\n%s", err, output)
|
||||
}
|
||||
if releaseCLI != "" {
|
||||
cmd = exec.CommandContext(ctx, "go", "list", "-m", "-json", frameworkModule)
|
||||
cmd.Dir = dir
|
||||
cmd.Env = append(os.Environ(), "GOWORK=off")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("module: %v\n%s", err, output)
|
||||
}
|
||||
var module struct {
|
||||
Version, Sum string
|
||||
Replace any
|
||||
}
|
||||
if err = json.Unmarshal(output, &module); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if module.Version != os.Getenv("GO_OMNI_RELEASE_VERSION") || module.Replace != nil || module.Sum == "" {
|
||||
t.Fatalf("not a fixed remote dependency: %+v", module)
|
||||
}
|
||||
t.Logf("remote framework %s checksum %s; no local replace", module.Version, module.Sum)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNestedDynamicAliases(t *testing.T) {
|
||||
source := readFile(t, filepath.Join(frameworkRoot(t), "examples/contracts/compatibility.api"))
|
||||
for _, typ := range []string{"[]interface{}", "map[string]interface{}", "map[string][]interface{}"} {
|
||||
s, err := parseText(t, strings.Replace(source, "Data any", "Data "+typ, 1))
|
||||
if err == nil {
|
||||
_, err = Compile(s)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", typ, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestSwitchFromManualRequiresExplicitMigration(t *testing.T) {
|
||||
root := frameworkRoot(t)
|
||||
o := Options{API: filepath.Join(root, "examples/contracts/compatibility.api"), Dir: filepath.Join(t.TempDir(), "consumer"), Module: "example.com/compatibility", FrameworkDir: root}
|
||||
if _, err := Generate(o); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The hand-written source may contain business code. Never delete it to make
|
||||
// the newly automatic handler compile; fail before touching generated files.
|
||||
o.API = filepath.Join(o.Dir, "api/compatibility.api")
|
||||
source := readFile(t, o.API)
|
||||
source = strings.Replace(source, "@handlerMode manual\n @produces \"application/octet-stream\"", "", 1)
|
||||
writeFile(t, o.API, source)
|
||||
before := snapshot(t, o.Dir)
|
||||
if _, err := Generate(o); err == nil || !strings.Contains(err.Error(), "migrate and remove") {
|
||||
t.Fatalf("unsafe mode switch: %v", err)
|
||||
}
|
||||
if !sameFiles(before, snapshot(t, o.Dir)) {
|
||||
t.Fatal("failed mode switch mutated project")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user