Cover WebSocket session lifecycle (result channels, SNMP batch flushing, heartbeats, restart), MikroTik TLS TOFU verification mismatch, worker pool exhaustion, plaintext WebSocket rejection, HTTP/TCP/DNS check execution, IPv4 fallback, and customer network edge cases. Make heartbeat intervals and toWebSocketURL exit injectable for testing.
135 lines
3.3 KiB
Go
135 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func TestHostKeyStoreTOFU(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "known_hosts.json")
|
|
s := newHostKeyStore(path)
|
|
|
|
// First connection should succeed (trust on first use)
|
|
if err := s.verify("10.0.0.1:22", "abc123"); err != nil {
|
|
t.Fatalf("first connect should succeed: %v", err)
|
|
}
|
|
|
|
// Same key should succeed
|
|
if err := s.verify("10.0.0.1:22", "abc123"); err != nil {
|
|
t.Fatalf("same key should succeed: %v", err)
|
|
}
|
|
|
|
// Different key should fail
|
|
if err := s.verify("10.0.0.1:22", "different"); err == nil {
|
|
t.Fatal("changed key should fail")
|
|
}
|
|
|
|
// New host should succeed
|
|
if err := s.verify("10.0.0.2:22", "def456"); err != nil {
|
|
t.Fatalf("new host should succeed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestHostKeyStorePersistence(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "known_hosts.json")
|
|
|
|
s1 := newHostKeyStore(path)
|
|
_ = s1.verify("host1:22", "fp1")
|
|
|
|
// Load from same file
|
|
s2 := newHostKeyStore(path)
|
|
if err := s2.verify("host1:22", "fp1"); err != nil {
|
|
t.Fatalf("persisted key should match: %v", err)
|
|
}
|
|
if err := s2.verify("host1:22", "changed"); err == nil {
|
|
t.Fatal("changed key should fail after reload")
|
|
}
|
|
}
|
|
|
|
func TestHostKeyStoreMissingFile(t *testing.T) {
|
|
s := newHostKeyStore("/nonexistent/path/known_hosts.json")
|
|
// Should work in memory even if file can't be read
|
|
if err := s.verify("host:22", "fp"); err != nil {
|
|
t.Fatalf("should work without file: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestHostKeyStoreConcurrency(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "known_hosts.json")
|
|
s := newHostKeyStore(path)
|
|
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 50; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
_ = s.verify("host:22", "fp1")
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestGetHostKeyStoreDefault(t *testing.T) {
|
|
// Reset the once for testing
|
|
origStore := globalHostKeys
|
|
defer func() {
|
|
hostKeysOnce = sync.Once{}
|
|
globalHostKeys = origStore
|
|
}()
|
|
hostKeysOnce = sync.Once{}
|
|
|
|
t.Setenv("TOWEROPS_HOST_KEYS_FILE", filepath.Join(t.TempDir(), "test_hosts.json"))
|
|
store := getHostKeyStore()
|
|
if store == nil {
|
|
t.Fatal("expected non-nil store")
|
|
}
|
|
}
|
|
|
|
func TestSSHHostKeyCallback(t *testing.T) {
|
|
// Reset global state
|
|
origStore := globalHostKeys
|
|
defer func() {
|
|
hostKeysOnce = sync.Once{}
|
|
globalHostKeys = origStore
|
|
}()
|
|
hostKeysOnce = sync.Once{}
|
|
t.Setenv("TOWEROPS_HOST_KEYS_FILE", filepath.Join(t.TempDir(), "hosts.json"))
|
|
|
|
cb := sshHostKeyCallback()
|
|
if cb == nil {
|
|
t.Fatal("expected non-nil callback")
|
|
}
|
|
}
|
|
|
|
func TestHostKeyStoreSaveError(t *testing.T) {
|
|
// Use a path in a non-existent directory
|
|
s := newHostKeyStore("/nonexistent/dir/hosts.json")
|
|
// verify should not error even if save fails (it logs instead)
|
|
if err := s.verify("host:22", "fp"); err != nil {
|
|
t.Fatalf("should succeed even if save fails: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTlsCertFingerprint(t *testing.T) {
|
|
cert := &x509.Certificate{
|
|
Raw: []byte("test certificate data"),
|
|
}
|
|
fp := tlsCertFingerprint(cert)
|
|
if fp == "" {
|
|
t.Error("expected non-empty fingerprint")
|
|
}
|
|
// Verify it's a hex-encoded SHA256 (64 chars)
|
|
if len(fp) != 64 {
|
|
t.Errorf("expected 64-char hex fingerprint, got %d chars: %s", len(fp), fp)
|
|
}
|
|
// Same input should produce same output
|
|
fp2 := tlsCertFingerprint(cert)
|
|
if fp != fp2 {
|
|
t.Error("fingerprint not deterministic")
|
|
}
|
|
}
|