package infra import ( "strings" "testing" ) func TestSSHConfigParse_MultipleHosts(t *testing.T) { content := ` Host prod HostName 10.0.0.1 User admin Port 22 IdentityFile ~/.ssh/id_prod Host staging HostName 10.0.0.2 User deploy Port 2222 ` entries := SSHConfigParse(content) if len(entries) != 2 { t.Fatalf("expected 2 entries, got %d", len(entries)) } if entries[0].Alias != "prod" { t.Errorf("expected alias prod, got %s", entries[0].Alias) } if entries[0].HostName != "10.0.0.1" { t.Errorf("expected hostname 10.0.0.1, got %s", entries[0].HostName) } if entries[0].User != "admin" { t.Errorf("expected user admin, got %s", entries[0].User) } if entries[0].Port != 22 { t.Errorf("expected port 22, got %d", entries[0].Port) } if entries[0].IdentityFile != "~/.ssh/id_prod" { t.Errorf("expected identity file ~/.ssh/id_prod, got %s", entries[0].IdentityFile) } if entries[1].Alias != "staging" { t.Errorf("expected alias staging, got %s", entries[1].Alias) } if entries[1].Port != 2222 { t.Errorf("expected port 2222, got %d", entries[1].Port) } } func TestSSHConfigParse_IgnoresCommentsAndBlanks(t *testing.T) { content := ` # This is a comment Host myserver HostName 192.168.1.1 # inline comment User root ` entries := SSHConfigParse(content) if len(entries) != 1 { t.Fatalf("expected 1 entry, got %d", len(entries)) } if entries[0].User != "root" { t.Errorf("expected user root, got %s", entries[0].User) } } func TestSSHConfigParse_IgnoresWildcards(t *testing.T) { content := ` Host * ServerAliveInterval 60 Host prod HostName 10.0.0.1 Host 192.168.* User local ` entries := SSHConfigParse(content) if len(entries) != 1 { t.Fatalf("expected 1 entry (wildcards ignored), got %d", len(entries)) } if entries[0].Alias != "prod" { t.Errorf("expected alias prod, got %s", entries[0].Alias) } } func TestSSHConfigParse_ExtraOptions(t *testing.T) { content := `Host jump HostName bastion.example.com User ops ForwardAgent yes ProxyJump none ` entries := SSHConfigParse(content) if len(entries) != 1 { t.Fatalf("expected 1 entry, got %d", len(entries)) } if entries[0].Options["ForwardAgent"] != "yes" { t.Errorf("expected ForwardAgent=yes, got %s", entries[0].Options["ForwardAgent"]) } if entries[0].Options["ProxyJump"] != "none" { t.Errorf("expected ProxyJump=none, got %s", entries[0].Options["ProxyJump"]) } } func TestSSHConfigRender_FullEntry(t *testing.T) { entries := []SSHConfigEntry{{ Alias: "prod", HostName: "10.0.0.1", User: "admin", Port: 2222, IdentityFile: "~/.ssh/id_prod", }} result := SSHConfigRender(entries) if !strings.Contains(result, "Host prod") { t.Error("missing Host directive") } if !strings.Contains(result, "HostName 10.0.0.1") { t.Error("missing HostName") } if !strings.Contains(result, "User admin") { t.Error("missing User") } if !strings.Contains(result, "Port 2222") { t.Error("missing Port") } if !strings.Contains(result, "IdentityFile ~/.ssh/id_prod") { t.Error("missing IdentityFile") } } func TestSSHConfigRender_MinimalEntry(t *testing.T) { entries := []SSHConfigEntry{{Alias: "local"}} result := SSHConfigRender(entries) if result != "Host local\n" { t.Errorf("expected minimal render, got %q", result) } } func TestSSHConfigRender_MultipleEntries(t *testing.T) { entries := []SSHConfigEntry{ {Alias: "a", HostName: "1.1.1.1"}, {Alias: "b", HostName: "2.2.2.2"}, } result := SSHConfigRender(entries) parts := strings.Split(result, "\n\n") if len(parts) < 2 { t.Error("expected blocks separated by blank line") } } func TestSSHConfigRender_OptionsSorted(t *testing.T) { entries := []SSHConfigEntry{{ Alias: "test", HostName: "1.1.1.1", Options: map[string]string{"ProxyJump": "bastion", "ForwardAgent": "yes"}, }} result := SSHConfigRender(entries) fwIdx := strings.Index(result, "ForwardAgent") pjIdx := strings.Index(result, "ProxyJump") if fwIdx < 0 || pjIdx < 0 { t.Fatal("missing options in render") } if fwIdx > pjIdx { t.Error("options should be sorted alphabetically") } } func TestSSHConfigFind_Exists(t *testing.T) { entries := []SSHConfigEntry{ {Alias: "prod", HostName: "10.0.0.1"}, {Alias: "staging", HostName: "10.0.0.2"}, } entry, ok := SSHConfigFind(entries, "staging") if !ok { t.Fatal("expected to find staging") } if entry.HostName != "10.0.0.2" { t.Errorf("expected hostname 10.0.0.2, got %s", entry.HostName) } } func TestSSHConfigFind_NotExists(t *testing.T) { entries := []SSHConfigEntry{{Alias: "prod"}} _, ok := SSHConfigFind(entries, "nope") if ok { t.Error("expected not found") } } func TestSSHConfigAddEntry_ToEmpty(t *testing.T) { entry := SSHConfigEntry{Alias: "new", HostName: "1.1.1.1"} result, err := SSHConfigAddEntry(nil, entry) if err != nil { t.Fatal(err) } if len(result) != 1 || result[0].Alias != "new" { t.Errorf("unexpected result: %+v", result) } } func TestSSHConfigAddEntry_ToExisting(t *testing.T) { existing := []SSHConfigEntry{{Alias: "old"}} entry := SSHConfigEntry{Alias: "new"} result, err := SSHConfigAddEntry(existing, entry) if err != nil { t.Fatal(err) } if len(result) != 2 { t.Fatalf("expected 2 entries, got %d", len(result)) } // Original no mutado if len(existing) != 1 { t.Error("original slice was mutated") } } func TestSSHConfigAddEntry_DuplicateAlias(t *testing.T) { existing := []SSHConfigEntry{{Alias: "prod"}} entry := SSHConfigEntry{Alias: "prod"} _, err := SSHConfigAddEntry(existing, entry) if err == nil { t.Error("expected error for duplicate alias") } } func TestSSHConfigRemoveEntry_Exists(t *testing.T) { entries := []SSHConfigEntry{ {Alias: "a"}, {Alias: "b"}, {Alias: "c"}, } result, err := SSHConfigRemoveEntry(entries, "b") if err != nil { t.Fatal(err) } if len(result) != 2 { t.Fatalf("expected 2 entries, got %d", len(result)) } if result[0].Alias != "a" || result[1].Alias != "c" { t.Errorf("unexpected order: %+v", result) } // Original no mutado if len(entries) != 3 { t.Error("original slice was mutated") } } func TestSSHConfigRemoveEntry_NotExists(t *testing.T) { entries := []SSHConfigEntry{{Alias: "prod"}} _, err := SSHConfigRemoveEntry(entries, "nope") if err == nil { t.Error("expected error for missing alias") } } func TestSSHConfigParseRender_Roundtrip(t *testing.T) { original := []SSHConfigEntry{ {Alias: "prod", HostName: "10.0.0.1", User: "admin", Port: 22, IdentityFile: "~/.ssh/id_prod"}, {Alias: "staging", HostName: "10.0.0.2", User: "deploy", Port: 2222}, } text := SSHConfigRender(original) parsed := SSHConfigParse(text) if len(parsed) != len(original) { t.Fatalf("roundtrip: expected %d entries, got %d", len(original), len(parsed)) } for i := range original { if parsed[i].Alias != original[i].Alias { t.Errorf("roundtrip[%d]: alias mismatch %s != %s", i, parsed[i].Alias, original[i].Alias) } if parsed[i].HostName != original[i].HostName { t.Errorf("roundtrip[%d]: hostname mismatch", i) } if parsed[i].User != original[i].User { t.Errorf("roundtrip[%d]: user mismatch", i) } if parsed[i].Port != original[i].Port { t.Errorf("roundtrip[%d]: port mismatch", i) } } }