Documentation
¶
Overview ¶
Package fetchtest builds fetch fact records for tests. Import it from any test in the module that needs a FactRecord; it is a normal package rather than a _test one for the same reason internal/cli/testfakes is.
A test should say what is interesting about the record it needs and nothing else. Record covers the valid case and seals the record itself, so no test calls CanonicalHasher.SetContentHash by hand; Unsealed and Tampered cover the two ways a record can be invalid, so a test that exists to prove an invalid record is rejected keeps proving it. Reaching for Record where Unsealed or Tampered is meant leaves such a test green while it tests nothing — the builder cannot detect that, so choose deliberately.
The records produced here are byte-for-byte what the fetch pipeline persists: the defaults, the go.mod-only shape and the module-hash serialisation all follow domain.NewFactRecord rather than approximating it.
Index ¶
- Constants
- func AssertComposesAcrossPipelineVersions(t testing.TB, s ComposedStore)
- func AssertRefusesUnsealed(t testing.TB, w RecordWriter)
- func AssertRefusesZeroIdentity(t testing.TB, name string, op func() error)
- func Blob(kind ports.BlobKind, hash domain.ModuleHash) ports.BlobIdentity
- func ComposeCoordinate(coord coordinate.ModuleCoordinate, records []domain.FactRecord) (domain.CompositeRecord, bool, error)
- func Composite(t testing.TB, opts ...Option) domain.CompositeRecord
- func GoModArtefact(value string) domain.ArtefactIdentity
- func GoModBlob(t testing.TB, content string) ports.BlobIdentity
- func GoModIdentity(t testing.TB, r domain.FactRecord) ports.BlobIdentity
- func H1(value string) domain.ModuleHash
- func Hash(algorithm, value string) domain.ModuleHash
- func ModuleZip(t testing.TB, path, version string, files map[string]string) ([]byte, ports.BlobIdentity)
- func Record(t testing.TB, opts ...Option) domain.FactRecord
- func Respelt(t testing.TB, r domain.FactRecord) (fetchedAt, seal string)
- func Sealed(t testing.TB, opts ...Option) domain.SealedRecord
- func Tampered(t testing.TB, opts ...Option) domain.FactRecord
- func Unsealed(t testing.TB, opts ...Option) domain.FactRecord
- func ZipArtefact(value string) domain.ArtefactIdentity
- func ZipIdentity(t testing.TB, r domain.FactRecord) ports.BlobIdentity
- type ComposedStore
- type Option
- func AcquisitionMode(m domain.AcquisitionMode) Option
- func Content(handle string) Option
- func Coordinate(c coordinate.ModuleCoordinate) Option
- func Detail(d string) Option
- func Digests(d domain.ArtifactDigests) Option
- func Ecosystem(e string) Option
- func FetchedAt(at time.Time) Option
- func GitReference(g domain.GitReference) Option
- func GoMod(handle string) Option
- func GoModHash(h domain.ModuleHash) Option
- func GoModOnly(goModHandle string) Option
- func MeasurementKind(k domain.MeasurementKind) Option
- func Module(path, version string) Option
- func ModuleHash(h domain.ModuleHash) Option
- func Path(path string) Option
- func PipelineVersion(v string) Option
- func Retracted(retracted bool) Option
- func SchemaVersion(v string) Option
- func Status(s domain.VerificationStatus) Option
- func SumDBCheck(p domain.LegProvenance, source string) Option
- func SumDBLookupFailed(failed bool) Option
- func VCSCheck(p domain.LegProvenance, source string) Option
- func VCSURLBinding(b domain.VCSURLBinding) Option
- func VersionTime(at time.Time) Option
- type RecordWriter
Constants ¶
const TamperDetail = "tampered after sealing"
TamperDetail is the verification detail Tampered writes after sealing. It is the mutated field because it is covered by the canonical hash yet is part of neither the store key nor any status assertion, so tampering is visible to the integrity check without disturbing how the record is looked up.
Variables ¶
This section is empty.
Functions ¶
func AssertComposesAcrossPipelineVersions ¶
func AssertComposesAcrossPipelineVersions(t testing.TB, s ComposedStore)
AssertComposesAcrossPipelineVersions is the ports.FactRecordComposer contract test, and it pins both halves of the defect the capability was added for.
It seeds one coordinate with two measurements at two different FETCH pipeline versions — an older one whose checksum-database lookup answered, and a newer one whose lookup failed — and asserts three things:
- the coordinate is found at all, though neither measurement sits at the pipeline version a reader would have guessed. A version-keyed read reports absence here, which is how a third of a real store became unextractable: the stage asked at the version it knew and the ledger answered "nothing", while the artefact sat in the blob store the whole time.
- BOTH measurements reach the composer. Filtering before composing hides from the composer exactly the records it exists to rank.
- the record served is the older, STRONGER one. A first-hit-wins fallback list returns whichever version it happened to name first, so a failed lookup appended after a good one becomes the answer — the defect domain.Compose's own doc says it was written to prevent. This is asserted against the served record's content hash, not against a version string.
Every implementation should call it, fakes included: a fake that answers a coordinate-only read by consulting one pipeline version lets an application-layer test go green on a read the real store answers differently.
func AssertRefusesUnsealed ¶
func AssertRefusesUnsealed(t testing.TB, w RecordWriter)
AssertRefusesUnsealed is the ports.FactStore contract test for the one value the PutFetchRecord signature cannot exclude.
SealedRecord is meant to be self-evidencing: Seal and Rehydrate are the only ways to make one, so holding one proves the record was hashed. The exported struct leaves a gap — domain.SealedRecord{} compiles in any package and seals nothing — and an implementation that stores it appends an all-empty row that every later read treats as a genuine measurement of the empty module at the empty version.
It lives here so the rule has one definition rather than one per store. Every FactStore implementation should call it, fakes included: a fake that accepts an unsealed record lets an application-layer test go green on a write the real store rejects, which is the failure this whole guard exists to prevent.
It asserts the refusal only. Whether the refusal also left the store untouched needs a reader, so that leg stays with the implementation that has one.
func AssertRefusesZeroIdentity ¶
AssertRefusesZeroIdentity asserts that op refuses the zero artefact identity with domain.ErrZeroIdentity.
It exists for the same reason AssertRefusesUnsealed and coordinatetest.AssertRefusesZeroCoordinate do: the rule needs one definition rather than one per store. Unexporting ArtefactIdentity's fields makes a hand-built identity impossible, but Go always permits the zero value, and the zero identity names no artefact at all — it would key a row on the empty hash on a write, and on a read it asks about nothing, to which absence is the wrong answer.
Every implementation that takes an identity should call it, fakes included: a fake that accepts the zero identity lets an application-layer test go green on a call the real store rejects.
It asserts the refusal only. Whether the refusal also left the store untouched needs a reader, so that leg stays with the implementation that has one.
func Blob ¶
func Blob(kind ports.BlobKind, hash domain.ModuleHash) ports.BlobIdentity
Blob is the blob identity of one artefact of a module version, for the tests that key a fake blob store directly rather than deriving the address from a record. A test that has a record should use ZipIdentity or GoModIdentity, which is the route production takes.
It goes through ports.NewBlobIdentity, so a fixture identity is one the constructor accepts, and it panics rather than taking a testing.TB for the reason H1 does — identities are built in table cases and package-level vars where no testing handle is in scope.
func ComposeCoordinate ¶
func ComposeCoordinate(coord coordinate.ModuleCoordinate, records []domain.FactRecord) (domain.CompositeRecord, bool, error)
ComposeCoordinate is the composed read a fact-store fake owes, factored out so each fake answers it the way the real store does rather than approximating it. It selects the measurements describing coord — whatever pipeline version filed them — orders them as an append-only ledger would, and folds them exactly as the sqlite store's read does, divergence guard included. The bool is false when the ledger holds nothing about the coordinate.
Records are ordered by measurement time, then pipeline version, then content hash. A fake holds its records in a map, and map iteration order is random, so without a total order a fake would compose a differently-ordered slice on every run — which matters for a local coordinate, where Compose reads the ORDER as the sequence of observations rather than as competing claims.
func Composite ¶
func Composite(t testing.TB, opts ...Option) domain.CompositeRecord
Composite returns the composed view of a single measurement — what a reader gets back from the store for an artefact measured once. It is what a test wants wherever production now hands out a CompositeRecord and the test's subject is the record's contents rather than the composition itself.
func GoModArtefact ¶
func GoModArtefact(value string) domain.ArtefactIdentity
GoModArtefact is the artefact identity of a go.mod-only measurement whose go.mod has the h1 hash of value. See ZipArtefact.
func GoModBlob ¶
func GoModBlob(t testing.TB, content string) ports.BlobIdentity
GoModBlob returns the blob identity a standalone go.mod holding content hashes to.
func GoModIdentity ¶
func GoModIdentity(t testing.TB, r domain.FactRecord) ports.BlobIdentity
GoModIdentity returns the blob identity of the record's standalone go.mod, failing the test when the record carries no go.mod hash.
func H1 ¶
func H1(value string) domain.ModuleHash
H1 is the h1 module hash of value, the only algorithm current Go toolchains produce. It exists so callers pass a hash rather than a pre-formatted string and the ":" separator stays the domain's business.
It panics on a value the domain refuses, and takes no testing.TB in order to do so: hashes are built in table-driven cases and package-level vars where no testing handle is in scope, on the same terms as coordinatetest.MustNew. A panic here is a bad fixture, reported with a stack trace pointing at it.
func Hash ¶
func Hash(algorithm, value string) domain.ModuleHash
Hash is the module hash of value under algorithm, for the tests whose subject is an artefact hashed by something other than h1 — a stdlib source tarball addressed by its SHA-256, say. It panics on a hash the domain refuses; see H1 for why it takes no testing.TB.
func ModuleZip ¶
func ModuleZip(t testing.TB, path, version string, files map[string]string) ([]byte, ports.BlobIdentity)
ModuleZip returns the bytes of a module zip holding files, named relative to the module root, and the blob identity those bytes hash to. A blob store verifies what it serves, so a fixture it is to serve has to be one.
func Record ¶
func Record(t testing.TB, opts ...Option) domain.FactRecord
Record returns a valid, sealed fact record: schema version and ecosystem are set, every option has been applied, and ContentHash covers the result. It is what a test wants whenever the record is a fixture rather than the subject.
func Respelt ¶
func Respelt(t testing.TB, r domain.FactRecord) (fetchedAt, seal string)
Respelt returns the row a build spelling fetched_at as a trimmed fraction would have stored for r, a whole-second record: the stored text, and the seal taken over it. Its stored values hash to that seal, and this build, which spells the parsed time at full width, cannot reproduce it.
func Sealed ¶
func Sealed(t testing.TB, opts ...Option) domain.SealedRecord
Sealed returns the same record as Record, wrapped in the SealedRecord the fact store accepts for writing. It is the counterpart the append-only ledger needs: PutFetchRecord takes only a sealed record, so a test that seeds a store goes through here rather than hand-rolling the seal.
It takes testing.TB, not *testing.T, so the walk fakes and the vulnerability benchmark — neither of which has a *testing.T — can seed records too.
func Tampered ¶
func Tampered(t testing.TB, opts ...Option) domain.FactRecord
Tampered returns a sealed record whose body was altered afterwards, so its stored content hash no longer recomputes. Use it for tests that prove a mutated record is rejected; Record would return a self-consistent record and the test would pass while testing nothing.
func Unsealed ¶
func Unsealed(t testing.TB, opts ...Option) domain.FactRecord
Unsealed returns a record with no content hash at all. Use it for tests that prove an unsealed record is rejected — Record would seal it and the test would pass without exercising the guard.
func ZipArtefact ¶
func ZipArtefact(value string) domain.ArtefactIdentity
ZipArtefact is the artefact identity of a module zip with the h1 hash of value, and GoModArtefact the identity of a go.mod-only measurement. They are for the tests whose subject is an identity rather than the record carrying one; a test that has a record should derive the identity from it with domain.ArtefactIdentityOf, which is what production does.
Both go through domain.ParseArtefactIdentity, so a fixture identity is one the reader can read — a hand-built value object could claim a shape the parser rejects and no test would notice.
func ZipIdentity ¶
func ZipIdentity(t testing.TB, r domain.FactRecord) ports.BlobIdentity
ZipIdentity returns the blob identity of the record's module zip, failing the test when the record describes no zip. Tests that seed a blob store must key the bytes by identity, because that is how production asks for them.
Types ¶
type ComposedStore ¶
type ComposedStore interface {
PutFetchRecord(ctx context.Context, record domain.SealedRecord) error
ComposeFetchRecord(ctx context.Context, coord coordinate.ModuleCoordinate) (domain.CompositeRecord, bool, error)
}
ComposedStore is the write half of ports.FactStore plus the coordinate-only read of ports.FactRecordComposer. The assertion below takes this rather than the full store so a fake that has no version-keyed read is still held to the composed one.
type Option ¶
type Option func(*domain.FactRecord)
Option configures the record a builder produces. Options are applied in the order given, before the record is sealed.
func AcquisitionMode ¶
func AcquisitionMode(m domain.AcquisitionMode) Option
AcquisitionMode sets the path the module's bytes arrived by, which is what makes the content handle resolvable.
func Content ¶
Content records where a measurement put the module zip, and gives the record a module hash to be addressed by when it has none.
The location is provenance only — production stores an artefact under the identity it measured, and a store either holds that identity or does not — so a record carrying a content location must also carry the zip hash that identifies it. Deriving one here from the handle keeps that invariant without every caller having to state a hash it does not care about; a caller that does care sets ModuleHash explicitly, and this leaves it alone.
func Coordinate ¶
func Coordinate(c coordinate.ModuleCoordinate) Option
Coordinate sets the module path and version.
func Digests ¶
func Digests(d domain.ArtifactDigests) Option
Digests sets the raw artefact digests of the module zip.
func Ecosystem ¶
Ecosystem overrides the ecosystem. It is for tests that prove a foreign ecosystem is rejected; kanonarion only ever writes domain.EcosystemGo.
func GitReference ¶
func GitReference(g domain.GitReference) Option
GitReference sets the VCS provenance the record was cross-verified against.
func GoMod ¶
GoMod records where a measurement put the standalone go.mod, and gives the record a go.mod hash to be addressed by when it has none. See Content.
func GoModHash ¶
func GoModHash(h domain.ModuleHash) Option
GoModHash sets the h1 hash of the module's go.mod.
func GoModOnly ¶
GoModOnly shapes the record as the go.mod-only acquisition path produces it: the go.mod is stored while the zip was never fetched, so ContentLocation is empty and no module hash was computed.
The absent module hash is written as ":" rather than "": domain.NewFactRecord stores ModuleHash.String(), which is Algorithm + ":" + Value, so a zero hash serialises as the bare separator. Records already in the store carry that value, and absence is tested with ModuleHash.IsZero(), so writing "" here would make the fixture diverge from both production and the persisted data.
func MeasurementKind ¶
func MeasurementKind(k domain.MeasurementKind) Option
MeasurementKind sets what the measurement did — acquired or revalidated.
func ModuleHash ¶
func ModuleHash(h domain.ModuleHash) Option
ModuleHash sets the h1 hash of the module zip.
func Path ¶
Path sets the module path, leaving the version as it is. It is for the tests whose subject is the path alone.
func PipelineVersion ¶
PipelineVersion sets the pipeline version the record is filed under.
func SchemaVersion ¶
SchemaVersion overrides the schema version. It is for tests whose subject is the schema field itself; every other record should carry the current version the default supplies.
func SumDBCheck ¶
func SumDBCheck(p domain.LegProvenance, source string) Option
SumDBCheck sets how the measurement came by its checksum-database leg, and source names the record it was inherited from (empty when rechecked).
func SumDBLookupFailed ¶
SumDBLookupFailed marks the record's checksum-database lookup as having failed rather than answered, which is what makes it ineligible as a cache hit.
func VCSCheck ¶
func VCSCheck(p domain.LegProvenance, source string) Option
VCSCheck sets how the measurement came by its VCS cross-verification leg, and source names the record it was inherited from (empty when rechecked). Leaving it unset is how a --skip-vcs run is represented: the leg is absent, which is a different claim from a check that ran and could not confirm.
func VCSURLBinding ¶
func VCSURLBinding(b domain.VCSURLBinding) Option
VCSURLBinding sets which clone URL the VCS cross-verification leg put to the test: one derived from the module path, or one the untrusted proxy named. Leaving it unset is how a record written before this was measured is represented — the record does not say, which is not a negative finding.
func VersionTime ¶
VersionTime sets the time the module source reported for the version. Leaving it unset is how a record whose source gave none, or one written before the field, is represented.
type RecordWriter ¶
type RecordWriter interface {
PutFetchRecord(ctx context.Context, record domain.SealedRecord) error
}
RecordWriter is the write half of ports.FactStore. The contract assertions below take this rather than the full interface so a fake that only writes — and the audit decorator, which is not a whole store either — can be checked without growing a read path it does not have.