check / check (push) Waiting to run
With debug logging on, each delivery logged the full Slack, Mattermost or ntfy URL, whose path or query lets anyone who has it post to the channel or topic. The two debug lines now give the endpoint's name and the title; sendSlack is told which of Slack or Mattermost it sends to. A webhook URL that does not parse gave a startup error quoting the whole URL; it now gives only the reason. Errors from a failed request come from the transport, not from an HTTP client, and carry no part of the path or query; a test now checks that too. Model: opus-5-5
124 lines
3.0 KiB
Go
124 lines
3.0 KiB
Go
package notify
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
)
|
|
|
|
// 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 newService(slog.New(slog.DiscardHandler), transport)
|
|
}
|
|
|
|
// NewTestServiceWithLogger creates a Service that writes to the
|
|
// given handler, so tests can assert on emitted log records.
|
|
func NewTestServiceWithLogger(
|
|
transport http.RoundTripper,
|
|
handler slog.Handler,
|
|
) *Service {
|
|
return newService(slog.New(handler), transport)
|
|
}
|
|
|
|
// Drain exports drain for testing.
|
|
func (svc *Service) Drain(ctx context.Context) {
|
|
svc.drain(ctx)
|
|
}
|
|
|
|
// OutstandingDeliveries reports how many delivery goroutines
|
|
// are currently tracked as in flight.
|
|
func (svc *Service) OutstandingDeliveries() int64 {
|
|
return svc.outstanding.Load()
|
|
}
|
|
|
|
// 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. It sends as the
|
|
// "slack" endpoint.
|
|
func (svc *Service) SendSlack(
|
|
ctx context.Context,
|
|
webhookURL *url.URL,
|
|
failed error,
|
|
title, message, priority string,
|
|
) error {
|
|
return svc.sendSlack(
|
|
ctx, webhookURL, "slack", failed, title, message, priority,
|
|
)
|
|
}
|
|
|
|
// SetRetryConfig overrides the retry configuration for
|
|
// testing.
|
|
func (svc *Service) SetRetryConfig(cfg RetryConfig) {
|
|
svc.retryConfig = cfg
|
|
}
|
|
|
|
// SetSleepFunc overrides the sleep function so tests can
|
|
// eliminate real delays.
|
|
func (svc *Service) SetSleepFunc(
|
|
fn func(time.Duration) <-chan time.Time,
|
|
) {
|
|
svc.sleepFn = fn
|
|
}
|
|
|
|
// DeliverWithRetry exports deliverWithRetry for testing.
|
|
func (svc *Service) DeliverWithRetry(
|
|
ctx context.Context,
|
|
endpoint string,
|
|
fn func(context.Context) error,
|
|
) error {
|
|
return svc.deliverWithRetry(ctx, endpoint, fn)
|
|
}
|
|
|
|
// BackoffDuration exports RetryConfig.backoff for testing.
|
|
func (rc RetryConfig) BackoffDuration(attempt int) time.Duration {
|
|
return rc.defaults().backoff(attempt)
|
|
}
|