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

apitest

package
v0.6.0 Latest Latest
Warning

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

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

Documentation

Overview

Package apitest is a fake Tailscale API for tests. It serves the OAuth token endpoint, requires the bearer token it issued, records every request, and answers registered routes. It contains no credential-shaped strings.

Index

Constants

View Source
const (
	ClientID     = "test-client-id"
	ClientSecret = "test-client-secret"
	AccessToken  = "test-access-token"
	Tailnet      = "T1234CNTRL"
	// FedClientID is the federated identity the token-exchange route accepts.
	FedClientID = "test-fed-client-id"
)

Fixed test identities. They are deliberately not shaped like real keys.

View Source
const CaptureDir = "testdata/observed"

CaptureDir is where the ledger looks for the capture an Observed shape names, relative to this package: the skeleton files that make probe-acl writes (its PROBE_OUT) are committed here. A test ties the Makefile's PROBE_OUT to this directory, so a run cannot write its captures where the ledger does not look.

View Source
const ValidatePath = "/api/v2/tailnet/" + Tailnet + "/acl/validate"

ValidatePath is the only path tailkeeper may send a request other than GET to: policy validation, which never changes the policy.

Variables

This section is empty.

Functions

func CaptureFile added in v0.6.0

func CaptureFile(dir, id string) string

CaptureFile is the path of the capture of the probe id below dir (CaptureDir for the ledger, a temporary directory for a test of the writer): <id>.json.

Types

type Endpoint added in v0.6.0

type Endpoint string

Endpoint is the call a shape answers.

const (
	EndpointValidate Endpoint = "validate" // POST .../acl/validate, either mode
	EndpointPolicy   Endpoint = "policy"   // GET .../acl, with or without details=true
	EndpointAudit    Endpoint = "audit"    // GET .../logging/configuration
)

The endpoints the ledger covers.

type Entry added in v0.6.0

type Entry struct {
	User     string   // the test's src; for a policy warning, the policy element it names
	Errors   []string // omitted when empty
	Warnings []string // omitted when empty
}

Entry is one element of the data array of a validate answer.

type Fake

type Fake struct {
	Server *httptest.Server
	// Scope is returned in the token response's "scope" field.
	Scope string
	// ExpiresIn is the token lifetime in seconds the OAuth token route grants; 0
	// grants an hour.
	ExpiresIn int
	// NumberTokens makes the OAuth token route issue AccessToken-1, AccessToken-2,
	// ... in turn; API routes then accept only the newest.
	NumberTokens bool
	// contains filtered or unexported fields
}

Fake is a running fake API server.

func New

func New(t testing.TB) *Fake

New starts a fake API and stops it when the test ends.

func (*Fake) AcceptIDToken

func (f *Fake) AcceptIDToken(tok string)

AcceptIDToken makes the token-exchange route accept tok for FedClientID.

func (*Fake) Client

func (f *Fake) Client(t testing.TB) *api.Client

Client returns an api.Client for the fake's tailnet using the fake OAuth client, with retries sleeping zero time.

func (*Fake) ExchangeRequests

func (f *Fake) ExchangeRequests() int

ExchangeRequests returns how many times the token-exchange endpoint was called.

func (*Fake) FailExchanges

func (f *Fake) FailExchanges(statuses ...int)

FailExchanges makes the next exchanges return these statuses, in order.

func (*Fake) Fallback

func (f *Fake) Fallback(h http.HandlerFunc)

Fallback registers a handler for every path that has no route of its own. The request is recorded and its bearer token checked first, as for any other.

func (*Fake) Handle

func (f *Fake) Handle(method, path string, h http.HandlerFunc)

Handle registers a handler for method and escaped path.

func (*Fake) JSON

func (f *Fake) JSON(method, path string, status int, body string)

JSON registers a fixed JSON response.

func (*Fake) PermitProbeRequests added in v0.6.0

func (f *Fake) PermitProbeRequests()

PermitProbeRequests lets the fake accept validate POSTs whose content type and body disagree (an array sent as HuJSON, an empty body, a byte order mark). The probe harness sends them on purpose, to see what the real API does with them. The callers allowed today are the tests of internal/probe, apitest/spy_test.go and internal/api/policy_test.go; no other test may call this.

func (*Fake) Raw

func (f *Fake) Raw(method, path string, status int, body string, headers map[string]string)

Raw registers a fixed response with any body (also empty) and extra headers, such as an ETag or a content type other than JSON.

func (*Fake) Requests

func (f *Fake) Requests() []Request

Requests returns the API requests recorded so far (token requests excluded).

func (*Fake) Serve added in v0.6.0

func (f *Fake) Serve(method, path string, s Shape)

Serve registers s as the answer to method and path.

func (*Fake) ServeDetails added in v0.6.0

func (f *Fake) ServeDetails(s Shape)

ServeDetails registers s as the answer to GET .../acl?details=true. A request without details=true fails the test: the answer is the JSON object, not the policy.

func (*Fake) ServePolicy added in v0.6.0

func (f *Fake) ServePolicy(s Shape)

ServePolicy registers s as the answer to GET .../acl without details. A request that asks for details=true fails the test: it would be answered with a plain policy where the caller expects JSON.

func (*Fake) ServeValidate added in v0.6.0

func (f *Fake) ServeValidate(s Shape)

ServeValidate registers s as the answer to POST .../acl/validate.

func (*Fake) TokenRequests

func (f *Fake) TokenRequests() int

TokenRequests returns how many times the token endpoint was called.

func (*Fake) URL

func (f *Fake) URL() string

URL is the base URL to pass as api.Options.BaseURL.

type Kind added in v0.6.0

type Kind string

Kind says how well a shape is known.

const (
	Documented Kind = "Documented"
	Observed   Kind = "Observed"
	Assumed    Kind = "Assumed"
)

The three kinds a shape can be.

type Provenance added in v0.6.0

type Provenance struct {
	Kind   Kind
	Date   string // Observed only: YYYY-MM-DD of the run
	Source string // a document, a capture, or the probe that saw it
}

Provenance is a shape's kind and where it comes from.

func (Provenance) String added in v0.6.0

func (p Provenance) String() string

type Request

type Request struct {
	Method      string
	Path        string // escaped path, e.g. /api/v2/tailnet/T1234CNTRL/devices
	Query       url.Values
	Auth        string
	Accept      string
	AcceptSent  bool // the request carried an Accept header, empty or not
	ContentType string
	IfNoneMatch string
	Body        []byte
}

Request is one recorded request.

type Shape added in v0.6.0

type Shape struct {
	Name     string
	Endpoint Endpoint
	Prov     Provenance
	Status   int
	Header   map[string]string
	Body     string
}

Shape is one answer of the API: a status, headers and a body, with its provenance.

func AllShapes added in v0.6.0

func AllShapes() []Shape

AllShapes returns one instance of every builder, for the tests that walk the ledger.

func AuditACLUpdates added in v0.6.0

func AuditACLUpdates() Shape

AuditACLUpdates is GET .../logging/configuration for ACL updates: old and new are JSON strings holding a policy object with a known section. Probe H-1 saw that shape on 20 Oct 2026; the texts here are examples, not a tailnet's policy.

func BareMessage200 added in v0.6.0

func BareMessage200(message string) Shape

BareMessage200 is a bare message with HTTP 200, no data: how the live API rejects a well-formed document it cannot accept (A-3, A-4, A-5; a syntax error, an unknown field, an unknown tag, a duplicate key). Only a body that is no policy at all is a 400.

func CanaryFailed added in v0.6.0

func CanaryFailed(src string, wantAccept bool) Shape

CanaryFailed is the answer to a policy that holds the canary of acl_validate: HTTP 200, "test(s) failed" and one entry whose user is the canary's src and whose one error is in the want/got grammar (A-9). The canary accepts and denies the same destination, so exactly one of the two fails: with no acls (allow-all) the denial fails (want Drop, got Accept), with acls [] (deny-all) the acceptance does (want Accept, got Drop).

func DetailsOK added in v0.6.0

func DetailsOK(policy, etag string, warnings, errs []string) Shape

DetailsOK is GET .../acl?details=true: the policy text in base64, the warnings and the errors (null when there are none), and the ETag header. The live API answered 200 with these three fields, both null for a clean policy, and the plain GET's ETag whether or not Accept asked for HuJSON (A-7, A-8). The skeleton does not record the Content-Type, and the run saw no warning (null); a list of strings is the documented form.

func ErrorsListAt200 added in v0.6.0

func ErrorsListAt200(errs ...string) Shape

ErrorsListAt200 is an answer that names none of the documented fields: a top-level errors list. It is not a known shape; it stands for "anything else".

func Forbidden403 added in v0.6.0

func Forbidden403() Shape

Forbidden403 is HTTP 403 with a message: the credential lacks the scope.

func PolicyDuplicateKey added in v0.6.0

func PolicyDuplicateKey(line1, col1, line2, col2 int) Shape

PolicyDuplicateKey is a policy that writes a key twice: HTTP 200 and a bare message that names the key and both positions (A-4). The server rejects every repeated key, a case collision included, and merges nothing. The capture records the grammar of the text, not its words; this wording has that grammar (TestTheNewObservedShapesMatchTheirCapture).

func PolicyKeyRejected added in v0.6.0

func PolicyKeyRejected(key string) Shape

PolicyKeyRejected is a rejection that echoes an object key: HTTP 200 and a bare message that quotes the key, as the server did for a login used as a key of hosts (A-14). The echo is Tailscale text like any other and is masked as such. The capture records the grammar of the text, not its words.

func PolicyServerError500 added in v0.6.0

func PolicyServerError500() Shape

PolicyServerError500 is the 500 the SaaS returns for a nodeAttrs entry that targets autogroup:self: a bare message of three words, the same both times it was sent (A-6).

func PolicySyntaxError added in v0.6.0

func PolicySyntaxError(line, column int, detail string) Shape

PolicySyntaxError is a syntax error in policy mode: HTTP 200 and a bare message that starts with the position (A-3), not an HTTP 400.

func PolicyTagNotFound added in v0.6.0

func PolicyTagNotFound(tag string) Shape

PolicyTagNotFound is a rule whose src names a tag the policy does not define: HTTP 200 and a bare message that starts with the field, `src=tag not found: "<tag>"` (A-3).

func PolicyText added in v0.6.0

func PolicyText(policy, etag string) Shape

PolicyText is GET .../acl without details: the policy as HuJSON, with its ETag. The live ETag is the quoted sha256 of the body as sent, and of hujson.Format of it (A-13).

func PolicyUnknownField added in v0.6.0

func PolicyUnknownField(name string) Shape

PolicyUnknownField is the server's strict decoder rejecting a section it does not know: HTTP 200 and a bare message (A-3, A-5), not an HTTP 400.

func Rejected400 added in v0.6.0

func Rejected400(message string) Shape

Rejected400 is a bare message with HTTP 400: the OpenAPI document's 400 has no data. The live API answered this form for a body that is not a policy at all (an empty array, an empty body, null, a byte order mark: A-2, A-11) and answered a well-formed document it rejects with HTTP 200 instead (BareMessage200, A-3).

func SSHCheckFailed added in v0.6.0

func SSHCheckFailed(src, srcAddr, dst, dstAddr, user string) Shape

SSHCheckFailed is a guess at the line a check user would produce. No probe has seen a check failure, so the words want "check" and got "accept" stay Assumed, and a reader must not turn the line into a pass.

func SSHTestFailed added in v0.6.0

func SSHTestFailed(src, srcAddr, dst, dstAddr, user string) Shape

SSHTestFailed is a failed sshTests entry. Policy mode runs sshTests and the line has this form (A-10); the entry's user is the test's src.

func TestUnevaluable added in v0.6.0

func TestUnevaluable(src string) Shape

TestUnevaluable is a test whose src is a tag the policy does not define: an error entry that is not in the want/got grammar (A-2). The line carries the `[acl test error]: ` heading and names the tag again, `user or host is invalid: tag not found: "<tag>"`.

func TestsEmptyData added in v0.6.0

func TestsEmptyData() Shape

TestsEmptyData is HTTP 200 and {"data":[]}. The tests-mode pass seen on 20 Oct 2026 was an empty body (A-12), not an empty list, so this stays Assumed and is not a pass.

func TestsEmptyMessage added in v0.6.0

func TestsEmptyMessage() Shape

TestsEmptyMessage is HTTP 200 and {"message":""}. An empty message with no entries was not seen on 20 Oct 2026, so this stays Assumed and is not a pass.

func TestsFailed added in v0.6.0

func TestsFailed(es ...Entry) Shape

TestsFailed is a failed test: HTTP 200, the message "test(s) failed" and one data entry per source with its errors. The same body carries a policy-mode failure. The live error lines read `[acl test error]: address "<dst>" (protocol "<p>"): want: <V>, got: <V>` (A-1, A-9); the entries a caller passes are its own.

func TestsFailedIn400 added in v0.6.0

func TestsFailedIn400(es ...Entry) Shape

TestsFailedIn400 is the failure of TestsFailed with HTTP 400. The documentation answers failed tests with 200, and so did every failure of the run of 20 Oct 2026 (A-1, A-9, A-10); no 400 carried data, so this stays a stand-in.

func TestsNullData added in v0.6.0

func TestsNullData() Shape

TestsNullData is HTTP 200 and {"data":null}. A null list is not an empty body and was not seen on 20 Oct 2026, so this stays Assumed and is not a pass.

func TestsPass added in v0.6.0

func TestsPass() Shape

TestsPass is a pass: HTTP 200 and an empty body ("An empty response body implies passing"). Tests mode (a JSON array of tests) answered so on 20 Oct 2026 (A-12); policy mode answered {} (TestsPassEmptyObject).

func TestsPassEmptyObject added in v0.6.0

func TestsPassEmptyObject() Shape

TestsPassEmptyObject is the pass the OpenAPI document gives as its example: HTTP 200 and {}. Policy mode (the policy object as HuJSON) answered so on 20 Oct 2026 (A-1, A-5, A-11); tests mode answered an empty body (TestsPass).

func TestsWarned added in v0.6.0

func TestsWarned(es ...Entry) Shape

TestsWarned is a pass with warnings: HTTP 200, the message "warning(s) found" and one data entry per warning, whose user is the policy element it names.

func (Shape) Write added in v0.6.0

func (s Shape) Write(w http.ResponseWriter)

Write answers a request with s: its headers, its status and its body. A handler that chooses its answer per request (a model of the API) uses it; a test that serves one answer uses Serve.

Jump to

Keyboard shortcuts

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