From bfb8cf60349da775c4d109ec01c0ee7aac4facef Mon Sep 17 00:00:00 2001 From: Virgil Date: Mon, 30 Mar 2026 01:07:12 +0000 Subject: [PATCH] refactor(ax): harden forgegen error flow and sync dev guide Co-Authored-By: Virgil --- cmd/forgegen/main.go | 19 ++++++++++++++----- cmd/forgegen/main_test.go | 40 +++++++++++++++++++++++++++++++++++++++ docs/development.md | 16 ++++++++-------- 3 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 cmd/forgegen/main_test.go diff --git a/cmd/forgegen/main.go b/cmd/forgegen/main.go index 469f626..08f95a3 100644 --- a/cmd/forgegen/main.go +++ b/cmd/forgegen/main.go @@ -2,6 +2,7 @@ package main import ( "flag" + "os" core "dappco.re/go/core" ) @@ -11,18 +12,26 @@ func main() { outDir := flag.String("out", "types", "output directory for generated types") flag.Parse() - spec, err := LoadSpec(*specPath) + if err := run(*specPath, *outDir); err != nil { + core.Print(os.Stderr, "forgegen: %v", err) + os.Exit(1) + } +} + +func run(specPath, outDir string) error { + spec, err := LoadSpec(specPath) if err != nil { - panic(core.E("forgegen.main", "load spec", err)) + return core.E("forgegen.main", "load spec", err) } types := ExtractTypes(spec) pairs := DetectCRUDPairs(spec) core.Print(nil, "Loaded %d types, %d CRUD pairs", len(types), len(pairs)) - core.Print(nil, "Output dir: %s", *outDir) + core.Print(nil, "Output dir: %s", outDir) - if err := Generate(types, pairs, *outDir); err != nil { - panic(core.E("forgegen.main", "generate types", err)) + if err := Generate(types, pairs, outDir); err != nil { + return core.E("forgegen.main", "generate types", err) } + return nil } diff --git a/cmd/forgegen/main_test.go b/cmd/forgegen/main_test.go new file mode 100644 index 0000000..426a10e --- /dev/null +++ b/cmd/forgegen/main_test.go @@ -0,0 +1,40 @@ +package main + +import ( + "testing" + + core "dappco.re/go/core" + coreio "dappco.re/go/core/io" +) + +func TestMain_Run_Good(t *testing.T) { + outDir := t.TempDir() + if err := run("../../testdata/swagger.v1.json", outDir); err != nil { + t.Fatal(err) + } + + entries, err := coreio.Local.List(outDir) + if err != nil { + t.Fatal(err) + } + + goFiles := 0 + for _, e := range entries { + if core.HasSuffix(e.Name(), ".go") { + goFiles++ + } + } + if goFiles == 0 { + t.Fatal("no .go files generated by run") + } +} + +func TestMain_Run_Bad(t *testing.T) { + err := run("/does/not/exist/swagger.v1.json", t.TempDir()) + if err == nil { + t.Fatal("expected error for invalid spec path") + } + if !core.Contains(err.Error(), "load spec") { + t.Fatalf("got error %q, expected load spec context", err.Error()) + } +} diff --git a/docs/development.md b/docs/development.md index ae9f377..2f6ca36 100644 --- a/docs/development.md +++ b/docs/development.md @@ -39,7 +39,7 @@ All tests use the standard `testing` package with `net/http/httptest` for HTTP s go test ./... # Run a specific test by name -go test -v -run TestClient_Good_Get ./... +go test -v -run TestClient_Get_Good ./... # Run tests with race detection go test -race ./... @@ -59,7 +59,7 @@ core go cov --open # Open coverage report in browser ### Test naming convention -Tests follow the `_Good`, `_Bad`, `_Ugly` suffix pattern: +Tests follow `Test__`: - **`_Good`** — Happy-path tests confirming correct behaviour. - **`_Bad`** — Expected error conditions (e.g. 404, 500 responses). @@ -67,11 +67,11 @@ Tests follow the `_Good`, `_Bad`, `_Ugly` suffix pattern: Examples: ``` -TestClient_Good_Get -TestClient_Bad_ServerError -TestClient_Bad_NotFound -TestClient_Good_ContextCancellation -TestResource_Good_ListAll +TestClient_Get_Good +TestClient_ServerError_Bad +TestClient_NotFound_Bad +TestClient_ContextCancellation_Good +TestResource_ListAll_Good ``` @@ -173,7 +173,7 @@ To add coverage for a new Forgejo API domain: ```go func (s *TopicService) ListRepoTopics(ctx context.Context, owner, repo string) ([]types.Topic, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/topics", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/topics", pathParams("owner", owner, "repo", repo)) return ListAll[types.Topic](ctx, s.client, path, nil) } ```