ananke/testing/sshutil/sshutil_quality_test.go

118 lines
3.9 KiB
Go
Raw Permalink Normal View History

package sshutilquality
import (
"bytes"
"context"
"errors"
"log"
"os"
"path/filepath"
"strings"
"testing"
"scm.bstein.dev/bstein/ananke/internal/sshutil"
)
// TestHostKeyHeuristics runs one orchestration or CLI step.
// Signature: TestHostKeyHeuristics(t *testing.T).
// Why: covers host-key error detection and repair-decision heuristics used by bootstrap SSH retries.
func TestHostKeyHeuristics(t *testing.T) {
if sshutil.IsHostKeyError("", nil) {
t.Fatalf("expected nil error to not be host-key related")
}
if !sshutil.IsHostKeyError("Host key verification failed", errors.New("ssh failure")) {
t.Fatalf("expected host-key verification text to be detected")
}
if !sshutil.ShouldAttemptKnownHostsRepair("", errors.New("exit status 255")) {
t.Fatalf("expected empty-output exit-255 branch to trigger known_hosts repair")
}
if sshutil.ShouldAttemptKnownHostsRepair("permission denied", errors.New("permission denied")) {
t.Fatalf("unexpected known_hosts repair suggestion for non host-key error")
}
}
// TestKnownHostsDiscoveryAndMissingBinaryBranch runs one orchestration or CLI step.
// Signature: TestKnownHostsDiscoveryAndMissingBinaryBranch(t *testing.T).
// Why: verifies known_hosts path discovery and the graceful no-op path when ssh-keygen is unavailable.
func TestKnownHostsDiscoveryAndMissingBinaryBranch(t *testing.T) {
dir := t.TempDir()
cfgPath := filepath.Join(dir, "config")
keyPath := filepath.Join(dir, "id_ed25519")
if err := os.WriteFile(cfgPath, []byte("Host *\n"), 0o600); err != nil {
t.Fatalf("write config: %v", err)
}
if err := os.WriteFile(keyPath, []byte("key"), 0o600); err != nil {
t.Fatalf("write identity: %v", err)
}
files := sshutil.KnownHostsFiles(cfgPath, keyPath)
foundLocal := false
for _, path := range files {
if strings.Contains(path, dir) {
foundLocal = true
break
}
}
if !foundLocal {
t.Fatalf("expected derived known_hosts entries under %s, got %v", dir, files)
}
originalPath := os.Getenv("PATH")
t.Setenv("PATH", filepath.Join(dir, "missing-bin"))
var logs bytes.Buffer
sshutil.RepairKnownHosts(context.Background(), log.New(&logs, "", 0), files, []string{"titan-db"}, 22)
t.Setenv("PATH", originalPath)
if !strings.Contains(logs.String(), "ssh-keygen missing") {
t.Fatalf("expected missing ssh-keygen warning, got: %s", logs.String())
}
}
// TestRepairKnownHostsCoversSuccessNotFoundAndFailure runs one orchestration or CLI step.
// Signature: TestRepairKnownHostsCoversSuccessNotFoundAndFailure(t *testing.T).
// Why: exercises cleanup outcomes for successful removal, absent entries, and hard failures.
func TestRepairKnownHostsCoversSuccessNotFoundAndFailure(t *testing.T) {
dir := t.TempDir()
scriptPath := filepath.Join(dir, "ssh-keygen")
script := `#!/usr/bin/env sh
args="$*"
if echo "$args" | grep -q "notfound"; then
echo "Host notfound not found in $args" >&2
exit 1
fi
if echo "$args" | grep -q "boom"; then
echo "boom failure" >&2
exit 2
fi
echo "updated" >&2
exit 0
`
if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil {
t.Fatalf("write fake ssh-keygen: %v", err)
}
t.Setenv("PATH", dir+":"+os.Getenv("PATH"))
knownHosts := filepath.Join(dir, "known_hosts")
if err := os.WriteFile(knownHosts, []byte("example"), 0o644); err != nil {
t.Fatalf("write known_hosts: %v", err)
}
if err := os.WriteFile(knownHosts+".old", []byte("backup"), 0o644); err != nil {
t.Fatalf("write known_hosts backup: %v", err)
}
var logs bytes.Buffer
logger := log.New(&logs, "", 0)
sshutil.RepairKnownHosts(
context.Background(),
logger,
[]string{knownHosts, knownHosts},
[]string{"good", "notfound", "boom", "good"},
22,
)
out := logs.String()
if !strings.Contains(out, "known_hosts repaired: removed good") {
t.Fatalf("expected successful cleanup log, got: %s", out)
}
if !strings.Contains(out, "warning: known_hosts cleanup failed for boom") {
t.Fatalf("expected failure warning for boom host, got: %s", out)
}
}