Files
2026-07-23 12:38:13 +03:00

169 lines
5.6 KiB
Go

package keymgr
import (
"strings"
"testing"
"ssh-access-tui/internal/model"
)
func TestHostAliases(t *testing.T) {
tests := []struct {
name string
forwards []model.Forward
want []string
}{
{
name: "empty",
forwards: nil,
want: []string{},
},
{
name: "single service per host uses bare host name",
forwards: []model.Forward{
{HostName: "db-prod-1", ServiceName: "ssh"},
{HostName: "web-1", ServiceName: "https"},
},
want: []string{"db-prod-1", "web-1"},
},
{
name: "multiple services on same host get host-service alias",
forwards: []model.Forward{
{HostName: "db-prod-1", ServiceName: "ssh"},
{HostName: "db-prod-1", ServiceName: "web-admin"},
},
want: []string{"db-prod-1-ssh", "db-prod-1-web-admin"},
},
{
name: "mixed: one host with a single service, another with several",
forwards: []model.Forward{
{HostName: "db-prod-1", ServiceName: "ssh"},
{HostName: "db-prod-1", ServiceName: "web-admin"},
{HostName: "cache-1", ServiceName: "redis"},
},
want: []string{"db-prod-1-ssh", "db-prod-1-web-admin", "cache-1"},
},
{
name: "order is preserved, not sorted",
forwards: []model.Forward{
{HostName: "zzz-host", ServiceName: "ssh"},
{HostName: "aaa-host", ServiceName: "ssh"},
},
want: []string{"zzz-host", "aaa-host"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := hostAliases(tt.forwards)
if len(got) != len(tt.want) {
t.Fatalf("hostAliases() = %v, want %v", got, tt.want)
}
for i := range got {
if got[i] != tt.want[i] {
t.Errorf("hostAliases()[%d] = %q, want %q (full: got=%v want=%v)", i, got[i], tt.want[i], got, tt.want)
}
}
})
}
}
func TestRenderClientConfigProxyJump(t *testing.T) {
forwards := []model.Forward{
{HostName: "db-prod-1", Address: "10.0.0.5", Port: 22, ServiceName: "ssh", Description: "primary db"},
{HostName: "db-prod-1", Address: "10.0.0.5", Port: 8080, ServiceName: "web-admin"},
{HostName: "cache-1", Address: "10.0.0.9", Port: 6379, ServiceName: "redis"},
}
out := RenderClientConfigProxyJump("bastion.example.com", 2222, "a-makarov", "a-makarov_jump_key", forwards)
// The one always-present "jump" block.
for _, want := range []string{
"Host jump\n",
" HostName bastion.example.com\n",
" Port 2222\n",
" User a-makarov\n",
" IdentityFile ~/.ssh/a-makarov_jump_key\n",
} {
if !strings.Contains(out, want) {
t.Errorf("output missing jump block line %q\nfull output:\n%s", want, out)
}
}
// LocalForward is the LocalForward-mode marker; it must never appear here.
if strings.Contains(out, "LocalForward") {
t.Errorf("ProxyJump-mode output must not contain LocalForward, got:\n%s", out)
}
// Every forward gets its own Host block, with the expected alias
// (deduplicated per hostAliases), ProxyJump jump, and no local port.
wantBlocks := []struct {
alias string
addr string
port string
}{
{"db-prod-1-ssh", "10.0.0.5", "22"},
{"db-prod-1-web-admin", "10.0.0.5", "8080"},
{"cache-1", "10.0.0.9", "6379"},
}
for _, wb := range wantBlocks {
if !strings.Contains(out, "Host "+wb.alias+"\n") {
t.Errorf("output missing %q Host block, got:\n%s", wb.alias, out)
}
}
if !strings.Contains(out, " ProxyJump jump\n") {
t.Errorf("output missing ProxyJump jump directive, got:\n%s", out)
}
proxyJumpCount := strings.Count(out, "ProxyJump jump")
if proxyJumpCount != len(forwards) {
t.Errorf("expected %d ProxyJump jump directives (one per forward), got %d", len(forwards), proxyJumpCount)
}
identitiesOnlyCount := strings.Count(out, "IdentitiesOnly yes")
// One for the "jump" block plus one per forward block.
if identitiesOnlyCount != len(forwards)+1 {
t.Errorf("expected %d IdentitiesOnly yes lines, got %d", len(forwards)+1, identitiesOnlyCount)
}
}
func TestRenderClientConfigProxyJump_NoForwards(t *testing.T) {
out := RenderClientConfigProxyJump("bastion.example.com", 22, "nobody", "nobody_jump_key", nil)
if !strings.Contains(out, "Host jump\n") {
t.Errorf("expected jump block even with no forwards, got:\n%s", out)
}
if strings.Contains(out, "ProxyJump jump") {
t.Errorf("no forwards means no per-service blocks, but found ProxyJump jump in:\n%s", out)
}
}
// TestRenderClientConfig_LocalForwardModeUnaffected pins down that the
// pre-existing LocalForward-mode renderer still behaves as before adding
// ProxyJump mode alongside it (regression guard).
func TestRenderClientConfig_LocalForwardModeUnaffected(t *testing.T) {
forwards := []AllocatedForward{
{Forward: model.Forward{HostName: "db-prod-1", Address: "10.0.0.5", Port: 22, ServiceName: "ssh", Description: "primary db"}, LocalPort: 20000},
{Forward: model.Forward{HostName: "cache-1", Address: "10.0.0.9", Port: 6379, ServiceName: "redis"}, LocalPort: 20001},
}
out := RenderClientConfig("bastion.example.com", 2222, "a-makarov", "a-makarov_jump_key", forwards)
if strings.Contains(out, "ProxyJump") {
t.Errorf("LocalForward-mode output must not contain ProxyJump, got:\n%s", out)
}
if !strings.Contains(out, "Host jump\n") {
t.Errorf("expected single jump block, got:\n%s", out)
}
if strings.Contains(out, "Host db-prod-1\n") || strings.Contains(out, "Host cache-1\n") {
t.Errorf("LocalForward mode must not emit per-service Host blocks, got:\n%s", out)
}
wantLines := []string{
" LocalForward 20000 10.0.0.5:22 # db-prod-1/ssh: primary db\n",
" LocalForward 20001 10.0.0.9:6379 # cache-1/redis\n",
}
for _, want := range wantLines {
if !strings.Contains(out, want) {
t.Errorf("missing expected LocalForward line %q, got:\n%s", want, out)
}
}
}