package felhomsshd import ( "strings" "testing" ) func TestRenderConfig_SafeTemplateAndByteStable(t *testing.T) { c, err := renderConfig(8822) if err != nil { t.Fatal(err) } for _, must := range []string{ "Port 8822\n", "AuthorizedKeysFile /etc/felhom-sshd/authorized_keys/%u\n", "HostKey /etc/felhom-sshd/ssh_host_ed25519_key\n", "PasswordAuthentication no\n", "PermitRootLogin prohibit-password\n", "AllowUsers root felhom-op\n", "PidFile /run/felhom-sshd.pid\n", } { if !strings.Contains(c, must) { t.Errorf("config missing %q:\n%s", must, c) } } // [SF-1] the incident cause must NEVER appear. if strings.Contains(c, "RuntimeDirectory") { t.Fatal("config/unit must never mention RuntimeDirectory") } // byte-stable for a given port (conf-hash stability → no reload churn). c2, _ := renderConfig(8822) if c != c2 { t.Fatal("renderConfig not deterministic") } } func TestRenderConfig_RefusesPort22AndOutOfRange(t *testing.T) { if _, err := renderConfig(22); err == nil { t.Fatal("renderConfig(22) must be refused — never claim the stock/customer port") } if _, err := renderConfig(0); err == nil { t.Fatal("port 0 accepted") } if _, err := renderConfig(70000); err == nil { t.Fatal("out-of-range port accepted") } } // claim harness: a fake free-set + an in-memory persisted port. func claimHarness(free map[int]bool, persisted int) (portProbe, func() (int, bool), func(int) error, *int) { stored := persisted isFree := func(p int) bool { return free[p] } read := func() (int, bool) { if stored == 0 { return 0, false } return stored, true } write := func(p int) error { stored = p; return nil } return isFree, read, write, &stored } func TestClaimPort_CleanContentionIdempotentExhaustion(t *testing.T) { cands := []int{8822, 2222, 8022, 62222} // clean → first candidate isFree, read, write, stored := claimHarness(map[int]bool{8822: true, 2222: true, 8022: true, 62222: true}, 0) if p, err := claimPort(cands, isFree, read, write); err != nil || p != 8822 { t.Fatalf("clean claim = %d / %v, want 8822", p, err) } if *stored != 8822 { t.Fatalf("clean claim not persisted, stored=%d", *stored) } // contention: 8822 busy → 2222 isFree, read, write, _ = claimHarness(map[int]bool{8822: false, 2222: true, 8022: true, 62222: true}, 0) if p, err := claimPort(cands, isFree, read, write); err != nil || p != 2222 { t.Fatalf("contention claim = %d / %v, want 2222", p, err) } // idempotent: a persisted port is KEPT unconditionally (even when isFree(cur) reports busy — // that's felhom-sshd itself holding it on a continuous reconcile; re-probing would flip-flop). isFree, read, write, _ = claimHarness(map[int]bool{8822: true, 2222: false, 8022: true, 62222: true}, 2222) if p, err := claimPort(cands, isFree, read, write); err != nil || p != 2222 { t.Fatalf("persisted port must be kept even when isFree says busy (own daemon), got %d / %v", p, err) } // exhaustion: all busy → LOUD error, no fallback isFree, read, write, stored = claimHarness(map[int]bool{8822: false, 2222: false, 8022: false, 62222: false}, 0) p, err := claimPort(cands, isFree, read, write) if err != ErrPortsExhausted { t.Fatalf("exhaustion must return ErrPortsExhausted, got %d / %v", p, err) } if p == 22 || p != 0 { t.Fatalf("exhaustion must NOT yield a port (esp. :22), got %d", p) } if *stored != 0 { t.Fatalf("exhaustion must persist nothing, stored=%d", *stored) } }