Skip to content

Commit 6be5752

Browse files
committed
Add invariant config coverage test
Check that every bundle resource type has an invariant config, and that resources supporting permissions/grants have at least one config exercising them. Known gaps are tracked in an explicit allowlist that can only shrink. Co-authored-by: Isaac
1 parent 3a3700e commit 6be5752

1 file changed

Lines changed: 139 additions & 0 deletions

File tree

acceptance/invariant_test.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package acceptance_test
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"sort"
7+
"strings"
8+
"testing"
9+
10+
"github.com/databricks/cli/bundle/config"
11+
"github.com/databricks/cli/libs/dyn"
12+
"github.com/databricks/cli/libs/dyn/yamlloader"
13+
"github.com/stretchr/testify/assert"
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
const invariantConfigsDir = "bundle/invariant/configs"
18+
19+
// LackingInvariantTest lists keys from config.ResourcesTypes that knowingly lack
20+
// a covering config in invariantConfigsDir. Keys match the ResourcesTypes
21+
// form: "<group>" for the resource itself, "<group>.permissions" / "<group>.grants"
22+
// for permissions/grants coverage. Add a config and remove the entry to close a gap;
23+
// the test fails if an entry here is actually covered, so the list only shrinks.
24+
var LackingInvariantTest = map[string]bool{
25+
"quality_monitors": true,
26+
27+
"alerts.permissions": true,
28+
"apps.permissions": true,
29+
"clusters.permissions": true,
30+
"dashboards.permissions": true,
31+
"database_instances.permissions": true,
32+
"experiments.permissions": true,
33+
"model_serving_endpoints.permissions": true,
34+
"pipelines.permissions": true,
35+
"postgres_projects.permissions": true,
36+
"sql_warehouses.permissions": true,
37+
38+
"catalogs.grants": true,
39+
"external_locations.grants": true,
40+
"registered_models.grants": true,
41+
"vector_search_indexes.grants": true,
42+
"volumes.grants": true,
43+
}
44+
45+
// TestInvariantConfigsCoverage ensures that the invariant test configs in
46+
// bundle/invariant/configs cover every bundle resource type, and that resource
47+
// types supporting permissions or grants have at least one config exercising them.
48+
//
49+
// config.ResourcesTypes is the source of truth: it maps each resource group
50+
// (e.g. "jobs") to its Go type and, where the resource struct has a Permissions
51+
// or Grants field, adds derived keys "<group>.permissions" and "<group>.grants".
52+
func TestInvariantConfigsCoverage(t *testing.T) {
53+
present, withPermissions, withGrants := scanInvariantConfigs(t)
54+
55+
keys := make([]string, 0, len(config.ResourcesTypes))
56+
for key := range config.ResourcesTypes {
57+
keys = append(keys, key)
58+
}
59+
sort.Strings(keys)
60+
61+
for _, key := range keys {
62+
var covered bool
63+
var hint string
64+
switch {
65+
case strings.HasSuffix(key, ".permissions"):
66+
group := strings.TrimSuffix(key, ".permissions")
67+
covered = withPermissions[group]
68+
hint = "attaches permissions to a " + group + " resource"
69+
case strings.HasSuffix(key, ".grants"):
70+
group := strings.TrimSuffix(key, ".grants")
71+
covered = withGrants[group]
72+
hint = "attaches grants to a " + group + " resource"
73+
default:
74+
covered = present[key]
75+
hint = "defines a " + key + " resource"
76+
}
77+
78+
if LackingInvariantTest[key] {
79+
assert.False(t, covered,
80+
"%q is covered by a config in %s; remove it from LackingInvariantTest", key, invariantConfigsDir)
81+
continue
82+
}
83+
assert.True(t, covered,
84+
"no config in %s %s; add one or allowlist %q", invariantConfigsDir, hint, key)
85+
}
86+
}
87+
88+
// scanInvariantConfigs parses every config in the invariant configs directory and
89+
// returns the set of resource groups present, the groups with at least one resource
90+
// carrying permissions, and the groups with at least one resource carrying grants.
91+
func scanInvariantConfigs(t *testing.T) (present, withPermissions, withGrants map[string]bool) {
92+
present = map[string]bool{}
93+
withPermissions = map[string]bool{}
94+
withGrants = map[string]bool{}
95+
96+
entries, err := os.ReadDir(invariantConfigsDir)
97+
require.NoError(t, err)
98+
99+
for _, entry := range entries {
100+
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".yml.tmpl") {
101+
continue
102+
}
103+
path := filepath.Join(invariantConfigsDir, entry.Name())
104+
contents, err := os.ReadFile(path)
105+
require.NoError(t, err)
106+
107+
v, err := yamlloader.LoadYAML(path, strings.NewReader(string(contents)))
108+
require.NoError(t, err, "failed to parse %s", path)
109+
110+
resources := v.Get("resources")
111+
if resources.Kind() != dyn.KindMap {
112+
// Some configs (e.g. PyDABs) declare resources outside of YAML.
113+
continue
114+
}
115+
116+
for _, group := range resources.MustMap().Pairs() {
117+
groupName := group.Key.MustString()
118+
present[groupName] = true
119+
120+
if group.Value.Kind() != dyn.KindMap {
121+
continue
122+
}
123+
for _, resource := range group.Value.MustMap().Pairs() {
124+
cfg := resource.Value
125+
if cfg.Kind() != dyn.KindMap {
126+
continue
127+
}
128+
if cfg.Get("permissions").Kind() != dyn.KindInvalid {
129+
withPermissions[groupName] = true
130+
}
131+
if cfg.Get("grants").Kind() != dyn.KindInvalid {
132+
withGrants[groupName] = true
133+
}
134+
}
135+
}
136+
}
137+
138+
return present, withPermissions, withGrants
139+
}

0 commit comments

Comments
 (0)