132 lines
4.9 KiB
Go
132 lines
4.9 KiB
Go
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"))
|
|
}
|