From ad3373b134097abe6cd4b2d340f902a2efecdf91 Mon Sep 17 00:00:00 2001 From: Virgil Date: Sun, 29 Mar 2026 21:34:46 +0000 Subject: [PATCH] fix(ax): align setup package docs and tests Co-Authored-By: Virgil --- pkg/setup/config.go | 18 ++++-- pkg/setup/config_test.go | 34 +++++++---- pkg/setup/detect.go | 13 +++-- pkg/setup/detect_test.go | 29 ++++++++-- pkg/setup/service.go | 23 +++++--- pkg/setup/service_test.go | 11 +++- pkg/setup/setup.go | 10 ++-- pkg/setup/setup_example_test.go | 36 ++++-------- pkg/setup/setup_extra_test.go | 94 ------------------------------- pkg/setup/setup_test.go | 99 ++++++++++++++++++--------------- 10 files changed, 161 insertions(+), 206 deletions(-) delete mode 100644 pkg/setup/setup_extra_test.go diff --git a/pkg/setup/config.go b/pkg/setup/config.go index 79c0f1e..ae9f70e 100644 --- a/pkg/setup/config.go +++ b/pkg/setup/config.go @@ -7,7 +7,9 @@ import ( "gopkg.in/yaml.v3" ) -// ConfigData holds the data passed to config templates. +// ConfigData supplies values when setup renders workspace templates. +// +// data := setup.ConfigData{Name: "agent", Type: "go", Repository: "core/agent"} type ConfigData struct { Name string Description string @@ -20,13 +22,17 @@ type ConfigData struct { Env map[string]string } -// Target is a build target (os/arch pair). +// Target describes one build target. +// +// target := setup.Target{OS: "linux", Arch: "amd64"} type Target struct { OS string Arch string } -// Command is a named runnable command. +// Command defines one named command in generated config. +// +// command := setup.Command{Name: "unit", Run: "go test ./..."} type Command struct { Name string Run string @@ -42,9 +48,9 @@ type configValue struct { Value any } -// GenerateBuildConfig renders a build.yaml for the detected project type. +// GenerateBuildConfig renders `build.yaml` content for a detected repo type. // -// content, err := setup.GenerateBuildConfig("/repo", setup.TypeGo) +// content, err := setup.GenerateBuildConfig("/srv/repos/agent", setup.TypeGo) func GenerateBuildConfig(path string, projType ProjectType) (string, error) { name := core.PathBase(path) sections := []configSection{ @@ -88,7 +94,7 @@ func GenerateBuildConfig(path string, projType ProjectType) (string, error) { return renderConfig(core.Concat(name, " build configuration"), sections) } -// GenerateTestConfig renders a test.yaml for the detected project type. +// GenerateTestConfig renders `test.yaml` content for a detected repo type. // // content, err := setup.GenerateTestConfig(setup.TypeGo) func GenerateTestConfig(projType ProjectType) (string, error) { diff --git a/pkg/setup/config_test.go b/pkg/setup/config_test.go index e1cb8b9..bc3034d 100644 --- a/pkg/setup/config_test.go +++ b/pkg/setup/config_test.go @@ -6,34 +6,49 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestConfig_GenerateBuildConfig_Good(t *testing.T) { +func TestConfig_GenerateBuildConfig_Good_Go(t *testing.T) { config, err := GenerateBuildConfig("/tmp/myapp", TypeGo) - assert.NoError(t, err) + require.NoError(t, err) + assert.Contains(t, config, "# myapp build configuration") assert.Contains(t, config, "type: go") assert.Contains(t, config, "name: myapp") + assert.Contains(t, config, "main: ./cmd/myapp") + assert.Contains(t, config, "cgo: false") } func TestConfig_GenerateBuildConfig_Bad_Unknown(t *testing.T) { config, err := GenerateBuildConfig("/tmp/myapp", TypeUnknown) - assert.NoError(t, err) + require.NoError(t, err) assert.NotEmpty(t, config) } -func TestConfig_GenerateTestConfig_Good(t *testing.T) { +func TestConfig_GenerateTestConfig_Good_Go(t *testing.T) { config, err := GenerateTestConfig(TypeGo) - assert.NoError(t, err) + require.NoError(t, err) assert.Contains(t, config, "go test") } -func TestConfig_ParseGitRemote_Good(t *testing.T) { - assert.Equal(t, "core/go-io", parseGitRemote("https://forge.lthn.ai/core/go-io.git")) - assert.Equal(t, "core/go-io", parseGitRemote("git@forge.lthn.ai:core/go-io.git")) +func TestConfig_ParseGitRemote_Good_CommonFormats(t *testing.T) { + tests := map[string]string{ + "https://github.com/dAppCore/go-io.git": "dAppCore/go-io", + "git@github.com:dAppCore/go-io.git": "dAppCore/go-io", + "ssh://git@forge.lthn.ai:2223/core/agent.git": "core/agent", + "ssh://git@forge.lthn.ai:2223/core/agent": "core/agent", + "git@forge.lthn.ai:core/agent.git": "core/agent", + "/srv/git/core/agent.git": "srv/git/core/agent", + } + + for remote, want := range tests { + assert.Equal(t, want, parseGitRemote(remote), remote) + } } -func TestConfig_ParseGitRemote_Ugly_Empty(t *testing.T) { +func TestConfig_ParseGitRemote_Bad_Empty(t *testing.T) { assert.Equal(t, "", parseGitRemote("")) + assert.Equal(t, "", parseGitRemote("origin")) } func TestConfig_TrimRemotePath_Good(t *testing.T) { @@ -48,4 +63,3 @@ func TestConfig_RenderConfig_Good(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "name: test") } - diff --git a/pkg/setup/detect.go b/pkg/setup/detect.go index 0f22a6b..688f148 100644 --- a/pkg/setup/detect.go +++ b/pkg/setup/detect.go @@ -1,13 +1,18 @@ // SPDX-License-Identifier: EUPL-1.2 -// Package setup provides workspace setup and scaffolding using lib templates. +// Package setup provisions `.core/` files and workspace scaffolds for a repo. +// +// svc := core.ServiceFor[*setup.Service](core.New(core.WithService(setup.Register)), "setup") package setup import ( core "dappco.re/go/core" ) -// ProjectType identifies what kind of project lives at a path. +// ProjectType records what setup detected in a repository path. +// +// projType := setup.Detect("/srv/repos/agent") +// if projType == setup.TypeGo { /* generate Go defaults */ } type ProjectType string const ( @@ -21,7 +26,7 @@ const ( // fs provides unrestricted filesystem access for setup operations. var fs = (&core.Fs{}).NewUnrestricted() -// Detect identifies the project type from files present at the given path. +// Detect inspects a repository path and returns the primary project type. // // projType := setup.Detect("./repo") func Detect(path string) ProjectType { @@ -43,7 +48,7 @@ func Detect(path string) ProjectType { return TypeUnknown } -// DetectAll returns all project types found at the path (polyglot repos). +// DetectAll returns every detected project type for a polyglot repository. // // types := setup.DetectAll("./repo") func DetectAll(path string) []ProjectType { diff --git a/pkg/setup/detect_test.go b/pkg/setup/detect_test.go index 4e9b65e..f6ff64c 100644 --- a/pkg/setup/detect_test.go +++ b/pkg/setup/detect_test.go @@ -7,32 +7,51 @@ import ( core "dappco.re/go/core" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestDetect_Detect_Good_Go(t *testing.T) { dir := t.TempDir() - fs.Write(core.JoinPath(dir, "go.mod"), "module test") + require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module test\n", 0644).OK) assert.Equal(t, TypeGo, Detect(dir)) } func TestDetect_Detect_Good_PHP(t *testing.T) { dir := t.TempDir() - fs.Write(core.JoinPath(dir, "composer.json"), "{}") + require.True(t, fs.WriteMode(core.JoinPath(dir, "composer.json"), "{}", 0644).OK) assert.Equal(t, TypePHP, Detect(dir)) } +func TestDetect_Detect_Good_Node(t *testing.T) { + dir := t.TempDir() + require.True(t, fs.WriteMode(core.JoinPath(dir, "package.json"), `{"name":"test"}`, 0644).OK) + assert.Equal(t, TypeNode, Detect(dir)) +} + +func TestDetect_Detect_Good_Wails(t *testing.T) { + dir := t.TempDir() + require.True(t, fs.WriteMode(core.JoinPath(dir, "wails.json"), `{}`, 0644).OK) + assert.Equal(t, TypeWails, Detect(dir)) +} + func TestDetect_Detect_Bad_Unknown(t *testing.T) { dir := t.TempDir() assert.Equal(t, TypeUnknown, Detect(dir)) } -func TestDetect_DetectAll_Good(t *testing.T) { +func TestDetect_DetectAll_Good_Polyglot(t *testing.T) { dir := t.TempDir() - fs.Write(core.JoinPath(dir, "go.mod"), "module test") - fs.Write(core.JoinPath(dir, "package.json"), "{}") + require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module test\n", 0644).OK) + require.True(t, fs.WriteMode(core.JoinPath(dir, "package.json"), `{"name":"test"}`, 0644).OK) types := DetectAll(dir) assert.Contains(t, types, TypeGo) assert.Contains(t, types, TypeNode) + assert.NotContains(t, types, TypePHP) +} + +func TestDetect_DetectAll_Bad_Empty(t *testing.T) { + dir := t.TempDir() + assert.Empty(t, DetectAll(dir)) } func TestDetect_AbsolutePath_Ugly_Empty(t *testing.T) { diff --git a/pkg/setup/service.go b/pkg/setup/service.go index f504de8..779685c 100644 --- a/pkg/setup/service.go +++ b/pkg/setup/service.go @@ -8,20 +8,23 @@ import ( core "dappco.re/go/core" ) -// SetupOptions configures the setup service. +// SetupOptions carries service-level setup configuration. +// +// opts := setup.SetupOptions{} type SetupOptions struct{} -// Service provides workspace setup and scaffolding as a Core service. -// Registers as "setup" — use s.Core().Process() for git operations. +// Service exposes workspace setup through Core service registration. // -// core.New(core.WithService(setup.Register)) +// c := core.New(core.WithService(setup.Register)) +// svc, _ := core.ServiceFor[*setup.Service](c, "setup") type Service struct { *core.ServiceRuntime[SetupOptions] } -// Register is the WithService factory for setup. +// Register wires the setup service into Core. // -// core.New(core.WithService(setup.Register)) +// c := core.New(core.WithService(setup.Register)) +// svc, _ := core.ServiceFor[*setup.Service](c, "setup") func Register(c *core.Core) core.Result { svc := &Service{ ServiceRuntime: core.NewServiceRuntime(c, SetupOptions{}), @@ -29,14 +32,16 @@ func Register(c *core.Core) core.Result { return core.Result{Value: svc, OK: true} } -// OnStartup implements core.Startable. +// OnStartup keeps the setup service ready for Core startup hooks. +// +// result := svc.OnStartup(context.Background()) func (s *Service) OnStartup(ctx context.Context) core.Result { return core.Result{OK: true} } -// DetectGitRemote extracts owner/repo from git remote origin via Core Process. +// DetectGitRemote reads `origin` and returns `owner/repo` when available. // -// remote := svc.DetectGitRemote("/repo") +// remote := svc.DetectGitRemote("/srv/repos/agent") func (s *Service) DetectGitRemote(path string) string { r := s.Core().Process().RunIn(context.Background(), path, "git", "remote", "get-url", "origin") if !r.OK { diff --git a/pkg/setup/service_test.go b/pkg/setup/service_test.go index 0cede9d..d1dd454 100644 --- a/pkg/setup/service_test.go +++ b/pkg/setup/service_test.go @@ -3,6 +3,7 @@ package setup import ( + "context" "testing" core "dappco.re/go/core" @@ -16,7 +17,15 @@ func TestService_Register_Good(t *testing.T) { assert.NotNil(t, svc) } -func TestService_DetectGitRemote_Good(t *testing.T) { +func TestService_OnStartup_Good(t *testing.T) { + c := core.New() + svc := &Service{ServiceRuntime: core.NewServiceRuntime(c, SetupOptions{})} + + result := svc.OnStartup(context.Background()) + assert.True(t, result.OK) +} + +func TestService_DetectGitRemote_Good_NonGitDir(t *testing.T) { c := core.New() svc := &Service{ServiceRuntime: core.NewServiceRuntime(c, SetupOptions{})} // Non-git dir returns empty diff --git a/pkg/setup/setup.go b/pkg/setup/setup.go index 489bbb4..e7a102a 100644 --- a/pkg/setup/setup.go +++ b/pkg/setup/setup.go @@ -7,9 +7,9 @@ import ( core "dappco.re/go/core" ) -// Options controls setup behaviour. +// Options controls one setup run. // -// err := svc.Run(setup.Options{Path: ".", Force: true}) +// err := svc.Run(setup.Options{Path: ".", Template: "auto", Force: true}) type Options struct { Path string // Target directory (default: cwd) DryRun bool // Preview only, don't write @@ -17,11 +17,9 @@ type Options struct { Template string // Workspace template or compatibility alias (default, review, security, agent, go, php, gui, auto) } -// Run performs the workspace setup at the given path. -// It detects the project type, generates .core/ configs, -// and optionally scaffolds a workspace from a dir template. +// Run generates `.core/` files and optional workspace scaffolding for a repo. // -// svc.Run(setup.Options{Path: ".", Template: "auto"}) +// err := svc.Run(setup.Options{Path: ".", Template: "auto"}) func (s *Service) Run(opts Options) error { if opts.Path == "" { opts.Path = core.Env("DIR_CWD") diff --git a/pkg/setup/setup_example_test.go b/pkg/setup/setup_example_test.go index 4fc5c8d..86ab3f0 100644 --- a/pkg/setup/setup_example_test.go +++ b/pkg/setup/setup_example_test.go @@ -6,31 +6,17 @@ import ( core "dappco.re/go/core" ) -func ExampleDetect() { - dir := (&core.Fs{}).NewUnrestricted().TempDir("example") - defer (&core.Fs{}).NewUnrestricted().DeleteAll(dir) - - // Empty dir — unknown type - core.Println(Detect(dir)) - // Output: unknown +func Example_defaultBuildCommand() { + core.Println(defaultBuildCommand(TypeGo)) + core.Println(defaultBuildCommand(TypePHP)) + // Output: + // go build ./... + // composer test } -func ExampleDetectAll() { - dir := (&core.Fs{}).NewUnrestricted().TempDir("example") - defer (&core.Fs{}).NewUnrestricted().DeleteAll(dir) - - // Create a Go project - (&core.Fs{}).NewUnrestricted().Write(core.JoinPath(dir, "go.mod"), "module test") - types := DetectAll(dir) - core.Println(types) - // Output: [go] -} - -func ExampleRegister() { - c := core.New(core.WithService(Register)) - - svc, ok := core.ServiceFor[*Service](c, "setup") - core.Println(ok) - _ = svc - // Output: true +func Example_formatFlow() { + core.Println(formatFlow(TypeNode)) + // Output: + // - Build: `npm run build` + // - Test: `npm test` } diff --git a/pkg/setup/setup_extra_test.go b/pkg/setup/setup_extra_test.go deleted file mode 100644 index 3dc4654..0000000 --- a/pkg/setup/setup_extra_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: EUPL-1.2 - -package setup - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// --- defaultBuildCommand --- - -func TestSetup_DefaultBuildCommand_Good(t *testing.T) { - assert.Equal(t, "go build ./...", defaultBuildCommand(TypeGo)) - assert.Equal(t, "go build ./...", defaultBuildCommand(TypeWails)) - assert.Equal(t, "composer test", defaultBuildCommand(TypePHP)) - assert.Equal(t, "npm run build", defaultBuildCommand(TypeNode)) - assert.Equal(t, "make build", defaultBuildCommand(TypeUnknown)) -} - -// --- defaultTestCommand --- - -func TestSetup_DefaultTestCommand_Good(t *testing.T) { - assert.Equal(t, "go test ./...", defaultTestCommand(TypeGo)) - assert.Equal(t, "go test ./...", defaultTestCommand(TypeWails)) - assert.Equal(t, "composer test", defaultTestCommand(TypePHP)) - assert.Equal(t, "npm test", defaultTestCommand(TypeNode)) - assert.Equal(t, "make test", defaultTestCommand(TypeUnknown)) -} - -// --- formatFlow --- - -func TestSetup_FormatFlow_Good(t *testing.T) { - goFlow := formatFlow(TypeGo) - assert.Contains(t, goFlow, "go build ./...") - assert.Contains(t, goFlow, "go test ./...") - - phpFlow := formatFlow(TypePHP) - assert.Contains(t, phpFlow, "composer test") - - nodeFlow := formatFlow(TypeNode) - assert.Contains(t, nodeFlow, "npm run build") - assert.Contains(t, nodeFlow, "npm test") -} - -// --- Detect --- - -func TestSetup_DetectGo_Good(t *testing.T) { - dir := t.TempDir() - fs.Write(dir+"/go.mod", "module test\n") - assert.Equal(t, TypeGo, Detect(dir)) -} - -func TestSetup_DetectPHP_Good(t *testing.T) { - dir := t.TempDir() - fs.Write(dir+"/composer.json", `{"name":"test"}`) - assert.Equal(t, TypePHP, Detect(dir)) -} - -func TestSetup_DetectNode_Good(t *testing.T) { - dir := t.TempDir() - fs.Write(dir+"/package.json", `{"name":"test"}`) - assert.Equal(t, TypeNode, Detect(dir)) -} - -func TestSetup_DetectWails_Good(t *testing.T) { - dir := t.TempDir() - fs.Write(dir+"/wails.json", `{}`) - assert.Equal(t, TypeWails, Detect(dir)) -} - -func TestSetup_Detect_Bad(t *testing.T) { - dir := t.TempDir() - assert.Equal(t, TypeUnknown, Detect(dir)) -} - -// --- DetectAll --- - -func TestSetup_DetectAll_Good(t *testing.T) { - dir := t.TempDir() - fs.Write(dir+"/go.mod", "module test\n") - fs.Write(dir+"/package.json", `{"name":"test"}`) - - types := DetectAll(dir) - assert.Contains(t, types, TypeGo) - assert.Contains(t, types, TypeNode) - assert.NotContains(t, types, TypePHP) -} - -func TestSetup_DetectAll_Bad(t *testing.T) { - dir := t.TempDir() - types := DetectAll(dir) - assert.Empty(t, types) -} diff --git a/pkg/setup/setup_test.go b/pkg/setup/setup_test.go index 0056f15..364de08 100644 --- a/pkg/setup/setup_test.go +++ b/pkg/setup/setup_test.go @@ -10,57 +10,16 @@ import ( "github.com/stretchr/testify/require" ) -// testSvc creates a setup Service for tests. -func testSvc() *Service { +func newSetupService() *Service { c := core.New() return &Service{ServiceRuntime: core.NewServiceRuntime(c, SetupOptions{})} } -func TestSetup_Detect_Good(t *testing.T) { +func TestSetup_Run_Good_WritesCoreConfigs(t *testing.T) { dir := t.TempDir() require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK) - assert.Equal(t, TypeGo, Detect(dir)) - assert.Equal(t, []ProjectType{TypeGo}, DetectAll(dir)) -} - -func TestSetup_GenerateBuildConfig_Good(t *testing.T) { - cfg, err := GenerateBuildConfig("/tmp/example", TypeGo) - require.NoError(t, err) - - assert.Contains(t, cfg, "# example build configuration") - assert.Contains(t, cfg, "project:") - assert.Contains(t, cfg, "name: example") - assert.Contains(t, cfg, "type: go") - assert.Contains(t, cfg, "main: ./cmd/example") - assert.Contains(t, cfg, "cgo: false") -} - -func TestSetup_ParseGitRemote_Good(t *testing.T) { - tests := map[string]string{ - "https://github.com/dAppCore/go-io.git": "dAppCore/go-io", - "git@github.com:dAppCore/go-io.git": "dAppCore/go-io", - "ssh://git@forge.lthn.ai:2223/core/agent.git": "core/agent", - "ssh://git@forge.lthn.ai:2223/core/agent": "core/agent", - "git@forge.lthn.ai:core/agent.git": "core/agent", - "/srv/git/core/agent.git": "srv/git/core/agent", - } - - for remote, want := range tests { - assert.Equal(t, want, parseGitRemote(remote), remote) - } -} - -func TestSetup_ParseGitRemote_Bad(t *testing.T) { - assert.Equal(t, "", parseGitRemote("")) - assert.Equal(t, "", parseGitRemote("origin")) -} - -func TestSetup_Run_Good(t *testing.T) { - dir := t.TempDir() - require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK) - - err := testSvc().Run(Options{Path: dir}) + err := newSetupService().Run(Options{Path: dir}) require.NoError(t, err) build := fs.Read(core.JoinPath(dir, ".core", "build.yaml")) @@ -72,14 +31,62 @@ func TestSetup_Run_Good(t *testing.T) { assert.Contains(t, test.Value.(string), "go test ./...") } -func TestSetup_RunTemplateAlias_Good(t *testing.T) { +func TestSetup_Run_Good_TemplateAlias(t *testing.T) { dir := t.TempDir() require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK) - err := testSvc().Run(Options{Path: dir, Template: "agent"}) + err := newSetupService().Run(Options{Path: dir, Template: "agent"}) require.NoError(t, err) prompt := fs.Read(core.JoinPath(dir, "PROMPT.md")) require.True(t, prompt.OK) assert.Contains(t, prompt.Value.(string), "This workspace was scaffolded by pkg/setup.") } + +func TestSetup_ResolveTemplateName_Good_Auto(t *testing.T) { + name, err := resolveTemplateName("auto", TypeGo) + require.NoError(t, err) + assert.Equal(t, "default", name) +} + +func TestSetup_ResolveTemplateName_Bad_Empty(t *testing.T) { + _, err := resolveTemplateName("", TypeGo) + require.Error(t, err) +} + +func TestSetup_TemplateExists_Good_Default(t *testing.T) { + assert.True(t, templateExists("default")) +} + +func TestSetup_TemplateExists_Bad_Missing(t *testing.T) { + assert.False(t, templateExists("missing-template")) +} + +func TestSetup_DefaultBuildCommand_Good(t *testing.T) { + assert.Equal(t, "go build ./...", defaultBuildCommand(TypeGo)) + assert.Equal(t, "go build ./...", defaultBuildCommand(TypeWails)) + assert.Equal(t, "composer test", defaultBuildCommand(TypePHP)) + assert.Equal(t, "npm run build", defaultBuildCommand(TypeNode)) + assert.Equal(t, "make build", defaultBuildCommand(TypeUnknown)) +} + +func TestSetup_DefaultTestCommand_Good(t *testing.T) { + assert.Equal(t, "go test ./...", defaultTestCommand(TypeGo)) + assert.Equal(t, "go test ./...", defaultTestCommand(TypeWails)) + assert.Equal(t, "composer test", defaultTestCommand(TypePHP)) + assert.Equal(t, "npm test", defaultTestCommand(TypeNode)) + assert.Equal(t, "make test", defaultTestCommand(TypeUnknown)) +} + +func TestSetup_FormatFlow_Good(t *testing.T) { + goFlow := formatFlow(TypeGo) + assert.Contains(t, goFlow, "go build ./...") + assert.Contains(t, goFlow, "go test ./...") + + phpFlow := formatFlow(TypePHP) + assert.Contains(t, phpFlow, "composer test") + + nodeFlow := formatFlow(TypeNode) + assert.Contains(t, nodeFlow, "npm run build") + assert.Contains(t, nodeFlow, "npm test") +}