feat: implement ntfy-discord bridge in Go
- SSE subscription to ntfy with auto-reconnect - Discord webhook integration with embed formatting - Priority to color mapping, tag to emoji conversion - Native HashiCorp Vault support (Kubernetes + token auth) - Hot reload secrets via fsnotify or Vault polling - Prometheus metrics (/metrics endpoint) - Health/ready endpoints for Kubernetes probes - Comprehensive unit tests and fuzz tests - Multi-stage Docker build (~10MB scratch image) - CI/CD pipeline for Gitea Actions
This commit is contained in:
125
internal/discord/fuzz_test.go
Normal file
125
internal/discord/fuzz_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package discord
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"git.daviestechlabs.io/daviestechlabs/ntfy-discord/internal/ntfy"
|
||||
)
|
||||
|
||||
// FuzzBuildEmbed tests that embed building doesn't panic on arbitrary input
|
||||
func FuzzBuildEmbed(f *testing.F) {
|
||||
// Seed with normal inputs
|
||||
f.Add("Test Title", "Test message body", 3, "warning", "test-topic", "https://example.com")
|
||||
f.Add("", "", 0, "", "", "")
|
||||
f.Add("Alert!", "Critical issue detected", 5, "fire", "alerts", "")
|
||||
|
||||
// Edge cases
|
||||
f.Add("x", "y", -1, "unknown", "t", "not-a-url")
|
||||
f.Add("x", "y", 100, "", "", "")
|
||||
f.Add("🔥 Fire Alert", "💀 Something broke", 5, "skull", "test", "")
|
||||
|
||||
// Long strings
|
||||
longStr := ""
|
||||
for i := 0; i < 10000; i++ {
|
||||
longStr += "x"
|
||||
}
|
||||
f.Add(longStr, longStr, 3, "tag", "topic", "https://example.com")
|
||||
|
||||
// Special characters
|
||||
f.Add("Title\x00with\x00nulls", "Message\nwith\nnewlines", 3, "tag", "topic", "")
|
||||
f.Add("<script>alert('xss')</script>", "```code```", 3, "", "", "")
|
||||
f.Add("Title\t\r\n", "Body\t\r\n", 3, "", "", "")
|
||||
|
||||
f.Fuzz(func(t *testing.T, title, message string, priority int, tag, topic, click string) {
|
||||
msg := ntfy.Message{
|
||||
Title: title,
|
||||
Message: message,
|
||||
Priority: priority,
|
||||
Tags: []string{tag},
|
||||
Topic: topic,
|
||||
Click: click,
|
||||
Time: 1706803200,
|
||||
}
|
||||
|
||||
client := NewClient()
|
||||
|
||||
// Should never panic
|
||||
embed := client.buildEmbed(msg)
|
||||
|
||||
// Resulting embed should be valid
|
||||
if embed.Footer == nil {
|
||||
t.Error("Footer should never be nil")
|
||||
}
|
||||
|
||||
// Color should always be set to a valid value
|
||||
if embed.Color == 0 {
|
||||
t.Error("Color should never be 0")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzExtractEmoji tests emoji extraction doesn't panic
|
||||
func FuzzExtractEmoji(f *testing.F) {
|
||||
// Valid tags
|
||||
f.Add("warning")
|
||||
f.Add("fire")
|
||||
f.Add("check")
|
||||
f.Add("rocket")
|
||||
|
||||
// Edge cases
|
||||
f.Add("")
|
||||
f.Add("unknown_tag")
|
||||
f.Add("WARNING") // uppercase
|
||||
f.Add("WaRnInG") // mixed case
|
||||
f.Add("\x00")
|
||||
f.Add("tag with spaces")
|
||||
f.Add("émoji")
|
||||
f.Add("🔥") // emoji as tag
|
||||
f.Add("a]b[c{d}") // special chars
|
||||
|
||||
f.Fuzz(func(t *testing.T, tag string) {
|
||||
client := NewClient()
|
||||
|
||||
// Should never panic
|
||||
tags := []string{tag}
|
||||
_ = client.extractEmoji(tags)
|
||||
|
||||
// Multiple tags
|
||||
_ = client.extractEmoji([]string{tag, tag, tag})
|
||||
|
||||
// Empty slice
|
||||
_ = client.extractEmoji([]string{})
|
||||
|
||||
// Nil slice
|
||||
_ = client.extractEmoji(nil)
|
||||
})
|
||||
}
|
||||
|
||||
// FuzzWebhookPayloadJSON tests JSON marshaling of payloads
|
||||
func FuzzWebhookPayloadJSON(f *testing.F) {
|
||||
f.Add("Title", "Description", 3066993, "Topic", "value", "footer")
|
||||
|
||||
f.Fuzz(func(t *testing.T, title, desc string, color int, fieldName, fieldValue, footer string) {
|
||||
payload := WebhookPayload{
|
||||
Embeds: []Embed{
|
||||
{
|
||||
Title: title,
|
||||
Description: desc,
|
||||
Color: color,
|
||||
Fields: []Field{
|
||||
{Name: fieldName, Value: fieldValue, Inline: true},
|
||||
},
|
||||
Footer: &Footer{Text: footer},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Marshaling should not panic
|
||||
_, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
// JSON encoding errors are acceptable for invalid UTF-8
|
||||
// but should not panic
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user