feat(dev): add api test-gen command
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
129199a5e0
commit
b7d70883e9
5 changed files with 192 additions and 5 deletions
|
|
@ -1,8 +1,8 @@
|
|||
package dev
|
||||
|
||||
import (
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
"dappco.re/go/core/i18n"
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
)
|
||||
|
||||
// addAPICommands adds the 'api' command and its subcommands to the given parent command.
|
||||
|
|
@ -17,6 +17,6 @@ func addAPICommands(parent *cli.Command) {
|
|||
// Add the 'sync' command to 'api'
|
||||
addSyncCommand(apiCmd)
|
||||
|
||||
// TODO: Add the 'test-gen' command to 'api'
|
||||
// addTestGenCommand(apiCmd)
|
||||
// Add the 'test-gen' command to 'api'
|
||||
addTestGenCommand(apiCmd)
|
||||
}
|
||||
|
|
|
|||
112
cmd/dev/cmd_api_testgen.go
Normal file
112
cmd/dev/cmd_api_testgen.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package dev
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"dappco.re/go/core/i18n"
|
||||
coreio "dappco.re/go/core/io"
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
)
|
||||
|
||||
func addTestGenCommand(parent *cli.Command) {
|
||||
testGenCmd := &cli.Command{
|
||||
Use: "test-gen",
|
||||
Short: i18n.T("cmd.dev.api.test_gen.short"),
|
||||
Long: i18n.T("cmd.dev.api.test_gen.long"),
|
||||
RunE: func(cmd *cli.Command, args []string) error {
|
||||
if err := runTestGen(); err != nil {
|
||||
return cli.Wrap(err, i18n.Label("error"))
|
||||
}
|
||||
cli.Text(i18n.T("i18n.done.sync", "public API tests"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
parent.AddCommand(testGenCmd)
|
||||
}
|
||||
|
||||
func runTestGen() error {
|
||||
pkgDir := "pkg"
|
||||
internalDirs, err := coreio.Local.List(pkgDir)
|
||||
if err != nil {
|
||||
return cli.Wrap(err, "failed to read pkg directory")
|
||||
}
|
||||
|
||||
for _, dir := range internalDirs {
|
||||
if !dir.IsDir() || dir.Name() == "core" {
|
||||
continue
|
||||
}
|
||||
|
||||
serviceName := dir.Name()
|
||||
internalFile := filepath.Join(pkgDir, serviceName, serviceName+".go")
|
||||
publicDir := serviceName
|
||||
publicTestFile := filepath.Join(publicDir, serviceName+"_test.go")
|
||||
|
||||
if !coreio.Local.IsFile(internalFile) {
|
||||
continue
|
||||
}
|
||||
|
||||
symbols, err := getExportedSymbols(internalFile)
|
||||
if err != nil {
|
||||
return cli.Wrap(err, cli.Sprintf("error getting symbols for service '%s'", serviceName))
|
||||
}
|
||||
|
||||
if len(symbols) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := generatePublicAPITestFile(publicDir, publicTestFile, serviceName, symbols); err != nil {
|
||||
return cli.Wrap(err, cli.Sprintf("error generating public API test file for service '%s'", serviceName))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const publicAPITestTemplate = `// Code generated by "core dev api test-gen"; DO NOT EDIT.
|
||||
package {{.ServiceName}}
|
||||
|
||||
import (
|
||||
impl "forge.lthn.ai/core/cli/{{.ServiceName}}"
|
||||
)
|
||||
|
||||
{{range .Symbols}}
|
||||
{{- if eq .Kind "type"}}
|
||||
type _ = impl.{{.Name}}
|
||||
{{- else if eq .Kind "const"}}
|
||||
const _ = impl.{{.Name}}
|
||||
{{- else if eq .Kind "var"}}
|
||||
var _ = impl.{{.Name}}
|
||||
{{- else if eq .Kind "func"}}
|
||||
var _ = impl.{{.Name}}
|
||||
{{- end}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
func generatePublicAPITestFile(dir, path, serviceName string, symbols []symbolInfo) error {
|
||||
if err := coreio.Local.EnsureDir(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("publicAPITest").Parse(publicAPITestTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := struct {
|
||||
ServiceName string
|
||||
Symbols []symbolInfo
|
||||
}{
|
||||
ServiceName: serviceName,
|
||||
Symbols: symbols,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.Execute(&buf, data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return coreio.Local.Write(path, buf.String())
|
||||
}
|
||||
70
cmd/dev/cmd_api_testgen_test.go
Normal file
70
cmd/dev/cmd_api_testgen_test.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package dev
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"dappco.re/go/core/io"
|
||||
)
|
||||
|
||||
func TestRunTestGen_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
originalWD, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = os.Chdir(originalWD)
|
||||
})
|
||||
require.NoError(t, os.Chdir(tmpDir))
|
||||
|
||||
serviceDir := filepath.Join(tmpDir, "pkg", "demo")
|
||||
require.NoError(t, io.Local.EnsureDir(serviceDir))
|
||||
require.NoError(t, io.Local.Write(filepath.Join(serviceDir, "demo.go"), `package demo
|
||||
|
||||
type Example struct{}
|
||||
|
||||
const Answer = 42
|
||||
|
||||
var Value = Example{}
|
||||
|
||||
func Run() {}
|
||||
`))
|
||||
|
||||
require.NoError(t, runTestGen())
|
||||
|
||||
generatedPath := filepath.Join(tmpDir, "demo", "demo_test.go")
|
||||
content, err := io.Local.Read(generatedPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, content, `// Code generated by "core dev api test-gen"; DO NOT EDIT.`)
|
||||
require.Contains(t, content, `package demo`)
|
||||
require.Contains(t, content, `impl "forge.lthn.ai/core/cli/demo"`)
|
||||
require.Contains(t, content, `type _ = impl.Example`)
|
||||
require.Contains(t, content, `const _ = impl.Answer`)
|
||||
require.Contains(t, content, `var _ = impl.Value`)
|
||||
require.Contains(t, content, `var _ = impl.Run`)
|
||||
}
|
||||
|
||||
func TestGeneratePublicAPITestFile_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
require.NoError(t, generatePublicAPITestFile(
|
||||
filepath.Join(tmpDir, "demo"),
|
||||
filepath.Join(tmpDir, "demo", "demo_test.go"),
|
||||
"demo",
|
||||
[]symbolInfo{
|
||||
{Name: "Example", Kind: "type"},
|
||||
{Name: "Answer", Kind: "const"},
|
||||
},
|
||||
))
|
||||
|
||||
content, err := io.Local.Read(filepath.Join(tmpDir, "demo", "demo_test.go"))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, strings.Contains(content, `type _ = impl.Example`))
|
||||
require.True(t, strings.Contains(content, `const _ = impl.Answer`))
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
//
|
||||
// API Tools:
|
||||
// - api sync: Synchronize public service APIs
|
||||
// - api test-gen: Generate compile-time API test stubs
|
||||
//
|
||||
// Dev Environment (VM management):
|
||||
// - install: Download dev environment image
|
||||
|
|
@ -33,8 +34,8 @@
|
|||
package dev
|
||||
|
||||
import (
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
"dappco.re/go/core/i18n"
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
|
||||
_ "dappco.re/go/core/devops/locales"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@
|
|||
"short": "Multi-repo development workflows",
|
||||
"long": "Development workflow commands for managing multiple repositories.\n\nIncludes git operations, forge integration, CI status, and dev environment management.",
|
||||
"api": {
|
||||
"short": "API synchronisation tools"
|
||||
"short": "API synchronisation tools",
|
||||
"test_gen": {
|
||||
"short": "Generate public API test stubs",
|
||||
"long": "Scan internal service packages and generate compile-time tests for their public API wrappers."
|
||||
}
|
||||
},
|
||||
"health": {
|
||||
"short": "Quick health check across all repos",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue