Help us shape the future of Go! Take the 2026 Go Developer Survey

webtest

package
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

Documentation

Overview

Package webtest is what the tests of internal/webserver and of the packages that leave it share: the harness that serves the API handler under a session and sends it requests, the canned seams and fixture constants, the event stream's reader, a seam call held on its context, the configuration's edits, the Taskwarrior fake and lefthook's fixtures. Each test still drives its code black-box: the server through webserver.Handler, or a package that left through its exported API. Only tests import it.

Index

Constants

View Source
const (
	TestKey        = "PROJ-412"
	TestSummary    = "Fix token redaction"
	TestProject    = "PROJ"
	TestReporter   = "Ana Lopez"
	TestBranchName = "fix/PROJ-412"
	TestAuthor     = "octocat"
	TestVersion    = "1.2.3"
	TestBugJQL     = "type = Bug"
	TestBugView    = "Bugs"
	TestBase       = "origin/main"
	TestChannel    = "#dev-workflow"
	// TestCommitSubject and TestCommitHash are the branch's one commit, shared by
	// the write-action tests that need a branch with a commit on it.
	TestCommitSubject = "feat: redact"
	TestCommitHash    = "abc1234"
	// NoBranchSeam is the shared name for the "no branch seam" case the write
	// handlers' unavailability tables each exercise.
	NoBranchSeam = "no branch seam"
	// NoForgeSeam is its twin for the reads and writes that ask the forge.
	NoForgeSeam = "no forge seam"
	// WaitAndTryAgain is what an answer to a service limiting requests says to
	// do, whichever service it is.
	WaitAndTryAgain = "wait and try again"
	// TryAgain is what the internal problem's detail says to do, whichever
	// request met it.
	TryAgain = "try again"
	// RepoPath is where the repository sits on disk, which a git error can
	// carry and no answer may repeat.
	RepoPath = "/home/dev/src/acme"
	// LoopbackHost is the Host the shared request helpers send, so requests pass
	// the loopback guard the same way a browser on 127.0.0.1 does. A test that
	// exercises the guard sets its own Host instead.
	LoopbackHost = "127.0.0.1:13579"
)

Fixtures the tests share.

View Source
const (
	PRTitle = "fix: redact tokens"
	PRBase  = "main"
	PRURL   = "https://x/7"
)

The pull request the tests open: its title, the base it targets and its address.

View Source
const (
	CheckoutPath = "/api/checkout"
	NoIssueKey   = `{"issue_key":""}`
)

The writes the contract's tests ask for, and the body that names no issue.

View Source
const (
	HookSetupAt = "/api/hooks/setup"
	HooksAt     = "/api/hooks"
)

HookSetupAt is where the lefthook offer is read and taken, and HooksAt where the hooks lefthook configures are listed.

View Source
const (
	PreCommitHook   = "pre-commit"
	PrePushHook     = "pre-push"
	CommitMsgHook   = "commit-msg"
	CommitMsgReason = "git hands it the file holding the commit message"
)

The hooks the tests' lefthook configures: pre-commit and pre-push, which a run by hand can run, and commit-msg, which only git runs, and why.

View Source
const (
	SnapshotMessage = "snapshot"
	SeedMessage     = "seed"
)

SnapshotMessage and SeedMessage name the stream's two kinds of message.

View Source
const (
	TasksPath = "/api/tasks"
	// StartedUUID is the started task's, linked to TestKey; PlainUUID an
	// unlinked task's; DoneUUID a completed task's linked to another issue;
	// AddedUUID the task an add creates.
	StartedUUID = "5f3c9a1e-8b2d-4c6f-9e7a-1d2b3c4d5e6f"
	PlainUUID   = "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"
	DoneUUID    = "9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a"
	AddedUUID   = "c0ffee00-1234-4abc-9def-0123456789ab"
	// OddUUID is the task whose status the spec does not know.
	OddUUID = "0dd0dd00-aaaa-4bbb-8ccc-ddddeeeeffff"
	// DataPath is where Taskwarrior keeps its data, and HomePath your home,
	// which its errors can carry and no answer may repeat.
	DataPath = "/Users/x/.local/share/task"
	HomePath = "/Users/x"
	// TaskContext is the context the fake has active.
	TaskContext = "work"
)

The tasks the Taskwarrior fake holds, where it keeps them, and the path the list is read at.

View Source
const EndsWithin = 2 * time.Second

EndsWithin is how long a context the server ends is given to be seen ended: one it never ends is never seen, so the bound only has to outlast a busy machine.

View Source
const FilledHead = "abc123"

FilledHead is the head commit of FilledDeps' branch, its one commit.

View Source
const HeldPatience = 2 * EndsWithin

HeldPatience is how long a call held until its context ends waits before giving up, so a context that never ends fails its test rather than holding it open: longer than EndsWithin, which the test waits for it.

View Source
const ProblemJSON = "application/problem+json"

ProblemJSON is the media type every refusal is written in.

View Source
const StreamedFrames = 3

StreamedFrames is how many snapshots the stream tests let a stream push: enough for a read made once to show against one made on every frame.

View Source
const TaskwarriorProgram = "/opt/homebrew/bin/task"

TaskwarriorProgram is a taskwarrior.program a user might set.

Variables

View Source
var ErrSeam = errors.New("the seam failed")

ErrSeam is what a failing seam returns: an error in none of fault's classes, so the server answers it with the opaque internal 500, whose detail carries none of the error's own text.

Functions

func Asking

func Asking(ctx context.Context, handler http.Handler, method, target string) <-chan struct{}

Asking sends a request under ctx on its own goroutine, as the HTTP server does, and closes the channel it returns once it has been answered.

func Decode

func Decode[T any](t *testing.T, recorder *httptest.ResponseRecorder) T

Decode unmarshals the recorder's JSON body into T, failing the test on error.

func DoneTask

func DoneTask() taskwarrior.Task

DoneTask is a completed task linked to another issue.

func FilledDeps

func FilledDeps() webserver.Deps

FilledDeps is a Deps with every seam populated with canned answers. A test nils a seam to exercise the not-configured path, or replaces one to fail.

func FirstSnapshot

func FirstSnapshot(t *testing.T, body string) api.Snapshot

FirstSnapshot is the first snapshot an event-stream body carries.

func Get

func Get(t *testing.T, handler http.Handler, target string) *httptest.ResponseRecorder

Get sends a GET and returns the recorder.

func Hearing

func Hearing(deps webserver.Deps) (webserver.Deps, *[]error)

Hearing is deps with Unexpected spied on, and what it heard.

func HookDeps

func HookDeps(found []hooks.GitHook, configured bool, written *[]hooks.Generated) webserver.Deps

HookDeps is FilledDeps with the hooks directory holding found, configured or not, and the configuration written kept in written.

func Marshal

func Marshal(t *testing.T, cfg config.Config) string

Marshal renders a configuration as the request body a client would send.

func OldHooks

func OldHooks() []hooks.GitHook

OldHooks is a repository's own pre-commit hook, which lefthook does not manage: two plain commands.

func PaceStart

func PaceStart() time.Time

PaceStart is the moment a paced stream's clock starts at.

func PlainTask

func PlainTask() taskwarrior.Task

PlainTask is a pending task linked to no issue, not started, with no dates but the two every task has.

func Presenting

func Presenting(t *testing.T, session edge.Session, handler http.Handler) http.Handler

Presenting hands handler each request with session presented as the page presents it, unless the request presents one itself, as a test of the session's check does.

func PutConfig

func PutConfig(t *testing.T, handler http.Handler, body string) *httptest.ResponseRecorder

PutConfig saves body as Settings does: it reads the configuration first, and saves over the revision that read returned.

func PutConfigOver

func PutConfigOver(t *testing.T, handler http.Handler, body, etag string) *httptest.ResponseRecorder

PutConfigOver saves body over the revision etag names, sent as If-Match; an empty etag sends none.

func Rewrite

func Rewrite(t *testing.T, path, contents string)

Rewrite leaves the file at path holding contents, as an edit made outside the browser does.

func Send

func Send(t *testing.T, handler http.Handler, method, target, body string) *httptest.ResponseRecorder

Send sends a request with an optional body and returns the recorder.

func Serve

func Serve(t *testing.T, deps webserver.Deps, cfg config.Config) http.Handler

Serve builds the API handler over deps and cfg, not in dry-run, so the write endpoints are reachable — the common case — with deps' Taskwarrior bound with cfg's settings, as at start. A test that exercises dry-run's read-only guard passes its own Info. Handler fails only when the embedded spec cannot load, which is a build defect, so the test fails there.

func ServeForALifetime

func ServeForALifetime(
	t *testing.T, deps webserver.Deps, cfg config.Config,
) (http.Handler, context.Context, context.CancelFunc)

ServeForALifetime is Serve over deps and cfg for a lifetime of its own, which webserver.Serve would derive each request's context from, and the function that stops the server, ending it.

func ServeUntilStopped

func ServeUntilStopped(t *testing.T, deps webserver.Deps) (http.Handler, context.CancelFunc)

ServeUntilStopped is Serve, and the function that stops the server it serves from, ending its lifetime.

func ServeWith

func ServeWith(t *testing.T, deps webserver.Deps, cfg config.Config, info webserver.Info) http.Handler

ServeWith is Serve with the caller's Info, for the tests that need a specific stream interval.

func ServeWorld

func ServeWorld(t *testing.T, world webserver.World, assets fs.FS) http.Handler

ServeWorld builds the handler for world with assets mounted, under a session of its own that it presents on each request as the page does. Handler fails only on a spec that cannot load, which is a build defect.

func SessionToken

func SessionToken(t *testing.T, session edge.Session) string

SessionToken is the session the address the server prints carries, read from its fragment as the page reads it.

func Snapshots

func Snapshots(t *testing.T, body string) []api.Snapshot

Snapshots decodes every snapshot event's data from an event-stream body, in the order the stream pushed them.

func StartedTask

func StartedTask() taskwarrior.Task

StartedTask is a pending task for TestKey, started, with a due date and a note.

func StreamOnce

func StreamOnce(t *testing.T, handler http.Handler, target string) *httptest.ResponseRecorder

StreamOnce runs the events handler with a context that is canceled at once, so it pushes a single snapshot and returns. It returns the recorder.

func StreamPaced

func StreamPaced(t *testing.T, deps webserver.Deps, cfg config.Config, step time.Duration) string

StreamPaced streams StreamedFrames snapshots over cfg, on a clock that starts at PaceStart and that step moves on between two frames, as that much time passing would.

func TaskDay

func TaskDay() time.Time

TaskDay is when the fake's tasks were made.

Types

type FailingFlushWriter

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

FailingFlushWriter can flush but fails every write, standing in for a client that has hung up mid-stream.

func (*FailingFlushWriter) Flush

func (f *FailingFlushWriter) Flush()

Flush does nothing; that it exists is what lets the stream start.

func (*FailingFlushWriter) Header

func (f *FailingFlushWriter) Header() http.Header

Header is the response's header, made on first use.

func (*FailingFlushWriter) Write

func (f *FailingFlushWriter) Write([]byte) (int, error)

Write fails with ErrSeam, as a write to a client that hung up does.

func (*FailingFlushWriter) WriteHeader

func (f *FailingFlushWriter) WriteHeader(int)

WriteHeader does nothing.

type HeldCall

type HeldCall struct {
	Entered chan struct{}

	Release chan struct{}
	// contains filtered or unexported fields
}

HeldCall is a seam call held until its context ends, or until it is let go: it says when it is entered, and how its context stood when it returned.

func NewHeld

func NewHeld() *HeldCall

NewHeld is a call held until it is let go, whatever its context does.

func NewHeldUntilEnded

func NewHeldUntilEnded() *HeldCall

NewHeldUntilEnded is a call held until its context ends, or until patience runs out.

func (*HeldCall) EndedWith

func (b *HeldCall) EndedWith(t *testing.T) error

EndedWith is how the call's context stood when it returned, or a failure once patience runs out.

func (*HeldCall) Once

func (b *HeldCall) Once() func(context.Context) error

Once is HeldCall's Wait for the first call alone: every later call answers at once, as a read made after the server stopped would.

func (*HeldCall) Wait

func (b *HeldCall) Wait(ctx context.Context) error

Wait is the call's body: it blocks until its context ends, or until it is let go for one that is held, and answers how its context stood then.

type Message

type Message struct {
	ID    string
	Event string
	Data  string
}

Message is one event-stream message: its id, its event and its data.

func Messages

func Messages(body string) []Message

Messages splits an event-stream body into its messages, in the order sent: each is separated from the next by a blank line.

type OpenedStream

type OpenedStream struct {
	Cancel context.CancelFunc
	Done   chan struct{}
	// contains filtered or unexported fields
}

OpenedStream is the events handler serving a page's connection on a goroutine of its own, as the HTTP server runs it.

func OpenStream

func OpenStream(t *testing.T, handler http.Handler, target string) *OpenedStream

OpenStream connects to target on handler, and disconnects when the test ends if Close has not already.

func OpenStreamOf

func OpenStreamOf(t *testing.T, handler http.Handler, request *http.Request) *OpenedStream

OpenStreamOf is OpenStream for request, disconnected by ending a context derived from its own: one derived from the server's lifetime, as webserver.Serve derives each, ends when the server stops too.

func (*OpenedStream) Close

func (s *OpenedStream) Close() string

Close disconnects, waits for the handler to return, and answers what the stream wrote.

type TaskFake

type TaskFake struct {
	Install taskwarrior.Install
	Pending taskwarrior.List
	Linked  []taskwarrior.Task
	Said    string
	Fail    map[string]error
	// contains filtered or unexported fields
}

TaskFake is Taskwarrior as the server sees it: the install, the pending list and the linked tasks it answers, what undo and sync say, and each call made of it, in order. A call whose verb Fail names answers that error instead. The stream calls it from the connection's goroutine, so it is locked.

func FakeTaskwarrior

func FakeTaskwarrior() *TaskFake

FakeTaskwarrior is a Taskwarrior with a sync backend and a context, whose pending list holds a started task for TestKey and an unlinked one, and whose linked tasks are the started one and a completed one.

func (*TaskFake) Asked

func (f *TaskFake) Asked() []string

Asked is every call made of the fake so far.

func (*TaskFake) Count

func (f *TaskFake) Count(verb string) int

Count is how many calls with verb were made of the fake.

func (*TaskFake) ReadAfter

func (f *TaskFake) ReadAfter(call string) bool

ReadAfter reports whether the pending list was read after call was made.

func (*TaskFake) Seams

func (f *TaskFake) Seams() seams.Tasks

Seams hands the fake to the server as its Taskwarrior.

type UnflushableWriter

type UnflushableWriter struct {
	Code int
	// contains filtered or unexported fields
}

UnflushableWriter is an http.ResponseWriter that does not support flushing, so the events handler must refuse it.

func (*UnflushableWriter) Header

func (u *UnflushableWriter) Header() http.Header

Header is the response's header, made on first use.

func (*UnflushableWriter) Write

func (u *UnflushableWriter) Write(b []byte) (int, error)

Write accepts b and discards it.

func (*UnflushableWriter) WriteHeader

func (u *UnflushableWriter) WriteHeader(code int)

WriteHeader keeps the status code in Code.

Jump to

Keyboard shortcuts

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