test: Add tests for instance lookup and setup token validation
- Add TestFindInstanceNameByHost (12 test cases): - PVE instance lookup by host - PBS instance lookup by host - Unknown node types return empty - Empty config handling - Add TestValidateSetupToken (8 test cases): - Empty token returns false - Valid/expired/used setup codes - Valid/expired recent setup tokens - Non-existent token handling - Precedence of setup codes over recent tokens Both functions now have 100% coverage of their logic paths.
This commit is contained in:
parent
b403cdd7b7
commit
4019b3006b
1 changed files with 304 additions and 1 deletions
|
|
@ -1,6 +1,12 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rcourtman/pulse-go-rewrite/internal/auth"
|
||||||
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
func TestNormalizePVEUser(t *testing.T) {
|
func TestNormalizePVEUser(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
@ -446,3 +452,300 @@ func TestShouldSkipClusterAutoDetection(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFindInstanceNameByHost(t *testing.T) {
|
||||||
|
t.Run("pve instances", func(t *testing.T) {
|
||||||
|
cfg := &config.Config{
|
||||||
|
PVEInstances: []config.PVEInstance{
|
||||||
|
{Name: "pve-node1", Host: "https://192.168.1.10:8006"},
|
||||||
|
{Name: "pve-node2", Host: "https://192.168.1.11:8006"},
|
||||||
|
{Name: "pve-node3", Host: "https://pve3.example.com:8006"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
h := &ConfigHandlers{config: cfg}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
nodeType string
|
||||||
|
host string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "finds first PVE node",
|
||||||
|
nodeType: "pve",
|
||||||
|
host: "https://192.168.1.10:8006",
|
||||||
|
want: "pve-node1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "finds second PVE node",
|
||||||
|
nodeType: "pve",
|
||||||
|
host: "https://192.168.1.11:8006",
|
||||||
|
want: "pve-node2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "finds PVE node by hostname",
|
||||||
|
nodeType: "pve",
|
||||||
|
host: "https://pve3.example.com:8006",
|
||||||
|
want: "pve-node3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns empty for non-existent PVE host",
|
||||||
|
nodeType: "pve",
|
||||||
|
host: "https://192.168.1.99:8006",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns empty for empty host",
|
||||||
|
nodeType: "pve",
|
||||||
|
host: "",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := h.findInstanceNameByHost(tt.nodeType, tt.host)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("findInstanceNameByHost(%q, %q) = %q, want %q", tt.nodeType, tt.host, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pbs instances", func(t *testing.T) {
|
||||||
|
cfg := &config.Config{
|
||||||
|
PBSInstances: []config.PBSInstance{
|
||||||
|
{Name: "pbs-backup1", Host: "https://192.168.1.20:8007"},
|
||||||
|
{Name: "pbs-backup2", Host: "https://backup.example.com:8007"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
h := &ConfigHandlers{config: cfg}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
nodeType string
|
||||||
|
host string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "finds PBS node by IP",
|
||||||
|
nodeType: "pbs",
|
||||||
|
host: "https://192.168.1.20:8007",
|
||||||
|
want: "pbs-backup1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "finds PBS node by hostname",
|
||||||
|
nodeType: "pbs",
|
||||||
|
host: "https://backup.example.com:8007",
|
||||||
|
want: "pbs-backup2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns empty for non-existent PBS host",
|
||||||
|
nodeType: "pbs",
|
||||||
|
host: "https://192.168.1.99:8007",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := h.findInstanceNameByHost(tt.nodeType, tt.host)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("findInstanceNameByHost(%q, %q) = %q, want %q", tt.nodeType, tt.host, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown node type", func(t *testing.T) {
|
||||||
|
cfg := &config.Config{
|
||||||
|
PVEInstances: []config.PVEInstance{
|
||||||
|
{Name: "pve-node1", Host: "https://192.168.1.10:8006"},
|
||||||
|
},
|
||||||
|
PBSInstances: []config.PBSInstance{
|
||||||
|
{Name: "pbs-backup1", Host: "https://192.168.1.20:8007"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
h := &ConfigHandlers{config: cfg}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
nodeType string
|
||||||
|
host string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "returns empty for unknown type",
|
||||||
|
nodeType: "unknown",
|
||||||
|
host: "https://192.168.1.10:8006",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns empty for pmg type (not implemented)",
|
||||||
|
nodeType: "pmg",
|
||||||
|
host: "https://192.168.1.30:8006",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns empty for empty type",
|
||||||
|
nodeType: "",
|
||||||
|
host: "https://192.168.1.10:8006",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := h.findInstanceNameByHost(tt.nodeType, tt.host)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("findInstanceNameByHost(%q, %q) = %q, want %q", tt.nodeType, tt.host, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty config", func(t *testing.T) {
|
||||||
|
cfg := &config.Config{}
|
||||||
|
h := &ConfigHandlers{config: cfg}
|
||||||
|
|
||||||
|
got := h.findInstanceNameByHost("pve", "https://192.168.1.10:8006")
|
||||||
|
if got != "" {
|
||||||
|
t.Errorf("expected empty string for empty config, got %q", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateSetupToken(t *testing.T) {
|
||||||
|
t.Run("empty token returns false", func(t *testing.T) {
|
||||||
|
h := &ConfigHandlers{
|
||||||
|
setupCodes: make(map[string]*SetupCode),
|
||||||
|
recentSetupTokens: make(map[string]time.Time),
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.ValidateSetupToken("") {
|
||||||
|
t.Error("expected false for empty token")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid setup code token", func(t *testing.T) {
|
||||||
|
token := "test-setup-token-12345"
|
||||||
|
tokenHash := auth.HashAPIToken(token)
|
||||||
|
|
||||||
|
h := &ConfigHandlers{
|
||||||
|
setupCodes: map[string]*SetupCode{
|
||||||
|
tokenHash: {
|
||||||
|
ExpiresAt: time.Now().Add(1 * time.Hour),
|
||||||
|
Used: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
recentSetupTokens: make(map[string]time.Time),
|
||||||
|
}
|
||||||
|
|
||||||
|
if !h.ValidateSetupToken(token) {
|
||||||
|
t.Error("expected true for valid setup code token")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("expired setup code returns false", func(t *testing.T) {
|
||||||
|
token := "expired-token-12345"
|
||||||
|
tokenHash := auth.HashAPIToken(token)
|
||||||
|
|
||||||
|
h := &ConfigHandlers{
|
||||||
|
setupCodes: map[string]*SetupCode{
|
||||||
|
tokenHash: {
|
||||||
|
ExpiresAt: time.Now().Add(-1 * time.Hour), // Expired
|
||||||
|
Used: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
recentSetupTokens: make(map[string]time.Time),
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.ValidateSetupToken(token) {
|
||||||
|
t.Error("expected false for expired setup code")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("used setup code returns false", func(t *testing.T) {
|
||||||
|
token := "used-token-12345"
|
||||||
|
tokenHash := auth.HashAPIToken(token)
|
||||||
|
|
||||||
|
h := &ConfigHandlers{
|
||||||
|
setupCodes: map[string]*SetupCode{
|
||||||
|
tokenHash: {
|
||||||
|
ExpiresAt: time.Now().Add(1 * time.Hour),
|
||||||
|
Used: true, // Already used
|
||||||
|
},
|
||||||
|
},
|
||||||
|
recentSetupTokens: make(map[string]time.Time),
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.ValidateSetupToken(token) {
|
||||||
|
t.Error("expected false for used setup code")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid recent setup token", func(t *testing.T) {
|
||||||
|
token := "recent-setup-token-12345"
|
||||||
|
tokenHash := auth.HashAPIToken(token)
|
||||||
|
|
||||||
|
h := &ConfigHandlers{
|
||||||
|
setupCodes: make(map[string]*SetupCode),
|
||||||
|
recentSetupTokens: map[string]time.Time{
|
||||||
|
tokenHash: time.Now().Add(1 * time.Hour), // Valid for another hour
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !h.ValidateSetupToken(token) {
|
||||||
|
t.Error("expected true for valid recent setup token")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("expired recent setup token returns false", func(t *testing.T) {
|
||||||
|
token := "expired-recent-token-12345"
|
||||||
|
tokenHash := auth.HashAPIToken(token)
|
||||||
|
|
||||||
|
h := &ConfigHandlers{
|
||||||
|
setupCodes: make(map[string]*SetupCode),
|
||||||
|
recentSetupTokens: map[string]time.Time{
|
||||||
|
tokenHash: time.Now().Add(-1 * time.Hour), // Expired
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.ValidateSetupToken(token) {
|
||||||
|
t.Error("expected false for expired recent setup token")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-existent token returns false", func(t *testing.T) {
|
||||||
|
h := &ConfigHandlers{
|
||||||
|
setupCodes: make(map[string]*SetupCode),
|
||||||
|
recentSetupTokens: make(map[string]time.Time),
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.ValidateSetupToken("non-existent-token") {
|
||||||
|
t.Error("expected false for non-existent token")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("setup code takes precedence over recent token", func(t *testing.T) {
|
||||||
|
token := "dual-token-12345"
|
||||||
|
tokenHash := auth.HashAPIToken(token)
|
||||||
|
|
||||||
|
h := &ConfigHandlers{
|
||||||
|
setupCodes: map[string]*SetupCode{
|
||||||
|
tokenHash: {
|
||||||
|
ExpiresAt: time.Now().Add(1 * time.Hour),
|
||||||
|
Used: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
recentSetupTokens: map[string]time.Time{
|
||||||
|
tokenHash: time.Now().Add(1 * time.Hour),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should return true (setup code is valid)
|
||||||
|
if !h.ValidateSetupToken(token) {
|
||||||
|
t.Error("expected true when both setup code and recent token exist")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue