payloadtest

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package payloadtest is PayCLI's hermetic test harness: a fixture-backed Payload server, a golden-file comparator and a deterministic clock.

It is imported only by _test.go files. Fixtures are plain, pretty-printed, key-sorted JSON that a human or an agent can `cat` and diff — go-vcr's cassettes were rejected precisely because they are not (§17.2).

Index

Constants

View Source
const (
	FixtureDirName = "fixtures"
	GoldenDirName  = "golden"
	// IndexName is the routing index NewServer reads.
	//
	// §17.2 calls it "manifest.json", but testdata/fixtures/manifest.json is
	// itself a fixture — a recorded discovery manifest (§3's fixture list). Two
	// different documents cannot share one name, so the router's index is
	// _index.json and manifest.json stays what §3 says it is.
	IndexName = "_index.json"
)

FixtureDirName and GoldenDirName are the two directories under testdata/.

View Source
const UpdateEnv = "UPDATE_GOLDEN"

UpdateEnv is the environment variable that rewrites golden files instead of comparing against them (`make golden`).

Variables

View Source
var Epoch = time.Date(2026, 9, 16, 12, 0, 0, 0, time.UTC)

Epoch is the instant every test clock starts at. A fixed, obviously synthetic timestamp keeps golden files stable and makes an accidental time.Now() leak visible at a glance.

Functions

func FixtureDir

func FixtureDir(t testing.TB) string

FixtureDir is the absolute path of testdata/fixtures.

func FixturePath

func FixturePath(t testing.TB, name string) string

FixturePath resolves one fixture by name, with or without the .json suffix.

func Golden

func Golden(t testing.TB, name string, res Result)

Golden compares a whole-command result against testdata/golden/<name>.{out,err,exit}, or rewrites them under UPDATE_GOLDEN=1.

Because cli.Run takes injected IO and returns the exit code (§17.3), this runs in-process: no subprocess, so coverage works and Windows is free.

func GoldenDir

func GoldenDir(t testing.TB) string

GoldenDir is the absolute path of testdata/golden.

func Has

func Has(t testing.TB, name string) bool

Has reports whether a fixture exists, for tests that adapt to what was recorded rather than failing on an optional one.

func Load

func Load(t testing.TB, name string) []byte

Load reads one fixture's bytes. A missing fixture is a test failure naming the command that re-records them.

func LoadJSON

func LoadJSON(t testing.TB, name string, v any)

LoadJSON decodes one fixture into v.

func Normalize

func Normalize(b []byte) []byte

Normalize applies the golden normalisers to one stream.

func Updating

func Updating() bool

Updating reports whether golden files are being rewritten.

os.LookupEnv rather than os.Getenv: it distinguishes "set to empty" from "unset", and it keeps this package clear of the identifier §3.1's arch-lint reserves for internal/cli/app.go. This package is imported only by _test.go files, so it is test tooling rather than program code either way.

Types

type Clock

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

Clock is a deterministic, monotonically advancing clock.

Every call to Now advances it by Step, so a command that measures its own duration produces the same number on every run instead of a flaky 0-3 ms.

func Frozen

func Frozen() *Clock

Frozen is the clock every golden test uses: it never moves, so meta.duration_ms is always 0.

func NewClock

func NewClock(step time.Duration) *Clock

NewClock returns a clock at Epoch advancing by step on each read. A zero step makes the clock frozen.

func (*Clock) Advance

func (c *Clock) Advance(d time.Duration)

Advance moves the clock forward.

func (*Clock) Func

func (c *Clock) Func() func() time.Time

Func is the injectable form App.Now takes.

func (*Clock) Now

func (c *Clock) Now() time.Time

Now returns the current instant and advances the clock.

func (*Clock) Peek

func (c *Clock) Peek() time.Time

Peek returns the current instant without advancing.

func (*Clock) Set

func (c *Clock) Set(t time.Time)

Set pins the clock to an absolute instant.

type Index

type Index struct {
	PayloadVersion string  `json:"payload_version"`
	RecordedAt     string  `json:"recorded_at"`
	BaseURL        string  `json:"base_url"`
	Requests       []Route `json:"requests"`
}

Index is testdata/fixtures/_index.json.

func LoadIndex

func LoadIndex(t testing.TB) Index

LoadIndex reads the routing index.

type Option

type Option func(*Server)

Option configures NewServer.

func Strict

func Strict() Option

Strict makes an unmatched request fail the test immediately. The default is to answer 404 with Payload's own "Route not found" body and record the miss, because discovery legitimately probes routes that do not exist.

func WithHandler

func WithHandler(method, path string, h http.HandlerFunc) Option

WithHandler overrides one route with a live handler — for a 429 with a Retry-After, a truncated body, or anything else no fixture can express.

type Recorded

type Recorded struct {
	Method string
	Path   string
	Query  string
	Body   string
	Header http.Header
}

Recorded is one request the harness actually received, so a test can assert on what PayCLI sent — the bulk-write "no limit= parameter" rule (§12.3) is exactly this kind of assertion.

type Result

type Result struct {
	Stdout []byte
	Stderr []byte
	Exit   int
}

Result is one whole-command run: the two streams and the exit status.

type Route

type Route struct {
	Name        string `json:"name"`
	Method      string `json:"method"`
	Path        string `json:"path"`
	Query       string `json:"query,omitempty"`
	Status      int    `json:"status"`
	ContentType string `json:"content_type"`
	File        string `json:"file"`
	// Headers are extra response headers to replay, e.g. Retry-After.
	Headers map[string]string `json:"headers,omitempty"`

	// The three matchers below disambiguate fixtures that share a method and
	// path. Payload's GraphQL endpoint is one URL for every query, /me answers
	// differently with and without a credential, and a German validation error
	// is the same request with one extra header (§17.1's regression guard).
	MatchBody      string            `json:"match_body,omitempty"`
	MatchHeader    map[string]string `json:"match_header,omitempty"`
	MatchAnonymous bool              `json:"match_anonymous,omitempty"`
}

Route is one recorded request/response pair.

type Server

type Server struct {
	*httptest.Server
	// contains filtered or unexported fields
}

Server is a fixture-backed Payload instance.

func NewServer

func NewServer(t testing.TB, opts ...Option) *Server

NewServer starts a server that answers from testdata/fixtures. It is closed automatically when the test ends.

func (*Server) Count

func (s *Server) Count(method, pathPrefix string) int

Count returns how many requests matched a method and path prefix.

func (*Server) Misses

func (s *Server) Misses() []Recorded

Misses returns the requests no fixture and no handler answered.

func (*Server) PayloadVersion

func (s *Server) PayloadVersion() string

PayloadVersion is the version the fixtures were recorded from.

func (*Server) Requests

func (s *Server) Requests() []Recorded

Requests returns every request the server received, in order.

func (*Server) Reset

func (s *Server) Reset()

Reset clears the recorded traffic between sub-tests.

Jump to

Keyboard shortcuts

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