ananke/internal/sshutil/repair_test.go

132 lines
4.9 KiB
Go
Raw Permalink Normal View History

package sshutil
import (
"context"
"errors"
"io"
"log"
"os"
"path/filepath"
"strings"
"testing"
)
// TestShouldAttemptKnownHostsRepairFalseWithoutError runs one orchestration or CLI step.
// Signature: TestShouldAttemptKnownHostsRepairFalseWithoutError(t *testing.T).
// Why: ensures repair logic does not trigger when command succeeded.
func TestShouldAttemptKnownHostsRepairFalseWithoutError(t *testing.T) {
if ShouldAttemptKnownHostsRepair("ok", nil) {
t.Fatalf("expected false when no error exists")
}
}
// TestIsHostKeyErrorRequiresErr runs one orchestration or CLI step.
// Signature: TestIsHostKeyErrorRequiresErr(t *testing.T).
// Why: covers guard branch that skips marker parsing when err is nil.
func TestIsHostKeyErrorRequiresErr(t *testing.T) {
if IsHostKeyError("REMOTE HOST IDENTIFICATION HAS CHANGED", nil) {
t.Fatalf("expected false when err is nil")
}
}
// TestRepairKnownHostsRemovesEntries runs one orchestration or CLI step.
// Signature: TestRepairKnownHostsRemovesEntries(t *testing.T).
// Why: validates known_hosts repair path actually removes target entries.
func TestRepairKnownHostsRemovesEntries(t *testing.T) {
tmp := t.TempDir()
knownHosts := filepath.Join(tmp, "known_hosts")
content := strings.Join([]string{
"titan-0a ssh-ed25519 AAAATESTKEYONE",
"[titan-0a]:2277 ssh-ed25519 AAAATESTKEYTWO",
"titan-0b ssh-ed25519 AAAATESTKEYTHREE",
"",
}, "\n")
if err := os.WriteFile(knownHosts, []byte(content), 0o600); err != nil {
t.Fatalf("write known_hosts: %v", err)
}
RepairKnownHosts(context.Background(), log.New(io.Discard, "", 0), []string{knownHosts}, []string{"titan-0a", "titan-0a", ""}, 2277)
b, err := os.ReadFile(knownHosts)
if err != nil {
t.Fatalf("read known_hosts: %v", err)
}
got := string(b)
if strings.Contains(got, "titan-0a") {
t.Fatalf("expected titan-0a entries removed, got:\n%s", got)
}
if !strings.Contains(got, "titan-0b") {
t.Fatalf("expected unrelated host to remain, got:\n%s", got)
}
}
// TestRepairKnownHostsNoSshKeygen runs one orchestration or CLI step.
// Signature: TestRepairKnownHostsNoSshKeygen(t *testing.T).
// Why: covers early-return branch when ssh-keygen is unavailable.
func TestRepairKnownHostsNoSshKeygen(t *testing.T) {
tmp := t.TempDir()
t.Setenv("PATH", tmp)
RepairKnownHosts(context.Background(), log.New(io.Discard, "", 0), []string{"/tmp/does-not-matter"}, []string{"titan-0a"}, 2277)
}
// TestRestoreOwnershipNoopOnMissing runs one orchestration or CLI step.
// Signature: TestRestoreOwnershipNoopOnMissing(t *testing.T).
// Why: covers missing-file branch in ownership restoration helper.
func TestRestoreOwnershipNoopOnMissing(t *testing.T) {
restoreOwnership(filepath.Join(t.TempDir(), "missing"), "", -1, -1, 0)
}
// TestCaptureOwnershipMissingFile runs one orchestration or CLI step.
// Signature: TestCaptureOwnershipMissingFile(t *testing.T).
// Why: covers missing-path branch in ownership capture helper.
func TestCaptureOwnershipMissingFile(t *testing.T) {
uid, gid, mode := captureOwnership(filepath.Join(t.TempDir(), "missing"))
if uid != -1 || gid != -1 || mode != 0 {
t.Fatalf("unexpected ownership for missing file uid=%d gid=%d mode=%v", uid, gid, mode)
}
}
// TestRemoveKnownHostEntryAbsentDoesNotFail runs one orchestration or CLI step.
// Signature: TestRemoveKnownHostEntryAbsentDoesNotFail(t *testing.T).
// Why: covers ssh-keygen "not found in" handling branch.
func TestRemoveKnownHostEntryAbsentDoesNotFail(t *testing.T) {
file := filepath.Join(t.TempDir(), "known_hosts")
if err := os.WriteFile(file, []byte("titan-0b ssh-ed25519 AAAA\n"), 0o600); err != nil {
t.Fatalf("write known_hosts: %v", err)
}
removeKnownHostEntry(context.Background(), log.New(io.Discard, "", 0), file, "titan-0a")
b, err := os.ReadFile(file)
if err != nil {
t.Fatalf("read known_hosts after remove: %v", err)
}
if !strings.Contains(string(b), "titan-0b") {
t.Fatalf("expected file content to remain for unrelated hosts")
}
}
// TestCaptureAndRestoreOwnershipRoundTrip runs one orchestration or CLI step.
// Signature: TestCaptureAndRestoreOwnershipRoundTrip(t *testing.T).
// Why: covers successful ownership/mode capture and restore path.
func TestCaptureAndRestoreOwnershipRoundTrip(t *testing.T) {
file := filepath.Join(t.TempDir(), "known_hosts")
if err := os.WriteFile(file, []byte("titan-0b ssh-ed25519 AAAA\n"), 0o600); err != nil {
t.Fatalf("write file: %v", err)
}
uid, gid, mode := captureOwnership(file)
restoreOwnership(file, "", uid, gid, mode)
info, err := os.Stat(file)
if err != nil {
t.Fatalf("stat restored file: %v", err)
}
if info.Mode().Perm() != mode {
t.Fatalf("expected mode %v, got %v", mode, info.Mode().Perm())
}
}
// TestLogfNoLoggerDoesNotPanic runs one orchestration or CLI step.
// Signature: TestLogfNoLoggerDoesNotPanic(t *testing.T).
// Why: covers no-op logger branch.
func TestLogfNoLoggerDoesNotPanic(t *testing.T) {
logf(nil, "message %v", errors.New("x"))
}