refactor(ax): harden forgegen error flow and sync dev guide
All checks were successful
Security Scan / security (push) Successful in 17s
Test / test (push) Successful in 41s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-30 01:07:12 +00:00
parent 2708eef359
commit bfb8cf6034
3 changed files with 62 additions and 13 deletions

View file

@ -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
}

40
cmd/forgegen/main_test.go Normal file
View file

@ -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())
}
}

View file

@ -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<TypeOrArea>_<MethodOrCase>_<Good|Bad|Ugly>`:
- **`_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)
}
```