337 lines
7.8 KiB
Go
337 lines
7.8 KiB
Go
package delivery
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
)
|
|
|
|
// Exported constants for test access.
|
|
const (
|
|
ExportDeliveryChannelSize = deliveryChannelSize
|
|
ExportRetryChannelSize = retryChannelSize
|
|
ExportDefaultFailureThreshold = defaultFailureThreshold
|
|
ExportDefaultCooldown = defaultCooldown
|
|
)
|
|
|
|
// ExportIsBlockedIP exposes isBlockedIP for testing.
|
|
func ExportIsBlockedIP(ip net.IP) bool {
|
|
return isBlockedIP(ip)
|
|
}
|
|
|
|
// ExportBlockedNetworks exposes blockedNetworks.
|
|
func ExportBlockedNetworks() []*net.IPNet {
|
|
return blockedNetworks
|
|
}
|
|
|
|
// ExportIsForwardableHeader exposes isForwardableHeader.
|
|
func ExportIsForwardableHeader(name string) bool {
|
|
return isForwardableHeader(name)
|
|
}
|
|
|
|
// ExportTruncate exposes truncate for testing.
|
|
func ExportTruncate(s string, maxLen int) string {
|
|
return truncate(s, maxLen)
|
|
}
|
|
|
|
// ExportDeliverHTTP delivers via the http target for testing.
|
|
func (e *Engine) ExportDeliverHTTP(
|
|
ctx context.Context,
|
|
webhookDB *gorm.DB,
|
|
d *database.Delivery,
|
|
task *Task,
|
|
) {
|
|
e.httpTarget.Deliver(ctx, webhookDB, d, task, e)
|
|
}
|
|
|
|
// ExportDeliverDatabase delivers via the database target.
|
|
func (e *Engine) ExportDeliverDatabase(
|
|
webhookDB *gorm.DB, d *database.Delivery,
|
|
) {
|
|
e.targets[database.TargetTypeDatabase].Deliver(
|
|
context.Background(), webhookDB, d, &Task{}, e,
|
|
)
|
|
}
|
|
|
|
// ExportDeliverLog delivers via the log target for testing.
|
|
func (e *Engine) ExportDeliverLog(
|
|
webhookDB *gorm.DB, d *database.Delivery,
|
|
) {
|
|
e.targets[database.TargetTypeLog].Deliver(
|
|
context.Background(), webhookDB, d, &Task{}, e,
|
|
)
|
|
}
|
|
|
|
// ExportDeliverSlack delivers via the slack target for
|
|
// testing.
|
|
func (e *Engine) ExportDeliverSlack(
|
|
ctx context.Context,
|
|
webhookDB *gorm.DB,
|
|
d *database.Delivery,
|
|
) {
|
|
task := &Task{
|
|
DeliveryID: d.ID,
|
|
TargetID: d.TargetID,
|
|
AttemptNum: 1,
|
|
}
|
|
|
|
e.targets[database.TargetTypeSlack].Deliver(
|
|
ctx, webhookDB, d, task, e,
|
|
)
|
|
}
|
|
|
|
// ExportProcessNewTask exposes processNewTask.
|
|
func (e *Engine) ExportProcessNewTask(
|
|
ctx context.Context, task *Task,
|
|
) {
|
|
e.processNewTask(ctx, task)
|
|
}
|
|
|
|
// ExportProcessRetryTask exposes processRetryTask.
|
|
func (e *Engine) ExportProcessRetryTask(
|
|
ctx context.Context, task *Task,
|
|
) {
|
|
e.processRetryTask(ctx, task)
|
|
}
|
|
|
|
// ExportProcessDelivery exposes processDelivery.
|
|
func (e *Engine) ExportProcessDelivery(
|
|
ctx context.Context,
|
|
webhookDB *gorm.DB,
|
|
d *database.Delivery,
|
|
task *Task,
|
|
) {
|
|
e.processDelivery(ctx, webhookDB, d, task)
|
|
}
|
|
|
|
// ExportGetCircuitBreaker exposes the http target's
|
|
// getCircuitBreaker.
|
|
func (e *Engine) ExportGetCircuitBreaker(
|
|
targetID string,
|
|
) *CircuitBreaker {
|
|
return e.httpTarget.getCircuitBreaker(targetID)
|
|
}
|
|
|
|
// ExportParseHTTPConfig exposes parseHTTPConfig.
|
|
func (e *Engine) ExportParseHTTPConfig(
|
|
configJSON string,
|
|
) (*HTTPTargetConfig, error) {
|
|
return parseHTTPConfig(configJSON)
|
|
}
|
|
|
|
// ExportParseSlackConfig exposes parseSlackConfig.
|
|
func (e *Engine) ExportParseSlackConfig(
|
|
configJSON string,
|
|
) (*SlackTargetConfig, error) {
|
|
return parseSlackConfig(configJSON)
|
|
}
|
|
|
|
// ExportDoHTTPRequest exposes the http target's
|
|
// doHTTPRequest.
|
|
func (e *Engine) ExportDoHTTPRequest(
|
|
ctx context.Context,
|
|
cfg *HTTPTargetConfig,
|
|
event *database.Event,
|
|
) (int, string, int64, error) {
|
|
return e.httpTarget.doHTTPRequest(ctx, cfg, event)
|
|
}
|
|
|
|
// ExportClientForConfig exposes the http target's
|
|
// clientForConfig.
|
|
func (e *Engine) ExportClientForConfig(
|
|
cfg *HTTPTargetConfig,
|
|
) *http.Client {
|
|
return e.httpTarget.clientForConfig(cfg)
|
|
}
|
|
|
|
// ExportClient returns the http target's shared HTTP client.
|
|
func (e *Engine) ExportClient() *http.Client {
|
|
return e.httpTarget.client
|
|
}
|
|
|
|
// ExportScheduleRetry exposes ScheduleRetry.
|
|
func (e *Engine) ExportScheduleRetry(
|
|
task Task, delay time.Duration,
|
|
) {
|
|
e.ScheduleRetry(task, delay)
|
|
}
|
|
|
|
// ExportRecoverPendingDeliveries exposes
|
|
// recoverPendingDeliveries.
|
|
func (e *Engine) ExportRecoverPendingDeliveries(
|
|
ctx context.Context,
|
|
webhookDB *gorm.DB,
|
|
webhookID string,
|
|
) {
|
|
e.recoverPendingDeliveries(
|
|
ctx, webhookDB, webhookID,
|
|
)
|
|
}
|
|
|
|
// ExportRecoverWebhookDeliveries exposes
|
|
// recoverWebhookDeliveries.
|
|
func (e *Engine) ExportRecoverWebhookDeliveries(
|
|
ctx context.Context, webhookID string,
|
|
) {
|
|
e.recoverWebhookDeliveries(ctx, webhookID)
|
|
}
|
|
|
|
// ExportRecoverInFlight exposes recoverInFlight.
|
|
func (e *Engine) ExportRecoverInFlight(
|
|
ctx context.Context,
|
|
) {
|
|
e.recoverInFlight(ctx)
|
|
}
|
|
|
|
// ExportStart exposes start for testing.
|
|
func (e *Engine) ExportStart(ctx context.Context) {
|
|
e.start(ctx)
|
|
}
|
|
|
|
// ExportStop exposes stop for testing.
|
|
func (e *Engine) ExportStop() {
|
|
e.stop()
|
|
}
|
|
|
|
// ExportDeliveryCh returns the delivery channel.
|
|
func (e *Engine) ExportDeliveryCh() chan Task {
|
|
return e.deliveryCh
|
|
}
|
|
|
|
// ExportRetryCh returns the retry channel.
|
|
func (e *Engine) ExportRetryCh() chan Task {
|
|
return e.retryCh
|
|
}
|
|
|
|
// NewTestEngine creates an Engine for unit tests without
|
|
// database dependencies.
|
|
func NewTestEngine(
|
|
log *slog.Logger,
|
|
client *http.Client,
|
|
workers int,
|
|
) *Engine {
|
|
e := &Engine{
|
|
log: log,
|
|
deliveryCh: make(chan Task, deliveryChannelSize),
|
|
retryCh: make(chan Task, retryChannelSize),
|
|
workers: workers,
|
|
}
|
|
e.initTargets(client)
|
|
|
|
return e
|
|
}
|
|
|
|
// NewTestEngineSmallRetry creates an Engine with a tiny
|
|
// retry channel buffer for overflow testing.
|
|
func NewTestEngineSmallRetry(
|
|
log *slog.Logger,
|
|
) *Engine {
|
|
e := &Engine{
|
|
log: log,
|
|
retryCh: make(chan Task, 1),
|
|
}
|
|
e.initTargets(nil)
|
|
|
|
return e
|
|
}
|
|
|
|
// NewTestEngineWithDB creates an Engine with a real
|
|
// database and dbManager for integration tests.
|
|
func NewTestEngineWithDB(
|
|
db *database.Database,
|
|
dbMgr *database.WebhookDBManager,
|
|
log *slog.Logger,
|
|
client *http.Client,
|
|
workers int,
|
|
) *Engine {
|
|
e := &Engine{
|
|
database: db,
|
|
dbManager: dbMgr,
|
|
log: log,
|
|
deliveryCh: make(chan Task, deliveryChannelSize),
|
|
retryCh: make(chan Task, retryChannelSize),
|
|
workers: workers,
|
|
}
|
|
e.initTargets(client)
|
|
|
|
return e
|
|
}
|
|
|
|
// NewTestCircuitBreaker creates a CircuitBreaker with
|
|
// custom settings for testing.
|
|
func NewTestCircuitBreaker(
|
|
threshold int, cooldown time.Duration,
|
|
) *CircuitBreaker {
|
|
return &CircuitBreaker{
|
|
state: CircuitClosed,
|
|
threshold: threshold,
|
|
cooldown: cooldown,
|
|
}
|
|
}
|
|
|
|
// ExportArchivedEvent aliases the archive row type so black-box
|
|
// tests can construct and read archive rows.
|
|
type ExportArchivedEvent = archivedEvent
|
|
|
|
// ExportArchiveWriter wraps an archiveWriter so black-box tests
|
|
// can exercise the per-webhook archive file mechanics.
|
|
type ExportArchiveWriter struct {
|
|
w *archiveWriter
|
|
}
|
|
|
|
// NewExportArchiveWriter builds an archive writer for tests,
|
|
// optionally overriding the reopen debounce (a non-positive
|
|
// debounce keeps the production default).
|
|
func NewExportArchiveWriter(
|
|
path string, log *slog.Logger, debounce time.Duration,
|
|
) *ExportArchiveWriter {
|
|
w := newArchiveWriter(path, log)
|
|
if debounce > 0 {
|
|
w.debounce = debounce
|
|
}
|
|
|
|
return &ExportArchiveWriter{w: w}
|
|
}
|
|
|
|
// Write archives a row through the writer.
|
|
func (e *ExportArchiveWriter) Write(
|
|
row ExportArchivedEvent, expiry time.Duration,
|
|
) error {
|
|
return e.w.write(row, expiry)
|
|
}
|
|
|
|
// Open opens the archive file, pruning when expiry is positive.
|
|
func (e *ExportArchiveWriter) Open(expiry time.Duration) error {
|
|
return e.w.open(expiry)
|
|
}
|
|
|
|
// Reopen closes and reopens the archive file.
|
|
func (e *ExportArchiveWriter) Reopen(
|
|
expiry time.Duration,
|
|
) error {
|
|
return e.w.reopen(expiry)
|
|
}
|
|
|
|
// Reopens reports how many times the file has been opened.
|
|
func (e *ExportArchiveWriter) Reopens() int {
|
|
return e.w.reopens
|
|
}
|
|
|
|
// DB returns the writer's current open handle for row
|
|
// inspection in tests.
|
|
func (e *ExportArchiveWriter) DB() *gorm.DB {
|
|
return e.w.db
|
|
}
|
|
|
|
// ExportParseArchiveExpiry exposes parseArchiveExpiry.
|
|
func ExportParseArchiveExpiry(
|
|
configJSON string,
|
|
) (time.Duration, error) {
|
|
return parseArchiveExpiry(configJSON)
|
|
}
|