Documentation
¶
Overview ¶
Package releasetest provides an in-memory forge.Provider test double and asset builders for exercising the self-update pipeline without network or disk access. It serves releases, tar.gz binary assets, checksum manifests and detached signatures entirely from memory, including the security-relevant corrupt-checksum and bad-signature variants.
The double is public (pkg/) so downstream tools building on GTB can test their own self-update wiring hermetically — the same reasoning that keeps forge.Provider public. It is usable both from tests and at runtime (the builders take no *testing.T), which lets the e2e binary inject a stub release source behind an env gate.
A Source always satisfies forge.ChecksumProvider and forge.SignatureProvider, but returns forge.ErrNotSupported unless configured with WithChecksumManifest / WithSignatureManifest. Per the provider contract, the update flow treats forge.ErrNotSupported exactly like "provider does not implement this interface" and falls back to locating checksums.txt / checksums.txt.sig by filename in the asset list — so a Source exercises both the manifest-provider path and the asset-by-name fallback.
See the injectable-release-source spec in go-tool-base: https://gitlab.com/phpboyscout/go-tool-base/-/wikis/specs/0088-injectable-release-source
Index ¶
- func AssetName(toolName string) string
- func CaptureLogger() (logger *slog.Logger, records func() []slog.Record)
- func Manifest(corrupt bool, over ...Asset) []byte
- func RunProviderConformance(t *testing.T, cfg ConformanceConfig)
- type Asset
- type Capabilities
- type CommentFixture
- type ConformanceConfig
- type DraftFixture
- type Option
- type PullRequestFixture
- type ReleaseFixture
- type RepositoryFixture
- type Source
- func (s *Source) DownloadChecksumManifest(_ context.Context, _ forge.Release, _ int64) ([]byte, error)
- func (s *Source) DownloadReleaseAsset(_ context.Context, _, _ string, a forge.ReleaseAsset) (io.ReadCloser, string, error)
- func (s *Source) DownloadSignature(_ context.Context, _ forge.Release, _ int64) ([]byte, error)
- func (s *Source) GetLatestRelease(_ context.Context, _, _ string) (forge.Release, error)
- func (s *Source) GetReleaseByTag(_ context.Context, _, _, tag string) (forge.Release, error)
- func (s *Source) ListReleases(_ context.Context, _, _ string, limit int) ([]forge.Release, error)
- type WikiFixture
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AssetName ¶
AssetName returns the OS/arch release-asset name the updater's findReleaseAsset expects for toolName on the current platform — e.g. "mytool_Linux_x86_64.tar.gz". It mirrors the naming convention in pkg/setup so a built asset is the one Update will look for.
func CaptureLogger ¶ added in v0.8.0
CaptureLogger returns a logger that records everything written to it, and a function returning what it has recorded so far.
It exists so a provider author can assert on their own diagnostics without building a handler by hand. The returned logger is wrapped exactly as forge.NormaliseLogger wraps a consumer's, so what a test observes is what a consumer would observe — including redaction.
Safe for concurrent use: a provider may log from more than one goroutine, and a capture that raced would fail intermittently in ways nobody enjoys diagnosing.
func Manifest ¶
Manifest builds a GoReleaser-style checksums.txt over the given assets (one "sha256 name" line each). When corrupt is true the recorded hashes intentionally cover a different payload, so verification against the real asset bodies fails.
func RunProviderConformance ¶
func RunProviderConformance(t *testing.T, cfg ConformanceConfig)
RunProviderConformance asserts that a forge.Provider honours the parts of the contract that are otherwise documented only in prose.
It exists because the contract has two halves, and only one is checkable by the compiler. The method set is enforced by the type system; the *protocol* — which error to return when an operation does not apply, that a nil result and a nil error may never be returned together, that a caller-supplied size bound is actually enforced — is enforced by nothing at all. A provider can satisfy forge.Provider completely and still be wrong in every way that matters to a caller.
Run it from the provider's own test suite:
func TestConformance(t *testing.T) {
forgetest.RunProviderConformance(t, forgetest.ConformanceConfig{
NewProvider: func() forge.Provider { return newTestProvider(t) },
Capabilities: forgetest.Capabilities{GetReleaseByTag: true, ListReleases: true},
Owner: "acme", Repo: "tool", Tag: "v1.0.0",
})
}
The harness never asserts on payload contents — that is the provider's own business — only on the contract every caller relies upon.
Types ¶
type Asset ¶
Asset is a single downloadable release asset served verbatim by a Source.
func ChecksumsAsset ¶
ChecksumsAsset wraps Manifest as a "checksums.txt" asset.
func SignatureAsset ¶
SignatureAsset builds a "checksums.txt.sig" ASCII-armored detached signature over manifest using entity. When bad is true it signs a different payload, so the signature does not verify against the served manifest.
func TarGzAsset ¶
TarGzAsset builds the platform release-binary asset for toolName: its Name is AssetName(toolName) and its Body is a .tar.gz archive whose single entry is binName with binBody as its contents.
type Capabilities ¶
type Capabilities struct {
// GetReleaseByTag is false for platforms with no addressable tag concept.
GetReleaseByTag bool
// ListReleases is false for platforms that expose no release listing.
ListReleases bool
// Checksums is true when the provider implements [forge.ChecksumProvider]
// AND is configured such that it will serve a manifest.
Checksums bool
// Signatures is true when the provider implements
// [forge.SignatureProvider] AND is configured to serve one.
Signatures bool
// Repositories is true when the provider implements
// [forge.Repositories] AND can enumerate ConformanceConfig.Namespace.
Repositories bool
// Contents is true when the provider implements [forge.Contents] AND can
// read ConformanceConfig.FilePath.
Contents bool
// Sites is true when the provider implements [forge.Sites] AND its forge
// publishes sites. Most providers are false here: two of the four
// first-party forges have no site feature at all, and that is the capability
// pattern working rather than a gap.
Sites bool
// Issues is true when the provider implements [forge.Issues] AND can read
// ConformanceConfig.IssueNumber.
Issues bool
// PullRequests is true when the provider implements [forge.PullRequests]
// AND ConformanceConfig.PullRequests names fixtures it can serve.
//
// The harness exercises the READ half only. Create, Update and Close are
// writes and this contract carries no delete, so a harness that opened a
// pull request would leave one behind on every run.
PullRequests bool
// Snippets is true when the provider implements [forge.Snippets] AND its
// forge still offers the feature. Most providers are false: of the four
// first-party forges, Bitbucket WITHDREW snippets and Gitea never had them.
//
// DECLARING THIS MAKES THE HARNESS WRITE. Unlike Capabilities.IssueFiler it
// also CLEANS UP — the contract carries a delete, so the check creates a
// snippet, reads it back and removes it. A failure mid-check can still
// leave one behind, so point it at a scope where that is acceptable.
Snippets bool
// ReleasePublisher is true when the provider implements
// [forge.ReleasePublisher].
//
// Most of its checks are REFUSALS and write nothing — an absent tag, a
// commit that does not match, a tag that already carries a release. Those
// run on this flag alone.
//
// The one check that writes is gated separately, on
// ReleaseFixture.PublishTag, so declaring this is safe by default.
ReleasePublisher bool
// ReleaseAssetPublisher is true when the provider implements
// [forge.ReleaseAssetPublisher], which publishes a release COMPLETE and
// attaches an asset the caller has already hosted.
//
// Most of its checks write nothing: a malformed asset must be refused
// before anything is created, and an absent tag must still be refused.
// Those run on this flag alone.
//
// The one check that writes is gated separately, on
// ReleaseFixture.PublishAssetsTag, so declaring this is safe by default.
ReleaseAssetPublisher bool
// IssueCommenter is true when the provider implements
// [forge.IssueCommenter] AND its forge has an issue tracker. Bitbucket
// Cloud is permanently false: Atlassian withdrew issues and the endpoint
// answers 410 Gone.
//
// Its checks write nothing unless CommentFixture.IssueNumber is set.
IssueCommenter bool
// PullRequestCommenter is true when the provider implements
// [forge.PullRequestCommenter].
//
// DECLARING THIS WITH CommentFixture.PullRequestNumber MAKES THE HARNESS
// WRITE, and this contract carries no delete, so the comments stay.
PullRequestCommenter bool
// Wikis is true when the provider implements [forge.Wikis] AND the project
// named below HAS a wiki.
//
// The second half is not pedantry. On GitHub and Bitbucket a wiki does not
// exist until a human creates its first page in a browser, and a provider
// pointed at a project without one correctly answers ErrNotSupported to
// everything — which the harness would otherwise read as a broken adapter.
//
// Most checks here write nothing. The one that does is gated separately on
// WikiFixture.WritePath, so declaring this is safe by default.
Wikis bool
// DraftPublisher is true when the provider implements
// [forge.DraftPublisher] AND its forge models an unpublished release.
// GitLab is permanently false: it has no draft concept.
//
// Its absent-release check writes nothing. The one check that PUBLISHES is
// gated separately on DraftFixture.PublishTag, so declaring this is safe by
// default.
DraftPublisher bool
// RepositoryCreator is true when the provider implements
// [forge.RepositoryCreator].
//
// Most of its checks write nothing — an empty name and an empty owner must
// both be refused before any request, and the collision check needs only a
// name that already exists. The one check that CREATES is gated separately,
// on RepositoryFixture.CreateIn, so declaring this is safe by default.
RepositoryCreator bool
// IssueFiler is true when the provider implements [forge.IssueFiler].
//
// DECLARING THIS MAKES THE HARNESS FILE ISSUES. It is the only capability
// whose checks WRITE, and the writes are not undone — there is no delete in
// the contract and most forges do not offer one.
//
// Point NewProvider at a fixture. A provider aimed at a real tracker will
// leave real issues in it, visible to everyone watching the project, and
// the harness cannot tell the difference.
IssueFiler bool
}
Capabilities declares what a provider under test supports, so the harness can tell "correctly reports it cannot do this" apart from "is broken".
Declare capabilities honestly. Claiming support the provider lacks makes the harness assert real behaviour it cannot deliver; claiming less than it supports means the harness checks the opt-out protocol instead, which is weaker but never wrong.
type CommentFixture ¶ added in v0.26.0
type CommentFixture struct {
// IssueNumber is an issue the provider may comment on. **DECLARING IT MAKES
// THE HARNESS WRITE**, and this contract carries no delete, so the comments
// stay. Empty skips the issue round trip.
IssueNumber int
// PullRequestNumber is a pull request the provider may comment on, with the
// same warning.
//
// It should carry MORE THAN ONE PAGE of existing comments. The idempotency
// check posts twice with one key and asserts a single comment, and that
// passes against a provider which reads only the first page so long as the
// target is short — the "passes either way" shape AGENTS.md names. The
// harness reports a fixture too small to discriminate rather than pretending
// the check was meaningful.
PullRequestNumber int
// MissingIssueNumber is an issue number the fixture guarantees does NOT
// exist. Empty skips the issue absent-object check AND the issue opt-out
// probe, because without it there is nothing safe to post to.
MissingIssueNumber int
// MissingPullRequestNumber is the same for pull requests, and it is a
// SEPARATE field on purpose.
//
// Only GitHub shares a number space between issues and pull requests. On
// GitLab, Gitea and Bitbucket they are numbered independently, so a number
// that is a missing issue is very likely a LIVE merge request — and a
// single field would have the harness post "must not be posted" onto it,
// with no delete in this contract to take it back.
MissingPullRequestNumber int
// PullRequestSpansPages declares that PullRequestNumber carries more than
// one page of comments, which is what makes the pagination half of the
// idempotency check meaningful. See checkCommentListing.
PullRequestSpansPages bool
}
CommentFixture parameterises the commenter checks.
type ConformanceConfig ¶
type ConformanceConfig struct {
// NewProvider returns a provider ready to serve the fixtures below. It is
// called once per check, so each gets an independent instance. Close over
// *testing.T if construction needs it for cleanup.
NewProvider func() forge.Provider
// NewProviderWithLogger returns the same provider NewProvider would, built
// to log through the supplied logger.
//
// Supplying it enables the check that a provider never logs at ERROR level
// for a condition it also returns as an error — see the levels table in the
// logging guide. The harness cannot inject a logger otherwise, because
// NewProvider hands back an already-built provider.
//
// Optional. When nil the rule is unverified rather than assumed satisfied,
// and the check is skipped.
NewProviderWithLogger func(logger *slog.Logger) forge.Provider
// NewProviderAt returns the provider NewProvider would, built to talk to a
// forge at baseURL rather than to the fixture.
//
// Supplying it enables the refusal checks: the harness stands up a server
// that answers with one status and set of headers, and proves the provider
// names WHICH refusal that was. It exists because three of the five
// refusals cannot be provoked from a fixture — nothing makes a healthy
// forge rate-limit a test — and the one with the worst consequence is
// among them.
//
// baseURL has no trailing slash. A provider whose SDK wants one appends it.
//
// Optional. When nil the mapping is unverified rather than assumed
// satisfied, and the checks are skipped.
NewProviderAt func(baseURL string) forge.Provider
// Capabilities describes what this provider can do.
Capabilities Capabilities
// PullRequests describes the pull-request fixtures the harness may use.
// Ignored unless Capabilities.PullRequests is set.
PullRequests PullRequestFixture
// Releases describes the release-publishing fixtures the harness may use.
// Ignored unless Capabilities.ReleasePublisher is set.
Releases ReleaseFixture
// Wikis parameterises the [forge.Wikis] checks. Its zero value skips every
// check that needs a fixture, leaving the opt-out and validation checks.
Wikis WikiFixture
// RepositoryCreation parameterises the [forge.RepositoryCreator] checks. Its
// zero value skips the round trip and the collision check, leaving the two
// validation checks, which write nothing.
//
// It is NOT Capabilities.Repositories, which gates the read side. The two
// are deliberately separate: enumerating a namespace says nothing about
// wanting to create in one.
RepositoryCreation RepositoryFixture
// Drafts parameterises the [forge.DraftPublisher] checks. Its zero value
// skips every check that needs a fixture, leaving the opt-out probe.
Drafts DraftFixture
// Comments parameterises the commenter checks. Its zero value skips every
// check that needs a fixture, leaving the opt-out checks.
Comments CommentFixture
// Owner, Repo and Tag are passed to the provider's methods. Tag must name
// a release the provider will find when GetReleaseByTag is supported.
Owner, Repo, Tag string
// MissingTag names a tag the provider will NOT find. Optional: when empty,
// the not-found check is skipped rather than guessed at.
MissingTag string
// Namespace is enumerated by the [forge.Repositories] checks. Required when
// Capabilities.Repositories is true.
Namespace string
// FilePath and FileRef name a file the provider WILL find, for the
// [forge.Contents] checks. Required when Capabilities.Contents is true.
FilePath, FileRef string
// MissingFilePath names a file the provider will NOT find at FileRef, so
// the harness can prove [forge.ErrNotFound] is returned rather than a
// bespoke error. Optional: when empty the check is skipped.
MissingFilePath string
// SiteRepo names a repository that HAS a published site, and
// MissingSiteRepo one that does not. Both optional, and only consulted when
// Capabilities.Sites is true.
SiteRepo, MissingSiteRepo string
// UnknownVisibilityPath is the canonical path of a repository your fixture
// reports WITHOUT a privacy field, so the harness can prove the provider
// decodes fail-closed rather than letting an absent field become false and
// therefore public.
//
// Optional, and worth supplying: it is the only mechanical check on the
// fail-closed rule, which is otherwise prose. Providers whose SDK models
// privacy as a plain bool are exactly the ones that need it.
UnknownVisibilityPath string
// IssueNumber names an issue the provider WILL find. Required when
// Capabilities.Issues is true.
IssueNumber int
// MissingIssueNumber names an issue the provider will NOT find, so the
// harness can prove [forge.ErrNotFound] comes back rather than a bespoke
// error. Optional: when zero the check is skipped.
MissingIssueNumber int
// IssueSearchText is text your fixture's issues match, for the search
// checks. Optional: when empty the search runs unfiltered.
IssueSearchText string
// IssueLabel is a label that EXISTS on the fixture repository, and
// IssueLabelMiscased is the same name with its case changed.
//
// Both empty skips the label checks. Setting them arms two things spec 0017
// requires, and the SECOND field is the one that matters:
//
// A random unknown name proves only that unresolvable labels are reported.
// It cannot tell an EXACT matcher from a case-insensitive one, and a
// case-insensitive resolver is the failure that is silently wrong — it
// binds a caller's name onto a label somebody else wrote and reports
// success. Point IssueLabelMiscased at a variant of IssueLabel ("Support"
// against a project carrying "support") and the check discriminates.
//
// The harness fails the FIXTURE when the two are equal, because a provider
// ignoring exactness entirely would otherwise pass.
IssueLabel string
IssueLabelMiscased string
// EchoesCreatedBody declares that your fixture returns the body it was
// SENT, rather than a canned one.
//
// The sanitisation checks compare what came back against what went in, so
// they can only run against a fixture that echoes. Left false they are
// skipped rather than producing a verdict the fixture cannot support —
// which matters, because a canned body would make the redaction check pass
// without redacting anything.
EchoesCreatedBody bool
}
ConformanceConfig parameterises RunProviderConformance.
type DraftFixture ¶ added in v0.27.0
type DraftFixture struct {
// PublishTag names a tag whose release the harness may PUBLISH. **DECLARING
// IT MAKES THE HARNESS WRITE**, and what it writes becomes visible to
// everyone the moment it succeeds — that is what the verb is for. There is
// no delete here to take it back.
//
// It may name a draft or an already-published release: the checks assert
// the same end state either way, which is what the idempotency guarantee
// means. Empty skips the round trip.
PublishTag string
// MissingTag names a tag the fixture guarantees carries NO release. Empty
// skips the absent-release check AND the opt-out probe, because without it
// there is nothing safe to publish at.
MissingTag string
}
DraftFixture parameterises the publish checks.
type Option ¶
type Option func(*Source)
Option configures a Source.
func WithChecksumManifest ¶
WithChecksumManifest puts the Source in checksum manifest-provider mode: it implements forge.ChecksumProvider by returning these bytes instead of the default asset-by-name fallback.
func WithDownloadError ¶
WithDownloadError makes Source.DownloadReleaseAsset fail with err.
func WithLatestTag ¶
WithLatestTag sets which registered tag Source.GetLatestRelease returns.
func WithMissingTag ¶
WithMissingTag makes Source.GetReleaseByTag(tag) return a not-found error wrapping forge.ErrReleaseNotFound.
func WithRelease ¶
WithRelease registers a release at tag exposing the given assets. The first registered release becomes the "latest" unless WithLatestTag overrides it.
func WithSignatureManifest ¶
WithSignatureManifest puts the Source in signature manifest-provider mode: it implements forge.SignatureProvider by returning these bytes instead of the default asset-by-name fallback.
type PullRequestFixture ¶ added in v0.16.0
type PullRequestFixture struct {
// OpenSourceBranch names a branch with exactly one OPEN pull request.
OpenSourceBranch string
// MissingSourceBranch names a branch with no open pull request. It drives
// the ErrNotFound half of Find, which matters more than the happy path: a
// caller that reads a failed lookup as "nothing open" opens a duplicate.
MissingSourceBranch string
// MergedSourceBranch names a branch whose pull request has merged.
MergedSourceBranch string
// MergedNumber is a MERGED pull request, and MergedCommit is the commit it
// actually produced ON THE TARGET BRANCH.
MergedNumber int
MergedCommit string
// StaleHead is the head SHA the forge RECORDS for MergedNumber, when the
// fixture reproduces an auto-rebase and that differs from MergedCommit.
//
// Setting it is what arms the check. The harness asserts it differs from
// MergedCommit and fails the FIXTURE if it does not, because a fixture
// whose recorded head already equals the landed commit cannot distinguish
// a conforming provider from one that returns the recorded head blindly —
// which is the single failure this capability exists to prevent.
//
// Leaving it empty is permitted and is reported as unverified rather than
// passed.
StaleHead string
// MissingNumber names a pull request that does not exist.
MissingNumber int
// OpenLabelWith is a label that the OpenSourceBranch pull request carries.
// Empty skips the label check.
//
// It hangs off OpenSourceBranch rather than naming its own pull request
// because [forge.PullRequests] has no get-by-number: Find and FindLastMerged
// are the only reads that return one, and both take a branch. A fixture
// field naming a number the harness cannot fetch would skip in silence.
//
// # Choose the LABEL to defeat pagination, not to be convenient
//
// The label resolution spec 0014 D7 requires is the half of this capability
// most likely to be got wrong, and the way it is got wrong is invisible: a
// provider that resolves against an UNPAGINATED label listing sees a
// truncated set and reports a label absent that the caller can see in the
// forge's UI. GitLab's default page is 20.
//
// So a project with FEWER labels than the forge's default page size cannot
// distinguish a paginating provider from one that does not. Point the
// fixture at a project with more, and name a label known to fall beyond the
// first page.
OpenLabelWith string
}
PullRequestFixture describes what the harness may exercise against a provider's forge.PullRequests implementation.
Every field is optional: a zero field skips the check that needs it rather than guessing at a value. What is NOT optional is honesty about StaleHead — see PullRequestFixture.StaleHead, which is the field that decides whether the most important check in this file can catch anything at all.
type ReleaseFixture ¶ added in v0.19.0
type ReleaseFixture struct {
// ExistingTag names a tag that ALREADY carries a release, and
// ExistingCommit the commit it points at.
//
// Together they drive the duplicate check: creating against ExistingTag
// must return [forge.ErrAlreadyExists] rather than a second release or a
// bespoke error.
ExistingTag string
ExistingCommit string
// MissingTag names a tag that does NOT exist.
//
// This is the check that matters most on an unfamiliar forge. Given an
// absent tag every first-party forge will CREATE it from whatever ref it
// was handed and return success, so a provider that simply forwards the
// call passes every happy-path test while quietly tagging the repository
// with the caller's credential.
MissingTag string
// WrongCommit is a REAL commit in the repository that ExistingTag does NOT
// point at.
//
// Setting it is what arms the mismatch check, and two things about it are
// load-bearing:
//
// - It must DIFFER from ExistingCommit. The harness fails the FIXTURE if
// it does not, because a provider that ignores the field entirely would
// otherwise pass.
// - It must be a real commit, not a plausible-looking string. A provider
// rejecting "deadbeef..." for being unresolvable has refused for the
// wrong reason, and the check would go green without the guarantee
// being present at all.
//
// Leaving it empty is permitted and is reported as unverified rather than
// passed.
WrongCommit string
// PublishTag names a tag with NO release that the harness MAY publish to,
// and PublishCommit the commit it points at.
//
// SETTING THIS MAKES THE HARNESS WRITE, and the write is not undone —
// [forge.ReleasePublisher] carries no delete. Point it at a throwaway
// project. Leaving it empty skips the write checks entirely and is the
// default.
PublishTag string
PublishCommit string
// PublishAssetsTag names a tag with NO release that the harness MAY publish
// to through [forge.ReleaseAssetPublisher], and PublishAssetsCommit the
// commit it points at.
//
// SETTING THIS MAKES THE HARNESS WRITE, on the same terms as PublishTag,
// and the write is not undone.
//
// It needs its own tag rather than sharing PublishTag: by the time this
// check runs that tag carries a release, so a second create would correctly
// return ErrAlreadyExists — a conformant answer that would be reported as a
// failure.
PublishAssetsTag string
PublishAssetsCommit string
// AssetLocation is an absolute URL the harness attaches as a
// location-shaped asset. Empty uses an example.invalid URL, which is enough
// for every check here because NOTHING FETCHES IT — a provider records the
// location, it does not resolve it.
//
// Set it to a URL that really serves bytes only if you want the resulting
// release to be useful to a human afterwards.
AssetLocation string
// CannotHostLocations declares that this forge has no concept of a release
// asset that is a link, so the harness expects a location to come back as
// [forge.ErrNotHonoured] BESIDE a created release whose notes carry the
// footer.
//
// GitHub and Gitea are both this. It is not a licence to drop the location:
// the check asserts the release exists, the error is returned, AND the body
// carries the link.
CannotHostLocations bool
// CannotDraft declares that this forge does not model draft or prerelease
// state, so the harness expects [forge.ErrNotHonoured] BESIDE a created
// release rather than instead of one.
//
// It is not a licence to fail: a provider must still create the release,
// and the check asserts exactly that pairing.
CannotDraft bool
}
ReleaseFixture describes what the harness may exercise against a provider's forge.ReleasePublisher implementation.
Most of this capability can be proved WITHOUT writing ¶
Three of the four guarantees are refusals — an absent tag, a commit that does not match, a tag that already has a release — and a provider honouring them creates nothing. Those checks run whenever the capability is declared, and they are the ones most worth having, because each covers a failure that is silent on at least one forge.
Only the last needs a real release, and it is gated separately on ReleaseFixture.PublishTag.
type RepositoryFixture ¶ added in v0.26.0
type RepositoryFixture struct {
// CreateIn is the namespace the harness may create in. **DECLARING IT MAKES
// THE HARNESS CREATE A REAL REPOSITORY**, and this contract carries no
// delete, so it stays. Empty skips the round trip and leaves the refusal
// checks, which write nothing.
CreateIn string
// CreateName is the repository name to create. Required alongside CreateIn.
//
// It must NOT already exist the first time and WILL exist afterwards, which
// is deliberate: the collision check re-uses it, so a second run of the
// harness against the same fixture exercises ErrAlreadyExists on the first
// call rather than the second. A fixture that is cleaned up between runs
// tests less, not more.
CreateName string
// ExistingName is a repository that already exists in CreateIn, used for the
// collision check without creating anything.
//
// Supplying it makes the collision check runnable with CreateIn empty, so a
// provider can prove the sentinel without the harness writing at all. Prefer
// it.
ExistingName string
// MissingNamespace is a namespace the fixture guarantees does NOT exist.
// Empty skips the opt-out probe.
//
// It exists because this capability's only method WRITES, so the probe that
// proves an undeclared provider answers ErrNotSupported has to be one that
// cannot create anything even against a provider that ignores the
// declaration. A namespace that does not resolve is that probe: a provider
// which supports the capability fails at the namespace, and one that does
// not answers ErrNotSupported before looking.
//
// An INVALID draft is not a substitute, and was tried first. A conformant
// provider validates before it does anything else, so an empty Name comes
// back as a validation error on a provider that implements the capability
// perfectly — the probe reported every correct implementation and could
// never pass.
MissingNamespace string
}
RepositoryFixture parameterises the creation checks.
type Source ¶
type Source struct {
// contains filtered or unexported fields
}
Source is a fully in-memory forge.Provider for tests and the e2e stub. Construct it with New and one or more [Option]s.
func (*Source) DownloadChecksumManifest ¶
func (s *Source) DownloadChecksumManifest(_ context.Context, _ forge.Release, _ int64) ([]byte, error)
DownloadChecksumManifest implements forge.ChecksumProvider. It returns forge.ErrNotSupported unless the Source was built with WithChecksumManifest, so the default Source uses the asset-by-name fallback.
func (*Source) DownloadReleaseAsset ¶
func (s *Source) DownloadReleaseAsset(_ context.Context, _, _ string, a forge.ReleaseAsset) (io.ReadCloser, string, error)
DownloadReleaseAsset returns the in-memory body registered for the asset.
func (*Source) DownloadSignature ¶
DownloadSignature implements forge.SignatureProvider. It returns forge.ErrNotSupported unless the Source was built with WithSignatureManifest, so the default Source uses the asset-by-name fallback.
func (*Source) GetLatestRelease ¶
GetLatestRelease returns the release at the configured latest tag.
func (*Source) GetReleaseByTag ¶
GetReleaseByTag returns the release registered at tag, or a not-found error when the tag is unknown or was marked missing.
type WikiFixture ¶ added in v0.24.0
type WikiFixture struct {
// ExistingPage is the path of a page the provider can read. Empty skips the
// read check.
ExistingPage string
// MissingPage is a path the fixture guarantees does NOT exist. Empty skips
// the absent-page check.
MissingPage string
// WritePath is where the round-trip check may create a page. **DECLARING IT
// MAKES THE HARNESS WRITE**, and this contract carries no delete, so the
// page stays behind.
//
// Give it a nested, hyphenated path — "conformance/round-trip-check" — or
// the check proves nothing. Both traps this capability exists to close need
// a slash AND a hyphen to reproduce: Gitea's dash marker fires on the
// hyphen, GitLab's slug rewrite on the slash.
WritePath string
}
WikiFixture parameterises the wiki checks.