test(notify): add comprehensive tests for notification delivery
All checks were successful
check / check (push) Successful in 37s

Add tests for sendNtfy, sendSlack, SendNotification, newRequest,
ntfyPriority, and slackColor covering:

- Correct ntfy headers (Title, Priority) and body content
- All priority-to-ntfy-priority mappings
- Correct Slack/Mattermost JSON payload structure and Content-Type
- All priority-to-color mappings
- HTTP error handling (4xx, 5xx status codes)
- Network transport failures
- Goroutine dispatch to all configured endpoints
- Individual endpoint dispatch (ntfy-only, slack-only, mattermost-only)
- No-op behavior when no webhooks are configured
- Error logging paths in SendNotification goroutines
- SlackPayload JSON marshaling and omitempty behavior
- HTTP request construction via newRequest
- Context propagation through newRequest

Uses httptest.Server for HTTP endpoint testing and a custom
failingTransport for network error simulation. Test helpers are
exported via export_test.go following standard Go patterns.

Coverage improved from 11.1% to 80.0% for the notify package.
This commit is contained in:
user
2026-03-01 23:52:12 -08:00
parent 0a74971ade
commit e8cd0705a7
2 changed files with 1205 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
package notify
import (
"context"
"io"
"log/slog"
"net/http"
"net/url"
)
// NtfyPriority exports ntfyPriority for testing.
func NtfyPriority(priority string) string {
return ntfyPriority(priority)
}
// SlackColor exports slackColor for testing.
func SlackColor(priority string) string {
return slackColor(priority)
}
// NewRequestForTest exports newRequest for testing.
func NewRequestForTest(
ctx context.Context,
method string,
target *url.URL,
body io.Reader,
) *http.Request {
return newRequest(ctx, method, target, body)
}
// NewTestService creates a Service suitable for unit testing.
// It discards log output and uses the given transport.
func NewTestService(transport http.RoundTripper) *Service {
return &Service{
log: slog.New(slog.DiscardHandler),
transport: transport,
}
}
// SetNtfyURL sets the ntfy URL on a Service for testing.
func (svc *Service) SetNtfyURL(u *url.URL) {
svc.ntfyURL = u
}
// SetSlackWebhookURL sets the Slack webhook URL on a
// Service for testing.
func (svc *Service) SetSlackWebhookURL(u *url.URL) {
svc.slackWebhookURL = u
}
// SetMattermostWebhookURL sets the Mattermost webhook URL on
// a Service for testing.
func (svc *Service) SetMattermostWebhookURL(u *url.URL) {
svc.mattermostWebhookURL = u
}
// SendNtfy exports sendNtfy for testing.
func (svc *Service) SendNtfy(
ctx context.Context,
topicURL *url.URL,
title, message, priority string,
) error {
return svc.sendNtfy(ctx, topicURL, title, message, priority)
}
// SendSlack exports sendSlack for testing.
func (svc *Service) SendSlack(
ctx context.Context,
webhookURL *url.URL,
title, message, priority string,
) error {
return svc.sendSlack(
ctx, webhookURL, title, message, priority,
)
}