package main import ( "bytes" "encoding/json" "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "testing" ) func TestGeneratedConsumerAndArtifactBoundary(t *testing.T) { root, err := filepath.Abs("../..") if err != nil { t.Fatal(err) } dir, err := filepath.EvalSymlinks(t.TempDir()) if err != nil { t.Fatal(err) } execute := func(cwd, name string, args ...string) []byte { t.Helper() cmd := exec.CommandContext(t.Context(), name, args...) cmd.Dir = cwd out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("%s %v: %v\n%s", name, args, err, out) } return out } tool := filepath.Join(dir, "corestack-goose") execute(root, "go", "build", "-o", tool, "./cmd/corestack-goose") migrations := filepath.Join(dir, "migrations") if err := os.Mkdir(migrations, 0o700); err != nil { t.Fatal(err) } osName := "windows" if runtime.GOOS == "windows" { osName = "linux" } for i, tc := range []struct{ name, kind string }{{"initial", "sql"}, {"test", "go"}, {"create_test", "go"}, {osName, "go"}} { if i%2 == 0 { execute(migrations, tool, "create", tc.name, tc.kind) execute(migrations, tool, "fix") } else { execute(dir, tool, "create", "--dir", migrations, tc.name, tc.kind) execute(dir, tool, "fix", "--dir", migrations) } } execute(dir, tool, "fix", "--dir", migrations) entries, err := os.ReadDir(migrations) if err != nil { t.Fatal(err) } wantFiles := []string{"00001_initial.sql", "00002_test_migration.go", "00003_create_test_migration.go", "00004_" + osName + "_migration.go", "register.go"} if len(entries) != len(wantFiles) { t.Fatalf("migration files: %v", entries) } for i, entry := range entries { if entry.Name() != wantFiles[i] { t.Fatalf("migration file: got %s, want %s", entry.Name(), wantFiles[i]) } } entries, err = os.ReadDir(dir) if err != nil { t.Fatal(err) } if len(entries) != 2 || entries[0].Name() != "corestack-goose" || entries[1].Name() != "migrations" { t.Fatalf("unexpected writes outside migration directory: %v", entries) } source, err := os.ReadFile("testdata/consumer/main.go") if err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "main.go"), source, 0o600); err != nil { t.Fatal(err) } module := fmt.Sprintf("module example.com/consumer\n\ngo 1.27.1\n\nrequire go.auvem.com/corestack v0.0.0\nreplace go.auvem.com/corestack => %s\n", filepath.ToSlash(root)) if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(module), 0o600); err != nil { t.Fatal(err) } binary := filepath.Join(dir, "consumer") execute(dir, "go", "build", "-mod=mod", "-o", binary, ".") out := execute(dir, binary, filepath.Join(dir, "consumer.db")) if !strings.Contains(string(out), "version=4 pending=false versions=1,2,3,4 fresh=true isolated-pending=true") { t.Fatal(string(out)) } // Decode package identities rather than guessing from substring matches in go list. deps := execute(dir, "go", "list", "-deps", "-json", ".") decoder := json.NewDecoder(bytes.NewReader(deps)) adapterFound := false for decoder.More() { var dep struct{ ImportPath string } if err := decoder.Decode(&dep); err != nil { t.Fatal(err) } if dep.ImportPath == "go.auvem.com/corestack/migrate/lifecycle" { adapterFound = true } if dep.ImportPath == "go.auvem.com/corestack/cmd/corestack-goose" || strings.HasPrefix(dep.ImportPath, "go.auvem.com/corestack/cmd/corestack-goose/") { t.Fatalf("production imports authoring: %s", dep.ImportPath) } } if !adapterFound { t.Fatal("production fixture did not import runtime adapter") } toolSymbols := execute(dir, "go", "tool", "nm", tool) toolEntries := map[string]bool{} for _, line := range strings.Split(string(toolSymbols), "\n") { fields := strings.Fields(line) if len(fields) >= 3 { toolEntries[fields[len(fields)-1]] = true } } for _, entry := range []string{"github.com/pressly/goose/v3.CreateWithTemplate", "go.auvem.com/corestack/cmd/corestack-goose/internal/authoring.Create", "go.auvem.com/corestack/cmd/corestack-goose/internal/authoring.Fix"} { if !toolEntries[entry] { t.Fatalf("standalone authoring entrypoint missing: %s", entry) } } runtimeFound := false symbols := execute(dir, "go", "tool", "nm", binary) for _, line := range strings.Split(string(symbols), "\n") { fields := strings.Fields(line) if len(fields) < 3 { continue } symbol := fields[len(fields)-1] if symbol == "go.auvem.com/corestack/migrate/lifecycle.Commands" { runtimeFound = true } if strings.HasPrefix(symbol, "go.auvem.com/corestack/cmd/corestack-goose/") { t.Fatalf("authoring symbol linked: %s", symbol) } for _, entry := range []string{"Create", "CreateWithTemplate", "Fix", "SetSequential"} { full := "github.com/pressly/goose/v3." + entry if symbol == full || strings.HasPrefix(symbol, full+".") { t.Fatalf("upstream authoring entrypoint linked: %s", symbol) } } } if !runtimeFound { t.Fatal("runtime command symbol missing from production executable") } t.Log("standalone create/fix; unchanged generated SQL+safe-suffix Go compiled and applied; runtime dependency identities and linker symbols verified") }