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")) }