148 lines
4.6 KiB
Go
148 lines
4.6 KiB
Go
package engine
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestSecretDependencyClosureMatchesFixedPointScan(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
paths map[string]Path
|
|
seed map[string]bool
|
|
dependencies []secretDependency
|
|
inherited []secretDependency
|
|
wantSensitivity map[string]bool
|
|
}{
|
|
{
|
|
name: "forward reverse chains and cycles",
|
|
paths: secretPaths(Keys("input"), Keys("derived"), Keys("output"), Keys("cycle")),
|
|
seed: secretSet(Keys("input")),
|
|
dependencies: []secretDependency{
|
|
{dependency: Keys("input"), target: Keys("derived")},
|
|
{dependency: Keys("derived"), target: Keys("output")},
|
|
{dependency: Keys("output"), target: Keys("cycle")},
|
|
{dependency: Keys("cycle"), target: Keys("derived")},
|
|
{dependency: Keys("input"), target: Keys("derived")},
|
|
},
|
|
},
|
|
{
|
|
name: "ordinary ancestor descendant and root relations",
|
|
paths: secretPaths(Path{}, Keys("branch", "leaf"), Keys("parent", "child")),
|
|
seed: secretSet(Keys("branch", "leaf"), Keys("parent")),
|
|
dependencies: []secretDependency{
|
|
{dependency: Keys("branch"), target: Keys("from-descendant")},
|
|
{dependency: Keys("parent", "child"), target: Keys("from-ancestor")},
|
|
{dependency: Path{}, target: Keys("from-root")},
|
|
},
|
|
},
|
|
{
|
|
name: "unindexed seed only activates inherited relations",
|
|
paths: secretPaths(Keys("ordinary"), Keys("copy")),
|
|
seed: secretSet(Keys("untracked")),
|
|
dependencies: []secretDependency{
|
|
{dependency: Keys("untracked"), target: Keys("ordinary")},
|
|
},
|
|
inherited: []secretDependency{
|
|
{dependency: Keys("untracked"), target: Keys("copy")},
|
|
},
|
|
wantSensitivity: map[string]bool{"/copy": true, "/ordinary": false},
|
|
},
|
|
{
|
|
name: "inheritance direction root escapes and numeric spellings",
|
|
paths: secretPaths(Path{}, Keys("a/b", "01"), Keys("source", "value")),
|
|
seed: secretSet(Path{}),
|
|
dependencies: []secretDependency{
|
|
{dependency: Keys("a/b", "01"), target: Keys("ordinary-copy")},
|
|
},
|
|
inherited: []secretDependency{
|
|
{dependency: Keys("a/b", "01"), target: Keys("destination", "01")},
|
|
{dependency: Keys("source"), target: Keys("destination")},
|
|
},
|
|
},
|
|
{
|
|
name: "inherited links do not trigger from classified descendants",
|
|
paths: secretPaths(Keys("parent", "secret", "nested")),
|
|
seed: secretSet(Keys("parent", "secret", "nested")),
|
|
inherited: []secretDependency{
|
|
{dependency: Keys("parent", "secret"), target: Keys("child", "secret")},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
want := referenceSecretDependencyClosure(test.paths, test.seed, test.dependencies, test.inherited)
|
|
got := closeSecretDependencies(test.paths, test.seed, test.dependencies, test.inherited)
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("closure = %#v, want %#v", got, want)
|
|
}
|
|
for pointer, sensitive := range test.wantSensitivity {
|
|
if got[pointer] != sensitive {
|
|
t.Errorf("sensitivity at %q = %v, want %v", pointer, got[pointer], sensitive)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func referenceSecretDependencyClosure(paths map[string]Path, seed map[string]bool, dependencies, inherited []secretDependency) map[string]bool {
|
|
secret := make(map[string]bool, len(seed))
|
|
for pointer, classified := range seed {
|
|
if classified {
|
|
secret[pointer] = true
|
|
}
|
|
}
|
|
for {
|
|
changed := false
|
|
for _, dependency := range dependencies {
|
|
if referenceSecretIntersectsPath(paths, secret, dependency.dependency) && !secret[dependency.target.String()] {
|
|
secret[dependency.target.String()] = true
|
|
changed = true
|
|
}
|
|
}
|
|
for _, link := range inherited {
|
|
if pathIsSecret(secret, link.dependency) && !secret[link.target.String()] {
|
|
secret[link.target.String()] = true
|
|
changed = true
|
|
}
|
|
if pathIsSecret(secret, link.target) && !secret[link.dependency.String()] {
|
|
secret[link.dependency.String()] = true
|
|
changed = true
|
|
}
|
|
}
|
|
if !changed {
|
|
return secret
|
|
}
|
|
}
|
|
}
|
|
|
|
func referenceSecretIntersectsPath(paths map[string]Path, secret map[string]bool, candidate Path) bool {
|
|
for pointer := range secret {
|
|
classified, ok := paths[pointer]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if pathContains(classified, candidate) || pathContains(candidate, classified) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func secretPaths(paths ...Path) map[string]Path {
|
|
result := make(map[string]Path, len(paths))
|
|
for _, path := range paths {
|
|
result[path.String()] = path
|
|
}
|
|
return result
|
|
}
|
|
|
|
func secretSet(paths ...Path) map[string]bool {
|
|
result := make(map[string]bool, len(paths))
|
|
for _, path := range paths {
|
|
result[path.String()] = true
|
|
}
|
|
return result
|
|
}
|