Project version protections through item placement
This commit is contained in:
@@ -193,6 +193,43 @@ func TestLayeredMountedVersionProtectionCoversInheritanceAndUpperTiers(t *testin
|
||||
})
|
||||
}
|
||||
|
||||
func TestLayeredItemVersionProtectionTracksAssignedPeerIndexes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
definition := Definition[map[string]any]{
|
||||
Version: &VersionRule{Path: Keys("schema", "version"), Check: func(Version, *Validation) error { return nil }},
|
||||
sequenceRegistrations: []sequenceRegistration{{path: Keys("catalog", "entries"), identityField: "id"}},
|
||||
}
|
||||
item := func(label string, value map[string]any) Input {
|
||||
return Input{Source: Mapping(label, value), Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem}
|
||||
}
|
||||
options := func(inputs ...Input) LayeredOptions[map[string]any] {
|
||||
return LayeredOptions[map[string]any]{Definition: definition, SourceVersionMode: sourceVersionModeIfPresent, Underlays: []Underlay{{Inputs: inputs}}}
|
||||
}
|
||||
main := item("main", map[string]any{"id": "main", "schema": map[string]any{"version": "v1"}})
|
||||
|
||||
t.Run("same assigned main path is protected", func(t *testing.T) {
|
||||
_, err := LoadLayered(main, testSecretBinding(), options(item("underlay", map[string]any{"id": "underlay", "schema": nil})))
|
||||
if got := groupedErrorCode(t, err); got != CodeUnderlayVersion {
|
||||
t.Fatalf("protected item claim code = %s, want %s", got, CodeUnderlayVersion)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("appended peer does not claim main path", func(t *testing.T) {
|
||||
loaded, err := LoadLayered(main, testSecretBinding(), options(
|
||||
item("first", map[string]any{"id": "first", "schema": map[string]any{"other": "value"}}),
|
||||
item("peer", map[string]any{"id": "peer", "schema": nil}),
|
||||
))
|
||||
if err != nil {
|
||||
t.Fatalf("unrelated appended peer rejected: %v", err)
|
||||
}
|
||||
entries := layeredMapValue(t, testLoaded(loaded).Config, "catalog", "entries").([]any)
|
||||
if len(entries) != 1 || entries[0].(map[string]any)["id"] != "main" {
|
||||
t.Fatalf("main item projection = %#v", entries)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type destinationDecodeProbe string
|
||||
|
||||
var destinationDecodeProbeValues []string
|
||||
|
||||
@@ -163,26 +163,54 @@ func nestedDirectiveDiagnostics(node *document.Node, current Path) []Diagnostic
|
||||
}
|
||||
|
||||
func fragmentVersionClaim(root *document.Node, rule *VersionRule) *document.Node {
|
||||
return fragmentVersionClaimProjected(root, rule, -1, nil)
|
||||
}
|
||||
|
||||
func fragmentVersionClaimProjected(root *document.Node, rule *VersionRule, sourceIndex int, ranges []sequenceRange) *document.Node {
|
||||
if rule == nil {
|
||||
return nil
|
||||
}
|
||||
current := root
|
||||
currentPath := Path{}
|
||||
if len(rule.Path.segments) == 0 {
|
||||
return current
|
||||
}
|
||||
for _, segment := range rule.Path.segments {
|
||||
if current.Kind != document.Mapping {
|
||||
switch current.Kind {
|
||||
case document.Mapping:
|
||||
if segment.kind == pathSegmentIndex {
|
||||
return current
|
||||
}
|
||||
index := documentEntryIndex(current.Mapping, segment.key)
|
||||
if index < 0 {
|
||||
return nil
|
||||
}
|
||||
current = current.Mapping[index].Value
|
||||
currentPath = currentPath.Key(segment.key)
|
||||
case document.Sequence:
|
||||
if segment.kind != pathSegmentIndex {
|
||||
return current
|
||||
}
|
||||
index := segment.index
|
||||
for _, assigned := range ranges {
|
||||
if assigned.sourceIndex != sourceIndex || assigned.path.String() != currentPath.String() {
|
||||
continue
|
||||
}
|
||||
if index < assigned.start || index >= assigned.end {
|
||||
return nil
|
||||
}
|
||||
index -= assigned.start
|
||||
break
|
||||
}
|
||||
if index < 0 || index >= len(current.Sequence) {
|
||||
return nil
|
||||
}
|
||||
current = current.Sequence[index]
|
||||
currentPath = currentPath.Index(index)
|
||||
default:
|
||||
// A scalar or null at an ancestor replaces the protected subtree.
|
||||
return current
|
||||
}
|
||||
if segment.kind == pathSegmentIndex {
|
||||
return nil
|
||||
}
|
||||
key := segment.key
|
||||
index := documentEntryIndex(current.Mapping, key)
|
||||
if index < 0 {
|
||||
return nil
|
||||
}
|
||||
current = current.Mapping[index].Value
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
@@ -230,6 +230,11 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
if len(failures) != 0 {
|
||||
return nil, cloneSourceInfos(state.sources), prependDiagnosticPaths(failures, destination.Index(0))
|
||||
}
|
||||
if versionState != nil {
|
||||
if diagnostic := importedVersionClaim(documents, completed, versionState.localRule, 0); diagnostic != nil {
|
||||
return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic}
|
||||
}
|
||||
}
|
||||
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(localScope)}
|
||||
projectNestedEvidence(completed, localScope)
|
||||
_, placed, err := placeDocumentWithMode(completed.root, destination, placementModeSequenceItem, body.Location, maxDocumentDepth, maxDocumentNodes)
|
||||
@@ -260,6 +265,11 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
documents = append(documents, contribution.document)
|
||||
}
|
||||
composed, diagnostics := composeDocumentsWithPolicy(policy, documents...)
|
||||
if composed != nil {
|
||||
if diagnostic := importedVersionClaim(documents, composed, rule, 0); diagnostic != nil {
|
||||
return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic}
|
||||
}
|
||||
}
|
||||
if composed != nil {
|
||||
projectNestedEvidence(composed, state.scope)
|
||||
projectAcquiredDestinations(composed, state.scope, state.sources)
|
||||
@@ -416,7 +426,11 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
|
||||
var diagnostics []Diagnostic
|
||||
var processing *sourceVersionProcessingResult
|
||||
if s.versionState != nil && s.versionState.mode != sourceVersionModeDefault {
|
||||
processed, failures := s.versionState.process(root, sourceVersionContribution, destination, sourceID)
|
||||
versionDestination := destination
|
||||
if s.itemScope {
|
||||
versionDestination = appendDestination(s.itemPrefix.Index(0), destination)
|
||||
}
|
||||
processed, failures := s.versionState.process(root, sourceVersionContribution, versionDestination, sourceID, declaration.placement)
|
||||
if len(failures) != 0 {
|
||||
return failures
|
||||
}
|
||||
@@ -452,18 +466,6 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &location)}
|
||||
}
|
||||
s.accounting.nodes += placementNodes
|
||||
protectionRule := s.rule
|
||||
if s.versionState != nil && s.versionState.effectiveRule != nil {
|
||||
protectionRule = s.versionState.effectiveRule
|
||||
}
|
||||
if versionNode := fragmentVersionClaim(placed, protectionRule); versionNode != nil {
|
||||
versionLocation := publicLocation(versionNode.Location)
|
||||
return []Diagnostic{{
|
||||
Code: CodeImportVersion, Severity: SeverityError, Path: protectionRule.Path.clone(),
|
||||
Message: "imported fragments cannot contribute to the schema-version path", Primary: &versionLocation,
|
||||
}}
|
||||
}
|
||||
|
||||
route := importRoute{canonical: canonical, locations: currentRoute.locations}
|
||||
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(info), payload: body, document: placed,
|
||||
@@ -530,17 +532,6 @@ func (s *importTraversal) visitItemSource(canonical string, declaration importDe
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &declaration.location)}
|
||||
}
|
||||
s.accounting.nodes += placementNodes
|
||||
protectionRule := s.rule
|
||||
if s.versionState != nil && s.versionState.effectiveRule != nil {
|
||||
protectionRule = s.versionState.effectiveRule
|
||||
}
|
||||
if versionNode := fragmentVersionClaim(placed, protectionRule); versionNode != nil {
|
||||
versionLocation := publicLocation(versionNode.Location)
|
||||
return []Diagnostic{{
|
||||
Code: CodeImportVersion, Severity: SeverityError, Path: protectionRule.Path.clone(),
|
||||
Message: "imported fragments cannot contribute to the schema-version path", Primary: &versionLocation,
|
||||
}}
|
||||
}
|
||||
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(info), payload: completed.root, document: placed,
|
||||
destination: pl.clone(), relativeDestination: relativeDestination.clone(), processing: processing,
|
||||
@@ -549,6 +540,25 @@ func (s *importTraversal) visitItemSource(canonical string, declaration importDe
|
||||
return nil
|
||||
}
|
||||
|
||||
func importedVersionClaim(documents []*document.Node, composed *composedDocument, rule *VersionRule, mainIndex int) *Diagnostic {
|
||||
if composed == nil || rule == nil {
|
||||
return nil
|
||||
}
|
||||
for index, contribution := range documents {
|
||||
if index == mainIndex {
|
||||
continue
|
||||
}
|
||||
if node := fragmentVersionClaimProjected(contribution, rule, index, composed.sequenceRanges); node != nil {
|
||||
location := publicLocation(node.Location)
|
||||
return &Diagnostic{
|
||||
Code: CodeImportVersion, Severity: SeverityError, Path: rule.Path.clone(),
|
||||
Message: "imported fragments cannot contribute to the schema-version path", Primary: &location,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func prependDiagnosticPaths(diagnostics []Diagnostic, prefix Path) []Diagnostic {
|
||||
cloned := append([]Diagnostic(nil), diagnostics...)
|
||||
for index := range cloned {
|
||||
@@ -647,6 +657,7 @@ func projectAcquiredDestinations(composed *composedDocument, scope acquisitionSc
|
||||
}
|
||||
contributionPrefix = appendDestination(container, Path{}.Index(start))
|
||||
contributionInsideItem = true
|
||||
projectSourceVersionEvidence(contribution.processing, contributionPrefix)
|
||||
} else if contributionInsideItem {
|
||||
exactDestination := appendDestination(contributionPrefix, contribution.destination)
|
||||
contribution.source.Destination = exactDestination.clone()
|
||||
@@ -656,6 +667,7 @@ func projectAcquiredDestinations(composed *composedDocument, scope acquisitionSc
|
||||
sources[position].Destination = exactDestination
|
||||
sources[position].destinationUnresolved = false
|
||||
}
|
||||
projectSourceVersionEvidence(contribution.processing, exactDestination)
|
||||
}
|
||||
if contribution.nested != nil && contribution.nested.acquisition != nil {
|
||||
for _, nestedScope := range contribution.nested.acquisition {
|
||||
@@ -667,6 +679,12 @@ func projectAcquiredDestinations(composed *composedDocument, scope acquisitionSc
|
||||
projectScope(composed, scope, Path{}, false, "")
|
||||
}
|
||||
|
||||
func projectSourceVersionEvidence(processing *sourceVersionProcessingResult, destination Path) {
|
||||
if processing != nil && processing.evidence != nil {
|
||||
processing.evidence.destination = destination.clone()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *importTraversal) readImport(name, sourceID string, optional bool) ([]byte, bool, *Diagnostic) {
|
||||
remaining := s.limits.bytes - s.accounting.bytes
|
||||
limit := min(int64(maxSourceBytes), remaining)
|
||||
|
||||
@@ -154,7 +154,7 @@ func loadWithSecretLookup[T any](source Source, secrets SecretBinding, lookup se
|
||||
definition: definition, sequencePolicy: policy, overlay: options.Overlay,
|
||||
overrides: options.Overrides, ordinary: true,
|
||||
sourceVersionMode: options.SourceVersionMode,
|
||||
effectiveVersion: effectiveSourceVersionRule(options.Definition.Version, Path{}, options.SourceVersionMode),
|
||||
effectiveVersion: effectiveSourceVersionRuleForPlacement(options.Definition.Version, options.destination, options.SourceVersionMode, options.placementMode),
|
||||
}
|
||||
return executeLoadRequest(request, secrets, lookup)
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func assembleBaseInputTrackedForRole[T any](source Source, options Options[T], d
|
||||
if parsed.source != nil {
|
||||
sourceID = parsed.source.ID
|
||||
}
|
||||
processed, failures := versionState.process(parsed.document, role, destination, sourceID)
|
||||
processed, failures := versionState.process(parsed.document, role, destination, sourceID, options.placementMode)
|
||||
if len(failures) != 0 {
|
||||
return input, failures
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func loadGroupedWithSecretLookup[T any](request loadRequest[T], secrets SecretBi
|
||||
}})
|
||||
request.main.destination = mainOptions.destination
|
||||
diagnostics = append(diagnostics, mainDiagnostics...)
|
||||
request.effectiveVersion = effectiveSourceVersionRule(request.definition.Version, request.main.destination, request.sourceVersionMode)
|
||||
request.effectiveVersion = effectiveSourceVersionRuleForPlacement(request.definition.Version, request.main.destination, request.sourceVersionMode, request.main.placementMode)
|
||||
for groupIndex := range request.underlays {
|
||||
for inputIndex := range request.underlays[groupIndex].inputs {
|
||||
input := &request.underlays[groupIndex].inputs[inputIndex]
|
||||
@@ -180,13 +180,6 @@ func acquireUnderlayGroupWithPolicyVersion(group loadGroup, index int, rule *Ver
|
||||
if acquired.document == nil {
|
||||
continue
|
||||
}
|
||||
if node := fragmentVersionClaim(acquired.document, rule); node != nil {
|
||||
location := publicLocation(node.Location)
|
||||
return nil, sources, contexts, []Diagnostic{{
|
||||
Code: CodeUnderlayVersion, Severity: SeverityError, Path: rule.Path.clone(),
|
||||
Message: "underlay inputs cannot contribute to the schema-version path", Primary: &location,
|
||||
}}
|
||||
}
|
||||
scopes = append(scopes, cloneAcquisitionScopes(acquired.acquisition)...)
|
||||
}
|
||||
var documents []*document.Node
|
||||
@@ -202,6 +195,17 @@ func acquireUnderlayGroupWithPolicyVersion(group loadGroup, index int, rule *Ver
|
||||
if len(diagnostics) != 0 {
|
||||
return nil, sources, contexts, diagnostics
|
||||
}
|
||||
if composition != nil && rule != nil {
|
||||
for sourceIndex, document := range documents {
|
||||
if node := fragmentVersionClaimProjected(document, rule, sourceIndex, composition.sequenceRanges); node != nil {
|
||||
location := publicLocation(node.Location)
|
||||
return nil, sources, contexts, []Diagnostic{{
|
||||
Code: CodeUnderlayVersion, Severity: SeverityError, Path: rule.Path.clone(),
|
||||
Message: "underlay inputs cannot contribute to the schema-version path", Primary: &location,
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
composition.inputBytes = accounting.bytes - initialBytes
|
||||
composition.inputNodes = accounting.nodes - initialNodes
|
||||
composition.acquisition = cloneAcquisitionScopes(scopes)
|
||||
|
||||
@@ -136,7 +136,7 @@ func loadSelectedOverlayWithAccessAndVersion(access *filesystemAccess, base *ass
|
||||
}
|
||||
overlayPayload := overlayRoot
|
||||
if versionState != nil && versionState.mode != sourceVersionModeDefault {
|
||||
processed, failures := versionState.process(overlayRoot, sourceVersionContribution, Path{}, sourceID)
|
||||
processed, failures := versionState.process(overlayRoot, sourceVersionContribution, Path{}, sourceID, placementModeMapping)
|
||||
if len(failures) != 0 {
|
||||
return sources, failures
|
||||
}
|
||||
|
||||
@@ -91,11 +91,15 @@ func sourceVersionCheckForEvidence(evidence *sourceVersionEvidence, sensitivity
|
||||
}
|
||||
}
|
||||
|
||||
func (state *sourceVersionState) process(root *document.Node, role sourceVersionRole, destination Path, sourceID string) (sourceVersionProcessingResult, []Diagnostic) {
|
||||
func (state *sourceVersionState) process(root *document.Node, role sourceVersionRole, destination Path, sourceID string, placement placementMode) (sourceVersionProcessingResult, []Diagnostic) {
|
||||
if state == nil || state.mode == sourceVersionModeDefault {
|
||||
return sourceVersionProcessingResult{}, nil
|
||||
}
|
||||
result := processSourceVersion(root, state.localRule, role, destination, state.mode)
|
||||
projectedDestination := destination.clone()
|
||||
if placement == placementModeSequenceItem {
|
||||
projectedDestination = projectedDestination.Index(0)
|
||||
}
|
||||
result := processSourceVersion(root, state.localRule, role, projectedDestination, state.mode)
|
||||
if result.evidence != nil {
|
||||
result.evidence.sourceID = sourceID
|
||||
state.evidence = append(state.evidence, result.evidence)
|
||||
@@ -119,6 +123,10 @@ type sourceVersionProcessingResult struct {
|
||||
}
|
||||
|
||||
func effectiveSourceVersionRule(local *VersionRule, destination Path, mode sourceVersionMode) *VersionRule {
|
||||
return effectiveSourceVersionRuleForPlacement(local, destination, mode, placementModeMapping)
|
||||
}
|
||||
|
||||
func effectiveSourceVersionRuleForPlacement(local *VersionRule, destination Path, mode sourceVersionMode, placement placementMode) *VersionRule {
|
||||
if local == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -126,9 +134,13 @@ func effectiveSourceVersionRule(local *VersionRule, destination Path, mode sourc
|
||||
return local
|
||||
}
|
||||
effective := *local
|
||||
prefix := destination.clone()
|
||||
if placement == placementModeSequenceItem {
|
||||
prefix = prefix.Index(0)
|
||||
}
|
||||
effective.Path = Path{
|
||||
segments: append(clonePathSegments(destination.segments, len(local.Path.segments)), local.Path.segments...),
|
||||
invalid: destination.invalid || local.Path.invalid,
|
||||
segments: append(clonePathSegments(prefix.segments, len(local.Path.segments)), local.Path.segments...),
|
||||
invalid: prefix.invalid || local.Path.invalid,
|
||||
}
|
||||
return &effective
|
||||
}
|
||||
|
||||
@@ -37,6 +37,40 @@ func TestSourceVersionAbsentPathKeepsUnresolvedSegmentIntent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFragmentVersionClaimUsesAssignedSequenceProjection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fragment := sourceVersionTestDocument(t, "items: [{version: v1}]\n")
|
||||
rangeForPeer := []sequenceRange{{sourceIndex: 1, path: Keys("items"), start: 1, end: 2}}
|
||||
if node := fragmentVersionClaimProjected(fragment, &VersionRule{Path: Keys("items").Index(0).Key("version")}, 1, rangeForPeer); node != nil {
|
||||
t.Fatalf("peer append claimed main index 0: %#v", node)
|
||||
}
|
||||
if node := fragmentVersionClaimProjected(fragment, &VersionRule{Path: Keys("items").Index(1).Key("version")}, 1, rangeForPeer); node == nil || node.Text != "v1" {
|
||||
t.Fatalf("assigned peer index 1 claim = %#v, want its version node", node)
|
||||
}
|
||||
nested := sourceVersionTestDocument(t, "items: [{nested: [{version: nested-v}]}]\n")
|
||||
nestedRanges := []sequenceRange{
|
||||
{sourceIndex: 1, path: Keys("items"), start: 1, end: 2},
|
||||
{sourceIndex: 1, path: Keys("items").Index(0).Key("nested"), start: 2, end: 3},
|
||||
}
|
||||
if node := fragmentVersionClaimProjected(nested, &VersionRule{Path: Keys("items").Index(1).Key("nested").Index(2).Key("version")}, 1, nestedRanges); node == nil || node.Text != "nested-v" {
|
||||
t.Fatalf("nested assigned claim = %#v, want nested version", node)
|
||||
}
|
||||
ancestor := sourceVersionTestDocument(t, "items: {version: replacement}\n")
|
||||
if node := fragmentVersionClaim(ancestor, &VersionRule{Path: Keys("items").Index(0).Key("version")}); node == nil || node.Kind != document.Mapping {
|
||||
t.Fatalf("destructive sequence ancestor claim = %#v, want mapping node", node)
|
||||
}
|
||||
|
||||
effective := effectiveSourceVersionRuleForPlacement(&VersionRule{Path: Keys("version")}, Keys("catalog", "entries"), sourceVersionModeRequired, placementModeSequenceItem)
|
||||
if got, want := effective.Path.String(), "/catalog/entries/0/version"; got != want {
|
||||
t.Fatalf("item effective version path = %q, want %q", got, want)
|
||||
}
|
||||
defaultRule := effectiveSourceVersionRuleForPlacement(&VersionRule{Path: Keys("version")}, Keys("catalog", "entries"), sourceVersionModeDefault, placementModeSequenceItem)
|
||||
if got, want := defaultRule.Path.String(), "/version"; got != want {
|
||||
t.Fatalf("default effective version path = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
type sourceVersionMountedService struct {
|
||||
Version string `yaml:"version"`
|
||||
}
|
||||
@@ -473,6 +507,83 @@ func TestSourceVersionLoadChecksMountedMainLocallyAndProtectsEffectivePath(t *te
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionItemPlacementKeepsLocalChecksAndProtectsProjectedPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
load func(Definition[map[string]any]) error
|
||||
}{
|
||||
{
|
||||
name: "ordinary",
|
||||
load: func(definition Definition[map[string]any]) error {
|
||||
_, err := Load(Mapping("main", map[string]any{"version": "v1", "name": "ordinary"}), testSecretBinding(), Options[map[string]any]{
|
||||
Definition: definition, SourceVersionMode: sourceVersionModeRequired,
|
||||
destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem,
|
||||
Overrides: []Override{{Path: Keys("catalog", "entries"), Value: []any{}, Flag: "--entries"}},
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "layered",
|
||||
load: func(definition Definition[map[string]any]) error {
|
||||
_, err := LoadLayered(Input{Source: Mapping("main", map[string]any{"version": "v1", "name": "layered"}), Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem}, testSecretBinding(), LayeredOptions[map[string]any]{
|
||||
Definition: definition, SourceVersionMode: sourceVersionModeRequired,
|
||||
Overrides: []Override{{Path: Keys("catalog", "entries"), Value: []any{}, Flag: "--entries"}},
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checks := 0
|
||||
definition := Definition[map[string]any]{Version: &VersionRule{
|
||||
Path: Keys("version"),
|
||||
Check: func(version Version, _ *Validation) error {
|
||||
checks++
|
||||
if !version.Present || version.Text != "v1" {
|
||||
t.Errorf("source-local version = %#v, want v1", version)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}}
|
||||
err := test.load(definition)
|
||||
var loadErr *LoadError
|
||||
if !errors.As(err, &loadErr) {
|
||||
t.Fatalf("item placement override error = %T %v, want *LoadError", err, err)
|
||||
}
|
||||
requireSourceVersionDiagnostic(t, loadErr.Diagnostics(), CodeOverrideVersion, "/catalog/entries")
|
||||
if checks != 1 {
|
||||
t.Fatalf("source-version checker called %d times, want once", checks)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionItemEvidenceUsesProjectedSensitivityPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const canary = "PROJECTED-VERSION-EVIDENCE-CANARY"
|
||||
secretPath := Keys("catalog", "entries").Index(0).Key("version")
|
||||
loaded, err := Load(Mapping("main", map[string]any{"version": canary}), testSecretBinding(), Options[map[string]any]{
|
||||
Definition: Definition[map[string]any]{
|
||||
Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }},
|
||||
Secret: func(path Path) bool { return path.String() == secretPath.String() },
|
||||
},
|
||||
SourceVersionMode: sourceVersionModeRequired,
|
||||
destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("item source-version load: %v", err)
|
||||
}
|
||||
sources := loaded.Inspection.Sources()
|
||||
if len(sources) != 1 || sources[0].VersionCheck == nil || !sources[0].VersionCheck.Redacted || sources[0].VersionCheck.Version.Text != "" {
|
||||
t.Fatalf("projected source-version evidence was not redacted: %#v", sources)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceVersionLoadChecksNullAndNoSourceMain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user