From 9f6fa7ec17ac20db2db7f1844630806db8f03d8d Mon Sep 17 00:00:00 2001 From: Snider Date: Mon, 27 Apr 2026 17:29:14 +0100 Subject: [PATCH] =?UTF-8?q?fix(devops):=20r2=20=E2=80=94=20replace=20must*?= =?UTF-8?q?=20helpers=20with=20stdlib=20+=20verify=20CLI=20module=20resolu?= =?UTF-8?q?tion=20on=20PR=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 2 follow-up to 907c5fa. Closes residual CodeRabbit findings. Test infra: - Replaced must* test helpers across cmd_apply_test, cmd_file_sync_test, cmd_vm_test, cmd_ci_test, cmd_repo_test, cmd_wizard_test, cmd_api_testgen_test, cmd_workflow_test, secret_test, coverage_test, scan_secrets_test, snapshot_test with stdlib checks. - Deleted stale package-level test_helpers_test.go files that only supported the old must* pattern. - AX-6 maintained: no testify Go imports / go.mod requires. Remaining go.sum testify entries are transitive checksums after go mod tidy. Module graph: - CLI imports switched back to Cobra-compatible dappco.re/go/core/cli/pkg/cli module + replacements for private vanity modules. GOWORK=off resolves cleanly under isolated cache. - locales/embed.go / go.sum: i18n checksum + go mod tidy clean. Verified-but-already-correct (no code change needed): - cmd/dev/service.go: no-op core.Result{OK:true} + prompt type assertion - cmd/workspace/config.go: filepath.Abs normalisation + fmt.Errorf wrapping - tests/cli/devops/main.go: raw walk/read errors wrapped - tests/cli/devops/Taskfile.yaml: strict shell flags - cmd/dev/cmd_issues.go + cmd_reviews.go: import grouping (after CLI module correction) Verification: gofmt clean, GOWORK=off go vet + go test -count=1 ./... pass with explicit cache paths. Closes residual findings on https://github.com/dAppCore/go-devops/pull/2 Co-authored-by: Codex --- CLAUDE.md | 2 +- cmd/deploy/cmd_commands.go | 2 +- cmd/deploy/cmd_deploy.go | 2 +- cmd/dev/cmd_api.go | 2 +- cmd/dev/cmd_api_testgen.go | 2 +- cmd/dev/cmd_api_testgen_test.go | 115 ++++++++++++++++++---------- cmd/dev/cmd_apply.go | 2 +- cmd/dev/cmd_apply_test.go | 29 ++++++-- cmd/dev/cmd_ci.go | 2 +- cmd/dev/cmd_commit.go | 4 +- cmd/dev/cmd_dev.go | 2 +- cmd/dev/cmd_file_sync.go | 2 +- cmd/dev/cmd_file_sync_test.go | 64 ++++++++++++---- cmd/dev/cmd_health.go | 4 +- cmd/dev/cmd_impact.go | 2 +- cmd/dev/cmd_issues.go | 2 +- cmd/dev/cmd_pull.go | 4 +- cmd/dev/cmd_push.go | 4 +- cmd/dev/cmd_reviews.go | 2 +- cmd/dev/cmd_sync.go | 2 +- cmd/dev/cmd_tag.go | 2 +- cmd/dev/cmd_vm.go | 4 +- cmd/dev/cmd_vm_test.go | 31 ++++++-- cmd/dev/cmd_work.go | 3 +- cmd/dev/cmd_workflow.go | 2 +- cmd/dev/cmd_workflow_test.go | 64 +++++++++++----- cmd/dev/registry.go | 2 +- cmd/dev/test_helpers_test.go | 80 -------------------- cmd/docs/cmd_commands.go | 2 +- cmd/docs/cmd_docs.go | 2 +- cmd/docs/cmd_list.go | 2 +- cmd/docs/cmd_scan.go | 2 +- cmd/docs/cmd_sync.go | 2 +- cmd/gitcmd/cmd_git.go | 2 +- cmd/setup/cmd_ci.go | 2 +- cmd/setup/cmd_ci_test.go | 47 +++++++++--- cmd/setup/cmd_commands.go | 2 +- cmd/setup/cmd_github.go | 2 +- cmd/setup/cmd_registry.go | 2 +- cmd/setup/cmd_repo.go | 2 +- cmd/setup/cmd_repo_test.go | 29 ++++++-- cmd/setup/cmd_setup.go | 2 +- cmd/setup/cmd_wizard.go | 2 +- cmd/setup/cmd_wizard_test.go | 21 ++++-- cmd/setup/github_diff.go | 2 +- cmd/setup/github_labels.go | 2 +- cmd/setup/github_protection.go | 2 +- cmd/setup/github_security.go | 2 +- cmd/setup/github_webhooks.go | 2 +- cmd/setup/test_helpers_test.go | 66 ---------------- cmd/workspace/config_test.go | 59 ++++++++------- devkit/coverage_test.go | 128 ++++++++++++++++++++++++-------- devkit/scan_secrets_test.go | 76 ++++++++++++++----- devkit/secret_test.go | 90 ++++++++++++++++------ devkit/test_helpers_test.go | 48 ------------ docs/architecture.md | 1 - docs/development.md | 14 ++-- go.mod | 47 +++++++++--- go.sum | 92 +++++++++++------------ snapshot/snapshot_test.go | 80 ++++++++++++++------ snapshot/test_helpers_test.go | 45 ----------- tests/cli/devops/main.go | 2 +- 62 files changed, 728 insertions(+), 591 deletions(-) delete mode 100644 cmd/dev/test_helpers_test.go delete mode 100644 cmd/setup/test_helpers_test.go delete mode 100644 devkit/test_helpers_test.go delete mode 100644 snapshot/test_helpers_test.go diff --git a/CLAUDE.md b/CLAUDE.md index 09a86cc..d7c978e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -77,7 +77,7 @@ Configuration lives in `.core/build.yaml` (targets, ldflags) and `.core/release. ## Coding Standards - **UK English**: colour, organisation, centre -- **Tests**: testify assert/require, `_Good`/`_Bad`/`_Ugly` naming convention +- **Tests**: standard `testing` checks only; no testify. Use `_Good`/`_Bad`/`_Ugly` naming convention - **Conventional commits**: `feat(ansible):`, `fix(infra):`, `refactor(build):` - **Co-Author**: `Co-Authored-By: Virgil ` - **Licence**: EUPL-1.2 diff --git a/cmd/deploy/cmd_commands.go b/cmd/deploy/cmd_commands.go index 0edeafc..670ea96 100644 --- a/cmd/deploy/cmd_commands.go +++ b/cmd/deploy/cmd_commands.go @@ -1,7 +1,7 @@ package deploy import ( - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" _ "dappco.re/go/devops/locales" ) diff --git a/cmd/deploy/cmd_deploy.go b/cmd/deploy/cmd_deploy.go index d296791..f76155e 100644 --- a/cmd/deploy/cmd_deploy.go +++ b/cmd/deploy/cmd_deploy.go @@ -6,7 +6,7 @@ import ( "fmt" "os" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/devops/deploy/coolify" "dappco.re/go/i18n" log "dappco.re/go/log" diff --git a/cmd/dev/cmd_api.go b/cmd/dev/cmd_api.go index 1d2dcad..528f78b 100644 --- a/cmd/dev/cmd_api.go +++ b/cmd/dev/cmd_api.go @@ -1,8 +1,8 @@ package dev import ( + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" - "dappco.re/go/cli/pkg/cli" ) // addAPICommands adds the 'api' command and its subcommands to the given parent command. diff --git a/cmd/dev/cmd_api_testgen.go b/cmd/dev/cmd_api_testgen.go index 0e63168..7d248f2 100644 --- a/cmd/dev/cmd_api_testgen.go +++ b/cmd/dev/cmd_api_testgen.go @@ -5,9 +5,9 @@ import ( "path/filepath" "text/template" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" coreio "dappco.re/go/io" - "dappco.re/go/cli/pkg/cli" ) func addTestGenCommand(parent *cli.Command) { diff --git a/cmd/dev/cmd_api_testgen_test.go b/cmd/dev/cmd_api_testgen_test.go index f2395c2..39eca39 100644 --- a/cmd/dev/cmd_api_testgen_test.go +++ b/cmd/dev/cmd_api_testgen_test.go @@ -1,8 +1,8 @@ package dev import ( - "os" "path/filepath" + "slices" "strings" "testing" @@ -11,17 +11,13 @@ import ( func TestRunTestGen_Good(t *testing.T) { tmpDir := t.TempDir() - - originalWD, err := os.Getwd() - mustNoError(t, err) - t.Cleanup(func() { - _ = os.Chdir(originalWD) - }) - mustNoError(t, os.Chdir(tmpDir)) + t.Setenv("CORE_WORKING_DIRECTORY", tmpDir) serviceDir := filepath.Join(tmpDir, "pkg", "demo") - mustNoError(t, io.Local.EnsureDir(serviceDir)) - mustNoError(t, io.Local.Write(filepath.Join(serviceDir, "demo.go"), `package demo + if err := io.Local.EnsureDir(serviceDir); err != nil { + t.Fatalf("create service dir: %v", err) + } + if err := io.Local.Write(filepath.Join(serviceDir, "demo.go"), `package demo type Example struct{} @@ -30,40 +26,58 @@ const Answer = 42 var Value = Example{} func Run() {} -`)) - mustNoError(t, io.Local.Write(filepath.Join(serviceDir, "extra.go"), `package demo +`); err != nil { + t.Fatalf("write demo.go: %v", err) + } + if err := io.Local.Write(filepath.Join(serviceDir, "extra.go"), `package demo type Another struct{} func Extra() {} -`)) - mustNoError(t, io.Local.Write(filepath.Join(serviceDir, "demo_test.go"), `package demo +`); err != nil { + t.Fatalf("write extra.go: %v", err) + } + if err := io.Local.Write(filepath.Join(serviceDir, "demo_test.go"), `package demo func Ignored() {} -`)) +`); err != nil { + t.Fatalf("write demo_test.go: %v", err) + } - mustNoError(t, runTestGen()) + if err := runTestGen(); err != nil { + t.Fatalf("run test generator: %v", err) + } generatedPath := filepath.Join(tmpDir, "demo", "demo_test.go") content, err := io.Local.Read(generatedPath) - mustNoError(t, err) + if err != nil { + t.Fatalf("read generated file: %v", err) + } - mustContains(t, content, `// Code generated by "core dev api test-gen"; DO NOT EDIT.`) - mustContains(t, content, `package demo`) - mustContains(t, content, `impl "dappco.re/go/cli/demo"`) - mustContains(t, content, `type _ = impl.Example`) - mustContains(t, content, `type _ = impl.Another`) - mustContains(t, content, `const _ = impl.Answer`) - mustContains(t, content, `var _ = impl.Value`) - mustContains(t, content, `var _ = impl.Run`) - mustContains(t, content, `var _ = impl.Extra`) - mustNotContains(t, content, `Ignored`) + for _, want := range []string{ + `// Code generated by "core dev api test-gen"; DO NOT EDIT.`, + `package demo`, + `impl "dappco.re/go/cli/demo"`, + `type _ = impl.Example`, + `type _ = impl.Another`, + `const _ = impl.Answer`, + `var _ = impl.Value`, + `var _ = impl.Run`, + `var _ = impl.Extra`, + } { + if !strings.Contains(content, want) { + t.Fatalf("generated content missing %q", want) + } + } + if strings.Contains(content, `Ignored`) { + t.Fatal("generated content includes ignored symbol") + } } func TestGeneratePublicAPITestFile_Good(t *testing.T) { tmpDir := t.TempDir() - mustNoError(t, generatePublicAPITestFile( + if err := generatePublicAPITestFile( filepath.Join(tmpDir, "demo"), filepath.Join(tmpDir, "demo", "demo_test.go"), "demo", @@ -71,44 +85,63 @@ func TestGeneratePublicAPITestFile_Good(t *testing.T) { {Name: "Example", Kind: "type"}, {Name: "Answer", Kind: "const"}, }, - )) + ); err != nil { + t.Fatalf("generate public API test file: %v", err) + } content, err := io.Local.Read(filepath.Join(tmpDir, "demo", "demo_test.go")) - mustNoError(t, err) + if err != nil { + t.Fatalf("read generated public API test file: %v", err) + } - mustTrue(t, strings.Contains(content, `type _ = impl.Example`)) - mustTrue(t, strings.Contains(content, `const _ = impl.Answer`)) + for _, want := range []string{`type _ = impl.Example`, `const _ = impl.Answer`} { + if !strings.Contains(content, want) { + t.Fatalf("generated content missing %q", want) + } + } } func TestGetExportedSymbols_MultiFile_Good(t *testing.T) { tmpDir := t.TempDir() serviceDir := filepath.Join(tmpDir, "demo") - mustNoError(t, io.Local.EnsureDir(serviceDir)) - mustNoError(t, io.Local.Write(filepath.Join(serviceDir, "demo.go"), `package demo + if err := io.Local.EnsureDir(serviceDir); err != nil { + t.Fatalf("create service dir: %v", err) + } + if err := io.Local.Write(filepath.Join(serviceDir, "demo.go"), `package demo type Example struct{} const Answer = 42 -`)) - mustNoError(t, io.Local.Write(filepath.Join(serviceDir, "extra.go"), `package demo +`); err != nil { + t.Fatalf("write demo.go: %v", err) + } + if err := io.Local.Write(filepath.Join(serviceDir, "extra.go"), `package demo var Value = Example{} func Run() {} -`)) - mustNoError(t, io.Local.Write(filepath.Join(serviceDir, "demo_test.go"), `package demo +`); err != nil { + t.Fatalf("write extra.go: %v", err) + } + if err := io.Local.Write(filepath.Join(serviceDir, "demo_test.go"), `package demo type Ignored struct{} -`)) +`); err != nil { + t.Fatalf("write demo_test.go: %v", err) + } symbols, err := getExportedSymbols(serviceDir) - mustNoError(t, err) + if err != nil { + t.Fatalf("get exported symbols: %v", err) + } want := []symbolInfo{ {Name: "Answer", Kind: "const"}, {Name: "Example", Kind: "type"}, {Name: "Run", Kind: "func"}, {Name: "Value", Kind: "var"}, } - mustDeepEqual(t, want, symbols) + if !slices.Equal(symbols, want) { + t.Fatalf("symbols = %v, want %v", symbols, want) + } } diff --git a/cmd/dev/cmd_apply.go b/cmd/dev/cmd_apply.go index aa58abd..7ec5afe 100644 --- a/cmd/dev/cmd_apply.go +++ b/cmd/dev/cmd_apply.go @@ -14,12 +14,12 @@ import ( "path/filepath" "sort" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" "dappco.re/go/io" core "dappco.re/go/log" "dappco.re/go/scm/git" "dappco.re/go/scm/repos" - "dappco.re/go/cli/pkg/cli" ) // Apply command flags diff --git a/cmd/dev/cmd_apply_test.go b/cmd/dev/cmd_apply_test.go index d2c6d6c..b406bdd 100644 --- a/cmd/dev/cmd_apply_test.go +++ b/cmd/dev/cmd_apply_test.go @@ -17,21 +17,34 @@ func TestFilterTargetRepos_Good(t *testing.T) { t.Run("exact names", func(t *testing.T) { matched := filterTargetRepos(registry, "core-api,docs-site") - mustLen(t, matched, 2) - mustEqual(t, "core-api", matched[0].Name) - mustEqual(t, "docs-site", matched[1].Name) + if len(matched) != 2 { + t.Fatalf("matched length = %d, want 2", len(matched)) + } + if matched[0].Name != "core-api" { + t.Fatalf("matched[0].Name = %q, want %q", matched[0].Name, "core-api") + } + if matched[1].Name != "docs-site" { + t.Fatalf("matched[1].Name = %q, want %q", matched[1].Name, "docs-site") + } }) t.Run("glob patterns", func(t *testing.T) { matched := filterTargetRepos(registry, "core-*,sites/*") - mustLen(t, matched, 3) - mustEqual(t, "core-api", matched[0].Name) - mustEqual(t, "core-web", matched[1].Name) - mustEqual(t, "docs-site", matched[2].Name) + if len(matched) != 3 { + t.Fatalf("matched length = %d, want 3", len(matched)) + } + wantNames := []string{"core-api", "core-web", "docs-site"} + for i, want := range wantNames { + if matched[i].Name != want { + t.Fatalf("matched[%d].Name = %q, want %q", i, matched[i].Name, want) + } + } }) t.Run("all repos when empty", func(t *testing.T) { matched := filterTargetRepos(registry, "") - mustLen(t, matched, 3) + if len(matched) != 3 { + t.Fatalf("matched length = %d, want 3", len(matched)) + } }) } diff --git a/cmd/dev/cmd_ci.go b/cmd/dev/cmd_ci.go index 7f7be34..ee82d1e 100644 --- a/cmd/dev/cmd_ci.go +++ b/cmd/dev/cmd_ci.go @@ -7,7 +7,7 @@ import ( "code.gitea.io/sdk/gitea" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" ) diff --git a/cmd/dev/cmd_commit.go b/cmd/dev/cmd_commit.go index 5b93d60..fd906bf 100644 --- a/cmd/dev/cmd_commit.go +++ b/cmd/dev/cmd_commit.go @@ -5,10 +5,10 @@ import ( "os" "path/filepath" - "dappco.re/go/cli/pkg/cli" - "dappco.re/go/scm/git" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" coreio "dappco.re/go/io" + "dappco.re/go/scm/git" ) // Commit command flags diff --git a/cmd/dev/cmd_dev.go b/cmd/dev/cmd_dev.go index a177c8a..f131142 100644 --- a/cmd/dev/cmd_dev.go +++ b/cmd/dev/cmd_dev.go @@ -34,8 +34,8 @@ package dev import ( + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" - "dappco.re/go/cli/pkg/cli" _ "dappco.re/go/devops/locales" ) diff --git a/cmd/dev/cmd_file_sync.go b/cmd/dev/cmd_file_sync.go index abdb3f2..e762486 100644 --- a/cmd/dev/cmd_file_sync.go +++ b/cmd/dev/cmd_file_sync.go @@ -14,12 +14,12 @@ import ( "path/filepath" "strings" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" coreio "dappco.re/go/io" "dappco.re/go/log" "dappco.re/go/scm/git" "dappco.re/go/scm/repos" - "dappco.re/go/cli/pkg/cli" ) // File sync command flags diff --git a/cmd/dev/cmd_file_sync_test.go b/cmd/dev/cmd_file_sync_test.go index 7ac5fef..da77906 100644 --- a/cmd/dev/cmd_file_sync_test.go +++ b/cmd/dev/cmd_file_sync_test.go @@ -1,9 +1,10 @@ package dev import ( + "slices" "testing" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" ) func TestAddFileSyncCommand_Good(t *testing.T) { @@ -12,28 +13,63 @@ func TestAddFileSyncCommand_Good(t *testing.T) { AddDevCommands(root) syncCmd, _, err := root.Find([]string{"dev", "sync"}) - mustNoError(t, err) - mustNotNil(t, syncCmd) + if err != nil { + t.Fatalf("find sync command: %v", err) + } + if syncCmd == nil { + t.Fatal("expected sync command") + } yesFlag := syncCmd.Flags().Lookup("yes") - mustNotNil(t, yesFlag) - mustEqual(t, "y", yesFlag.Shorthand) + if yesFlag == nil { + t.Fatal("expected yes flag") + } + if yesFlag.Shorthand != "y" { + t.Fatalf("yes shorthand = %q, want %q", yesFlag.Shorthand, "y") + } - mustNotNil(t, syncCmd.Flags().Lookup("dry-run")) - mustNotNil(t, syncCmd.Flags().Lookup("push")) + if syncCmd.Flags().Lookup("dry-run") == nil { + t.Fatal("expected dry-run flag") + } + if syncCmd.Flags().Lookup("push") == nil { + t.Fatal("expected push flag") + } } func TestSplitPatterns_Good(t *testing.T) { patterns := splitPatterns("packages/core-*, apps/* ,services/*,") want := []string{"packages/core-*", "apps/*", "services/*"} - mustDeepEqual(t, want, patterns) + if !slices.Equal(patterns, want) { + t.Fatalf("patterns = %v, want %v", patterns, want) + } } func TestMatchGlob_Good(t *testing.T) { - mustTrue(t, matchGlob("packages/core-xyz", "packages/core-*")) - mustTrue(t, matchGlob("packages/core-xyz", "*/core-*")) - mustTrue(t, matchGlob("a-b", "a?b")) - mustTrue(t, matchGlob("foo", "foo")) - mustFalse(t, matchGlob("core-other", "packages/*")) - mustFalse(t, matchGlob("abc", "[]")) + trueCases := []struct { + name string + pattern string + }{ + {name: "packages/core-xyz", pattern: "packages/core-*"}, + {name: "packages/core-xyz", pattern: "*/core-*"}, + {name: "a-b", pattern: "a?b"}, + {name: "foo", pattern: "foo"}, + } + for _, tc := range trueCases { + if !matchGlob(tc.name, tc.pattern) { + t.Fatalf("matchGlob(%q, %q) = false, want true", tc.name, tc.pattern) + } + } + + falseCases := []struct { + name string + pattern string + }{ + {name: "core-other", pattern: "packages/*"}, + {name: "abc", pattern: "[]"}, + } + for _, tc := range falseCases { + if matchGlob(tc.name, tc.pattern) { + t.Fatalf("matchGlob(%q, %q) = true, want false", tc.name, tc.pattern) + } + } } diff --git a/cmd/dev/cmd_health.go b/cmd/dev/cmd_health.go index aa9c8bb..1608387 100644 --- a/cmd/dev/cmd_health.go +++ b/cmd/dev/cmd_health.go @@ -7,9 +7,9 @@ import ( "slices" "strings" - "dappco.re/go/cli/pkg/cli" - "dappco.re/go/scm/git" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" + "dappco.re/go/scm/git" ) // Health command flags diff --git a/cmd/dev/cmd_impact.go b/cmd/dev/cmd_impact.go index d2c401e..92ac8ce 100644 --- a/cmd/dev/cmd_impact.go +++ b/cmd/dev/cmd_impact.go @@ -3,7 +3,7 @@ package dev import ( "slices" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" "dappco.re/go/io" log "dappco.re/go/log" diff --git a/cmd/dev/cmd_issues.go b/cmd/dev/cmd_issues.go index 9a0f5c7..fe46ba3 100644 --- a/cmd/dev/cmd_issues.go +++ b/cmd/dev/cmd_issues.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" "code.gitea.io/sdk/gitea" diff --git a/cmd/dev/cmd_pull.go b/cmd/dev/cmd_pull.go index b3aecc7..effa432 100644 --- a/cmd/dev/cmd_pull.go +++ b/cmd/dev/cmd_pull.go @@ -4,9 +4,9 @@ import ( "context" "os/exec" - "dappco.re/go/cli/pkg/cli" - "dappco.re/go/scm/git" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" + "dappco.re/go/scm/git" ) // Pull command flags diff --git a/cmd/dev/cmd_push.go b/cmd/dev/cmd_push.go index e3a715a..082030b 100644 --- a/cmd/dev/cmd_push.go +++ b/cmd/dev/cmd_push.go @@ -5,9 +5,9 @@ import ( "os" "path/filepath" - "dappco.re/go/cli/pkg/cli" - "dappco.re/go/scm/git" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" + "dappco.re/go/scm/git" ) // Push command flags diff --git a/cmd/dev/cmd_reviews.go b/cmd/dev/cmd_reviews.go index 657df60..c46ca7d 100644 --- a/cmd/dev/cmd_reviews.go +++ b/cmd/dev/cmd_reviews.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" "code.gitea.io/sdk/gitea" diff --git a/cmd/dev/cmd_sync.go b/cmd/dev/cmd_sync.go index 22eee31..18fdec5 100644 --- a/cmd/dev/cmd_sync.go +++ b/cmd/dev/cmd_sync.go @@ -13,7 +13,7 @@ import ( "dappco.re/go/i18n" coreio "dappco.re/go/io" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "golang.org/x/text/cases" "golang.org/x/text/language" diff --git a/cmd/dev/cmd_tag.go b/cmd/dev/cmd_tag.go index 8e5e5ff..4dc723e 100644 --- a/cmd/dev/cmd_tag.go +++ b/cmd/dev/cmd_tag.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" log "dappco.re/go/log" ) diff --git a/cmd/dev/cmd_vm.go b/cmd/dev/cmd_vm.go index 2e378a4..ff77dd3 100644 --- a/cmd/dev/cmd_vm.go +++ b/cmd/dev/cmd_vm.go @@ -5,11 +5,11 @@ import ( "os" "time" + "dappco.re/go/container/devenv" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" "dappco.re/go/io" log "dappco.re/go/log" - "dappco.re/go/cli/pkg/cli" - "dappco.re/go/container/devenv" ) // addVMCommands adds the dev environment VM commands to the dev parent command. diff --git a/cmd/dev/cmd_vm_test.go b/cmd/dev/cmd_vm_test.go index 2281bf0..34a9502 100644 --- a/cmd/dev/cmd_vm_test.go +++ b/cmd/dev/cmd_vm_test.go @@ -1,9 +1,10 @@ package dev import ( + "slices" "testing" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" ) func TestAddVMStatusCommand_Good(t *testing.T) { @@ -12,13 +13,27 @@ func TestAddVMStatusCommand_Good(t *testing.T) { AddDevCommands(root) statusCmd, _, err := root.Find([]string{"dev", "status"}) - mustNoError(t, err) - mustNotNil(t, statusCmd) - mustEqual(t, "status", statusCmd.Use) - mustContainsString(t, statusCmd.Aliases, "vm-status") + if err != nil { + t.Fatalf("find status command: %v", err) + } + if statusCmd == nil { + t.Fatal("expected status command") + } + if statusCmd.Use != "status" { + t.Fatalf("status command use = %q, want %q", statusCmd.Use, "status") + } + if !slices.Contains(statusCmd.Aliases, "vm-status") { + t.Fatalf("status aliases = %v, want vm-status", statusCmd.Aliases) + } aliasCmd, _, err := root.Find([]string{"dev", "vm-status"}) - mustNoError(t, err) - mustNotNil(t, aliasCmd) - mustTrue(t, statusCmd == aliasCmd) + if err != nil { + t.Fatalf("find vm-status alias: %v", err) + } + if aliasCmd == nil { + t.Fatal("expected vm-status alias command") + } + if statusCmd != aliasCmd { + t.Fatal("expected vm-status alias to resolve to status command") + } } diff --git a/cmd/dev/cmd_work.go b/cmd/dev/cmd_work.go index 6d0759f..3d0abb6 100644 --- a/cmd/dev/cmd_work.go +++ b/cmd/dev/cmd_work.go @@ -6,7 +6,7 @@ import ( "slices" "strings" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" "dappco.re/go/scm/git" ) @@ -286,4 +286,3 @@ func printStatusTable(statuses []git.RepoStatus) { ) } } - diff --git a/cmd/dev/cmd_workflow.go b/cmd/dev/cmd_workflow.go index 7bffdaa..754f21b 100644 --- a/cmd/dev/cmd_workflow.go +++ b/cmd/dev/cmd_workflow.go @@ -7,10 +7,10 @@ import ( "slices" "strings" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" "dappco.re/go/io" "dappco.re/go/scm/repos" - "dappco.re/go/cli/pkg/cli" ) // Workflow command flags diff --git a/cmd/dev/cmd_workflow_test.go b/cmd/dev/cmd_workflow_test.go index 82d6080..1700018 100644 --- a/cmd/dev/cmd_workflow_test.go +++ b/cmd/dev/cmd_workflow_test.go @@ -13,18 +13,26 @@ func TestFindWorkflows_Good(t *testing.T) { // Create a temp directory with workflow files tmpDir := t.TempDir() workflowsDir := filepath.Join(tmpDir, ".github", "workflows") - mustNoError(t, io.Local.EnsureDir(workflowsDir)) + if err := io.Local.EnsureDir(workflowsDir); err != nil { + t.Fatalf("create workflows dir: %v", err) + } // Create some workflow files for _, name := range []string{"qa.yml", "tests.yml", "codeql.yaml"} { - mustNoError(t, io.Local.Write(filepath.Join(workflowsDir, name), "name: Test")) + if err := io.Local.Write(filepath.Join(workflowsDir, name), "name: Test"); err != nil { + t.Fatalf("write workflow %s: %v", name, err) + } } // Create a non-workflow file (should be ignored) - mustNoError(t, io.Local.Write(filepath.Join(workflowsDir, "readme.md"), "# Workflows")) + if err := io.Local.Write(filepath.Join(workflowsDir, "readme.md"), "# Workflows"); err != nil { + t.Fatalf("write readme: %v", err) + } workflows := findWorkflows(tmpDir) - mustLen(t, workflows, 3) + if len(workflows) != 3 { + t.Fatalf("workflows length = %d, want 3", len(workflows)) + } // Check that all expected workflows are found found := make(map[string]bool) @@ -33,7 +41,9 @@ func TestFindWorkflows_Good(t *testing.T) { } for _, expected := range []string{"qa.yml", "tests.yml", "codeql.yaml"} { - mustTrue(t, found[expected]) + if !found[expected] { + t.Fatalf("expected workflow %s in %v", expected, workflows) + } } } @@ -41,43 +51,61 @@ func TestFindWorkflows_NoWorkflowsDir_Bad(t *testing.T) { tmpDir := t.TempDir() workflows := findWorkflows(tmpDir) - mustLen(t, workflows, 0) + if len(workflows) != 0 { + t.Fatalf("workflows length = %d, want 0", len(workflows)) + } } func TestFindTemplateWorkflow_Good(t *testing.T) { tmpDir := t.TempDir() templatesDir := filepath.Join(tmpDir, ".github", "workflow-templates") - mustNoError(t, io.Local.EnsureDir(templatesDir)) + if err := io.Local.EnsureDir(templatesDir); err != nil { + t.Fatalf("create templates dir: %v", err) + } templateContent := "name: QA\non: [push]" - mustNoError(t, io.Local.Write(filepath.Join(templatesDir, "qa.yml"), templateContent)) + if err := io.Local.Write(filepath.Join(templatesDir, "qa.yml"), templateContent); err != nil { + t.Fatalf("write template workflow: %v", err) + } // Test finding with .yml extension result := findTemplateWorkflow(tmpDir, "qa.yml") - mustTrue(t, result != "") + if result == "" { + t.Fatal("expected template workflow for qa.yml") + } // Test finding without extension (should auto-add .yml) result = findTemplateWorkflow(tmpDir, "qa") - mustTrue(t, result != "") + if result == "" { + t.Fatal("expected template workflow for qa") + } } func TestFindTemplateWorkflow_FallbackToWorkflows_Good(t *testing.T) { tmpDir := t.TempDir() workflowsDir := filepath.Join(tmpDir, ".github", "workflows") - mustNoError(t, io.Local.EnsureDir(workflowsDir)) + if err := io.Local.EnsureDir(workflowsDir); err != nil { + t.Fatalf("create workflows dir: %v", err) + } templateContent := "name: Tests\non: [push]" - mustNoError(t, io.Local.Write(filepath.Join(workflowsDir, "tests.yml"), templateContent)) + if err := io.Local.Write(filepath.Join(workflowsDir, "tests.yml"), templateContent); err != nil { + t.Fatalf("write workflow: %v", err) + } result := findTemplateWorkflow(tmpDir, "tests.yml") - mustTrue(t, result != "") + if result == "" { + t.Fatal("expected fallback workflow") + } } func TestFindTemplateWorkflow_NotFound_Bad(t *testing.T) { tmpDir := t.TempDir() result := findTemplateWorkflow(tmpDir, "nonexistent.yml") - mustEqual(t, "", result) + if result != "" { + t.Fatalf("result = %q, want empty", result) + } } func TestTemplateNames_Good(t *testing.T) { @@ -89,8 +117,8 @@ func TestTemplateNames_Good(t *testing.T) { names := slices.Sorted(maps.Keys(templateSet)) - mustLen(t, names, 3) - mustEqual(t, "a.yml", names[0]) - mustEqual(t, "m.yml", names[1]) - mustEqual(t, "z.yml", names[2]) + want := []string{"a.yml", "m.yml", "z.yml"} + if !slices.Equal(names, want) { + t.Fatalf("names = %v, want %v", names, want) + } } diff --git a/cmd/dev/registry.go b/cmd/dev/registry.go index 12cb819..e1c6bdf 100644 --- a/cmd/dev/registry.go +++ b/cmd/dev/registry.go @@ -5,11 +5,11 @@ import ( "path/filepath" "strings" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/devops/cmd/workspace" "dappco.re/go/i18n" "dappco.re/go/io" "dappco.re/go/scm/repos" - "dappco.re/go/cli/pkg/cli" ) // loadRegistryWithConfig loads the registry and applies workspace configuration. diff --git a/cmd/dev/test_helpers_test.go b/cmd/dev/test_helpers_test.go deleted file mode 100644 index 2a85ade..0000000 --- a/cmd/dev/test_helpers_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package dev - -import ( - "reflect" - "strings" - "testing" -) - -func mustNoError(t *testing.T, err error) { - t.Helper() - if err != nil { - t.Fatalf("unexpected error: %v", err) - } -} - -func mustEqual[T comparable](t *testing.T, want, got T) { - t.Helper() - if want != got { - t.Fatalf("want %v, got %v", want, got) - } -} - -func mustDeepEqual(t *testing.T, want, got any) { - t.Helper() - if !reflect.DeepEqual(want, got) { - t.Fatalf("want %v, got %v", want, got) - } -} - -func mustContains(t *testing.T, s, sub string) { - t.Helper() - if !strings.Contains(s, sub) { - t.Fatalf("expected %q to contain %q", s, sub) - } -} - -func mustContainsString(t *testing.T, haystack []string, needle string) { - t.Helper() - for _, s := range haystack { - if s == needle { - return - } - } - t.Fatalf("expected %v to contain %q", haystack, needle) -} - -func mustNotContains(t *testing.T, s, sub string) { - t.Helper() - if strings.Contains(s, sub) { - t.Fatalf("expected %q to not contain %q", s, sub) - } -} - -func mustTrue(t *testing.T, cond bool) { - t.Helper() - if !cond { - t.Fatal("expected true") - } -} - -func mustFalse(t *testing.T, cond bool) { - t.Helper() - if cond { - t.Fatal("expected false") - } -} - -func mustLen[T any](t *testing.T, got []T, want int) { - t.Helper() - if len(got) != want { - t.Fatalf("want length %d, got %d", want, len(got)) - } -} - -func mustNotNil(t *testing.T, v any) { - t.Helper() - if v == nil { - t.Fatal("expected non-nil") - } -} diff --git a/cmd/docs/cmd_commands.go b/cmd/docs/cmd_commands.go index 8f0b462..a0675ff 100644 --- a/cmd/docs/cmd_commands.go +++ b/cmd/docs/cmd_commands.go @@ -9,7 +9,7 @@ package docs import ( - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" _ "dappco.re/go/devops/locales" ) diff --git a/cmd/docs/cmd_docs.go b/cmd/docs/cmd_docs.go index 91d4b70..266dce3 100644 --- a/cmd/docs/cmd_docs.go +++ b/cmd/docs/cmd_docs.go @@ -2,7 +2,7 @@ package docs import ( - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" ) diff --git a/cmd/docs/cmd_list.go b/cmd/docs/cmd_list.go index cfe57d2..e7054ee 100644 --- a/cmd/docs/cmd_list.go +++ b/cmd/docs/cmd_list.go @@ -3,7 +3,7 @@ package docs import ( "strings" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" ) diff --git a/cmd/docs/cmd_scan.go b/cmd/docs/cmd_scan.go index d93278a..0810727 100644 --- a/cmd/docs/cmd_scan.go +++ b/cmd/docs/cmd_scan.go @@ -6,11 +6,11 @@ import ( "path/filepath" "strings" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/devops/cmd/workspace" "dappco.re/go/i18n" "dappco.re/go/io" "dappco.re/go/scm/repos" - "dappco.re/go/cli/pkg/cli" ) // RepoDocInfo holds documentation info for a repo diff --git a/cmd/docs/cmd_sync.go b/cmd/docs/cmd_sync.go index 41a9359..8d30ee6 100644 --- a/cmd/docs/cmd_sync.go +++ b/cmd/docs/cmd_sync.go @@ -6,10 +6,10 @@ import ( "path/filepath" "strings" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" "dappco.re/go/io" "dappco.re/go/scm/repos" - "dappco.re/go/cli/pkg/cli" ) // Flag variables for sync command diff --git a/cmd/gitcmd/cmd_git.go b/cmd/gitcmd/cmd_git.go index e63d826..16fe748 100644 --- a/cmd/gitcmd/cmd_git.go +++ b/cmd/gitcmd/cmd_git.go @@ -13,8 +13,8 @@ package gitcmd import ( + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/devops/cmd/dev" - "dappco.re/go/cli/pkg/cli" "dappco.re/go/i18n" ) diff --git a/cmd/setup/cmd_ci.go b/cmd/setup/cmd_ci.go index 2dd35b7..eff22ff 100644 --- a/cmd/setup/cmd_ci.go +++ b/cmd/setup/cmd_ci.go @@ -6,8 +6,8 @@ import ( "path/filepath" "runtime" + "dappco.re/go/core/cli/pkg/cli" coreio "dappco.re/go/io" - "dappco.re/go/cli/pkg/cli" "gopkg.in/yaml.v3" ) diff --git a/cmd/setup/cmd_ci_test.go b/cmd/setup/cmd_ci_test.go index 2f9d8bc..4715f86 100644 --- a/cmd/setup/cmd_ci_test.go +++ b/cmd/setup/cmd_ci_test.go @@ -4,6 +4,7 @@ import ( "bytes" "io" "os" + "strings" "testing" ) @@ -12,7 +13,9 @@ func captureStdout(t *testing.T, fn func() error) (string, error) { oldStdout := os.Stdout r, w, err := os.Pipe() - mustNoError(t, err) + if err != nil { + t.Fatalf("create pipe: %v", err) + } defer func() { _ = r.Close() }() @@ -34,8 +37,12 @@ func captureStdout(t *testing.T, fn func() error) (string, error) { runErr := fn() - mustNoError(t, w.Close()) - mustNoError(t, <-errC) + if err := w.Close(); err != nil { + t.Fatalf("close pipe writer: %v", err) + } + if err := <-errC; err != nil { + t.Fatalf("copy stdout: %v", err) + } out := <-outC return out, runErr @@ -44,19 +51,35 @@ func captureStdout(t *testing.T, fn func() error) (string, error) { func TestDefaultCIConfig_Good(t *testing.T) { cfg := DefaultCIConfig() - mustEqual(t, "host-uk/tap", cfg.Tap) - mustEqual(t, "core", cfg.Formula) - mustEqual(t, "https://forge.lthn.ai/core/scoop-bucket.git", cfg.ScoopBucket) - mustEqual(t, "core-cli", cfg.ChocolateyPkg) - mustEqual(t, "host-uk/core", cfg.Repository) - mustEqual(t, "dev", cfg.DefaultVersion) + checks := map[string]struct { + got string + want string + }{ + "tap": {got: cfg.Tap, want: "host-uk/tap"}, + "formula": {got: cfg.Formula, want: "core"}, + "scoop bucket": {got: cfg.ScoopBucket, want: "https://forge.lthn.ai/core/scoop-bucket.git"}, + "chocolatey pkg": {got: cfg.ChocolateyPkg, want: "core-cli"}, + "repository": {got: cfg.Repository, want: "host-uk/core"}, + "default version": {got: cfg.DefaultVersion, want: "dev"}, + } + for name, check := range checks { + if check.got != check.want { + t.Fatalf("%s = %q, want %q", name, check.got, check.want) + } + } } func TestOutputPowershellInstall_Good(t *testing.T) { out, err := captureStdout(t, func() error { return outputPowershellInstall(DefaultCIConfig(), "dev") }) - mustNoError(t, err) - mustContains(t, out, `scoop bucket add host-uk $ScoopBucket`) - mustNotContains(t, out, `https://https://forge.lthn.ai/core/scoop-bucket.git`) + if err != nil { + t.Fatalf("output powershell install: %v", err) + } + if !strings.Contains(out, `scoop bucket add host-uk $ScoopBucket`) { + t.Fatalf("output missing scoop bucket command: %q", out) + } + if strings.Contains(out, `https://https://forge.lthn.ai/core/scoop-bucket.git`) { + t.Fatalf("output contains doubled URL scheme: %q", out) + } } diff --git a/cmd/setup/cmd_commands.go b/cmd/setup/cmd_commands.go index cb74f7b..5ac5c23 100644 --- a/cmd/setup/cmd_commands.go +++ b/cmd/setup/cmd_commands.go @@ -24,7 +24,7 @@ package setup import ( - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" _ "dappco.re/go/devops/locales" diff --git a/cmd/setup/cmd_github.go b/cmd/setup/cmd_github.go index 8d83b87..89fca77 100644 --- a/cmd/setup/cmd_github.go +++ b/cmd/setup/cmd_github.go @@ -21,7 +21,7 @@ import ( "os/exec" "path/filepath" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" coreio "dappco.re/go/io" log "dappco.re/go/log" diff --git a/cmd/setup/cmd_registry.go b/cmd/setup/cmd_registry.go index 7087fc2..d5cec4b 100644 --- a/cmd/setup/cmd_registry.go +++ b/cmd/setup/cmd_registry.go @@ -13,12 +13,12 @@ import ( "path/filepath" "strings" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/devops/cmd/workspace" "dappco.re/go/i18n" coreio "dappco.re/go/io" log "dappco.re/go/log" "dappco.re/go/scm/repos" - "dappco.re/go/cli/pkg/cli" ) // runRegistrySetup loads a registry from path and runs setup. diff --git a/cmd/setup/cmd_repo.go b/cmd/setup/cmd_repo.go index 8458530..180a3c5 100644 --- a/cmd/setup/cmd_repo.go +++ b/cmd/setup/cmd_repo.go @@ -14,10 +14,10 @@ import ( "path/filepath" "strings" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" coreio "dappco.re/go/io" log "dappco.re/go/log" - "dappco.re/go/cli/pkg/cli" ) var repoDryRun bool diff --git a/cmd/setup/cmd_repo_test.go b/cmd/setup/cmd_repo_test.go index 00ebc59..18788a7 100644 --- a/cmd/setup/cmd_repo_test.go +++ b/cmd/setup/cmd_repo_test.go @@ -8,23 +8,34 @@ import ( func TestRunRepoSetup_CreatesCoreConfigs_Good(t *testing.T) { dir := t.TempDir() - mustNoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/test\n"), 0o644)) + if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/test\n"), 0o644); err != nil { + t.Fatalf("write go.mod: %v", err) + } - mustNoError(t, runRepoSetup(dir, false)) + if err := runRepoSetup(dir, false); err != nil { + t.Fatalf("run repo setup: %v", err) + } for _, name := range []string{"build.yaml", "release.yaml", "test.yaml"} { path := filepath.Join(dir, ".core", name) - _, err := os.Stat(path) - mustNoErrorf(t, err, "expected %s to exist", path) + if _, err := os.Stat(path); err != nil { + t.Fatalf("expected %s to exist: %v", path, err) + } } } func TestDetectProjectType_PrefersPackageOverComposer_Good(t *testing.T) { dir := t.TempDir() - mustNoError(t, os.WriteFile(filepath.Join(dir, "package.json"), []byte("{}\n"), 0o644)) - mustNoError(t, os.WriteFile(filepath.Join(dir, "composer.json"), []byte("{}\n"), 0o644)) + if err := os.WriteFile(filepath.Join(dir, "package.json"), []byte("{}\n"), 0o644); err != nil { + t.Fatalf("write package.json: %v", err) + } + if err := os.WriteFile(filepath.Join(dir, "composer.json"), []byte("{}\n"), 0o644); err != nil { + t.Fatalf("write composer.json: %v", err) + } - mustEqual(t, "node", detectProjectType(dir)) + if got := detectProjectType(dir); got != "node" { + t.Fatalf("detectProjectType = %q, want %q", got, "node") + } } func TestParseGitHubRepoURL_Good(t *testing.T) { @@ -44,7 +55,9 @@ func TestParseGitHubRepoURL_Good(t *testing.T) { for remote, expected := range cases { t.Run(remote, func(t *testing.T) { - mustEqual(t, expected, parseGitHubRepoURL(remote)) + if got := parseGitHubRepoURL(remote); got != expected { + t.Fatalf("parseGitHubRepoURL(%q) = %q, want %q", remote, got, expected) + } }) } } diff --git a/cmd/setup/cmd_setup.go b/cmd/setup/cmd_setup.go index a337955..3ffb558 100644 --- a/cmd/setup/cmd_setup.go +++ b/cmd/setup/cmd_setup.go @@ -2,8 +2,8 @@ package setup import ( + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" - "dappco.re/go/cli/pkg/cli" ) // Style aliases from shared package diff --git a/cmd/setup/cmd_wizard.go b/cmd/setup/cmd_wizard.go index 1a9c96e..7a7c836 100644 --- a/cmd/setup/cmd_wizard.go +++ b/cmd/setup/cmd_wizard.go @@ -7,9 +7,9 @@ import ( "os" "slices" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" "dappco.re/go/scm/repos" - "dappco.re/go/cli/pkg/cli" "golang.org/x/term" ) diff --git a/cmd/setup/cmd_wizard_test.go b/cmd/setup/cmd_wizard_test.go index 2fac324..00452d2 100644 --- a/cmd/setup/cmd_wizard_test.go +++ b/cmd/setup/cmd_wizard_test.go @@ -1,6 +1,7 @@ package setup import ( + "slices" "testing" "dappco.re/go/scm/repos" @@ -15,9 +16,15 @@ func TestFilterReposByTypes_Good(t *testing.T) { filtered := filterReposByTypes(reposList, []string{"module", "product"}) - mustLen(t, filtered, 2) - mustEqual(t, "module-a", filtered[0].Name) - mustEqual(t, "product-a", filtered[1].Name) + if len(filtered) != 2 { + t.Fatalf("filtered length = %d, want 2", len(filtered)) + } + if filtered[0].Name != "module-a" { + t.Fatalf("filtered[0].Name = %q, want %q", filtered[0].Name, "module-a") + } + if filtered[1].Name != "product-a" { + t.Fatalf("filtered[1].Name = %q, want %q", filtered[1].Name, "product-a") + } } func TestFilterReposByTypes_EmptyFilter_Good(t *testing.T) { @@ -28,6 +35,10 @@ func TestFilterReposByTypes_EmptyFilter_Good(t *testing.T) { filtered := filterReposByTypes(reposList, nil) - mustLen(t, filtered, 2) - mustDeepEqual(t, reposList, filtered) + if len(filtered) != 2 { + t.Fatalf("filtered length = %d, want 2", len(filtered)) + } + if !slices.Equal(filtered, reposList) { + t.Fatalf("filtered = %v, want %v", filtered, reposList) + } } diff --git a/cmd/setup/github_diff.go b/cmd/setup/github_diff.go index a27c278..5a2c27e 100644 --- a/cmd/setup/github_diff.go +++ b/cmd/setup/github_diff.go @@ -8,7 +8,7 @@ import ( "slices" "strings" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" "dappco.re/go/i18n" ) diff --git a/cmd/setup/github_labels.go b/cmd/setup/github_labels.go index d9abbad..3f490e3 100644 --- a/cmd/setup/github_labels.go +++ b/cmd/setup/github_labels.go @@ -12,7 +12,7 @@ import ( "os/exec" "strings" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" ) // GitHubLabel represents a label as returned by the GitHub API. diff --git a/cmd/setup/github_protection.go b/cmd/setup/github_protection.go index 306401f..6ec111a 100644 --- a/cmd/setup/github_protection.go +++ b/cmd/setup/github_protection.go @@ -12,7 +12,7 @@ import ( "os/exec" "strings" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" log "dappco.re/go/log" ) diff --git a/cmd/setup/github_security.go b/cmd/setup/github_security.go index 529d764..02bd1d4 100644 --- a/cmd/setup/github_security.go +++ b/cmd/setup/github_security.go @@ -14,7 +14,7 @@ import ( "os/exec" "strings" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" log "dappco.re/go/log" ) diff --git a/cmd/setup/github_webhooks.go b/cmd/setup/github_webhooks.go index 8f72720..9310c3d 100644 --- a/cmd/setup/github_webhooks.go +++ b/cmd/setup/github_webhooks.go @@ -12,7 +12,7 @@ import ( "os/exec" "strings" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" log "dappco.re/go/log" ) diff --git a/cmd/setup/test_helpers_test.go b/cmd/setup/test_helpers_test.go deleted file mode 100644 index c2cc3b0..0000000 --- a/cmd/setup/test_helpers_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package setup - -import ( - "reflect" - "strings" - "testing" -) - -func mustNoError(t *testing.T, err error) { - t.Helper() - if err != nil { - t.Fatalf("unexpected error: %v", err) - } -} - -func mustNoErrorf(t *testing.T, err error, format string, args ...any) { - t.Helper() - if err != nil { - t.Fatalf(format+": %v", append(args, err)...) - } -} - -func mustEqual[T comparable](t *testing.T, want, got T) { - t.Helper() - if want != got { - t.Fatalf("want %v, got %v", want, got) - } -} - -func mustDeepEqual(t *testing.T, want, got any) { - t.Helper() - if !reflect.DeepEqual(want, got) { - t.Fatalf("want %v, got %v", want, got) - } -} - -func mustContains(t *testing.T, s, sub string) { - t.Helper() - if !strings.Contains(s, sub) { - t.Fatalf("expected %q to contain %q", s, sub) - } -} - -func mustNotContains(t *testing.T, s, sub string) { - t.Helper() - if strings.Contains(s, sub) { - t.Fatalf("expected %q to not contain %q", s, sub) - } -} - -func mustLen[T any](t *testing.T, got []T, want int) { - t.Helper() - if len(got) != want { - t.Fatalf("want length %d, got %d", want, len(got)) - } -} - -func mustContainsString(t *testing.T, haystack []string, needle string) { - t.Helper() - for _, s := range haystack { - if s == needle { - return - } - } - t.Fatalf("expected %v to contain %q", haystack, needle) -} diff --git a/cmd/workspace/config_test.go b/cmd/workspace/config_test.go index b6c61cb..22425bb 100644 --- a/cmd/workspace/config_test.go +++ b/cmd/workspace/config_test.go @@ -8,44 +8,43 @@ import ( func TestLoadConfig_RelativeDirFindsParentConfig_Good(t *testing.T) { root := t.TempDir() - mustNoError(t, os.MkdirAll(filepath.Join(root, ".core"), 0o755)) - mustNoError(t, os.MkdirAll(filepath.Join(root, "packages", "app"), 0o755)) - mustNoError(t, os.WriteFile(filepath.Join(root, ".core", "workspace.yaml"), []byte(`version: 1 + if err := os.MkdirAll(filepath.Join(root, ".core"), 0o755); err != nil { + t.Fatalf("create .core dir: %v", err) + } + if err := os.MkdirAll(filepath.Join(root, "packages", "app"), 0o755); err != nil { + t.Fatalf("create app dir: %v", err) + } + if err := os.WriteFile(filepath.Join(root, ".core", "workspace.yaml"), []byte(`version: 1 active: app packages_dir: ./packages -`), 0o600)) +`), 0o600); err != nil { + t.Fatalf("write workspace config: %v", err) + } originalWD, err := os.Getwd() - mustNoError(t, err) + if err != nil { + t.Fatalf("get working directory: %v", err) + } t.Cleanup(func() { - mustNoError(t, os.Chdir(originalWD)) + if err := os.Chdir(originalWD); err != nil { + t.Fatalf("restore working directory: %v", err) + } }) - mustNoError(t, os.Chdir(filepath.Join(root, "packages", "app"))) + if err := os.Chdir(filepath.Join(root, "packages", "app")); err != nil { + t.Fatalf("change working directory: %v", err) + } cfg, err := LoadConfig(".") - mustNoError(t, err) - mustNotNil(t, cfg) - mustEqual(t, "app", cfg.Active) - mustEqual(t, "./packages", cfg.PackagesDir) -} - -func mustNoError(t *testing.T, err error) { - t.Helper() if err != nil { - t.Fatalf("unexpected error: %v", err) - } -} - -func mustEqual[T comparable](t *testing.T, want, got T) { - t.Helper() - if want != got { - t.Fatalf("want %v, got %v", want, got) - } -} - -func mustNotNil(t *testing.T, got any) { - t.Helper() - if got == nil { - t.Fatal("expected non-nil") + t.Fatalf("load config: %v", err) + } + if cfg == nil { + t.Fatal("expected config") + } + if cfg.Active != "app" { + t.Fatalf("active = %q, want app", cfg.Active) + } + if cfg.PackagesDir != "./packages" { + t.Fatalf("packages dir = %q, want ./packages", cfg.PackagesDir) } } diff --git a/devkit/coverage_test.go b/devkit/coverage_test.go index e7d5218..2dec93d 100644 --- a/devkit/coverage_test.go +++ b/devkit/coverage_test.go @@ -1,6 +1,7 @@ package devkit import ( + "math" "os" "path/filepath" "testing" @@ -13,29 +14,58 @@ github.com/acme/project/foo/foo.go:1.1,3.1 2 1 github.com/acme/project/foo/bar.go:1.1,4.1 3 0 github.com/acme/project/baz/baz.go:1.1,2.1 4 4 `) - mustNoError(t, err) - mustLen(t, snapshot.Packages, 2) - mustEqual(t, "github.com/acme/project/baz", snapshot.Packages[0].Name) - mustEqual(t, "github.com/acme/project/foo", snapshot.Packages[1].Name) - mustInDelta(t, 100.0, snapshot.Packages[0].Coverage, 0.0001) - mustInDelta(t, 40.0, snapshot.Packages[1].Coverage, 0.0001) - mustInDelta(t, 66.6667, snapshot.Total.Coverage, 0.0001) + if err != nil { + t.Fatalf("parse cover profile: %v", err) + } + if len(snapshot.Packages) != 2 { + t.Fatalf("packages length = %d, want 2", len(snapshot.Packages)) + } + if snapshot.Packages[0].Name != "github.com/acme/project/baz" { + t.Fatalf("packages[0].Name = %q, want github.com/acme/project/baz", snapshot.Packages[0].Name) + } + if snapshot.Packages[1].Name != "github.com/acme/project/foo" { + t.Fatalf("packages[1].Name = %q, want github.com/acme/project/foo", snapshot.Packages[1].Name) + } + for name, check := range map[string]struct { + got float64 + want float64 + }{ + "baz coverage": {got: snapshot.Packages[0].Coverage, want: 100.0}, + "foo coverage": {got: snapshot.Packages[1].Coverage, want: 40.0}, + "total coverage": {got: snapshot.Total.Coverage, want: 66.6667}, + } { + if math.Abs(check.got-check.want) > 0.0001 { + t.Fatalf("%s = %v, want %v", name, check.got, check.want) + } + } } func TestParseCoverProfile_Bad(t *testing.T) { _, err := ParseCoverProfile("mode: set\nbroken line") - mustError(t, err) + if err == nil { + t.Fatal("expected parse error") + } } func TestParseCoverOutput_Good(t *testing.T) { snapshot, err := ParseCoverOutput(`ok github.com/acme/project/foo 0.123s coverage: 75.0% of statements ok github.com/acme/project/bar 0.456s coverage: 50.0% of statements `) - mustNoError(t, err) - mustLen(t, snapshot.Packages, 2) - mustEqual(t, "github.com/acme/project/bar", snapshot.Packages[0].Name) - mustEqual(t, "github.com/acme/project/foo", snapshot.Packages[1].Name) - mustInDelta(t, 62.5, snapshot.Total.Coverage, 0.0001) + if err != nil { + t.Fatalf("parse cover output: %v", err) + } + if len(snapshot.Packages) != 2 { + t.Fatalf("packages length = %d, want 2", len(snapshot.Packages)) + } + if snapshot.Packages[0].Name != "github.com/acme/project/bar" { + t.Fatalf("packages[0].Name = %q, want github.com/acme/project/bar", snapshot.Packages[0].Name) + } + if snapshot.Packages[1].Name != "github.com/acme/project/foo" { + t.Fatalf("packages[1].Name = %q, want github.com/acme/project/foo", snapshot.Packages[1].Name) + } + if math.Abs(snapshot.Total.Coverage-62.5) > 0.0001 { + t.Fatalf("total coverage = %v, want 62.5", snapshot.Total.Coverage) + } } func TestCompareCoverage_Good(t *testing.T) { @@ -56,14 +86,30 @@ func TestCompareCoverage_Good(t *testing.T) { } comparison := CompareCoverage(previous, current) - mustLen(t, comparison.Regressions, 1) - mustLen(t, comparison.Improvements, 1) - mustLen(t, comparison.NewPackages, 1) - mustEmpty(t, comparison.Removed) - mustEqual(t, "pkg/a", comparison.Regressions[0].Name) - mustEqual(t, "pkg/b", comparison.Improvements[0].Name) - mustEqual(t, "pkg/c", comparison.NewPackages[0].Name) - mustInDelta(t, 4.0, comparison.TotalDelta, 0.0001) + if len(comparison.Regressions) != 1 { + t.Fatalf("regressions length = %d, want 1", len(comparison.Regressions)) + } + if len(comparison.Improvements) != 1 { + t.Fatalf("improvements length = %d, want 1", len(comparison.Improvements)) + } + if len(comparison.NewPackages) != 1 { + t.Fatalf("new packages length = %d, want 1", len(comparison.NewPackages)) + } + if len(comparison.Removed) != 0 { + t.Fatalf("removed length = %d, want 0", len(comparison.Removed)) + } + if comparison.Regressions[0].Name != "pkg/a" { + t.Fatalf("regressions[0].Name = %q, want pkg/a", comparison.Regressions[0].Name) + } + if comparison.Improvements[0].Name != "pkg/b" { + t.Fatalf("improvements[0].Name = %q, want pkg/b", comparison.Improvements[0].Name) + } + if comparison.NewPackages[0].Name != "pkg/c" { + t.Fatalf("newPackages[0].Name = %q, want pkg/c", comparison.NewPackages[0].Name) + } + if math.Abs(comparison.TotalDelta-4.0) > 0.0001 { + t.Fatalf("total delta = %v, want 4.0", comparison.TotalDelta) + } } func TestCoverageStore_Good(t *testing.T) { @@ -81,26 +127,46 @@ func TestCoverageStore_Good(t *testing.T) { Total: CoveragePackage{Name: "total", Coverage: 82.5}, } - mustNoError(t, store.Append(first)) - mustNoError(t, store.Append(second)) + if err := store.Append(first); err != nil { + t.Fatalf("append first snapshot: %v", err) + } + if err := store.Append(second); err != nil { + t.Fatalf("append second snapshot: %v", err) + } snapshots, err := store.Load() - mustNoError(t, err) - mustLen(t, snapshots, 2) - mustEqual(t, first.CapturedAt, snapshots[0].CapturedAt) - mustEqual(t, second.CapturedAt, snapshots[1].CapturedAt) + if err != nil { + t.Fatalf("load snapshots: %v", err) + } + if len(snapshots) != 2 { + t.Fatalf("snapshots length = %d, want 2", len(snapshots)) + } + if !snapshots[0].CapturedAt.Equal(first.CapturedAt) { + t.Fatalf("snapshots[0].CapturedAt = %v, want %v", snapshots[0].CapturedAt, first.CapturedAt) + } + if !snapshots[1].CapturedAt.Equal(second.CapturedAt) { + t.Fatalf("snapshots[1].CapturedAt = %v, want %v", snapshots[1].CapturedAt, second.CapturedAt) + } latest, err := store.Latest() - mustNoError(t, err) - mustEqual(t, second.CapturedAt, latest.CapturedAt) + if err != nil { + t.Fatalf("load latest snapshot: %v", err) + } + if !latest.CapturedAt.Equal(second.CapturedAt) { + t.Fatalf("latest.CapturedAt = %v, want %v", latest.CapturedAt, second.CapturedAt) + } } func TestCoverageStore_Bad(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "coverage.json") - mustNoError(t, os.WriteFile(path, []byte("{"), 0o600)) + if err := os.WriteFile(path, []byte("{"), 0o600); err != nil { + t.Fatalf("write coverage store: %v", err) + } store := NewCoverageStore(path) _, err := store.Load() - mustError(t, err) + if err == nil { + t.Fatal("expected load error") + } } diff --git a/devkit/scan_secrets_test.go b/devkit/scan_secrets_test.go index 51f4c99..d83af98 100644 --- a/devkit/scan_secrets_test.go +++ b/devkit/scan_secrets_test.go @@ -12,7 +12,9 @@ func TestScanSecrets_Good(t *testing.T) { }) scanSecretsRunner = func(dir string) ([]byte, error) { - mustEqual(t, "/tmp/project", dir) + if dir != "/tmp/project" { + t.Fatalf("dir = %q, want /tmp/project", dir) + } return []byte(`RuleID,File,StartLine,StartColumn,Description,Match github-token,config.yml,12,4,GitHub token detected,ghp_exampletoken1234567890 aws-access-key-id,creds.txt,7,1,AWS access key detected,AKIA1234567890ABCDEF @@ -20,20 +22,44 @@ aws-access-key-id,creds.txt,7,1,AWS access key detected,AKIA1234567890ABCDEF } findings, err := ScanSecrets("/tmp/project") - mustNoError(t, err) - mustLen(t, findings, 2) + if err != nil { + t.Fatalf("scan secrets: %v", err) + } + if len(findings) != 2 { + t.Fatalf("findings length = %d, want 2", len(findings)) + } - mustEqual(t, "github-token", findings[0].Rule) - mustEqual(t, "config.yml", findings[0].Path) - mustEqual(t, 12, findings[0].Line) - mustEqual(t, 4, findings[0].Column) - mustEqual(t, "ghp_exampletoken1234567890", findings[0].Snippet) + if findings[0].Rule != "github-token" { + t.Fatalf("findings[0].Rule = %q, want github-token", findings[0].Rule) + } + if findings[0].Path != "config.yml" { + t.Fatalf("findings[0].Path = %q, want config.yml", findings[0].Path) + } + if findings[0].Line != 12 { + t.Fatalf("findings[0].Line = %d, want 12", findings[0].Line) + } + if findings[0].Column != 4 { + t.Fatalf("findings[0].Column = %d, want 4", findings[0].Column) + } + if findings[0].Snippet != "ghp_exampletoken1234567890" { + t.Fatalf("findings[0].Snippet = %q, want ghp_exampletoken1234567890", findings[0].Snippet) + } - mustEqual(t, "aws-access-key-id", findings[1].Rule) - mustEqual(t, "creds.txt", findings[1].Path) - mustEqual(t, 7, findings[1].Line) - mustEqual(t, 1, findings[1].Column) - mustEqual(t, "AKIA1234567890ABCDEF", findings[1].Snippet) + if findings[1].Rule != "aws-access-key-id" { + t.Fatalf("findings[1].Rule = %q, want aws-access-key-id", findings[1].Rule) + } + if findings[1].Path != "creds.txt" { + t.Fatalf("findings[1].Path = %q, want creds.txt", findings[1].Path) + } + if findings[1].Line != 7 { + t.Fatalf("findings[1].Line = %d, want 7", findings[1].Line) + } + if findings[1].Column != 1 { + t.Fatalf("findings[1].Column = %d, want 1", findings[1].Column) + } + if findings[1].Snippet != "AKIA1234567890ABCDEF" { + t.Fatalf("findings[1].Snippet = %q, want AKIA1234567890ABCDEF", findings[1].Snippet) + } } func TestScanSecrets_ReportsFindingsOnExitError_Good(t *testing.T) { @@ -49,14 +75,26 @@ token,test.txt,3,2,Token detected,secret-value } findings, err := ScanSecrets("/tmp/project") - mustNoError(t, err) - mustLen(t, findings, 1) - mustEqual(t, "token", findings[0].Rule) - mustEqual(t, 3, findings[0].Line) - mustEqual(t, 2, findings[0].Column) + if err != nil { + t.Fatalf("scan secrets: %v", err) + } + if len(findings) != 1 { + t.Fatalf("findings length = %d, want 1", len(findings)) + } + if findings[0].Rule != "token" { + t.Fatalf("findings[0].Rule = %q, want token", findings[0].Rule) + } + if findings[0].Line != 3 { + t.Fatalf("findings[0].Line = %d, want 3", findings[0].Line) + } + if findings[0].Column != 2 { + t.Fatalf("findings[0].Column = %d, want 2", findings[0].Column) + } } func TestParseGitleaksCSV_Bad(t *testing.T) { _, err := parseGitleaksCSV([]byte("rule_id,file,start_line\nunterminated,\"broken")) - mustError(t, err) + if err == nil { + t.Fatal("expected parse error") + } } diff --git a/devkit/secret_test.go b/devkit/secret_test.go index 44eec68..2e5af25 100644 --- a/devkit/secret_test.go +++ b/devkit/secret_test.go @@ -9,47 +9,91 @@ import ( func TestScanDir_Good(t *testing.T) { root := t.TempDir() - mustNoError(t, os.WriteFile(filepath.Join(root, "config.yml"), []byte(` + if err := os.WriteFile(filepath.Join(root, "config.yml"), []byte(` api_key: "ghp_abcdefghijklmnopqrstuvwxyz1234" -`), 0o600)) +`), 0o600); err != nil { + t.Fatalf("write config.yml: %v", err) + } - mustNoError(t, os.Mkdir(filepath.Join(root, "nested"), 0o755)) - mustNoError(t, os.WriteFile(filepath.Join(root, "nested", "creds.txt"), []byte("access_key = AKIA1234567890ABCDEF\n"), 0o600)) + if err := os.Mkdir(filepath.Join(root, "nested"), 0o755); err != nil { + t.Fatalf("create nested dir: %v", err) + } + if err := os.WriteFile(filepath.Join(root, "nested", "creds.txt"), []byte("access_key = AKIA1234567890ABCDEF\n"), 0o600); err != nil { + t.Fatalf("write creds.txt: %v", err) + } findings, err := ScanDir(root) - mustNoError(t, err) - mustLen(t, findings, 2) + if err != nil { + t.Fatalf("scan dir: %v", err) + } + if len(findings) != 2 { + t.Fatalf("findings length = %d, want 2", len(findings)) + } - mustEqual(t, "github-token", findings[0].Rule) - mustEqual(t, 2, findings[0].Line) - mustEqual(t, "config.yml", filepath.Base(findings[0].Path)) + if findings[0].Rule != "github-token" { + t.Fatalf("findings[0].Rule = %q, want %q", findings[0].Rule, "github-token") + } + if findings[0].Line != 2 { + t.Fatalf("findings[0].Line = %d, want 2", findings[0].Line) + } + if got := filepath.Base(findings[0].Path); got != "config.yml" { + t.Fatalf("findings[0] path base = %q, want %q", got, "config.yml") + } - mustEqual(t, "aws-access-key-id", findings[1].Rule) - mustEqual(t, 1, findings[1].Line) - mustEqual(t, "creds.txt", filepath.Base(findings[1].Path)) + if findings[1].Rule != "aws-access-key-id" { + t.Fatalf("findings[1].Rule = %q, want %q", findings[1].Rule, "aws-access-key-id") + } + if findings[1].Line != 1 { + t.Fatalf("findings[1].Line = %d, want 1", findings[1].Line) + } + if got := filepath.Base(findings[1].Path); got != "creds.txt" { + t.Fatalf("findings[1] path base = %q, want %q", got, "creds.txt") + } } func TestScanDir_SkipsBinaryAndIgnoredDirs_Good(t *testing.T) { root := t.TempDir() - mustNoError(t, os.Mkdir(filepath.Join(root, ".git"), 0o755)) - mustNoError(t, os.WriteFile(filepath.Join(root, ".git", "config"), []byte("token=ghp_abcdefghijklmnopqrstuvwxyz1234"), 0o600)) - mustNoError(t, os.WriteFile(filepath.Join(root, "blob.bin"), []byte{0, 1, 2, 3, 4}, 0o600)) + if err := os.Mkdir(filepath.Join(root, ".git"), 0o755); err != nil { + t.Fatalf("create .git dir: %v", err) + } + if err := os.WriteFile(filepath.Join(root, ".git", "config"), []byte("token=ghp_abcdefghijklmnopqrstuvwxyz1234"), 0o600); err != nil { + t.Fatalf("write .git config: %v", err) + } + if err := os.WriteFile(filepath.Join(root, "blob.bin"), []byte{0, 1, 2, 3, 4}, 0o600); err != nil { + t.Fatalf("write blob.bin: %v", err) + } findings, err := ScanDir(root) - mustNoError(t, err) - mustEmpty(t, findings) + if err != nil { + t.Fatalf("scan dir: %v", err) + } + if len(findings) != 0 { + t.Fatalf("findings length = %d, want 0", len(findings)) + } } func TestScanDir_ReportsGenericAssignments_Bad(t *testing.T) { root := t.TempDir() - mustNoError(t, os.WriteFile(filepath.Join(root, "secrets.env"), []byte("client_secret: abcdefghijklmnop\n"), 0o600)) + if err := os.WriteFile(filepath.Join(root, "secrets.env"), []byte("client_secret: abcdefghijklmnop\n"), 0o600); err != nil { + t.Fatalf("write secrets.env: %v", err) + } findings, err := ScanDir(root) - mustNoError(t, err) - mustLen(t, findings, 1) - mustEqual(t, "generic-secret-assignment", findings[0].Rule) - mustEqual(t, 1, findings[0].Line) - mustEqual(t, 1, findings[0].Column) + if err != nil { + t.Fatalf("scan dir: %v", err) + } + if len(findings) != 1 { + t.Fatalf("findings length = %d, want 1", len(findings)) + } + if findings[0].Rule != "generic-secret-assignment" { + t.Fatalf("findings[0].Rule = %q, want %q", findings[0].Rule, "generic-secret-assignment") + } + if findings[0].Line != 1 { + t.Fatalf("findings[0].Line = %d, want 1", findings[0].Line) + } + if findings[0].Column != 1 { + t.Fatalf("findings[0].Column = %d, want 1", findings[0].Column) + } } diff --git a/devkit/test_helpers_test.go b/devkit/test_helpers_test.go deleted file mode 100644 index 0dba87d..0000000 --- a/devkit/test_helpers_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package devkit - -import ( - "math" - "testing" -) - -func mustNoError(t *testing.T, err error) { - t.Helper() - if err != nil { - t.Fatalf("unexpected error: %v", err) - } -} - -func mustError(t *testing.T, err error) { - t.Helper() - if err == nil { - t.Fatal("expected error, got nil") - } -} - -func mustEqual[T comparable](t *testing.T, want, got T) { - t.Helper() - if want != got { - t.Fatalf("want %v, got %v", want, got) - } -} - -func mustLen[T any](t *testing.T, got []T, want int) { - t.Helper() - if len(got) != want { - t.Fatalf("want length %d, got %d", want, len(got)) - } -} - -func mustEmpty[T any](t *testing.T, got []T) { - t.Helper() - if len(got) != 0 { - t.Fatalf("expected empty, got %d entries", len(got)) - } -} - -func mustInDelta(t *testing.T, want, got, delta float64) { - t.Helper() - if math.Abs(want-got) > delta { - t.Fatalf("want %v±%v, got %v", want, delta, got) - } -} diff --git a/docs/architecture.md b/docs/architecture.md index b90aee8..6f1ff41 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -469,7 +469,6 @@ Each generator implements the `Generator` interface. Supported languages: TypeSc | `github.com/spf13/cobra` | CLI framework for `build/buildcmd/` | | `golang.org/x/crypto` | SSH connections in `ansible/ssh.go` | | `gopkg.in/yaml.v3` | Playbook and config YAML parsing | -| `github.com/stretchr/testify` | Test assertions | ## Dependency on `forge.lthn.ai/core/go` diff --git a/docs/development.md b/docs/development.md index cfbae77..a7423e6 100644 --- a/docs/development.md +++ b/docs/development.md @@ -85,21 +85,23 @@ func TestParsePlaybook_Bad(t *testing.T) { ... } func TestParsePlaybook_Ugly(t *testing.T) { ... } ``` -### Assertion Library +### Assertions -Use `github.com/stretchr/testify`. Prefer `require` over `assert` when subsequent assertions depend on the previous one passing: +Use the standard `testing` package. Do not add third-party assertion libraries: ```go import ( "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestSomething_Good(t *testing.T) { result, err := SomeFunction() - require.NoError(t, err) - assert.Equal(t, "expected", result.Field) + if err != nil { + t.Fatalf("some function: %v", err) + } + if result.Field != "expected" { + t.Fatalf("field = %q, want %q", result.Field, "expected") + } } ``` diff --git a/go.mod b/go.mod index 7128d69..e936d3d 100644 --- a/go.mod +++ b/go.mod @@ -3,26 +3,29 @@ module dappco.re/go/devops go 1.26.0 require ( - code.gitea.io/sdk/gitea v0.23.2 // Note: Gitea SDK for repository and automation API integration; no core.* equivalent. + code.gitea.io/sdk/gitea v0.24.1 // Note: Gitea SDK for repository and automation API integration; no core.* equivalent. dappco.re/go/agent v0.8.0-alpha.1 - dappco.re/go/core v0.8.0-alpha.1 - dappco.re/go/cli v0.8.0-alpha.1 dappco.re/go/container v0.8.0-alpha.1 + dappco.re/go/core v0.8.0-alpha.1 + dappco.re/go/core/cli v0.5.2 dappco.re/go/i18n v0.8.0-alpha.1 dappco.re/go/io v0.8.0-alpha.1 dappco.re/go/log v0.8.0-alpha.1 dappco.re/go/scm v0.8.0-alpha.1 github.com/kluctl/go-embed-python v0.0.0-3.13.1-20241219-1 // Note: CPython embedding for Ansible playbook execution; no go/* equivalent. - golang.org/x/term v0.41.0 - golang.org/x/text v0.35.0 + golang.org/x/term v0.42.0 + golang.org/x/text v0.36.0 gopkg.in/yaml.v3 v3.0.1 // Note: YAML parser for Ansible inventory and playbook files; no core.* YAML equivalent. ) require ( - codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 // indirect + codeberg.org/forgejo/go-sdk v0.0.0 // indirect dappco.re/go/config v0.8.0-alpha.1 // indirect + dappco.re/go/core/i18n v0.2.3 // indirect + dappco.re/go/core/inference v0.2.1 // indirect + dappco.re/go/core/log v0.1.2 // indirect dappco.re/go/inference v0.8.0-alpha.1 // indirect - github.com/42wim/httpsig v1.2.3 // indirect + github.com/42wim/httpsig v1.2.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/bubbletea v1.3.10 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect @@ -37,7 +40,6 @@ require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect - github.com/goccy/go-json v0.10.6 // indirect github.com/gofrs/flock v0.13.0 // indirect github.com/hashicorp/go-version v1.8.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -58,9 +60,34 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.21.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.49.0 // indirect + golang.org/x/crypto v0.50.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect + golang.org/x/sys v0.43.0 // indirect +) + +replace ( + codeberg.org/forgejo/go-sdk => github.com/dAppCore/go-scm/third_party/forgejo v0.0.0-20260424224729-c5374e1b928e + dappco.re/go/agent => github.com/dAppCore/agent v0.8.0-alpha.1 + dappco.re/go/ai => github.com/dAppCore/go-ai v0.8.0-alpha.1 + dappco.re/go/api => github.com/dAppCore/api v0.8.0-alpha.1 + dappco.re/go/config => ../go-config + dappco.re/go/container => github.com/dAppCore/go-container v0.8.0-alpha.1 + dappco.re/go/core => ../go + dappco.re/go/forge => github.com/dAppCore/go-forge v0.8.0-alpha.1 + dappco.re/go/i18n => github.com/dAppCore/go-i18n v0.8.0-alpha.1 + dappco.re/go/inference => github.com/dAppCore/go-inference v0.8.0-alpha.1 + dappco.re/go/io => github.com/dAppCore/go-io v0.8.0-alpha.1 + dappco.re/go/log => github.com/dAppCore/go-log v0.8.0-alpha.1 + dappco.re/go/mcp => github.com/dAppCore/mcp v0.8.0-alpha.1 + dappco.re/go/process => github.com/dAppCore/go-process v0.8.0-alpha.1 + dappco.re/go/rag => github.com/dAppCore/go-rag v0.8.0-alpha.1 + dappco.re/go/scm => github.com/dAppCore/go-scm v0.8.0-alpha.1 + dappco.re/go/store => github.com/dAppCore/go-store v0.8.0-alpha.1 + dappco.re/go/webview => github.com/dAppCore/go-webview v0.8.0-alpha.1 + dappco.re/go/ws => github.com/dAppCore/go-ws v0.8.0-alpha.1 ) diff --git a/go.sum b/go.sum index c281c45..a3f7ff4 100644 --- a/go.sum +++ b/go.sum @@ -1,47 +1,15 @@ -code.gitea.io/sdk/gitea v0.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg= -code.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM= -codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 h1:HTCWpzyWQOHDWt3LzI6/d2jvUDsw/vgGRWm/8BTvcqI= -codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0/go.mod h1:ZglEEDj+qkxYUb+SQIeqGtFxQrbaMYqIOgahNKb7uxs= -dappco.re/go/agent v0.8.0-alpha.1 h1:7jtrDGh5CHUVsvvQiG8gjQxfdlI+ncJrIHXEMksJ8bc= -dappco.re/go/agent v0.8.0-alpha.1/go.mod h1:jiShGsIfHS7b7rJXMdb30K+wKL8Kx8w/VUrLNDYRbCo= -dappco.re/go/agent v0.11.0 h1:5PKzxJf+z0WF+QsxgkMwvDUODj38DGCx0uMk1KxtWkg= -dappco.re/go/agent v0.11.0/go.mod h1:nBF4HMMSZD/YJg+MTHqTv71csgFlCyy62Ux084yjw+U= -dappco.re/go/cli v0.8.0-alpha.1 h1:UUnkSvAgNeRtu4kc96hr4WUpe9WTBxDY+1Co5IDVlbk= -dappco.re/go/cli v0.8.0-alpha.1/go.mod h1:jRJuSyEB7pAmyiAyTPSh7l1ens627vfxhBcUhi3sOEY= -dappco.re/go/config v0.8.0-alpha.1 h1:YpfPi7PHId0Wc2C/h07rmTZG06a+ONHrBLG9KDg45Uo= -dappco.re/go/config v0.8.0-alpha.1/go.mod h1:Ryvf7Fncq4p+mZQnHjP5h8OmDcbE2JBf99E6hDdpeN4= -dappco.re/go/container v0.8.0-alpha.1 h1:jrC308wXpooaHMjvhEvPwPfK4KOXTuFYz4y/Es+uhY4= -dappco.re/go/container v0.8.0-alpha.1/go.mod h1:5F+NPSBG3LtgfBTGvmGcVWLmax4LrmxBgexOHG4gnKc= -dappco.re/go/core v0.8.0-alpha.1 h1:gj7+Scv+L63Z7wMxbJYHhaRFkHJo2u4MMPuUSv/Dhtk= -dappco.re/go/core v0.8.0-alpha.1/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A= +code.gitea.io/sdk/gitea v0.24.1 h1:hpaqcdGcBmfMpV7JSbBJVwE99qo+WqGreJYKrDKEyW8= +code.gitea.io/sdk/gitea v0.24.1/go.mod h1:5/77BL3sHneCMEiZaMT9lfTvnnibsYxyO48mceCF3qA= dappco.re/go/core/cli v0.5.2 h1:mo+PERo3lUytE+r3ArHr8o2nTftXjgPPsU/rn3ETXDM= dappco.re/go/core/cli v0.5.2/go.mod h1:D4zfn3ec/hb72AWX/JWDvkW+h2WDKQcxGUrzoss7q2s= -dappco.re/go/core/config v0.2.3 h1:sEX0Vtm3WfzsJWhqegWTVr2W90MK3BQ6FQ3cU/2MC+o= -dappco.re/go/core/config v0.2.3/go.mod h1:/rOblY96zfANXywS+WCukJYESmmkeFnYWvI19vy5NYY= -dappco.re/go/core/container v0.2.2 h1:x4JI/GmtX/TGBGa7WJu6dSVQzAHpptKHtAKvwYUxAyg= -dappco.re/go/core/container v0.2.2/go.mod h1:XEV22GjJa8zQhawKnt8dDFEQ1NqSy7xG/bwAd8iqcDA= dappco.re/go/core/i18n v0.2.3 h1:GqFaTR1I0SfSEc4WtsAkgao+jp8X5qcMPqrX0eMAOrY= dappco.re/go/core/i18n v0.2.3/go.mod h1:LoyX/4fIEJO/wiHY3Q682+4P0Ob7zPemcATfwp0JBUg= -dappco.re/go/core/inference v0.3.0 h1:ANFnlVO1LEYDipeDeBgqmb8CHvOTUFhMPyfyHGqO0IY= -dappco.re/go/core/inference v0.3.0/go.mod h1:wbRY0v6iwOoJCpTvcBFarAM08bMgpPcrF6yv3vccYoA= -dappco.re/go/core/io v0.4.1 h1:15dm7ldhFIAuZOrBiQG6XVZDpSvCxtZsUXApwTAB3wQ= -dappco.re/go/core/io v0.4.1/go.mod h1:w71dukyunczLb8frT9JOd5B78PjwWQD3YAXiCt3AcPA= +dappco.re/go/core/inference v0.2.1 h1:/kgZuG4tXibgwTB3VIB1zg9UMCHRm9T3EWoDLjptBpY= +dappco.re/go/core/inference v0.2.1/go.mod h1:wbRY0v6iwOoJCpTvcBFarAM08bMgpPcrF6yv3vccYoA= dappco.re/go/core/log v0.1.2 h1:pQSZxKD8VycdvjNJmatXbPSq2OxcP2xHbF20zgFIiZI= dappco.re/go/core/log v0.1.2/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs= -dappco.re/go/core/scm v0.6.1 h1:nQWr2AGreLzhp//2zZolol87TCKlzV2/I/hpBVkv0Gc= -dappco.re/go/core/scm v0.6.1/go.mod h1:fYy/xazjyv84X8sxBIpTBikSdU5nQq4qf/IR2hXnd5E= -dappco.re/go/i18n v0.8.0-alpha.1 h1:9LI/PrF41XeQu69eOaBTz3LMrXTJ08O2f1EEATq9k5A= -dappco.re/go/i18n v0.8.0-alpha.1/go.mod h1:aSfWSAW2EVh/aMbMplc27URnjl6DvRVvWfvRC2my7AY= -dappco.re/go/inference v0.8.0-alpha.1 h1:Cc3YZr04rNSqqHQBm7v53mzfn6e17sf7oDe+TqQnzwo= -dappco.re/go/inference v0.8.0-alpha.1/go.mod h1:vMXtaGSKvom7B5rjOjzl4taSOXbbVmnsLlYd0X/PFo0= -dappco.re/go/io v0.8.0-alpha.1 h1:tIJ/Nd6lGr2DFEUj2HzGM8dPglS5bEAI4h2RAgzGCNE= -dappco.re/go/io v0.8.0-alpha.1/go.mod h1:5u1TImtXPdJKDgh59Nw4rsbMUkq02uVDDsL5bE1mhBk= -dappco.re/go/log v0.8.0-alpha.1 h1:eXTdrt88Ovbdm0KJkJDaEpgLUHUZgJ2xYEu2uN3eV4I= -dappco.re/go/log v0.8.0-alpha.1/go.mod h1:IC04Em9SfVTcXiWc1BqZDQfa1MtOuMDEermZkQcTz9c= -dappco.re/go/scm v0.8.0-alpha.1 h1:pXiO5Hp5tky3shekYERUK9KsQy9xoWQQW0I40mPyKvA= -dappco.re/go/scm v0.8.0-alpha.1/go.mod h1:11xL67SU5TJ+fTBLyqYDDwotl7Y1qy5rWY+JgEQ16UQ= -github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs= -github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM= +github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU= +github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= @@ -61,6 +29,23 @@ github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3 github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/dAppCore/agent v0.8.0-alpha.1 h1:ubnM4dh7TabJBT84xXkQBIzUi8yu3qQmX/0CVrtyf4M= +github.com/dAppCore/agent v0.8.0-alpha.1/go.mod h1:jiShGsIfHS7b7rJXMdb30K+wKL8Kx8w/VUrLNDYRbCo= +github.com/dAppCore/go-container v0.8.0-alpha.1 h1:kmJ2UAyEPWcnWcJ3ee9xDlfEeNsrSh9bcQvl+aZGEj0= +github.com/dAppCore/go-container v0.8.0-alpha.1/go.mod h1:mUhwLQuvvplHkThNauwB/Yxq7tM7ZLBwx/fIrk7yeO4= +github.com/dAppCore/go-i18n v0.8.0-alpha.1 h1:jZ+neNdWR3LdDtukoNnGWeTynoLZR12UC/gprQM7D9I= +github.com/dAppCore/go-i18n v0.8.0-alpha.1/go.mod h1:aSfWSAW2EVh/aMbMplc27URnjl6DvRVvWfvRC2my7AY= +github.com/dAppCore/go-inference v0.8.0-alpha.1 h1:feRcT6vRelon8j1tQfg2ZrD7Y3vLjydVeHMbeVlyxJ0= +github.com/dAppCore/go-inference v0.8.0-alpha.1/go.mod h1:rfNXLcfMilEI3nKpcdrC0PQKyUyaf6bDYseowgRwDP8= +github.com/dAppCore/go-io v0.8.0-alpha.1 h1:DWmaksBY7FpwoQnRHYDN/yPCGLMR794D8OcNdk0RyB8= +github.com/dAppCore/go-io v0.8.0-alpha.1/go.mod h1:491Lt0LOTK4/88EGWVWhrACuXAoxPXvXYu/iIwYc9C0= +github.com/dAppCore/go-log v0.8.0-alpha.1 h1:yAhNK38/QHBaxXgz0Db5HUkrgX16SkxbsVYJ1DvsQn4= +github.com/dAppCore/go-log v0.8.0-alpha.1/go.mod h1:IC04Em9SfVTcXiWc1BqZDQfa1MtOuMDEermZkQcTz9c= +github.com/dAppCore/go-scm v0.8.0-alpha.1 h1:jMo3A2MN1xtybf7Z/kNbdBXH4D4VJIJoO+vxdqUuCOM= +github.com/dAppCore/go-scm v0.8.0-alpha.1/go.mod h1:11xL67SU5TJ+fTBLyqYDDwotl7Y1qy5rWY+JgEQ16UQ= +github.com/dAppCore/go-scm/third_party/forgejo v0.0.0-20260424224729-c5374e1b928e h1:EWNlwllH8MSI1Zv++EWX03I5fOcLOrn/7L0FK7m40lA= +github.com/dAppCore/go-scm/third_party/forgejo v0.0.0-20260424224729-c5374e1b928e/go.mod h1:uG/dXIqfx0NfIjTyTwdB38VbGfPlf2fueeEP3nvSPrg= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= @@ -77,12 +62,10 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= -github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -109,6 +92,7 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= @@ -131,10 +115,18 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -142,8 +134,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -155,15 +147,15 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/snapshot/snapshot_test.go b/snapshot/snapshot_test.go index 92b3a83..9fa2ab8 100644 --- a/snapshot/snapshot_test.go +++ b/snapshot/snapshot_test.go @@ -2,6 +2,8 @@ package snapshot import ( "encoding/json" + "slices" + "strings" "testing" "time" @@ -28,28 +30,51 @@ func TestGenerate_Good(t *testing.T) { } data, err := GenerateAt(m, "abc123def456", "v1.0.0", fixedTime) - mustNoError(t, err) + if err != nil { + t.Fatalf("generate snapshot: %v", err) + } var snap Snapshot - mustNoError(t, json.Unmarshal(data, &snap)) + if err := json.Unmarshal(data, &snap); err != nil { + t.Fatalf("unmarshal snapshot: %v", err) + } - mustEqual(t, 1, snap.Schema) - mustEqual(t, "test-app", snap.Code) - mustEqual(t, "Test App", snap.Name) - mustEqual(t, "1.0.0", snap.Version) - mustEqual(t, "A test application", snap.Description) - mustEqual(t, "abc123def456", snap.Commit) - mustEqual(t, "v1.0.0", snap.Tag) - mustEqual(t, "2026-03-09T15:00:00Z", snap.Built) - mustEqual(t, "HLCRF", snap.Layout) - mustEqual(t, "main-content", snap.Slots["C"]) - mustLenMap(t, snap.Daemons, 1) - mustEqual(t, "core-php", snap.Daemons["serve"].Binary) + if snap.Schema != 1 { + t.Fatalf("schema = %d, want 1", snap.Schema) + } + for name, check := range map[string]struct { + got string + want string + }{ + "code": {got: snap.Code, want: "test-app"}, + "name": {got: snap.Name, want: "Test App"}, + "version": {got: snap.Version, want: "1.0.0"}, + "description": {got: snap.Description, want: "A test application"}, + "commit": {got: snap.Commit, want: "abc123def456"}, + "tag": {got: snap.Tag, want: "v1.0.0"}, + "built": {got: snap.Built, want: "2026-03-09T15:00:00Z"}, + "layout": {got: snap.Layout, want: "HLCRF"}, + "slot C": {got: snap.Slots["C"], want: "main-content"}, + } { + if check.got != check.want { + t.Fatalf("%s = %q, want %q", name, check.got, check.want) + } + } + if len(snap.Daemons) != 1 { + t.Fatalf("daemons length = %d, want 1", len(snap.Daemons)) + } + if snap.Daemons["serve"].Binary != "core-php" { + t.Fatalf("serve binary = %q, want core-php", snap.Daemons["serve"].Binary) + } if snap.Permissions == nil { t.Fatal("expected non-nil permissions") } - mustDeepEqual(t, []string{"./photos/"}, snap.Permissions.Read) - mustDeepEqual(t, []string{"core/media"}, snap.Modules) + if !slices.Equal(snap.Permissions.Read, []string{"./photos/"}) { + t.Fatalf("permission reads = %v, want [./photos/]", snap.Permissions.Read) + } + if !slices.Equal(snap.Modules, []string{"core/media"}) { + t.Fatalf("modules = %v, want [core/media]", snap.Modules) + } } func TestGenerate_NoDaemons_Good(t *testing.T) { @@ -60,13 +85,21 @@ func TestGenerate_NoDaemons_Good(t *testing.T) { } data, err := GenerateAt(m, "abc123", "v0.1.0", fixedTime) - mustNoError(t, err) + if err != nil { + t.Fatalf("generate snapshot: %v", err) + } var snap Snapshot - mustNoError(t, json.Unmarshal(data, &snap)) + if err := json.Unmarshal(data, &snap); err != nil { + t.Fatalf("unmarshal snapshot: %v", err) + } - mustEqual(t, 1, snap.Schema) - mustEqual(t, "simple", snap.Code) + if snap.Schema != 1 { + t.Fatalf("schema = %d, want 1", snap.Schema) + } + if snap.Code != "simple" { + t.Fatalf("code = %q, want simple", snap.Code) + } if snap.Daemons != nil { t.Fatalf("expected nil daemons, got %v", snap.Daemons) } @@ -77,5 +110,10 @@ func TestGenerate_NoDaemons_Good(t *testing.T) { func TestGenerate_NilManifest_Bad(t *testing.T) { _, err := Generate(nil, "abc123", "v1.0.0") - mustErrorContains(t, err, "manifest is nil") + if err == nil { + t.Fatal("expected error") + } + if !strings.Contains(err.Error(), "manifest is nil") { + t.Fatalf("error = %q, want substring %q", err.Error(), "manifest is nil") + } } diff --git a/snapshot/test_helpers_test.go b/snapshot/test_helpers_test.go deleted file mode 100644 index 987f16a..0000000 --- a/snapshot/test_helpers_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package snapshot - -import ( - "reflect" - "strings" - "testing" -) - -func mustNoError(t *testing.T, err error) { - t.Helper() - if err != nil { - t.Fatalf("unexpected error: %v", err) - } -} - -func mustEqual[T comparable](t *testing.T, want, got T) { - t.Helper() - if want != got { - t.Fatalf("want %v, got %v", want, got) - } -} - -func mustDeepEqual(t *testing.T, want, got any) { - t.Helper() - if !reflect.DeepEqual(want, got) { - t.Fatalf("want %v, got %v", want, got) - } -} - -func mustLenMap[K comparable, V any](t *testing.T, m map[K]V, want int) { - t.Helper() - if len(m) != want { - t.Fatalf("want length %d, got %d", want, len(m)) - } -} - -func mustErrorContains(t *testing.T, err error, sub string) { - t.Helper() - if err == nil { - t.Fatal("expected error, got nil") - } - if !strings.Contains(err.Error(), sub) { - t.Fatalf("expected error %q to contain %q", err.Error(), sub) - } -} diff --git a/tests/cli/devops/main.go b/tests/cli/devops/main.go index edfd761..192ebad 100644 --- a/tests/cli/devops/main.go +++ b/tests/cli/devops/main.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - "dappco.re/go/cli/pkg/cli" + "dappco.re/go/core/cli/pkg/cli" deploycmd "dappco.re/go/devops/cmd/deploy" devcmd "dappco.re/go/devops/cmd/dev" docscmd "dappco.re/go/devops/cmd/docs"