Validate search filesystem context after selection
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Configuration Discovery Review Findings
|
# Configuration Discovery Review Findings
|
||||||
|
|
||||||
Status: five open findings. Implementation has not been changed by these reviews.
|
Status: finding 1 complete; findings 2–5 remain open.
|
||||||
|
|
||||||
Reviewed commit: `e50fee2`, including the discovery implementation commits after
|
Reviewed commit: `e50fee2`, including the discovery implementation commits after
|
||||||
`424c897`. The review compared the implementation with the
|
`424c897`. The review compared the implementation with the
|
||||||
@@ -10,7 +10,13 @@ configuration and architecture contracts.
|
|||||||
|
|
||||||
## 1. Defer Absence-Only Context Requirements Until The Search Outcome Is Known
|
## 1. Defer Absence-Only Context Requirements Until The Search Outcome Is Known
|
||||||
|
|
||||||
Priority: P2. Status: open.
|
Priority: P2. Status: complete.
|
||||||
|
|
||||||
|
**Completed:** search prevalidation now defers absence-only root requirements
|
||||||
|
until selection. Ordinary, layered-main, and underlay regression cases compare
|
||||||
|
searched files with explicit files under an empty context and retain errors for
|
||||||
|
absent inputs. The regression failed before the fix. Focused context/absence
|
||||||
|
tests and `./scripts/check.sh` passed with Go 1.26.10 on Linux.
|
||||||
|
|
||||||
**Location:** [filesystem.go](../../internal/engine/filesystem.go), lines 77–123,
|
**Location:** [filesystem.go](../../internal/engine/filesystem.go), lines 77–123,
|
||||||
and the outcome-specific validation in
|
and the outcome-specific validation in
|
||||||
|
|||||||
@@ -204,6 +204,53 @@ func TestSearchContextCompatibilityUsesSelectedOutcome(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchEmptyContextUsesSelectedOutcome(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
root := t.TempDir()
|
||||||
|
selected := writeImportFile(t, root, "selected.yml", "value: selected\n")
|
||||||
|
missing := filepath.Join(root, "missing.yml")
|
||||||
|
context := &FilesystemContext{}
|
||||||
|
for _, entry := range []struct {
|
||||||
|
name string
|
||||||
|
load func(Source) (*Loaded[discoveryLoadConfig], error)
|
||||||
|
}{
|
||||||
|
{"ordinary", func(source Source) (*Loaded[discoveryLoadConfig], error) {
|
||||||
|
return Load(source, testSecretBinding(), Options[discoveryLoadConfig]{Imports: true, Filesystem: context})
|
||||||
|
}},
|
||||||
|
{"layered main", func(source Source) (*Loaded[discoveryLoadConfig], error) {
|
||||||
|
return LoadLayered(Input{Source: source, Imports: true, Filesystem: context}, testSecretBinding(), LayeredOptions[discoveryLoadConfig]{})
|
||||||
|
}},
|
||||||
|
{"underlay", func(source Source) (*Loaded[discoveryLoadConfig], error) {
|
||||||
|
return LoadLayered(Input{Source: NoSource()}, testSecretBinding(), LayeredOptions[discoveryLoadConfig]{
|
||||||
|
Underlays: []Underlay{{Inputs: []Input{{Source: source, Imports: true, Filesystem: context}}}},
|
||||||
|
})
|
||||||
|
}},
|
||||||
|
} {
|
||||||
|
t.Run(entry.name, func(t *testing.T) {
|
||||||
|
explicit, err := entry.load(File(selected))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
searched, err := entry.load(SearchConfig("", Paths(missing, selected)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertEquivalentDiscoveryLoads(t, searched.Config, searched.Inspection, explicit.Config, explicit.Inspection, Keys("value"))
|
||||||
|
if searched.Config.Value != "selected" {
|
||||||
|
t.Fatalf("selected value = %q", searched.Config.Value)
|
||||||
|
}
|
||||||
|
for _, source := range []Source{NoSource(), SearchConfig("", Paths(missing))} {
|
||||||
|
result, err := entry.load(source)
|
||||||
|
var failure *LoadError
|
||||||
|
if result != nil || !errors.As(err, &failure) || len(failure.Diagnostics()) != 1 || failure.Diagnostics()[0].Code != CodeOptionsInvalid {
|
||||||
|
t.Fatalf("absent input with empty context = %#v, %v", result, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSearchSourceObservesFileChangesBetweenLoads(t *testing.T) {
|
func TestSearchSourceObservesFileChangesBetweenLoads(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,14 @@ func filesystemOptionDiagnostics(source Source, context *FilesystemContext) []Di
|
|||||||
case sourceNone:
|
case sourceNone:
|
||||||
return absentFilesystemOptionDiagnostics(context)
|
return absentFilesystemOptionDiagnostics(context)
|
||||||
case sourceSearch:
|
case sourceSearch:
|
||||||
return absentFilesystemOptionDiagnostics(context)
|
if context.FS != nil {
|
||||||
|
return absentFilesystemOptionDiagnostics(context)
|
||||||
|
}
|
||||||
|
// A selected host file can derive its root; absence requires an
|
||||||
|
// explicit root. Check that compatibility only after selection.
|
||||||
|
if context.BaseDir != "" && !filepath.IsAbs(context.BaseDir) {
|
||||||
|
return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "filesystem base directory must be an absolute host path")}
|
||||||
|
}
|
||||||
case sourceFile:
|
case sourceFile:
|
||||||
if context.FS != nil {
|
if context.FS != nil {
|
||||||
return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "host file sources cannot use a native filesystem context")}
|
return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "host file sources cannot use a native filesystem context")}
|
||||||
|
|||||||
Reference in New Issue
Block a user