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
- func CaptureFile(dir, id string) string
- type Endpoint
- type Entry
- type Fake
- func (f *Fake) AcceptIDToken(tok string)
- func (f *Fake) Client(t testing.TB) *api.Client
- func (f *Fake) ExchangeRequests() int
- func (f *Fake) FailExchanges(statuses ...int)
- func (f *Fake) Fallback(h http.HandlerFunc)
- func (f *Fake) Handle(method, path string, h http.HandlerFunc)
- func (f *Fake) JSON(method, path string, status int, body string)
- func (f *Fake) PermitProbeRequests()
- func (f *Fake) Raw(method, path string, status int, body string, headers map[string]string)
- func (f *Fake) Requests() []Request
- func (f *Fake) Serve(method, path string, s Shape)
- func (f *Fake) ServeDetails(s Shape)
- func (f *Fake) ServePolicy(s Shape)
- func (f *Fake) ServeValidate(s Shape)
- func (f *Fake) TokenRequests() int
- func (f *Fake) URL() string
- type Kind
- type Provenance
- type Request
- type Shape
- func AllShapes() []Shape
- func AuditACLUpdates() Shape
- func BareMessage200(message string) Shape
- func CanaryFailed(src string, wantAccept bool) Shape
- func DetailsOK(policy, etag string, warnings, errs []string) Shape
- func ErrorsListAt200(errs ...string) Shape
- func Forbidden403() Shape
- func PolicyDuplicateKey(line1, col1, line2, col2 int) Shape
- func PolicyKeyRejected(key string) Shape
- func PolicyServerError500() Shape
- func PolicySyntaxError(line, column int, detail string) Shape
- func PolicyTagNotFound(tag string) Shape
- func PolicyText(policy, etag string) Shape
- func PolicyUnknownField(name string) Shape
- func Rejected400(message string) Shape
- func SSHCheckFailed(src, srcAddr, dst, dstAddr, user string) Shape
- func SSHTestFailed(src, srcAddr, dst, dstAddr, user string) Shape
- func TestUnevaluable(src string) Shape
- func TestsEmptyData() Shape
- func TestsEmptyMessage() Shape
- func TestsFailed(es ...Entry) Shape
- func TestsFailedIn400(es ...Entry) Shape
- func TestsNullData() Shape
- func TestsPass() Shape
- func TestsPassEmptyObject() Shape
- func TestsWarned(es ...Entry) Shape
Constants ¶
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.
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.
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
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 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 (*Fake) AcceptIDToken ¶
AcceptIDToken makes the token-exchange route accept tok for FedClientID.
func (*Fake) Client ¶
Client returns an api.Client for the fake's tailnet using the fake OAuth client, with retries sleeping zero time.
func (*Fake) ExchangeRequests ¶
ExchangeRequests returns how many times the token-exchange endpoint was called.
func (*Fake) FailExchanges ¶
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) 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 ¶
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 ¶
Requests returns the API requests recorded so far (token requests excluded).
func (*Fake) ServeDetails ¶ added in v0.6.0
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
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
ServeValidate registers s as the answer to POST .../acl/validate.
func (*Fake) TokenRequests ¶
TokenRequests returns how many times the token endpoint was called.
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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.