test: Add tests for webhook template generation and bootstrap token functions
- Add comprehensive tests for generatePayloadFromTemplateWithService covering: - Valid JSON template rendering - Invalid template syntax error handling - Template execution errors - ntfy service (plain text, skips JSON validation) - Various services (discord, telegram, pagerduty) JSON validation - Template function usage (upper, title) - Numeric and boolean values in templates - Special characters and empty template edge cases - Add tests for bootstrapTokenValid covering: - Nil router handling - Empty hash and empty token cases - Valid token validation - Invalid token rejection - Whitespace trimming - Add tests for clearBootstrapToken covering: - Nil router safety - Token file deletion - Missing file handling - Empty path handling
This commit is contained in:
parent
cb683597c6
commit
67a351fbd9
2 changed files with 441 additions and 0 deletions
|
|
@ -5,6 +5,8 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
)
|
||||
|
||||
func TestLoadOrCreateBootstrapToken_EmptyDataPath(t *testing.T) {
|
||||
|
|
@ -290,3 +292,164 @@ func TestGenerateBootstrapToken(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBootstrapTokenValid(t *testing.T) {
|
||||
t.Run("nil router returns false", func(t *testing.T) {
|
||||
var r *Router = nil
|
||||
if r.bootstrapTokenValid("anything") {
|
||||
t.Error("expected false for nil router")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty hash returns false", func(t *testing.T) {
|
||||
r := &Router{bootstrapTokenHash: ""}
|
||||
if r.bootstrapTokenValid("anything") {
|
||||
t.Error("expected false when bootstrapTokenHash is empty")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty token returns false", func(t *testing.T) {
|
||||
// Generate a token and create a router with its hash
|
||||
token, err := generateBootstrapToken()
|
||||
if err != nil {
|
||||
t.Fatalf("generateBootstrapToken() error: %v", err)
|
||||
}
|
||||
r := &Router{}
|
||||
// Create a token and store its hash - use loadOrCreateBootstrapToken to get a hash indirectly
|
||||
tmpDir := t.TempDir()
|
||||
loadedToken, _, _, err := loadOrCreateBootstrapToken(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("loadOrCreateBootstrapToken() error: %v", err)
|
||||
}
|
||||
// Since we can't directly access HashAPIToken, we need to use initializeBootstrapToken
|
||||
// Instead, let's test with a known hash by using the auth package
|
||||
_ = token
|
||||
_ = loadedToken
|
||||
// For this test, we just need any non-empty hash
|
||||
r.bootstrapTokenHash = "somehash"
|
||||
|
||||
if r.bootstrapTokenValid("") {
|
||||
t.Error("expected false for empty token")
|
||||
}
|
||||
if r.bootstrapTokenValid(" ") {
|
||||
t.Error("expected false for whitespace-only token")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("valid token returns true", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
token, _, _, err := loadOrCreateBootstrapToken(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("loadOrCreateBootstrapToken() error: %v", err)
|
||||
}
|
||||
|
||||
// Create a router that simulates having loaded the token
|
||||
// We need to use the auth package to hash the token
|
||||
cfg := &config.Config{DataPath: tmpDir}
|
||||
r := &Router{config: cfg}
|
||||
r.initializeBootstrapToken()
|
||||
|
||||
if !r.bootstrapTokenValid(token) {
|
||||
t.Error("expected true for valid token")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid token returns false", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_, _, _, err := loadOrCreateBootstrapToken(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("loadOrCreateBootstrapToken() error: %v", err)
|
||||
}
|
||||
|
||||
cfg := &config.Config{DataPath: tmpDir}
|
||||
r := &Router{config: cfg}
|
||||
r.initializeBootstrapToken()
|
||||
|
||||
if r.bootstrapTokenValid("wrongtoken") {
|
||||
t.Error("expected false for wrong token")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("token with whitespace is trimmed", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
token, _, _, err := loadOrCreateBootstrapToken(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("loadOrCreateBootstrapToken() error: %v", err)
|
||||
}
|
||||
|
||||
cfg := &config.Config{DataPath: tmpDir}
|
||||
r := &Router{config: cfg}
|
||||
r.initializeBootstrapToken()
|
||||
|
||||
// Token with leading/trailing whitespace should still validate
|
||||
if !r.bootstrapTokenValid(" " + token + " ") {
|
||||
t.Error("expected true for token with surrounding whitespace")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestClearBootstrapToken(t *testing.T) {
|
||||
t.Run("nil router does not panic", func(t *testing.T) {
|
||||
var r *Router = nil
|
||||
// Should not panic
|
||||
r.clearBootstrapToken()
|
||||
})
|
||||
|
||||
t.Run("clears token hash and path", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_, _, tokenPath, err := loadOrCreateBootstrapToken(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("loadOrCreateBootstrapToken() error: %v", err)
|
||||
}
|
||||
|
||||
r := &Router{
|
||||
bootstrapTokenHash: "somehash",
|
||||
bootstrapTokenPath: tokenPath,
|
||||
}
|
||||
|
||||
r.clearBootstrapToken()
|
||||
|
||||
if r.bootstrapTokenHash != "" {
|
||||
t.Errorf("expected empty bootstrapTokenHash, got %q", r.bootstrapTokenHash)
|
||||
}
|
||||
if r.bootstrapTokenPath != "" {
|
||||
t.Errorf("expected empty bootstrapTokenPath, got %q", r.bootstrapTokenPath)
|
||||
}
|
||||
|
||||
// Verify file was deleted
|
||||
if _, err := os.Stat(tokenPath); !os.IsNotExist(err) {
|
||||
t.Error("expected token file to be deleted")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("handles missing file gracefully", func(t *testing.T) {
|
||||
r := &Router{
|
||||
bootstrapTokenHash: "somehash",
|
||||
bootstrapTokenPath: "/nonexistent/path/token",
|
||||
}
|
||||
|
||||
// Should not panic, should clear the hash and path
|
||||
r.clearBootstrapToken()
|
||||
|
||||
if r.bootstrapTokenHash != "" {
|
||||
t.Errorf("expected empty bootstrapTokenHash, got %q", r.bootstrapTokenHash)
|
||||
}
|
||||
if r.bootstrapTokenPath != "" {
|
||||
t.Errorf("expected empty bootstrapTokenPath, got %q", r.bootstrapTokenPath)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("handles empty path", func(t *testing.T) {
|
||||
r := &Router{
|
||||
bootstrapTokenHash: "somehash",
|
||||
bootstrapTokenPath: "",
|
||||
}
|
||||
|
||||
// Should not panic
|
||||
r.clearBootstrapToken()
|
||||
|
||||
if r.bootstrapTokenHash != "" {
|
||||
t.Errorf("expected empty bootstrapTokenHash, got %q", r.bootstrapTokenHash)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2210,3 +2210,281 @@ func TestCheckWebhookRateLimit(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGeneratePayloadFromTemplateWithService(t *testing.T) {
|
||||
t.Run("valid JSON template", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{
|
||||
ResourceName: "test-vm",
|
||||
Message: "CPU usage high",
|
||||
Level: "warning",
|
||||
}
|
||||
template := `{"resource": "{{.ResourceName}}", "message": "{{.Message}}", "level": "{{.Level}}"}`
|
||||
|
||||
result, err := nm.generatePayloadFromTemplateWithService(template, data, "generic")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
expected := `{"resource": "test-vm", "message": "CPU usage high", "level": "warning"}`
|
||||
if string(result) != expected {
|
||||
t.Fatalf("expected %q, got %q", expected, string(result))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid template syntax", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{ResourceName: "test"}
|
||||
// Missing closing brace in template
|
||||
template := `{"resource": "{{.ResourceName}"}`
|
||||
|
||||
_, err := nm.generatePayloadFromTemplateWithService(template, data, "generic")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid template syntax")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "invalid template") {
|
||||
t.Fatalf("expected 'invalid template' in error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("template execution error - missing method", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{ResourceName: "test"}
|
||||
// Reference a non-existent field/method
|
||||
template := `{"value": "{{.NonExistentMethod}}"}`
|
||||
|
||||
_, err := nm.generatePayloadFromTemplateWithService(template, data, "generic")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for non-existent method")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "template execution failed") {
|
||||
t.Fatalf("expected 'template execution failed' in error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ntfy service skips JSON validation", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{
|
||||
ResourceName: "server1",
|
||||
Message: "Alert triggered",
|
||||
}
|
||||
// Plain text template (not valid JSON)
|
||||
template := `Alert: {{.ResourceName}} - {{.Message}}`
|
||||
|
||||
result, err := nm.generatePayloadFromTemplateWithService(template, data, "ntfy")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error for ntfy plain text, got %v", err)
|
||||
}
|
||||
|
||||
expected := "Alert: server1 - Alert triggered"
|
||||
if string(result) != expected {
|
||||
t.Fatalf("expected %q, got %q", expected, string(result))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("non-ntfy service validates JSON", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{ResourceName: "test"}
|
||||
// Plain text (invalid JSON) for non-ntfy service
|
||||
template := `Plain text: {{.ResourceName}}`
|
||||
|
||||
_, err := nm.generatePayloadFromTemplateWithService(template, data, "slack")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for non-JSON output on slack service")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "template produced invalid JSON") {
|
||||
t.Fatalf("expected 'template produced invalid JSON' in error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("discord service validates JSON", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{
|
||||
ResourceName: "vm-100",
|
||||
Message: "Memory threshold exceeded",
|
||||
}
|
||||
template := `{"content": "{{.ResourceName}}: {{.Message}}"}`
|
||||
|
||||
result, err := nm.generatePayloadFromTemplateWithService(template, data, "discord")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
expected := `{"content": "vm-100: Memory threshold exceeded"}`
|
||||
if string(result) != expected {
|
||||
t.Fatalf("expected %q, got %q", expected, string(result))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("telegram service validates JSON", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{
|
||||
ChatID: "12345",
|
||||
Message: "Alert notification",
|
||||
}
|
||||
template := `{"chat_id": "{{.ChatID}}", "text": "{{.Message}}"}`
|
||||
|
||||
result, err := nm.generatePayloadFromTemplateWithService(template, data, "telegram")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
expected := `{"chat_id": "12345", "text": "Alert notification"}`
|
||||
if string(result) != expected {
|
||||
t.Fatalf("expected %q, got %q", expected, string(result))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("template with numeric values", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{
|
||||
ResourceName: "vm-100",
|
||||
Value: 85.5,
|
||||
Threshold: 80.0,
|
||||
}
|
||||
template := `{"resource": "{{.ResourceName}}", "value": {{.Value}}, "threshold": {{.Threshold}}}`
|
||||
|
||||
result, err := nm.generatePayloadFromTemplateWithService(template, data, "generic")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Verify it's valid JSON
|
||||
var parsed map[string]interface{}
|
||||
if err := json.Unmarshal(result, &parsed); err != nil {
|
||||
t.Fatalf("result is not valid JSON: %v", err)
|
||||
}
|
||||
if parsed["value"].(float64) != 85.5 {
|
||||
t.Fatalf("expected value 85.5, got %v", parsed["value"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("template with boolean values", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{
|
||||
ResourceName: "test",
|
||||
Acknowledged: true,
|
||||
}
|
||||
template := `{"resource": "{{.ResourceName}}", "acknowledged": {{.Acknowledged}}}`
|
||||
|
||||
result, err := nm.generatePayloadFromTemplateWithService(template, data, "generic")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
var parsed map[string]interface{}
|
||||
if err := json.Unmarshal(result, &parsed); err != nil {
|
||||
t.Fatalf("result is not valid JSON: %v", err)
|
||||
}
|
||||
if parsed["acknowledged"].(bool) != true {
|
||||
t.Fatalf("expected acknowledged true, got %v", parsed["acknowledged"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("template with special characters in strings", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{
|
||||
ResourceName: "test",
|
||||
Message: `Line1\nLine2 with "quotes" and \t tabs`,
|
||||
}
|
||||
// Use printf to escape for JSON
|
||||
template := `{"message": "{{.Message}}"}`
|
||||
|
||||
// This will produce invalid JSON because of unescaped characters
|
||||
_, err := nm.generatePayloadFromTemplateWithService(template, data, "generic")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unescaped special characters in JSON")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "template produced invalid JSON") {
|
||||
t.Fatalf("expected 'template produced invalid JSON' in error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty template", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{}
|
||||
template := ""
|
||||
|
||||
_, err := nm.generatePayloadFromTemplateWithService(template, data, "generic")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty template producing invalid JSON")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("template with template functions", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{
|
||||
ResourceName: "test-server",
|
||||
Level: "warning",
|
||||
}
|
||||
template := `{"resource": "{{upper .ResourceName}}", "level": "{{title .Level}}"}`
|
||||
|
||||
result, err := nm.generatePayloadFromTemplateWithService(template, data, "generic")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
var parsed map[string]interface{}
|
||||
if err := json.Unmarshal(result, &parsed); err != nil {
|
||||
t.Fatalf("result is not valid JSON: %v", err)
|
||||
}
|
||||
if parsed["resource"] != "TEST-SERVER" {
|
||||
t.Fatalf("expected 'TEST-SERVER', got %v", parsed["resource"])
|
||||
}
|
||||
if parsed["level"] != "Warning" {
|
||||
t.Fatalf("expected 'Warning', got %v", parsed["level"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pagerduty service validates JSON", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{
|
||||
ResourceName: "critical-service",
|
||||
Message: "Service down",
|
||||
Level: "critical",
|
||||
}
|
||||
template := `{"routing_key": "test", "event_action": "trigger", "payload": {"summary": "{{.Message}}"}}`
|
||||
|
||||
result, err := nm.generatePayloadFromTemplateWithService(template, data, "pagerduty")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
var parsed map[string]interface{}
|
||||
if err := json.Unmarshal(result, &parsed); err != nil {
|
||||
t.Fatalf("result is not valid JSON: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("generic service validates JSON", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{
|
||||
ResourceName: "test",
|
||||
}
|
||||
template := `not valid json at all`
|
||||
|
||||
_, err := nm.generatePayloadFromTemplateWithService(template, data, "generic")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid JSON")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unknown service still validates JSON", func(t *testing.T) {
|
||||
nm := &NotificationManager{}
|
||||
data := WebhookPayloadData{
|
||||
ResourceName: "test",
|
||||
}
|
||||
// Valid JSON
|
||||
template := `{"test": "{{.ResourceName}}"}`
|
||||
|
||||
result, err := nm.generatePayloadFromTemplateWithService(template, data, "unknown_service")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error for valid JSON on unknown service, got %v", err)
|
||||
}
|
||||
|
||||
expected := `{"test": "test"}`
|
||||
if string(result) != expected {
|
||||
t.Fatalf("expected %q, got %q", expected, string(result))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue