backendconformance

package
v0.9.2 Latest Latest
Warning

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

Go to latest
Published: Jul 24, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package backendconformance is a reusable test suite that a config backend adapter runs against its own backend to prove it behaves like a first-class layer.

It is the remote-source counterpart of config/conformance, which does the same job for file codecs. A codec plugs into shared file machinery, so that suite can construct everything from a byte sample. A backend owns its whole interaction with a remote system — reading, versioning, staging, committing, watching — so this suite cannot build one itself. The adapter supplies a factory instead (Suite.NewBackend), plus a Control that stands in for another client of the same system: it changes the backing store out of band and re-opens a backend over it.

The trap this exists to make unmissable is the same one config/conformance guards for files, wearing a remote costume: the conflict fingerprint — here a version — must be captured at Load, not at Prepare. A backend that fetches a fresh version inside Prepare and compares Verify against that compares the intruder's data with itself, so every stale write is accepted and conflict detection silently never fires. The conflict_detected subtest catches it.

An adapter writes one test:

func TestConformance(t *testing.T) {
	backendconformance.Run(t, backendconformance.Suite{
		NewBackend: func(t *testing.T, seed map[string]any) (config.Backend, backendconformance.Control) {
			remote := newFakeRemote(seed) // nil seed => absent source
			return newConsulBackend(remote, "app/"), &control{remote}
		},
		Seed:     map[string]any{"server": map[string]any{"port": 9090}},
		Defines:  map[string]string{"server.port": "9090"},
		WriteKey: "server.port", WriteValue: "8080",
	})
}

It uses the standard library testing package and nothing else — deliberately no testify — so an adapter that runs it takes on no assertion-library dependency it would otherwise avoid.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run(t *testing.T, s Suite)

Run executes the whole suite against s, one named subtest per contract, so a failing adapter sees exactly which one it breaks.

Every backend is checked for per-key merge and precedence with a layer of another format, tolerance of an absent source, and provenance naming the backend. A config.WritableBackend additionally has its write round-trip and — the trap the suite exists for — its refusal of a change that landed between load and commit with config.ErrConflict checked; a read-only backend instead has its layer confirmed skipped by write routing — or, when it is sensitive (a secrets backend), the routed-beneath write confirmed refused with config.ErrSensitiveLeak. A config.WatchableBackend has a foreign change confirmed to reach observers.

Types

type Control

type Control interface {
	// Mutate changes the backing store as another client would: it moves the
	// version the backend's conflict check compares against, and — for a
	// watchable backend — emits the backend's change signal. It must change at
	// least one value visible in the merged config, so a reload is not coalesced
	// away as a no-op.
	Mutate(t *testing.T)

	// Reopen returns a fresh backend over the same backing store its NewBackend
	// call created, so the suite can prove a committed write actually landed
	// there rather than only in the Store's in-memory snapshot.
	Reopen(t *testing.T) config.Backend
}

Control stands in for another client of the same backing store. It is how the suite simulates a foreign change — for the conflict and watch assertions — and how it re-reads the store to prove a committed write reached it.

type Suite

type Suite struct {
	// NewBackend builds a fresh backend over a fresh backing store seeded with
	// the given nested values, plus a [Control] over that same store. A nil seed
	// means an absent source: the backend's Load must report it not there
	// (fs.ErrNotExist) rather than erroring. Each call must be independent — two
	// calls must not share a backing store — except through the [Control], whose
	// Reopen returns a backend over the store its NewBackend call created.
	NewBackend func(t *testing.T, seed map[string]any) (config.Backend, Control)

	// Seed is the nested values the backend is seeded with, and Defines is what
	// they read back through a View. Both are required, with at least one key.
	// Seed carries native types ({"server": {"port": 9090}}); Defines is the
	// string each reads back (GetString), so the suite asserts decode, merge and
	// provenance without knowing the system.
	Seed    map[string]any
	Defines map[string]string

	// WriteKey and WriteValue are a key the backend can set and the value it is
	// set to — required only when NewBackend returns a [config.WritableBackend].
	// The value must read back after the write.
	WriteKey   string
	WriteValue string
}

Suite describes a backend and the minimal knowledge the assertions need. The capability subtests — write and watch — run only when the backend the factory returns satisfies config.WritableBackend or config.WatchableBackend, so a read-only backend runs the read contract and no more.

Jump to

Keyboard shortcuts

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