- 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
116 lines
2.3 KiB
Go
116 lines
2.3 KiB
Go
package bridge
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.daviestechlabs.io/daviestechlabs/ntfy-discord/internal/ntfy"
|
|
)
|
|
|
|
func TestBridge_IsReady(t *testing.T) {
|
|
// Create a minimal bridge for testing ready state
|
|
b := &Bridge{}
|
|
|
|
// Initially not ready
|
|
if b.IsReady() {
|
|
t.Error("bridge should not be ready before Run()")
|
|
}
|
|
|
|
// Set ready
|
|
b.ready.Store(true)
|
|
if !b.IsReady() {
|
|
t.Error("bridge should be ready after ready.Store(true)")
|
|
}
|
|
|
|
// Unset ready
|
|
b.ready.Store(false)
|
|
if b.IsReady() {
|
|
t.Error("bridge should not be ready after ready.Store(false)")
|
|
}
|
|
}
|
|
|
|
func TestMetricsRegistered(t *testing.T) {
|
|
// Verify metrics are registered by checking they're not nil
|
|
if messagesReceived == nil {
|
|
t.Error("messagesReceived metric not registered")
|
|
}
|
|
if messagesSent == nil {
|
|
t.Error("messagesSent metric not registered")
|
|
}
|
|
if messagesErrors == nil {
|
|
t.Error("messagesErrors metric not registered")
|
|
}
|
|
}
|
|
|
|
// Test that Bridge correctly uses the ready atomic
|
|
func TestBridge_ReadyState_Concurrent(t *testing.T) {
|
|
b := &Bridge{}
|
|
|
|
// Test concurrent access
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 100; i++ {
|
|
wg.Add(2)
|
|
go func() {
|
|
defer wg.Done()
|
|
b.ready.Store(true)
|
|
}()
|
|
go func() {
|
|
defer wg.Done()
|
|
_ = b.IsReady()
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
|
|
// Should not have any race conditions
|
|
}
|
|
|
|
// Test message channel buffer
|
|
func TestMessageChannelBuffer(t *testing.T) {
|
|
msgCh := make(chan ntfy.Message, 100)
|
|
|
|
// Should be able to buffer 100 messages without blocking
|
|
for i := 0; i < 100; i++ {
|
|
select {
|
|
case msgCh <- ntfy.Message{ID: "test"}:
|
|
// OK
|
|
default:
|
|
t.Fatalf("channel blocked at message %d", i)
|
|
}
|
|
}
|
|
|
|
// 101st should block (use select to avoid blocking test)
|
|
select {
|
|
case msgCh <- ntfy.Message{ID: "overflow"}:
|
|
t.Error("channel should be full")
|
|
default:
|
|
// Expected - channel full
|
|
}
|
|
}
|
|
|
|
// Test context cancellation behavior
|
|
func TestContextCancellation(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
// Simulate Run() loop behavior
|
|
select {
|
|
case <-ctx.Done():
|
|
close(done)
|
|
case <-time.After(5 * time.Second):
|
|
// Should not reach here
|
|
}
|
|
}()
|
|
|
|
cancel()
|
|
|
|
select {
|
|
case <-done:
|
|
// Success
|
|
case <-time.After(time.Second):
|
|
t.Error("context cancellation not handled")
|
|
}
|
|
}
|