fix(handlers): remove duplicate test declarations; skip SSH tests gracefully #961
@ -18,6 +18,7 @@ require (
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/redis/go-redis/v9 v9.19.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.moleculesai.app/plugin/gh-identity v0.0.0-20260509010445-788988195fce
|
||||
golang.org/x/crypto v0.50.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@ -33,6 +34,7 @@ require (
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@ -58,6 +60,7 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
|
||||
@ -79,8 +79,17 @@ func hasUnresolvedVarRef(original, expanded string) bool {
|
||||
|
||||
// expandWithEnv expands ${VAR} and $VAR references in s using the env map.
|
||||
// Falls back to the platform process env if a var isn't in the map.
|
||||
// Shell variables must start with a letter or '_' per POSIX; invalid identifiers
|
||||
// are returned literally so that "$100" and "$5" stay as-is.
|
||||
func expandWithEnv(s string, env map[string]string) string {
|
||||
return os.Expand(s, func(key string) string {
|
||||
if len(key) == 0 {
|
||||
return "$"
|
||||
}
|
||||
c := key[0]
|
||||
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') {
|
||||
return "$" + key // not a valid shell identifier — return literal
|
||||
}
|
||||
if v, ok := env[key]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
@ -356,12 +356,6 @@ func TestExpandWithEnv_UnsetVar(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasUnresolvedVarRef_NoVars(t *testing.T) {
|
||||
if hasUnresolvedVarRef("plain text", "plain text") {
|
||||
t.Error("plain text should not be flagged")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasUnresolvedVarRef_LiteralDollar(t *testing.T) {
|
||||
// "$5" is a literal price, not a var ref — should NOT be flagged
|
||||
if hasUnresolvedVarRef("price: $5", "price: $5") {
|
||||
@ -369,20 +363,6 @@ func TestHasUnresolvedVarRef_LiteralDollar(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasUnresolvedVarRef_Resolved(t *testing.T) {
|
||||
// Original had ${VAR}, expanded to "value" — fully resolved
|
||||
if hasUnresolvedVarRef("${VAR}", "value") {
|
||||
t.Error("fully resolved var should not be flagged")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasUnresolvedVarRef_Unresolved(t *testing.T) {
|
||||
// Original had ${VAR}, expanded to "" — unresolved
|
||||
if !hasUnresolvedVarRef("${VAR}", "") {
|
||||
t.Error("unresolved var should be flagged")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasUnresolvedVarRef_DollarVarSyntax(t *testing.T) {
|
||||
// $VAR syntax (no braces) — also a real ref
|
||||
if !hasUnresolvedVarRef("$MISSING_VAR", "") {
|
||||
@ -1091,93 +1071,6 @@ func TestWalkOrgWorkspaceNames_Empty(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkOrgWorkspaceNames_SingleNode(t *testing.T) {
|
||||
workspaces := []OrgWorkspace{
|
||||
{Name: "alpha"},
|
||||
}
|
||||
var names []string
|
||||
walkOrgWorkspaceNames(workspaces, &names)
|
||||
if len(names) != 1 || names[0] != "alpha" {
|
||||
t.Errorf("single node: got %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkOrgWorkspaceNames_NestedChildren(t *testing.T) {
|
||||
workspaces := []OrgWorkspace{
|
||||
{Name: "root", Children: []OrgWorkspace{
|
||||
{Name: "child1", Children: []OrgWorkspace{
|
||||
{Name: "grandchild"},
|
||||
}},
|
||||
{Name: "child2"},
|
||||
}},
|
||||
}
|
||||
var names []string
|
||||
walkOrgWorkspaceNames(workspaces, &names)
|
||||
sort.Strings(names)
|
||||
want := []string{"child1", "child2", "grandchild", "root"}
|
||||
if !stringSlicesEqual(names, want) {
|
||||
t.Errorf("nested: got %v, want %v", names, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkOrgWorkspaceNames_SkipsEmptyNames(t *testing.T) {
|
||||
workspaces := []OrgWorkspace{
|
||||
{Name: "", Children: []OrgWorkspace{
|
||||
{Name: "has-name"},
|
||||
{Name: ""},
|
||||
}},
|
||||
}
|
||||
var names []string
|
||||
walkOrgWorkspaceNames(workspaces, &names)
|
||||
sort.Strings(names)
|
||||
want := []string{"has-name"}
|
||||
if !stringSlicesEqual(names, want) {
|
||||
t.Errorf("skips empty: got %v, want %v", names, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkOrgWorkspaceNames_DeeplyNested(t *testing.T) {
|
||||
// Build 5 levels deep
|
||||
l5 := []OrgWorkspace{{Name: "lvl5"}}
|
||||
l4 := []OrgWorkspace{{Name: "lvl4", Children: l5}}
|
||||
l3 := []OrgWorkspace{{Name: "lvl3", Children: l4}}
|
||||
l2 := []OrgWorkspace{{Name: "lvl2", Children: l3}}
|
||||
l1 := []OrgWorkspace{{Name: "lvl1", Children: l2}}
|
||||
var names []string
|
||||
walkOrgWorkspaceNames(l1, &names)
|
||||
sort.Strings(names)
|
||||
want := []string{"lvl1", "lvl2", "lvl3", "lvl4", "lvl5"}
|
||||
if !stringSlicesEqual(names, want) {
|
||||
t.Errorf("deeply nested: got %v, want %v", names, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkOrgWorkspaceNames_MultipleRoots(t *testing.T) {
|
||||
workspaces := []OrgWorkspace{
|
||||
{Name: "root-a", Children: []OrgWorkspace{{Name: "a-child"}}},
|
||||
{Name: "root-b"},
|
||||
}
|
||||
var names []string
|
||||
walkOrgWorkspaceNames(workspaces, &names)
|
||||
sort.Strings(names)
|
||||
want := []string{"a-child", "root-a", "root-b"}
|
||||
if !stringSlicesEqual(names, want) {
|
||||
t.Errorf("multiple roots: got %v, want %v", names, want)
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// resolveProvisionConcurrency tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestResolveProvisionConcurrency_Default(t *testing.T) {
|
||||
t.Setenv("MOLECULE_PROVISION_CONCURRENCY", "")
|
||||
got := resolveProvisionConcurrency()
|
||||
if got != defaultProvisionConcurrency {
|
||||
t.Errorf("unset: got %d, want %d", got, defaultProvisionConcurrency)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveProvisionConcurrency_ValidPositive(t *testing.T) {
|
||||
t.Setenv("MOLECULE_PROVISION_CONCURRENCY", "8")
|
||||
got := resolveProvisionConcurrency()
|
||||
|
||||
@ -215,51 +215,6 @@ func TestTarWalk_EmptyDirectory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestTarWalk_NestedDirs: deeply nested directories produce all intermediate
|
||||
// dir entries plus leaf entries. This exercises the recursive walk.
|
||||
func TestTarWalk_NestedDirs(t *testing.T) {
|
||||
hostDir := t.TempDir()
|
||||
deep := filepath.Join(hostDir, "a", "b", "c")
|
||||
if err := os.MkdirAll(deep, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(deep, "leaf.txt"), []byte("content"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
tw := newTarWriter(&buf)
|
||||
if err := tarWalk(hostDir, "configs/plugins/.staging", tw); err != nil {
|
||||
t.Fatalf("tarWalk: %v", err)
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Fatalf("Close: %v", err)
|
||||
}
|
||||
entries := readTarNames(&buf)
|
||||
// Must include: prefix/, prefix/a/, prefix/a/b/, prefix/a/b/c/, prefix/a/b/c/leaf.txt
|
||||
expected := []string{
|
||||
"configs/plugins/.staging/",
|
||||
"configs/plugins/.staging/a/",
|
||||
"configs/plugins/.staging/a/b/",
|
||||
"configs/plugins/.staging/a/b/c/",
|
||||
"configs/plugins/.staging/a/b/c/leaf.txt",
|
||||
}
|
||||
if len(entries) != len(expected) {
|
||||
t.Errorf("nested dirs: got %d entries; want %d: %v", len(entries), len(expected), entries)
|
||||
}
|
||||
for _, e := range expected {
|
||||
found := false
|
||||
for _, g := range entries {
|
||||
if g == e {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("missing entry: %q", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTarWalk_DirEntryHasTrailingSlash: directory entries must end with '/'
|
||||
// per tar format; tar.Header.Typeflag '5' (dir) must produce "name/" not "name".
|
||||
func TestTarWalk_DirEntryHasTrailingSlash(t *testing.T) {
|
||||
|
||||
@ -24,6 +24,9 @@ import (
|
||||
// - response is HTTP 200 (the endpoint always returns 200; failure is
|
||||
// in the JSON body so callers don't need branch-on-status)
|
||||
func TestHandleDiagnose_RoutesToRemote(t *testing.T) {
|
||||
if _, err := exec.LookPath("ssh-keygen"); err != nil {
|
||||
t.Skip("ssh-keygen not available in PATH:", err)
|
||||
}
|
||||
mock := setupTestDB(t)
|
||||
setupTestRedis(t)
|
||||
|
||||
@ -167,6 +170,12 @@ func TestHandleDiagnose_KI005_RejectsCrossWorkspace(t *testing.T) {
|
||||
// to differentiate "IAM broke" (send-key fails) from "sshd broke" (probe
|
||||
// fails) from "SG/network broke" (wait-for-port fails).
|
||||
func TestDiagnoseRemote_StopsAtSSHProbe(t *testing.T) {
|
||||
if _, err := exec.LookPath("ssh-keygen"); err != nil {
|
||||
t.Skip("ssh-keygen not available in PATH:", err)
|
||||
}
|
||||
if _, err := exec.LookPath("nc"); err != nil {
|
||||
t.Skip("nc not available in PATH:", err)
|
||||
}
|
||||
mock := setupTestDB(t)
|
||||
setupTestRedis(t)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user