smoke

package
v0.20.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package smoke drives an end-to-end delivery test against a running notifycat stack. It forges a correctly-signed `pull_request: opened` webhook, POSTs it to the live /webhook/github endpoint — exercising the real signature middleware, dispatcher, and Slack client — and then reads the resulting Slack message timestamp back from the database so the operator can confirm a real message landed in the mapped channel.

With reactions requested (the --reactions flag), it additionally replays the review lifecycle for the same synthetic PR — a comment, an approval, and a merge — and verifies, via reactions.get, that the server added the configured emoji to the message. The CLI entry point lives in cmd/notifycat-smoke; the wrapper exposes it as `./notifycat smoke owner/repo`.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoMapping means the repository is absent from config.yaml's mappings. Returned
	// before any network call so the operator fixes config first.
	ErrNoMapping = errors.New("smoke: repository not present in mappings")
	// ErrSignatureRejected means the server answered 401 — the secret this
	// command signed with does not match the one the server runs with.
	ErrSignatureRejected = errors.New("smoke: server rejected the signature")
	// ErrUnreachable means the POST never reached a server.
	ErrUnreachable = errors.New("smoke: could not reach the server")
	// ErrUnexpectedStatus means the server answered with a non-200, non-401 code.
	ErrUnexpectedStatus = errors.New("smoke: unexpected response status")
)

Sentinel errors let the CLI render a clear remediation message — and pick an exit code — without parsing strings or leaking a stack trace.

Functions

This section is empty.

Types

type Doer

type Doer interface {
	Do(req *http.Request) (*http.Response, error)
}

Doer is the slice of *http.Client the runner needs.

type MessageStore

type MessageStore interface {
	Messages(ctx context.Context, repository string, prNumber int) ([]store.Message, error)
	Delete(ctx context.Context, repository string, prNumber int) error
}

MessageStore reads back the stored Slack message timestamp for a PR, and deletes the row again so a smoke run leaves no orphan behind in a live database. *store.PullRequests satisfies it.

type ReactionCheck

type ReactionCheck struct {
	Step      string
	Emoji     string
	Present   bool
	VerifyErr error
}

ReactionCheck is the outcome of one lifecycle step: the event that was replayed, the emoji the server was expected to add, and whether reactions.get confirmed it. VerifyErr is set when the reaction could not be read back at all (e.g. the bot token lacks reactions:read) — distinct from a confirmed absence.

type ReactionReader

type ReactionReader interface {
	GetReactions(ctx context.Context, channel, ts string) ([]slack.Reaction, error)
}

ReactionReader reads the reactions attached to a Slack message, so the smoke test can confirm the server actually reacted. *slack.Client satisfies it.

type RepoMappings

type RepoMappings interface {
	Get(ctx context.Context, repository string) (store.RepoMapping, error)
}

RepoMappings looks up the Slack routing for a repository. *mappings.Provider satisfies it; declared here so the consumer owns its interface.

type Result

type Result struct {
	Repository string
	Channel    string
	PRNumber   int
	Title      string
	Timestamp  string
	URL        string

	// ReactionsRequested is true when the caller asked for the lifecycle pass.
	ReactionsRequested bool
	// ReactionsEnabled mirrors the server's SLACK_REACTIONS_ENABLED. When a
	// caller requests reactions but this is false, the lifecycle is skipped.
	ReactionsEnabled bool
	Reactions        []ReactionCheck

	// IgnoreAIReviews and BotReviewMarker let the CLI explain why the bot-review
	// step was skipped: either bot reviews are muted, or the marker is disabled.
	IgnoreAIReviews bool
	BotReviewMarker string
}

Result describes a successful delivery: which channel received the message, the PR number used, the Slack timestamp the server stored, and — when reactions were requested — the per-step reaction verifications.

type Smoke

type Smoke struct {
	// contains filtered or unexported fields
}

Smoke runs the delivery test. Construct once via New; Run is safe to call repeatedly (each call uses a fresh PR number, so the open handler posts anew rather than treating it as an already-sent duplicate).

func New

func New(mappings RepoMappings, messages MessageStore, reactions ReactionReader, httpClient Doer, secret, url string, rxCfg config.Reactions, ignoreAIReviews bool, now func() time.Time) *Smoke

New wires a Smoke. url is the full webhook endpoint to POST to (e.g. http://notifycat:8080/webhook/github); secret is GITHUB_WEBHOOK_SECRET; rxCfg mirrors the server's reaction config so the verifier knows which emoji to expect; ignoreAIReviews mirrors NOTIFYCAT_IGNORE_AI_REVIEWS so the runner only replays a bot review when the server would actually mark it; now supplies the clock used to derive a unique PR number per run.

func (*Smoke) Run

func (s *Smoke) Run(ctx context.Context, target string, withReactions bool) (res Result, err error)

Run validates that target is mapped, posts a signed synthetic `pull_request: opened` to the live endpoint, and reports the channel and the Slack timestamp read back from the store. Mapping is checked first so an unmapped repo fails (ErrNoMapping) without any network traffic.

When withReactions is set and the server has reactions enabled, Run then replays a comment, an approval, and a merge for the same PR and verifies the configured emoji appeared on the message. A missing emoji is recorded in the Result (not returned as an error) so the CLI can report every step.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL