diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md deleted file mode 100644 index ab29c44..0000000 --- a/docs/roadmap/implementation.md +++ /dev/null @@ -1,642 +0,0 @@ -# CLI and Configuration Contract Test Plan - -This plan adds deliberate contract and risk-based test coverage for -`internal/cli` and `internal/core/config`. It does not pursue a statement -coverage target. Coverage reports are diagnostic input: they may reveal an -important untested branch, but a percentage alone does not justify a test. - -The audience is a relatively small LLM coding agent. Implement one stage per -prompt, in order. Each stage must leave the repository compiling and passing -all tests. - -## Scope and fixed decisions - -Tests added by this plan protect: - -- external contracts documented in `docs/cli.md`, `docs/config.md`, - `docs/operations.md`, and maintained examples; -- internal contracts around configuration loading, precedence, validation, - resolution, cloning, and redaction; -- high-risk CLI composition paths involving run selection, references, LLM - profiles, output, cache, resume, and debug state; and -- production composition and the shortest maintained end-to-end workflows. - -The following decisions are fixed: - -- Do not set or enforce a repository-wide coverage percentage. -- Do not restore deleted test files wholesale. The former suite predates - ADR-0006 and includes obsolete workspace and diagnostics behavior. Historical - tests immediately before commit `26142f0` may be consulted for fixture and - harness ideas only; every restored assertion must be checked against current - code and canonical documentation. -- Prefer behavior tests at the narrowest useful boundary. Pure configuration - rules belong in `internal/core/config`; CLI tests should prove parsing, - composition, process-facing behavior, and cross-component wiring rather than - repeat every configuration permutation. -- Test stable observable facts: exit code, output stream, selected collaborator, - logical artifact, path, or resolved field. Avoid exact whole-error strings, - entire JSON snapshots, timestamps, random identifiers, and internal call - sequences unless those are the contract under test. -- Tests must be deterministic, offline, and secret-free. Inject catalogs, - registries, clocks, run-ID generators, cache collaborators, and fake LLM - clients. Use `t.TempDir`, `t.Setenv`, and `t.Chdir` where appropriate. Never - use a real provider, credential, user cache directory, or shared output root. -- Do not weaken a test to match an apparent defect. If current behavior - conflicts with a canonical contract, stop the stage and report the conflict. - Production fixes are outside this test-coverage plan unless separately - authorized. -- Preserve the focused ADR-0006 state tests already present. Consolidate - helpers only when it materially reduces duplication and does not obscure the - contract being tested. -- Checkpoint wire compatibility is already owned by - `internal/framework/checkpoint` tests. Do not add a second legacy checkpoint - fixture in CLI tests unless a distinct CLI compatibility boundary is found. - -## Execution rules for every stage - -Before editing: - -1. Read `docs/development.md` and follow its task-specific reading guide. -2. Read both files under `docs/policy/`. -3. Read this plan and all current source, tests, and canonical documents named - by the stage. -4. Run `git status --short`; preserve all existing changes. -5. Run the stage's package tests once to establish a baseline. - -While editing: - -- Add tests only for the cases listed in the stage. Do not expand into adjacent - subsystem redesign. -- Use table-driven tests for validation and syntax matrices when the setup and - assertion are genuinely shared. -- Give failures enough context to identify the contract case. -- Assert both the positive outcome and important negative side effects, such as - a root not being resolved or a file not being created. -- Do not run environment- or working-directory-mutating tests in parallel. - -At the end of each stage: - -1. Run the focused tests named by that stage. -2. Run `go test ./...`. -3. Run `go vet ./...`. -4. Run `go build ./cmd/notarius`. -5. Run `git diff --check`. -6. Review the diff for assertions tied to obsolete implementation details. - -Do not mark a stage complete until all checks pass. - -## Stage 1: File schema, defaults, and decoding contracts - -**Status:** Not started - -### Objective - -Protect the version 3 file format and the translation from YAML into the -runtime configuration model. - -### Read first - -- `docs/config.md`, through **Module Bindings** -- `internal/core/config/config.go` -- `internal/core/config/file_config.go` -- `internal/core/config/v3_test.go` - -### Implement - -Add focused tests in `internal/core/config/file_config_contract_test.go`. Reuse -small helper constructors within that file; do not reproduce complete example -files for unit cases. - -Cover these contracts: - -1. `Default` returns the documented concurrency, output, cache-family, debug, - and empty-pipeline defaults. Mutating maps in one returned configuration - must not affect a later `Default` result. -2. A minimal `version: 3` file parses and applies over defaults. -3. Missing, version 2, and unsupported versions fail with actionable version - context before ordinary field decoding. -4. Unknown top-level, pipeline, lane, and module-binding fields are rejected. - Include removed `workspace`, `diagnostics`, and `llm_profiles` fields as - representative compatibility failures, without testing every old field. -5. Shorthand and object module bindings preserve module, profile, retries, - options, references, and the distinction between omitted validators and an - explicit empty validator chain. -6. Pipeline, target-local, and lane compatibility reference maps apply with the - documented precedence, including `extract.references` overriding the - lane-level alias. -7. Chunk, extract, merge, and normalize stage-local validator bindings retain - configured order and fields. -8. Scriptorium source, concurrency, output, chunk-plan cache, checkpoint cache, - and debug sections apply without coupling their roots. -9. Keys that collide after trimming—pipeline IDs, lane IDs, and representative - reference slots—are rejected. Do not enumerate the same normalization rule - at every possible reference location. -10. `LoadFileConfig` reports both missing-file and malformed-YAML context. - -Where `v3_test.go` already proves a case completely, either leave it there or -move it without duplicating it. - -### Validation - -```sh -go test ./internal/core/config -run 'Test(FileConfig|Default|Version3)' -``` - -### Exit criteria - -The maintained version 3 schema, defaults, binding forms, state sections, and -normalization rules can be refactored without silently changing their contract. - -## Stage 2: Precedence and validation matrices - -**Status:** Not started - -### Objective - -Protect configuration precedence and high-risk invalid combinations after all -sources have been applied. - -### Read first - -- `docs/config.md`, especially **Discovery**, **Environment Overrides**, - **Concurrency**, **Module Bindings**, and **State Surfaces** -- `internal/core/config/env.go` -- `internal/core/config/validation.go` -- current tests from Stage 1 - -### Implement - -Add `internal/core/config/env_contract_test.go` and -`internal/core/config/validation_contract_test.go`. - -Precedence tests must prove: - -1. File values override built-in defaults. -2. Every current `NOTARIUS_*` operational variable overrides the corresponding - file value: total LLM concurrency, extract workers, output directory, - chunk-plan mode and directory, checkpoint directory, and debug directory. -3. Extract workers default to the final effective total when not explicitly - configured, but an explicit file or environment worker value is retained. -4. Empty cache-directory fields in a file remain valid and select deferred - per-user defaults; empty directory environment overrides are errors. -5. Invalid integers and chunk-cache modes report the responsible environment - variable and do not panic. -6. Removed provider environment variables are ignored. Do not place credential - values in assertions or failure output. - -Validation tables must cover one representative case for each rule family: - -- non-positive total concurrency; extract worker below one, above total, and at - both valid boundaries; unknown and blank worker keys; -- mutually exclusive Scriptorium profile sources; -- blank required output/debug roots, NUL in every physical root family, and an - invalid chunk-plan mode; -- empty and trim-duplicated pipeline/lane/reference identifiers; -- negative retries and whitespace-only explicit LLM profiles; -- references on unsupported input/output bindings; -- invalid validator bindings: empty module, retries, references, nested - validators, and validator chains on unsupported stages; and -- deprecated non-empty lane-level validators rejected while stage-local omitted, - empty, and non-empty overrides remain valid structurally. - -Assert stable contextual fragments rather than complete error text. - -### Validation - -```sh -go test ./internal/core/config -run 'Test(Env|Precedence|Validate)' -``` - -### Exit criteria - -Every documented precedence edge and every materially different validation -rule family has a direct, readable regression test. - -## Stage 3: Effective configuration, cloning, and redaction - -**Status:** Not started - -### Objective - -Protect the internal contract that validated configuration resolves into an -isolated, auditable pipeline without leaking sensitive options. - -### Read first - -- `docs/policy/architecture.md`, especially **Pipeline Composition And - Ownership**, **Configuration And Provenance**, and **State, Output, And - Safety** -- `internal/core/config/effective_config.go` -- `internal/core/config/redaction.go` -- `internal/core/config/redaction_test.go` -- `internal/framework/pipeline/profile.go` - -### Implement - -Add `internal/core/config/effective_config_contract_test.go` and extend -`redaction_test.go` only for missing cases. - -Use a minimal fake `pipeline.ModuleCatalog` with enough variants and -capabilities to make each resolution outcome explicit. Cover: - -1. Empty and unknown pipeline IDs fail; a map key with harmless surrounding - whitespace resolves to its normalized ID. -2. `Only` selects exactly the requested lanes, rejects unknown lanes, and does - not mutate the source configuration. -3. Default chunk, merge, normalize, and output modules are materialized through - the catalog. -4. Unknown modules, missing capabilities, missing artifact variants, invalid - module options, and invalid validator options retain pipeline/lane/stage - context. -5. An LLM-profile override applies to LLM-capable pipeline module bindings - before digest calculation, does not override validator-specific profiles, - and changes the digest when effective behavior changes. -6. Omitted, explicitly empty, and configured validator chains resolve - distinctly and preserve configured order. -7. Returned `EffectiveConfig`, resolved bindings, nested options, reference - slices/content, and validator chains do not alias the input configuration or - resolution inputs. -8. Both redacted summary payloads cover every binding and recursively redact - sensitive keys in untyped and typed/aliased option containers while - preserving safe neighbors and excluding materialized reference content. - -Do not duplicate detailed reference-selector behavior owned by Stage 6. - -### Validation - -```sh -go test ./internal/core/config -run 'Test(Resolve|Effective|Redacted)' -``` - -### Exit criteria - -Resolution defaults, selection, compatibility failures, profile overrides, -digests, deep-copy isolation, and redaction are directly protected. - -## Stage 4: CLI command and configuration-loading contracts - -**Status:** Not started - -### Objective - -Protect the public command surface, exit-code classification, configuration -discovery, validation command, and pipeline listing. - -### Read first - -- `docs/cli.md` -- `docs/config.md#discovery` -- command dispatch, `runConfigValidate`, `runPipelinesList`, and configuration - loading in `internal/cli/run.go` -- `internal/cli/state_surfaces_test.go` - -### Implement - -Add `internal/cli/command_contract_test.go`. Use injected options and temporary -files; no case may require production credentials or provider access. - -Cover: - -1. No arguments and each documented help spelling write usage to stdout and - return `0`. -2. Unknown commands/subcommands and malformed command syntax write to stderr - and return `2`. -3. Configuration discovery precedence is explicit `--config`, then non-empty - `NOTARIUS_CONFIG`, then the compiled default path. Test the first two with - temporary files. For the compiled path, inspect its host state first: assert - selection when it is a regular file, or the documented not-found error when - it is absent. Never create, replace, or remove the system path. -4. Missing and malformed configuration files return `1` with actionable path - or parse context. -5. `config validate` succeeds for a valid file, resolves a requested pipeline - against an injected catalog, rejects an unknown pipeline/lane, requires - `--pipeline` with `--only`, and returns `2` for malformed `--only` syntax. -6. `pipelines list` sorts normalized IDs in text and JSON forms; JSON is decoded - and compared structurally rather than as raw formatting. -7. Removed structural/state flags such as `--diagnostics-dir` are rejected with - exit `2` instead of being silently ignored. -8. Representative malformed run flags and valid-runtime failures establish the - documented `2` versus `1` boundary. - -### Validation - -```sh -go test ./internal/cli -run 'Test(Command|Help|ConfigDiscovery|ConfigValidate|PipelinesList|ExitCode)' -``` - -### Exit criteria - -The documented command grammar, streams, discovery order, listing behavior, -and exit-code classes are protected without invoking a real pipeline. - -## Stage 5: Run controls, profile selection, and process-facing results - -**Status:** Not started - -### Objective - -Protect the public run controls and the CLI-to-pipeline composition boundary. - -### Read first - -- `docs/cli.md#run` -- `internal/cli/run.go` -- `internal/cli/scriptorium_profiles.go` -- `internal/cli/state_hardening_test.go` and its fake harness - -### Implement - -Add `internal/cli/run_contract_test.go`. Reuse or minimally generalize the -existing deterministic state-test harness. Keep helper implementations at the -bottom of a test file or in one clearly named `test_helpers_test.go` file. - -Cover: - -1. Missing pipeline ID, missing `--input`, unknown flags, blank values, and - multiple positional IDs return `2` without allocating output or debug state. -2. Unknown pipeline/lane and unreadable input are valid invocations that return - `1`; when debug is requested after allocation, its path is reported. -3. A successful run writes the logical output files, reports normalized and - rejected counts, and passes the same deterministic run ID and start time to - the manifest and debug bundle. -4. `--only` executes and reports only selected lanes. -5. `--output-dir` and `--debug-dir` override environment, file, and default - roots; `--debug-dir` without `--debug` returns `2` and creates nothing. -6. `--llm-profile` reaches every effective LLM-capable pipeline module binding, - leaves validator-specific profiles unchanged, selects the expected factory - profile when exactly one is effective, and validates explicit Scriptorium - profile IDs without making provider calls. -7. `effectiveLLMProfileIDs` ignores deterministic stages and returns stable, - deduplicated ordering across chunk, lane stages, and LLM-backed validators. -8. `--session-id` rejects missing/blank values and passes a trimmed explicit - value through prompt-facing requests. With no override, the parsed source - document ID is used as the session identifier. -9. LLM factory, preparation, pipeline execution, output persistence, and debug - persistence failures return `1`, preserve the primary error, and do not print - a success message. -10. Successful warnings remain exit `0`, are counted on stderr, and appear in - durable output and requested debug summary. - -Do not repeat the output-collision and terminalization matrices already covered -by `run_id_test.go` and `state_hardening_test.go`. - -### Validation - -```sh -go test ./internal/cli -run 'TestRun' -``` - -### Exit criteria - -Every public non-reference run flag and every major CLI-to-runner handoff has a -contract test, including representative failures. - -## Stage 6: CLI reference selector and override contracts - -**Status:** Not started - -### Objective - -Protect the high-risk selector grammar and target-resolution behavior without -duplicating reference materialization internals. - -### Read first - -- reference sections of `docs/cli.md` and `docs/config.md` -- reference parsing and resolution helpers in `internal/cli/run.go` -- reference resolution/materialization tests under `internal/framework/pipeline` - -### Implement - -Add `internal/cli/reference_contract_test.go`. Build a compact catalog with -chunk, extractor, merger, and normalizer specs declaring distinct, shared, -optional, and required slots, including at least two artifact kinds where -variant lookup matters. - -Cover: - -1. Parse and apply every documented selector form: flat, `chunk.slot`, - `merge.slot`, `lane.slot`, and explicit lane extract/merge/normalize. -2. Flat and lane selectors succeed only with one eligible selected target. - Ambiguity errors identify viable explicit selectors. -3. Selected lanes constrain reference discovery; selectors for unselected or - unknown lanes fail before reference file materialization. -4. Malformed bindings—missing selector, path, separator, or excess selector - segments—and malformed unbind selectors return `2`. -5. Repeated overrides use the final explicit binding for that exact target; - target-specific overrides do not alter same-named slots on other targets. -6. `--without-reference` removes optional configured bindings and fails when a - required chunk, extract, merge, or normalize slot would remain unbound. -7. CLI paths resolve from the working directory while config paths resolve from - the config file directory. Assert recorded provenance, not private helper - call order. -8. Target lookup uses the selected artifact-kind variant and returns useful - context when the requested merger/normalizer/extractor variant is absent. - -Use small UTF-8 temporary reference files. Media-type and malformed-content -details already proven by pipeline materialization tests need only one CLI -smoke case. - -### Validation - -```sh -go test ./internal/cli -run 'Test.*Reference' -``` - -### Exit criteria - -All public selector forms, ambiguity boundaries, lane selection, binding -precedence, unbinding, and path-origin rules are protected. - -## Stage 7: Cache, resume, output, and debug integration risks - -**Status:** Not started - -### Objective - -Fill state-integration gaps without duplicating the focused ADR-0005 and -ADR-0006 tests already present. - -### Read first - -- `docs/operations.md` -- `docs/internal/state.md` -- ADR-0005 and ADR-0006 -- `internal/cli/run_id_test.go` -- `internal/cli/state_surfaces_test.go` -- `internal/cli/state_hardening_test.go` -- tests under `internal/framework/chunkplan` and - `internal/framework/checkpoint` - -### Implement - -Add `internal/cli/cache_contract_test.go` only for uncovered CLI composition -contracts. Extend an existing state test when it already owns the behavior. - -Cover or confirm existing coverage for: - -1. Chunk-plan mode precedence: CLI flag, environment, file, default. - Invalid CLI syntax returns `2`; invalid environment/file modes return `1`. -2. An explicit chunk-plan root is passed through exactly. An empty configured - root selects `/notarius/chunk-plans`. `bypass` never calls - `UserCacheDir`, constructs a store, or creates a root. -3. Root-resolution and store-construction failures preserve context and create - no durable output. -4. `auto` reuses a plan across independent invocations with the same source - digest even when pipeline, chunk module settings, references, or selected - lanes differ. `refresh` replaces only after valid chunking; `bypass` does no - cache I/O. Reuse details below the CLI remain owned by chunk-plan package - tests. -5. `--resume` alone constructs checkpoint loader and recorder under the exact - configured or per-user checkpoint root. Without it, neither root resolution - nor checkpoint I/O occurs. -6. Chunk-plan reuse and checkpoint resume remain independent across the - relevant mode matrix. Reuse a compact table rather than duplicating every - existing state-surface assertion. -7. Existing tests continue to prove exclusive output allocation, atomic files, - retained new partial output, shared run identity, debug opt-in, restrictive - debug permissions, redacted summaries, and exactly-once terminal reports. - Add a case only if one of these facts is not directly asserted. -8. Config-oriented commands do not resolve or create output, cache, or debug - state. - -Do not introduce automatic cleanup tests: Notarius intentionally retains output -and requested debug bundles. - -### Validation - -```sh -go test ./internal/cli -run 'Test.*(Cache|ChunkPlan|Checkpoint|Resume|State|Output|Debug|RunID)' -go test ./internal/framework/chunkplan ./internal/framework/checkpoint -``` - -### Exit criteria - -CLI state-root selection, opt-in I/O, cache mode precedence, cross-run reuse, -and cache-family independence are covered at their composition boundary. - -## Stage 8: Production composition and maintained examples - -**Status:** Not started - -### Objective - -Protect the small number of production and end-to-end contracts most likely to -break while unit tests continue to pass. - -### Read first - -- `README.md` -- maintained files under `examples/` -- `docs/config.md#implemented-production-modules` -- `internal/cli/catalog.go` -- `internal/modules/integration` tests and helpers - -### Implement - -Add `internal/cli/production_contract_test.go` and -`internal/cli/example_contract_test.go`. Reuse production registries and prompt -assets, but always inject a deterministic structured fake LLM client. - -Production composition tests must prove: - -1. The production catalog exposes every module, artifact codec, validator, - default validator chain, and prompt asset named by current configuration and - maintained examples. -2. Catalog/registry conversion preserves typed artifact codec variants and - validator-chain registrations. -3. Production prompt assets allow the configured D&D scene chunker and spell - extractor to prepare without reading provider credentials. -4. `config validate --pipeline` accepts the maintained production module graph - and rejects representative unknown modules, unknown validators, invalid - artifact variants, and deterministic validators with LLM profiles. -5. A small test configuration selecting `dnd/scenes` records that chunker and - its expected warning/provenance fields during a fake-LLM run. - -Example tests must prove: - -1. Every maintained example configuration parses, validates, resolves its - documented pipeline, and appears correctly in `pipelines list`. -2. The README/minimal example invocation runs with a fake LLM and produces the - documented logical JSON bundle. Decode manifest, index, warning, rejection, - and artifact files and assert contract fields; do not snapshot whitespace or - incidental timestamps. -3. The documented `--only spells` invocation selects exactly the `spells` lane. -4. One malformed input fixture returns `1`, produces no successful output - bundle, and records a failure report only when debug is explicitly enabled. - -If an example requires a provider profile, supply a test-only fake through -`cli.Options`; never modify the example to embed credentials. - -### Validation - -```sh -go test ./internal/cli -run 'Test(Production|Example|Maintained)' -go test ./internal/modules/integration -``` - -### Exit criteria - -Production registration, prompts, maintained configuration, and the shortest -end-to-end user workflow are protected by offline tests. - -## Stage 9: Coverage audit and plan closure - -**Status:** Not started - -### Objective - -Confirm that contract and high-risk coverage is complete without converting -coverage percentage into a goal. - -### Read first - -- all tests added by Stages 1–8 -- current canonical CLI, configuration, operations, integration, and internal - state documents -- `docs/policy/documentation.md` - -### Implement - -1. Build a temporary checklist mapping every current CLI command/flag and every - current configuration section/source to at least one owning test. Do not - commit the checklist if the test names themselves make ownership clear. -2. Run statement coverage for `internal/cli` and `internal/core/config`. Inspect - uncovered functions and branches. Add a test only when the uncovered code is - a documented contract, safety boundary, destructive/stateful path, complex - selector/precedence branch, or failure path likely to regress. -3. Remove redundant cases that prove no additional contract or risk. Keep a - small end-to-end layer and more numerous narrow unit tests. -4. Confirm tests do not access the network, depend on host credentials, use the - real user cache directory, write outside temporary roots, or depend on test - ordering. -5. Run race-enabled tests for the two changed packages. Fix test races; report - production races separately unless authorized to change production code. -6. Update `docs/development.md` only if a stable testing procedure beyond its - existing validation commands is now necessary. Do not document a coverage - threshold. -7. Once all checks pass, delete this completed implementation plan. The tests - and canonical current-behavior documents remain authoritative. - -### Validation - -```sh -go test -coverprofile=/tmp/notarius-cli.cover ./internal/cli -go test -coverprofile=/tmp/notarius-config.cover ./internal/core/config -go tool cover -func=/tmp/notarius-cli.cover -go tool cover -func=/tmp/notarius-config.cover -go test -race ./internal/cli ./internal/core/config -go test ./... -go vet ./... -go build ./cmd/notarius -git diff --check -``` - -The `/tmp` coverage profiles are inspection artifacts and must not be committed. - -### Exit criteria - -Every current CLI and configuration contract has an identifiable owning test; -high-risk state, safety, precedence, selector, and failure paths are covered; -the suite remains deterministic and maintainable; and no numeric coverage target -has been introduced. diff --git a/internal/cli/production_contract_test.go b/internal/cli/production_contract_test.go index 1c7d1a3..90cd858 100644 --- a/internal/cli/production_contract_test.go +++ b/internal/cli/production_contract_test.go @@ -111,6 +111,31 @@ func TestProductionCatalogCoversMaintainedConfigurations(t *testing.T) { } } +func TestDefaultCLICompositionResolvesMaintainedConfigurations(t *testing.T) { + catalog, err := effectiveCatalog(Options{}) + if err != nil { + t.Fatalf("resolve default catalog: %v", err) + } + if isEmptyCatalog(catalog) { + t.Fatal("default catalog is empty") + } + registries, err := effectiveRegistries(Options{}) + if err != nil { + t.Fatalf("resolve default registries: %v", err) + } + if isEmptyRegistries(registries) { + t.Fatal("default registries are empty") + } + + for _, example := range maintainedExampleFiles(t) { + var stdout, stderr strings.Builder + code := RunWithOptions([]string{"config", "validate", "--config", example.path, "--pipeline", "dnd-session"}, &stdout, &stderr, Options{LookupEnv: emptyLookup}) + if code != 0 || stderr.Len() != 0 { + t.Fatalf("validate maintained %s config with defaults: code=%d stdout=%q stderr=%q", example.name, code, stdout.String(), stderr.String()) + } + } +} + func TestProductionPromptAssetsPrepareWithoutProviderCredentials(t *testing.T) { components := productionTestComponents(t) cfg := config.Default() diff --git a/internal/core/config/env_contract_test.go b/internal/core/config/env_contract_test.go index def39fa..6d55ff1 100644 --- a/internal/core/config/env_contract_test.go +++ b/internal/core/config/env_contract_test.go @@ -1,6 +1,7 @@ package config import ( + "errors" "reflect" "strings" "testing" @@ -144,6 +145,42 @@ cache: } } +func TestDefaultCacheRootsRejectInvalidUserCacheResolvers(t *testing.T) { + tests := []struct { + name string + resolver func() (string, error) + want string + }{ + {name: "nil resolver", want: "must not be nil"}, + { + name: "resolver failure", + resolver: func() (string, error) { + return "", errors.New("cache home unavailable") + }, + want: "resolve user cache directory", + }, + {name: "empty directory", resolver: func() (string, error) { return " ", nil }, want: "must not be empty"}, + } + families := []struct { + name string + root func(func() (string, error)) (string, error) + }{ + {name: "chunk plans", root: DefaultChunkPlanRoot}, + {name: "checkpoints", root: DefaultCheckpointRoot}, + } + + for _, family := range families { + for _, tt := range tests { + t.Run(family.name+"/"+tt.name, func(t *testing.T) { + _, err := family.root(tt.resolver) + if err == nil || !strings.Contains(err.Error(), tt.want) { + t.Fatalf("error = %v, want substring %q", err, tt.want) + } + }) + } + } +} + func TestEnvEmptyDirectoryOverridesAreErrors(t *testing.T) { tests := []string{ "NOTARIUS_OUTPUT_DIR",