fix(devops): r2 — replace must* helpers with stdlib + verify CLI module resolution on PR #2
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 <noreply@openai.com>
This commit is contained in:
parent
907c5fa64c
commit
9f6fa7ec17
62 changed files with 728 additions and 591 deletions
|
|
@ -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 <virgil@lethean.io>`
|
||||
- **Licence**: EUPL-1.2
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
package docs
|
||||
|
||||
import (
|
||||
"dappco.re/go/cli/pkg/cli"
|
||||
"dappco.re/go/core/cli/pkg/cli"
|
||||
"dappco.re/go/i18n"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"slices"
|
||||
"strings"
|
||||
|
||||
"dappco.re/go/cli/pkg/cli"
|
||||
"dappco.re/go/core/cli/pkg/cli"
|
||||
"dappco.re/go/i18n"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
47
go.mod
47
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
|
||||
)
|
||||
|
|
|
|||
92
go.sum
92
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=
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue