package apigen import ( "context" "encoding/json" "os" "os/exec" "path/filepath" "strings" "testing" "time" ) func TestGeneratedConfigurationConsumer(t *testing.T) { if os.Getenv("GO_OMNI_SCAFFOLD_INTEGRATION") != "1" { t.Skip("set GO_OMNI_SCAFFOLD_INTEGRATION=1") } dir := filepath.Join(t.TempDir(), "app") api := filepath.Join(t.TempDir(), "service.api") writeFile(t, api, "service configuration {\n@handler ping\nget /ping\n}\n") o := Options{API: api, Dir: dir, Module: "example.com/configuration", FrameworkDir: frameworkRoot(t)} if err := generateManagedTestProject(t, o); err != nil { t.Fatal(err) } main := readFile(t, filepath.Join(dir, "main.go")) // The business only changes fields/defaults/validation, not the loading flow. writeFile(t, filepath.Join(dir, "internal/config/config.go"), `package config import("git.yuntuo.hk/k2safe/go-omni/config";"git.yuntuo.hk/k2safe/go-omni/logx") type Config struct { Server config.Config `+"`json:\"server\"`"+`;Limit int `+"`json:\"limit\"`"+` } func Default()Config{ c:=config.Default();c.Log.File="./var/log/app.log";c.Log.Rotation=logx.RotationConfig{Enabled:true,MaxSizeMB:10};return Config{Server:c,Limit:10} } func(c Config)Validate()error{if err:=c.Server.Validate();err!=nil{return config.AtField("server",err)};if c.Limit<0{return config.Invalid("limit","must not be negative")};return nil} `) writeFile(t, filepath.Join(dir, "internal/config/loader.go"), `package config import omniConfig "git.yuntuo.hk/k2safe/go-omni/config" func NewLoader()(*omniConfig.Loader,error){return omniConfig.NewLoader(omniConfig.WithDecoder("custom",omniConfig.JSONDecoder{}))} `) svc := filepath.Join(dir, "internal/svc/serviceContext.go") writeFile(t, svc, strings.Replace(readFile(t, svc), "return nil", "panic(\"dependencies must not initialize on invalid config\")", 1)) writeFile(t, filepath.Join(dir, "consumer_test.go"), readFile(t, "testdata/configuration/consumer_test.go")) runConsumerGo(t, dir, "mod", "tidy") runConsumerGo(t, dir, "test", "-race", "-count=1", "./...") runConsumerGo(t, dir, "build", ".") o.API = filepath.Join(dir, "api/service.api") before := snapshot(t, dir) for _, mode := range []string{"dry", "generate", "check"} { o.DryRun = mode == "dry" o.Check = mode == "check" if err := generateManagedTestProject(t, o); err != nil { t.Fatal(err) } } if !sameFiles(before, snapshot(t, dir)) { t.Fatal("business files overwritten") } if main != readFile(t, filepath.Join(dir, "main.go")) { t.Fatal("main changed") } load := filepath.Join(dir, "internal/config/loadGen.go") original := readFile(t, load) writeFile(t, load, original+"\n// drift\n") if err := generateManagedTestProject(t, o); err == nil { t.Fatal("loadGen drift accepted") } writeFile(t, load, original) if os.Getenv("GO_OMNI_RELEASE_CLI") != "" { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "go", "list", "-m", "-json", frameworkModule) cmd.Dir = dir cmd.Env = append(os.Environ(), "GOWORK=off") data, err := cmd.CombinedOutput() if err != nil { t.Fatal(err, string(data)) } var dependency struct { Version, Sum string Replace any } if err = json.Unmarshal(data, &dependency); err != nil { t.Fatal(err) } if dependency.Version != os.Getenv("GO_OMNI_RELEASE_VERSION") || dependency.Replace != nil || dependency.Sum == "" { t.Fatal("not a remote consumer") } t.Logf("remote framework %s checksum %s; no local replace", dependency.Version, dependency.Sum) } } func TestConfigLoadMigrationGuard(t *testing.T) { dir := filepath.Join(t.TempDir(), "app") o := Options{Dir: dir, Module: "example.com/upgrade", FrameworkDir: frameworkRoot(t)} if _, err := New(o); err != nil { t.Fatal(err) } o.API = filepath.Join(dir, "api/service.api") matches, err := filepath.Glob(filepath.Join(dir, "api/*.api")) if err != nil || len(matches) != 1 { t.Fatal(matches, err) } o.API = matches[0] writeFile(t, filepath.Join(dir, "internal/config/legacy.go"), "package config\nfunc Load(path string)(Config,error){return Default(),nil}\n") before := snapshot(t, dir) for _, mode := range []string{"dry", "generate", "check"} { o.DryRun = mode == "dry" o.Check = mode == "check" _, err := Generate(o) if err == nil || !strings.Contains(err.Error(), "CONFIG.md") { t.Fatal("missing migration diagnostic", err) } if !sameFiles(before, snapshot(t, dir)) { t.Fatal("migration guard changed files") } } } func TestRemovedEnvironmentTagsRejectBeforeGeneration(t *testing.T) { dir := filepath.Join(t.TempDir(), "app") o := Options{Dir: dir, Module: "example.com/migrate", FrameworkDir: frameworkRoot(t)} if _, err := New(o); err != nil { t.Fatal(err) } o.API = filepath.Join(dir, "api/service.api") path := filepath.Join(dir, "internal/config/config.go") source := readFile(t, path) writeFile(t, path, strings.Replace(source, "json:\"server\"", "json:\"server\" env:\"OLD_SERVER\"", 1)) before := snapshot(t, dir) for _, mode := range []string{"dry", "generate", "check"} { o.DryRun = mode == "dry" o.Check = mode == "check" if _, err := Generate(o); err == nil || !strings.Contains(err.Error(), "env/envDefault") { t.Fatal(err) } if !sameFiles(before, snapshot(t, dir)) { t.Fatal("migration rejection wrote files") } } }