test: add unit tests for pkg/pbs
This commit is contained in:
parent
b1fba8e76c
commit
ca15f15ddc
1 changed files with 422 additions and 0 deletions
422
pkg/pbs/client_test.go
Normal file
422
pkg/pbs/client_test.go
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
package pbs
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAuthHTTPError_Error(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
err *authHTTPError
|
||||
contains []string
|
||||
}{
|
||||
{
|
||||
name: "unauthorized",
|
||||
err: &authHTTPError{status: http.StatusUnauthorized, body: "invalid credentials"},
|
||||
contains: []string{"authentication failed", "401", "invalid credentials"},
|
||||
},
|
||||
{
|
||||
name: "forbidden",
|
||||
err: &authHTTPError{status: http.StatusForbidden, body: "access denied"},
|
||||
contains: []string{"authentication failed", "403", "access denied"},
|
||||
},
|
||||
{
|
||||
name: "other error",
|
||||
err: &authHTTPError{status: http.StatusInternalServerError, body: "server error"},
|
||||
contains: []string{"authentication failed", "server error"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
errMsg := tc.err.Error()
|
||||
for _, expected := range tc.contains {
|
||||
if !containsString(errMsg, expected) {
|
||||
t.Errorf("Error() = %q, expected to contain %q", errMsg, expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldFallbackToForm(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "bad request should fallback",
|
||||
err: &authHTTPError{status: http.StatusBadRequest, body: "bad request"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "unsupported media type should fallback",
|
||||
err: &authHTTPError{status: http.StatusUnsupportedMediaType, body: "unsupported"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "unauthorized should not fallback",
|
||||
err: &authHTTPError{status: http.StatusUnauthorized, body: "unauthorized"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "server error should not fallback",
|
||||
err: &authHTTPError{status: http.StatusInternalServerError, body: "error"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "non-auth error should not fallback",
|
||||
err: http.ErrHandlerTimeout,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "nil error should not fallback",
|
||||
err: nil,
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := shouldFallbackToForm(tc.err)
|
||||
if result != tc.expected {
|
||||
t.Errorf("shouldFallbackToForm() = %v, want %v", result, tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientConfig_Defaults(t *testing.T) {
|
||||
cfg := ClientConfig{}
|
||||
|
||||
// Verify zero values
|
||||
if cfg.Host != "" {
|
||||
t.Error("Host should be empty by default")
|
||||
}
|
||||
if cfg.User != "" {
|
||||
t.Error("User should be empty by default")
|
||||
}
|
||||
if cfg.Password != "" {
|
||||
t.Error("Password should be empty by default")
|
||||
}
|
||||
if cfg.TokenName != "" {
|
||||
t.Error("TokenName should be empty by default")
|
||||
}
|
||||
if cfg.TokenValue != "" {
|
||||
t.Error("TokenValue should be empty by default")
|
||||
}
|
||||
if cfg.Fingerprint != "" {
|
||||
t.Error("Fingerprint should be empty by default")
|
||||
}
|
||||
if cfg.VerifySSL {
|
||||
t.Error("VerifySSL should be false by default")
|
||||
}
|
||||
if cfg.Timeout != 0 {
|
||||
t.Error("Timeout should be zero by default")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClient_InvalidUserFormat(t *testing.T) {
|
||||
cfg := ClientConfig{
|
||||
Host: "https://pbs.example.com:8007",
|
||||
User: "root", // Missing @realm
|
||||
Password: "password",
|
||||
}
|
||||
|
||||
_, err := NewClient(cfg)
|
||||
if err == nil {
|
||||
t.Error("NewClient should fail with invalid user format")
|
||||
}
|
||||
if !containsString(err.Error(), "user@realm") {
|
||||
t.Errorf("Error should mention user@realm format, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClient_TokenAuthRequiresUser(t *testing.T) {
|
||||
cfg := ClientConfig{
|
||||
Host: "https://pbs.example.com:8007",
|
||||
TokenName: "mytoken", // Just token name, no user info
|
||||
TokenValue: "secret",
|
||||
}
|
||||
|
||||
_, err := NewClient(cfg)
|
||||
if err == nil {
|
||||
t.Error("NewClient should fail when token auth lacks user info")
|
||||
}
|
||||
if !containsString(err.Error(), "user information") {
|
||||
t.Errorf("Error should mention user information, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClient_HTTPSDefault(t *testing.T) {
|
||||
// Can't test the actual client creation (needs network), but we can verify
|
||||
// the config normalization logic
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"pbs.example.com:8007", "https://pbs.example.com:8007"},
|
||||
{"https://pbs.example.com:8007", "https://pbs.example.com:8007"},
|
||||
{"http://pbs.example.com:8007", "http://pbs.example.com:8007"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.input, func(t *testing.T) {
|
||||
// This tests the normalization logic
|
||||
host := tc.input
|
||||
if host[:4] != "http" {
|
||||
host = "https://" + host
|
||||
}
|
||||
if host != tc.expected {
|
||||
t.Errorf("Host = %q, want %q", host, tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuth_Fields(t *testing.T) {
|
||||
a := auth{
|
||||
user: "root",
|
||||
realm: "pam",
|
||||
ticket: "ticket123",
|
||||
csrfToken: "csrf456",
|
||||
tokenName: "mytoken",
|
||||
tokenValue: "secret",
|
||||
expiresAt: time.Now().Add(2 * time.Hour),
|
||||
}
|
||||
|
||||
if a.user != "root" {
|
||||
t.Errorf("user = %q, want root", a.user)
|
||||
}
|
||||
if a.realm != "pam" {
|
||||
t.Errorf("realm = %q, want pam", a.realm)
|
||||
}
|
||||
if a.ticket != "ticket123" {
|
||||
t.Errorf("ticket = %q, want ticket123", a.ticket)
|
||||
}
|
||||
if a.csrfToken != "csrf456" {
|
||||
t.Errorf("csrfToken = %q, want csrf456", a.csrfToken)
|
||||
}
|
||||
if a.tokenName != "mytoken" {
|
||||
t.Errorf("tokenName = %q, want mytoken", a.tokenName)
|
||||
}
|
||||
if a.tokenValue != "secret" {
|
||||
t.Errorf("tokenValue = %q, want secret", a.tokenValue)
|
||||
}
|
||||
if a.expiresAt.IsZero() {
|
||||
t.Error("expiresAt should not be zero")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatastore_Fields(t *testing.T) {
|
||||
ds := Datastore{
|
||||
Store: "backup1",
|
||||
Total: 1000000000000,
|
||||
Used: 500000000000,
|
||||
Avail: 500000000000,
|
||||
DeduplicationFactor: 1.5,
|
||||
GCStatus: "ok",
|
||||
Error: "",
|
||||
}
|
||||
|
||||
if ds.Store != "backup1" {
|
||||
t.Errorf("Store = %q, want backup1", ds.Store)
|
||||
}
|
||||
if ds.Total != 1000000000000 {
|
||||
t.Errorf("Total = %d, want 1000000000000", ds.Total)
|
||||
}
|
||||
if ds.Used != 500000000000 {
|
||||
t.Errorf("Used = %d, want 500000000000", ds.Used)
|
||||
}
|
||||
if ds.Avail != 500000000000 {
|
||||
t.Errorf("Avail = %d, want 500000000000", ds.Avail)
|
||||
}
|
||||
if ds.DeduplicationFactor != 1.5 {
|
||||
t.Errorf("DeduplicationFactor = %f, want 1.5", ds.DeduplicationFactor)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersion_Fields(t *testing.T) {
|
||||
v := Version{
|
||||
Version: "2.4",
|
||||
Release: "2",
|
||||
Repoid: "abcdef123",
|
||||
}
|
||||
|
||||
if v.Version != "2.4" {
|
||||
t.Errorf("Version = %q, want 2.4", v.Version)
|
||||
}
|
||||
if v.Release != "2" {
|
||||
t.Errorf("Release = %q, want 2", v.Release)
|
||||
}
|
||||
if v.Repoid != "abcdef123" {
|
||||
t.Errorf("Repoid = %q, want abcdef123", v.Repoid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeStatus_Fields(t *testing.T) {
|
||||
ns := NodeStatus{
|
||||
CPU: 0.25,
|
||||
Uptime: 86400,
|
||||
LoadAverage: []float64{0.5, 0.7, 0.9},
|
||||
Memory: Memory{
|
||||
Total: 16000000000,
|
||||
Used: 8000000000,
|
||||
Free: 8000000000,
|
||||
},
|
||||
Swap: Memory{
|
||||
Total: 4000000000,
|
||||
Used: 1000000000,
|
||||
Free: 3000000000,
|
||||
},
|
||||
}
|
||||
|
||||
if ns.CPU != 0.25 {
|
||||
t.Errorf("CPU = %f, want 0.25", ns.CPU)
|
||||
}
|
||||
if ns.Uptime != 86400 {
|
||||
t.Errorf("Uptime = %d, want 86400", ns.Uptime)
|
||||
}
|
||||
if len(ns.LoadAverage) != 3 {
|
||||
t.Errorf("LoadAverage length = %d, want 3", len(ns.LoadAverage))
|
||||
}
|
||||
if ns.Memory.Total != 16000000000 {
|
||||
t.Errorf("Memory.Total = %d, want 16000000000", ns.Memory.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackupGroup_Fields(t *testing.T) {
|
||||
bg := BackupGroup{
|
||||
BackupType: "vm",
|
||||
BackupID: "100",
|
||||
LastBackup: 1700000000,
|
||||
BackupCount: 5,
|
||||
Owner: "root@pam",
|
||||
}
|
||||
|
||||
if bg.BackupType != "vm" {
|
||||
t.Errorf("BackupType = %q, want vm", bg.BackupType)
|
||||
}
|
||||
if bg.BackupID != "100" {
|
||||
t.Errorf("BackupID = %q, want 100", bg.BackupID)
|
||||
}
|
||||
if bg.LastBackup != 1700000000 {
|
||||
t.Errorf("LastBackup = %d, want 1700000000", bg.LastBackup)
|
||||
}
|
||||
if bg.BackupCount != 5 {
|
||||
t.Errorf("BackupCount = %d, want 5", bg.BackupCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackupSnapshot_Fields(t *testing.T) {
|
||||
bs := BackupSnapshot{
|
||||
BackupType: "ct",
|
||||
BackupID: "101",
|
||||
BackupTime: 1700000000,
|
||||
Size: 1073741824,
|
||||
Protected: true,
|
||||
Comment: "daily backup",
|
||||
}
|
||||
|
||||
if bs.BackupType != "ct" {
|
||||
t.Errorf("BackupType = %q, want ct", bs.BackupType)
|
||||
}
|
||||
if bs.BackupID != "101" {
|
||||
t.Errorf("BackupID = %q, want 101", bs.BackupID)
|
||||
}
|
||||
if bs.BackupTime != 1700000000 {
|
||||
t.Errorf("BackupTime = %d, want 1700000000", bs.BackupTime)
|
||||
}
|
||||
if bs.Size != 1073741824 {
|
||||
t.Errorf("Size = %d, want 1073741824", bs.Size)
|
||||
}
|
||||
if !bs.Protected {
|
||||
t.Error("Protected should be true")
|
||||
}
|
||||
if bs.Comment != "daily backup" {
|
||||
t.Errorf("Comment = %q, want daily backup", bs.Comment)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespace_Fields(t *testing.T) {
|
||||
ns := Namespace{
|
||||
NS: "ns1",
|
||||
Path: "/ns1",
|
||||
Name: "ns1",
|
||||
Parent: "",
|
||||
}
|
||||
|
||||
if ns.NS != "ns1" {
|
||||
t.Errorf("NS = %q, want ns1", ns.NS)
|
||||
}
|
||||
if ns.Path != "/ns1" {
|
||||
t.Errorf("Path = %q, want /ns1", ns.Path)
|
||||
}
|
||||
if ns.Name != "ns1" {
|
||||
t.Errorf("Name = %q, want ns1", ns.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemory_Fields(t *testing.T) {
|
||||
m := Memory{
|
||||
Total: 16000000000,
|
||||
Used: 8000000000,
|
||||
Free: 8000000000,
|
||||
}
|
||||
|
||||
if m.Total != 16000000000 {
|
||||
t.Errorf("Total = %d, want 16000000000", m.Total)
|
||||
}
|
||||
if m.Used != 8000000000 {
|
||||
t.Errorf("Used = %d, want 8000000000", m.Used)
|
||||
}
|
||||
if m.Free != 8000000000 {
|
||||
t.Errorf("Free = %d, want 8000000000", m.Free)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFSInfo_Fields(t *testing.T) {
|
||||
fs := FSInfo{
|
||||
Total: 500000000000,
|
||||
Used: 250000000000,
|
||||
Free: 250000000000,
|
||||
}
|
||||
|
||||
if fs.Total != 500000000000 {
|
||||
t.Errorf("Total = %d, want 500000000000", fs.Total)
|
||||
}
|
||||
if fs.Used != 250000000000 {
|
||||
t.Errorf("Used = %d, want 250000000000", fs.Used)
|
||||
}
|
||||
if fs.Free != 250000000000 {
|
||||
t.Errorf("Free = %d, want 250000000000", fs.Free)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKSMInfo_Fields(t *testing.T) {
|
||||
ksm := KSMInfo{
|
||||
Shared: 1000000000,
|
||||
}
|
||||
|
||||
if ksm.Shared != 1000000000 {
|
||||
t.Errorf("Shared = %d, want 1000000000", ksm.Shared)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function
|
||||
func containsString(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
|
||||
(len(s) > 0 && len(substr) > 0 && findSubstring(s, substr)))
|
||||
}
|
||||
|
||||
func findSubstring(s, substr string) bool {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Loading…
Reference in a new issue