adofake

package
v0.8.2 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package adofake is a local, hermetic fake of the Azure DevOps HTTP surface Wardyn's ADO broker/gate work (#375) talks to: git smart-HTTP (served by the real `git http-backend`, not reimplemented), and the REST endpoints for branch policies, pull requests, refs, pushes, work items, connection data, projects and the personal-access-token lifecycle.

The fake exists to make a NARROWED token provable without parsing it: every endpoint declares the real Azure DevOps OAuth scope it requires, a test registers which scopes a given bearer/PAT carries, and a request presenting a token that lacks the scope gets back exactly the shape the real service returns — a 401 with typeKey UnauthorizedRequestException. A caller that can only see the fake's responses (not its internals) still learns whether its token was accepted for the right reason.

Index

Constants

View Source
const (
	SpacedProject = "Payments Platform"
	SpacedRepo    = "Card Auth (v2).Service"
)

SpacedProject and SpacedRepo are a project and a repository named the way Azure DevOps permits and field reports broke on (#485): a space in both, and parentheses and a dot in the repository. The cmd registers them beside its -project/-repo pair, so every lane driving the fake reaches one.

View Source
const (
	ScopeCodeRead    = "vso.code"
	ScopeCodeWrite   = "vso.code_write"
	ScopeWorkRead    = "vso.work"
	ScopeWorkWrite   = "vso.work_write"
	ScopeProjectRead = "vso.project"
	ScopeTokens      = "vso.tokens"
	// ScopePats and ScopePatsManage are the two delegated permissions the
	// token lifecycle routes require of an ENTRA bearer, measured together
	// against a real tenant (a token holding only these two created and
	// revoked a PAT). Which one alone would do is unmeasured, so the fake
	// asks for both.
	ScopePats       = "vso.pats"
	ScopePatsManage = "vso.pats_manage"
)

The real Azure DevOps OAuth scope literals each endpoint below requires — see https://learn.microsoft.com/azure/devops/integrate/get-started/authentication/oauth for the canonical list. A fake that invented its own names would not prove anything about a token minted against the real scope model.

View Source
const SignInPage = `` /* 226-byte string literal not displayed */

SignInPage is the body FaultSignInPage answers with.

Variables

This section is empty.

Functions

func NewFixtureRepo

func NewFixtureRepo(tb testing.TB) string

NewFixtureRepo creates a bare git repository with two commits on `main`, suitable for RegisterRepo — enough history that both a clone and a push (of a third commit) are meaningful. Skips the calling test with a clear message if `git` is not on PATH.

Types

type Endpoint

type Endpoint string

Endpoint names one handler's counter/override slot and its entry in a recorded request — stable strings a test can assert against, independent of the URL shape underneath.

const (
	EndpointGitAdvertise      Endpoint = "git.advertise"
	EndpointGitUploadPack     Endpoint = "git.upload-pack"
	EndpointGitReceivePack    Endpoint = "git.receive-pack"
	EndpointPolicyGet         Endpoint = "policy.get"
	EndpointPolicyPost        Endpoint = "policy.post"
	EndpointPolicyPut         Endpoint = "policy.put"
	EndpointPullRequestsPost  Endpoint = "pullrequests.post"
	EndpointPullRequestsPatch Endpoint = "pullrequests.patch"
	EndpointRefsPost          Endpoint = "refs.post"
	EndpointPushesGet         Endpoint = "pushes.get"
	EndpointPushesPost        Endpoint = "pushes.post"
	EndpointWiqlPost          Endpoint = "wiql.post"
	EndpointWorkItemsPatch    Endpoint = "workitems.patch"
	EndpointConnectionData    Endpoint = "connectiondata.get"
	EndpointProjectsGet       Endpoint = "projects.get"
	EndpointRepositoriesGet   Endpoint = "repositories.get"
	EndpointPatsList          Endpoint = "tokens.pats.list"
	EndpointPatsCreate        Endpoint = "tokens.pats.create"
	EndpointPatsUpdate        Endpoint = "tokens.pats.update"
	EndpointPatsRevoke        Endpoint = "tokens.pats.revoke"
)

type Fault

type Fault int

Fault is one of the refusal shapes the live Azure DevOps service answers with, as measured (F-LIVE-7): status, the headers that tell the shapes apart, and the body, byte for byte where the service's is short.

const (
	// FaultSignInPage is the answer to an absent, malformed or unknown
	// credential: 203 with a text/html sign-in page (the live page is ~16 KB;
	// this keeps its doctype and title).
	FaultSignInPage Fault = iota + 1
	// FaultBasicUnauthorized is a token missing the scope, expired or revoked —
	// the three are byte-identical: 401, WWW-Authenticate Basic, no
	// Content-Type, an empty body.
	FaultBasicUnauthorized
	// FaultBearerTF400813 is an Entra bearer refused: 401, WWW-Authenticate
	// Bearer, X-TFS-ServiceError, and a TF400813 body behind a UTF-8 BOM.
	FaultBearerTF400813
	// FaultRepoNotFound is an authenticated identity without read on the
	// repository: 404 TF401019 with X-VSS-UserData.
	FaultRepoNotFound
	// FaultPolicyRejected is an authenticated identity a branch policy
	// refuses: 403 TF402455 with X-VSS-UserData.
	FaultPolicyRejected
)

type PatTokenError

type PatTokenError string

PatTokenError is the real Azure DevOps Tokens API's patTokenError enum — the value a CreatePat/UpdatePat response carries alongside a nil patToken when creation is refused for a policy reason rather than a scope failure.

const (
	PatTokenErrorNone                     PatTokenError = "none"
	PatTokenErrorFullScopePolicyViolation PatTokenError = "fullScopePatPolicyViolation"
	PatTokenErrorAccessDenied             PatTokenError = "accessDenied"
	PatTokenErrorLifespanPolicyViolation  PatTokenError = "patLifespanPolicyViolation"
	PatTokenErrorGlobalPolicyViolation    PatTokenError = "globalPatPolicyViolation"
	PatTokenErrorInvalidValidTo           PatTokenError = "invalidValidTo"
)

type RecordedRequest

type RecordedRequest struct {
	Method        string
	Path          string
	Query         string
	Headers       http.Header
	Endpoint      Endpoint
	RequiredScope string // "" when the endpoint needs no scope; space-separated when it needs several
	Token         string // the bearer/PAT presented, "" if none
	Authorized    bool   // true when no scope was required, or the token carried it
	// APIVersion is the request's ?api-version= value, "" if absent. Real
	// Azure DevOps requires this on every REST call; this fake does NOT
	// refuse its absence (that would break every caller that hasn't wired it
	// up yet), but records it so a lane that cares can assert its own client
	// actually sent one.
	APIVersion string
}

RecordedRequest is one call the fake answered, kept so a test can assert what actually reached the forge — method, path, every header seen, and the scope decision the fake made.

type Server

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

Server is the fake Azure DevOps org. Zero value is not usable; use New.

func New

func New() *Server

New starts a fake Azure DevOps server.

func NewHandler

func NewHandler() (*Server, http.Handler)

NewHandler returns an UNSTARTED fake plus its handler, for a caller that owns its own listener (test/adofake/cmd serves it behind a TLS front). Mirrors entrafake.NewHandler; an unstarted Server's URL is "".

func (*Server) AddProject

func (s *Server) AddProject(org, id, name string)

AddProject registers a project fixture that GET /{org}/_apis/projects returns; id defaults to name if empty.

func (*Server) ClearOverride

func (s *Server) ClearOverride(endpoint Endpoint)

ClearOverride removes a forced response set by SetOverride.

func (*Server) Close

func (s *Server) Close()

Close shuts down the underlying httptest server.

func (*Server) Count

func (s *Server) Count(endpoint Endpoint) int

Count returns how many requests endpoint has answered so far (overridden responses count too).

func (*Server) RegisterIdentity added in v0.8.2

func (s *Server) RegisterIdentity(token, account string)

RegisterIdentity makes connectionData name account as the owner of token — the sign-in name Azure DevOps shows under authenticatedUser's Account property. A token with no identity registered is answered with the fake's canned user, which carries no Account at all.

func (*Server) RegisterRepo

func (s *Server) RegisterRepo(org, project, repo, barePath string)

RegisterRepo makes barePath (a bare git repository — see NewFixtureRepo, or a caller-built one) reachable at /{org}/{project}/_git/{repo}, the real Azure DevOps git smart-HTTP URL shape. It sets http.receivepack=true on barePath: git http-backend refuses receive-pack for any repo that hasn't opted in, regardless of GIT_HTTP_EXPORT_ALL, so a caller-supplied repo that skipped this would be granted ScopeCodeWrite by the fake and then die 403 inside git for an unrelated config reason. Panics (loudly, at registration time rather than at the first confusing push) if barePath isn't a git directory git can configure.

func (*Server) RegisterServerIdentity added in v0.8.2

func (s *Server) RegisterServerIdentity(token, account, mail string)

RegisterServerIdentity makes connectionData answer as Azure DevOps Server does for a directory account: Account is the DOMAIN\user sign-in name and Mail the account's email address.

func (*Server) RegisterToken

func (s *Server) RegisterToken(token string, scopes ...string)

RegisterToken makes token (a bearer access token OR a personal access token, presented either as "Authorization: Bearer <token>" or as HTTP Basic with the token as the password) carry exactly scopes, with no expiry — replacing any previous grant for the same token. A PAT minted through handlePatsCreate registers itself the same way, but with the validTo it declared.

func (*Server) Requests

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

Requests returns every recorded request in arrival order.

func (*Server) SetFault

func (s *Server) SetFault(endpoint Endpoint, fault Fault)

SetFault makes every subsequent call to endpoint answer with fault's shape, the way SetOverride does (scope is still decided and recorded). ClearOverride removes it.

func (*Server) SetOverride

func (s *Server) SetOverride(endpoint Endpoint, status int, body []byte)

SetOverride forces every subsequent call to endpoint to answer status/body verbatim, bypassing scope enforcement — for exercising a retry or a sweep against a simulated outage. ClearOverride restores normal behaviour.

func (*Server) SetPatCreateError

func (s *Server) SetPatCreateError(err PatTokenError)

SetPatCreateError makes every subsequent PAT-create call answer err instead of minting a token (PatTokenErrorNone, the default, restores normal creation) — for exercising the policy-violation paths a real tenant can return without a real tenant.

func (*Server) SetPatLifespanLimit added in v0.8.2

func (s *Server) SetPatLifespanLimit(max time.Duration, err PatTokenError)

SetPatLifespanLimit makes a create whose validTo is more than max from now answer err with no token, and any shorter one succeed — the organisation's "Enforce maximum personal access token lifespan" policy with err PatTokenErrorLifespanPolicyViolation, or another refusal of a long validTo. A zero max removes the limit.

func (*Server) URL

func (s *Server) URL() string

URL is the fake's base URL (an org's own address in the real service — the caller supplies the {org}/{project} path segments).

Directories

Path Synopsis
Command adofake serves test/entrafake AND test/adofake from ONE process, behind a TLS-terminating forward proxy, so the kind walk's Azure DevOps profile (scripts/lib/kind-sso-walk-ado.sh) can reach "login.microsoftonline.com" and "dev.azure.com" by their real names on a cluster with no Microsoft tenant.
Command adofake serves test/entrafake AND test/adofake from ONE process, behind a TLS-terminating forward proxy, so the kind walk's Azure DevOps profile (scripts/lib/kind-sso-walk-ado.sh) can reach "login.microsoftonline.com" and "dev.azure.com" by their real names on a cluster with no Microsoft tenant.

Jump to

Keyboard shortcuts

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