check / check (push) Waiting to run
The "Last event" figure in the webhook page's Statistics block now shows how long ago the newest event arrived, as its two largest units of d, h, m and s with no spaces ("59s", "1m1s", "3d4h"; a zero second unit is left off). It is worked out when the page is served, and a time ahead of the server clock shows "0s". Its tooltip is the full time in UTC. With no event it still shows "none", without a tooltip. A unit test pins each unit boundary, and the headless-browser test checks the figure and its tooltip.
Model: opus-5-5
174 lines
5.4 KiB
Go
174 lines
5.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"html/template"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
)
|
|
|
|
// SetLogForTest replaces the handler's logger, so the handlers_test
|
|
// package can assert on what a log line actually contains rather than
|
|
// on what it is meant to contain.
|
|
func (s *Handlers) SetLogForTest(log *slog.Logger) {
|
|
s.log = log
|
|
}
|
|
|
|
// MaxRenderedBodyBytesForTest exposes the body cap of the lists
|
|
// of events to the handlers_test package.
|
|
const MaxRenderedBodyBytesForTest = maxRenderedBodyBytes
|
|
|
|
// NewBodyViewForTest exposes newBodyView for use in the
|
|
// handlers_test package.
|
|
func NewBodyViewForTest(body []byte, size int64) BodyView {
|
|
return newBodyView("/hook/w/events/e", body, size)
|
|
}
|
|
|
|
// MaxRenderedResponseBytesForTest exposes the event log's
|
|
// delivery response cap to the handlers_test package.
|
|
const MaxRenderedResponseBytesForTest = maxRenderedResponseBytes
|
|
|
|
// MaxRenderedAttemptsForTest exposes the event log's
|
|
// per-delivery attempt ceiling to the handlers_test package.
|
|
const MaxRenderedAttemptsForTest = maxRenderedAttempts
|
|
|
|
// MaxTargetAttemptsForTest exposes the target max_attempts ceiling to
|
|
// the handlers_test package, so the tests assert against the constant
|
|
// the handlers enforce rather than a number copied beside it.
|
|
const MaxTargetAttemptsForTest = maxTargetAttempts
|
|
|
|
// EventDBLeftMsgForTest and SidecarLeftMsgForTest expose the two
|
|
// messages the webhook delete handler logs when a file of the event
|
|
// database is left on disk, so a test checking that one is absent
|
|
// checks for the handler's own wording.
|
|
const (
|
|
EventDBLeftMsgForTest = eventDBLeftMsg
|
|
SidecarLeftMsgForTest = sidecarLeftMsg
|
|
)
|
|
|
|
// DummyVerificationsForTest reports how many equivalent-cost
|
|
// verifications were charged for usernames that do not exist. It
|
|
// lets a test prove the anti-enumeration path ran without timing
|
|
// anything.
|
|
func (s *Handlers) DummyVerificationsForTest() uint64 {
|
|
return s.dummyVerifications.Load()
|
|
}
|
|
|
|
// TrimPartialRuneForTest exposes trimPartialRune for use in the
|
|
// handlers_test package.
|
|
func TrimPartialRuneForTest(b []byte) []byte {
|
|
return trimPartialRune(b)
|
|
}
|
|
|
|
// LoadEventLogViewsForTest exposes loadEventsWithDeliveries for
|
|
// use in the handlers_test package. Assertions on the projected
|
|
// body need the bytes as loaded: html/template rewrites invalid
|
|
// UTF-8 on the way out, so the rendered page cannot show whether
|
|
// a binary body survived the projection intact.
|
|
func (s *Handlers) LoadEventLogViewsForTest(
|
|
w http.ResponseWriter,
|
|
webhook database.Webhook,
|
|
) []EventLogView {
|
|
views, _, _ := s.loadEventsWithDeliveries(
|
|
w, newRequestForTest(), webhook, nil, nil,
|
|
)
|
|
|
|
return views
|
|
}
|
|
|
|
// WebhookStatsForTest returns the figures the statistics pane on a
|
|
// webhook's page shows, from the webhook's entrypoints and targets
|
|
// loaded as that page loads them.
|
|
func (s *Handlers) WebhookStatsForTest(webhookID string) *WebhookStats {
|
|
var entrypoints []database.Entrypoint
|
|
|
|
s.db.DB().Where("webhook_id = ?", webhookID).Find(&entrypoints)
|
|
|
|
var targets []database.Target
|
|
|
|
s.db.DB().Where("webhook_id = ?", webhookID).Find(&targets)
|
|
|
|
return s.loadWebhookStats(webhookID, entrypoints, targets)
|
|
}
|
|
|
|
// RelativeTimeForTest exposes relativeTime for use in the
|
|
// handlers_test package.
|
|
func RelativeTimeForTest(at, now time.Time) string {
|
|
return relativeTime(at, now)
|
|
}
|
|
|
|
// FinishedByTargetForTest exposes finishedByTarget for use in the
|
|
// handlers_test package.
|
|
func FinishedByTargetForTest(
|
|
webhookDB *gorm.DB, since time.Time,
|
|
) ([]TargetFinished, error) {
|
|
return finishedByTarget(webhookDB, since)
|
|
}
|
|
|
|
// newRequestForTest is the request the helpers here pass on for
|
|
// callers that have none: it is used only to render the error page.
|
|
func newRequestForTest() *http.Request {
|
|
return httptest.NewRequestWithContext(
|
|
context.Background(), http.MethodGet, "/", nil,
|
|
)
|
|
}
|
|
|
|
// AddTemplateForTest registers a template under a page name so that
|
|
// the handlers_test package can drive the render path with a
|
|
// template of its own.
|
|
func (s *Handlers) AddTemplateForTest(
|
|
pageTemplate string,
|
|
tmpl *template.Template,
|
|
) {
|
|
s.templates[pageTemplate] = tmpl
|
|
}
|
|
|
|
// RenderTemplateForTest exposes renderTemplate for use in the
|
|
// handlers_test package.
|
|
func (s *Handlers) RenderTemplateForTest(
|
|
w http.ResponseWriter,
|
|
r *http.Request,
|
|
pageTemplate string,
|
|
data any,
|
|
) {
|
|
s.renderTemplate(w, r, pageTemplate, data)
|
|
}
|
|
|
|
// SetSlackTargetSettingsForTest exposes setSlackTargetSettings for use
|
|
// in the handlers_test package.
|
|
func (s *Handlers) SetSlackTargetSettingsForTest(
|
|
ctx context.Context,
|
|
target *database.Target,
|
|
targetURL string,
|
|
) string {
|
|
return s.setSlackTargetSettings(ctx, target, targetURL)
|
|
}
|
|
|
|
// SetDatabaseTargetSettingsForTest exposes setDatabaseTargetSettings
|
|
// for use in the handlers_test package.
|
|
func SetDatabaseTargetSettingsForTest(
|
|
target *database.Target,
|
|
expiry, rotation string,
|
|
) string {
|
|
return setDatabaseTargetSettings(target, expiry, rotation)
|
|
}
|
|
|
|
// FormatArchiveExpiryForTest exposes formatArchiveExpiry.
|
|
func FormatArchiveExpiryForTest(expiry time.Duration) string {
|
|
return formatArchiveExpiry(expiry)
|
|
}
|
|
|
|
// ArchiveFileViewForTest exposes archiveFileView, which describes a
|
|
// database target's archive files as the target list shows them at
|
|
// now.
|
|
func (s *Handlers) ArchiveFileViewForTest(
|
|
webhook *database.Webhook, target *database.Target, now time.Time,
|
|
) *ArchiveFileView {
|
|
return s.archiveFileView(webhook, target, now)
|
|
}
|