Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tests/bdd/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ refactor in every consumer; that is a feature.
| `Then file {string} should exist` | |
| `Then yaml file {string} key {string} should equal {string}` | Reads the YAML file, walks the dotted key path, compares to the value (with `${VAR}` expansion). |
| `Then yaml file {string} key {string} should not be empty` | Same key resolution; passes if the resolved value is non-empty. Use for non-deterministic outputs (cluster IDs, identity sources) where exact-value assertions are wrong. |
| `Then yaml file {string} should have non-empty keys:` (table) | Requires a `key` header and one or more dotted key paths. Each key must exist and resolve to a non-empty value; failures distinguish a missing key from an empty value. |
| `Then yaml file {string} should match:` (docstring) | Parses the docstring as YAML, parses the file as YAML, and asserts strict equality of the two trees. Every key in expected must exist in actual and vice versa. `${VAR}` expansion applies to the docstring before parsing. See "YAML comparison semantics" below for tree rules. |
| `Then yaml file {string} key {string} should match:` (docstring) | Same as above but compares only the subtree at the dotted key path. |
| `Then yaml file {string} should contain:` (docstring) | Subset variant: every key in expected must exist in actual with the same value; extra keys in actual are allowed. Use this when the file has dynamic or future-additive fields. `${VAR}` expansion applies. |
Expand Down
19 changes: 19 additions & 0 deletions tests/bdd/dsl/yamledit.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,25 @@ func ReadYAMLKey(path, dottedKey string) (string, bool, error) {
return fmt.Sprint(value), true, nil
}

// RequireNonEmptyYAMLKeys asserts that every dotted key exists in path and
// resolves to a non-empty scalar representation. The first failing error names
// the table row and distinguishes a missing key from an empty value.
func RequireNonEmptyYAMLKeys(path string, keys []string) error {
for index, key := range keys {
got, found, err := ReadYAMLKey(path, key)
if err != nil {
return fmt.Errorf("row %d key %q: %w", index+1, key, err)
}
if !found {
return fmt.Errorf("row %d: %s key %q is missing (%s)", index+1, path, key, DescribeMissingKey(path, key))
}
if got == "" {
return fmt.Errorf("row %d: %s key %q is empty", index+1, path, key)
}
}
return nil
}

// MatchYAMLSubtree compares the subtree at keyPath inside the YAML file
// at filePath to the parsed expectedYAML. Empty keyPath compares against
// the whole file. The expected docstring runs through Interpolate before
Expand Down
28 changes: 28 additions & 0 deletions tests/bdd/dsl/yamledit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,34 @@ list:
}
}

func TestRequireNonEmptyYAMLKeys(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "registration.yaml")
writeFile(t, path, `clusterID: cluster-123
clusterGroupID: ""
selfManaged:
identitySource: psat
`)

if err := RequireNonEmptyYAMLKeys(path, []string{"clusterID", "selfManaged.identitySource"}); err != nil {
t.Fatalf("non-empty keys: %v", err)
}

t.Run("missing key", func(t *testing.T) {
err := RequireNonEmptyYAMLKeys(path, []string{"clusterID", "missingID"})
if err == nil || !strings.Contains(err.Error(), `row 2`) || !strings.Contains(err.Error(), `key "missingID" is missing`) {
t.Fatalf("err = %v, want row-specific missing-key error", err)
}
})

t.Run("empty value", func(t *testing.T) {
err := RequireNonEmptyYAMLKeys(path, []string{"clusterGroupID"})
if err == nil || !strings.Contains(err.Error(), `row 1`) || !strings.Contains(err.Error(), `key "clusterGroupID" is empty`) {
t.Fatalf("err = %v, want row-specific empty-value error", err)
}
})
}

func TestMatchYAMLSubtreeExactAndSubset(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "profile.yaml")
Expand Down
6 changes: 4 additions & 2 deletions tests/bdd/features/multi-cluster-eks-helmfile.feature
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,10 @@ Feature: Install a multi-cluster NVCF stack across two pre-provisioned EKS clust
selfManaged:
identitySource: psat
"""
And yaml file "deploy/stacks/nvcf-compute-plane/registration/${EKS_COMPUTE_CLUSTER_NAME}-register-values.yaml" key "clusterID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/registration/${EKS_COMPUTE_CLUSTER_NAME}-register-values.yaml" key "clusterGroupID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/registration/${EKS_COMPUTE_CLUSTER_NAME}-register-values.yaml" should have non-empty keys:
| key |
| clusterID |
| clusterGroupID |

# The register-values URLs stay as cluster register's bare-ELB
# output. Gateway HTTPRoute matching is handled by the chart-native
Expand Down
6 changes: 4 additions & 2 deletions tests/bdd/features/multi-cluster-helmfile.feature
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,10 @@ Feature: Install a local multi-cluster NVCF stack with Helmfile
selfManaged:
identitySource: psat
"""
And yaml file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-compute-1-register-values.yaml" key "clusterID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-compute-1-register-values.yaml" key "clusterGroupID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-compute-1-register-values.yaml" should have non-empty keys:
| key |
| clusterID |
| clusterGroupID |

And the "nvcr-pull-secret" image pull secret exists in namespaces:
| nvca-operator |
Expand Down
12 changes: 7 additions & 5 deletions tests/bdd/features/multi-cluster-up.feature
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ Feature: Bring up a local multi-cluster NVCF stack with the CLI
And file "deploy/stacks/nvcf-compute-plane/out/ncp-local-compute-1-register-values.yaml" should exist
# Subset match (should contain, not should match) because the
# values file carries non-deterministic IDs alongside the
# deterministic block. The IDs are asserted by individual
# should-not-be-empty steps below.
# deterministic block. The generated values are asserted as
# non-empty keys below.
And yaml file "deploy/stacks/nvcf-compute-plane/out/ncp-local-compute-1-register-values.yaml" should contain:
"""
clusterName: ncp-local-compute-1
Expand All @@ -169,9 +169,11 @@ Feature: Bring up a local multi-cluster NVCF stack with the CLI
revalServiceURL: http://reval.localhost:8080
natsURL: nats://nats.localhost:4222
"""
And yaml file "deploy/stacks/nvcf-compute-plane/out/ncp-local-compute-1-register-values.yaml" key "clusterID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/out/ncp-local-compute-1-register-values.yaml" key "clusterGroupID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/out/ncp-local-compute-1-register-values.yaml" key "selfManaged.identitySource" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/out/ncp-local-compute-1-register-values.yaml" should have non-empty keys:
| key |
| clusterID |
| clusterGroupID |
| selfManaged.identitySource |

@nvca-registration
Scenario: Operator installs the first compute plane
Expand Down
6 changes: 4 additions & 2 deletions tests/bdd/features/observability-all.feature
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,10 @@ Feature: Install local Helmfile observability for both planes
"""
Then the command exit code should be 0
And file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-register-values.yaml" should exist
And yaml file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-register-values.yaml" key "clusterID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-register-values.yaml" key "clusterGroupID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-register-values.yaml" should have non-empty keys:
| key |
| clusterID |
| clusterGroupID |

When I run command:
"""
Expand Down
6 changes: 4 additions & 2 deletions tests/bdd/features/observability-compute.feature
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,10 @@ Feature: Install local Helmfile observability with the compute profile
"""
Then the command exit code should be 0
And file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-compute-1-register-values.yaml" should exist
And yaml file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-compute-1-register-values.yaml" key "clusterID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-compute-1-register-values.yaml" key "clusterGroupID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-compute-1-register-values.yaml" should have non-empty keys:
| key |
| clusterID |
| clusterGroupID |

When I run command:
"""
Expand Down
6 changes: 4 additions & 2 deletions tests/bdd/features/single-cluster-eks-helmfile.feature
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,10 @@ Feature: Install a single-cluster NVCF stack on a pre-provisioned EKS cluster wi
selfManaged:
identitySource: psat
"""
And yaml file "deploy/stacks/nvcf-compute-plane/registration/${EKS_CLUSTER_NAME}-register-values.yaml" key "clusterID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/registration/${EKS_CLUSTER_NAME}-register-values.yaml" key "clusterGroupID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/registration/${EKS_CLUSTER_NAME}-register-values.yaml" should have non-empty keys:
| key |
| clusterID |
| clusterGroupID |

# The register-values URLs stay as cluster register's bare-ELB
# output (http://${EKS_GATEWAY_ADDR}, nats://${EKS_GATEWAY_ADDR}:4222).
Expand Down
6 changes: 4 additions & 2 deletions tests/bdd/features/single-cluster-helmfile.feature
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,10 @@ Feature: Install a local single-cluster NVCF stack with Helmfile
revalServiceURL: http://reval.localhost:8080
natsURL: nats://nats.localhost:4222
"""
And yaml file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-register-values.yaml" key "clusterID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-register-values.yaml" key "clusterGroupID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/registration/ncp-local-register-values.yaml" should have non-empty keys:
| key |
| clusterID |
| clusterGroupID |

When I run command:
"""
Expand Down
12 changes: 7 additions & 5 deletions tests/bdd/features/single-cluster-up.feature
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ Feature: Bring up a local single-cluster NVCF stack with the CLI

# Subset match (should contain, not should match) because the
# values file carries non-deterministic IDs alongside the
# deterministic block. The IDs are asserted by individual
# should-not-be-empty steps below.
# deterministic block. The generated values are asserted as
# non-empty keys below.
#
# The values file uses in-cluster URLs because compute-plane
# register resolves the URL layer from the kube-context: when
Expand All @@ -195,6 +195,8 @@ Feature: Bring up a local single-cluster NVCF stack with the CLI
revalServiceURL: http://reval.nvcf.svc.cluster.local:8080
natsURL: nats://nats.nats-system.svc.cluster.local:4222
"""
And yaml file "deploy/stacks/nvcf-compute-plane/out/ncp-local-register-values.yaml" key "clusterID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/out/ncp-local-register-values.yaml" key "clusterGroupID" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/out/ncp-local-register-values.yaml" key "selfManaged.identitySource" should not be empty
And yaml file "deploy/stacks/nvcf-compute-plane/out/ncp-local-register-values.yaml" should have non-empty keys:
| key |
| clusterID |
| clusterGroupID |
| selfManaged.identitySource |
9 changes: 9 additions & 0 deletions tests/bdd/steps/assertion_steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func registerAssertionSteps(ctx *godog.ScenarioContext, sc *ScenarioContext) {
ctx.Step(`^file "([^"]*)" should exist$`, sc.fileShouldExist)
ctx.Step(`^yaml file "([^"]*)" key "([^"]*)" should equal "([^"]*)"$`, sc.yamlFileKeyShouldEqual)
ctx.Step(`^yaml file "([^"]*)" key "([^"]*)" should not be empty$`, sc.yamlFileKeyShouldNotBeEmpty)
ctx.Step(`^yaml file "([^"]*)" should have non-empty keys:$`, sc.yamlFileShouldHaveNonEmptyKeys)
ctx.Step(`^yaml file "([^"]*)" should match:$`, sc.yamlFileShouldMatch)
ctx.Step(`^yaml file "([^"]*)" key "([^"]*)" should match:$`, sc.yamlFileKeyShouldMatch)
ctx.Step(`^yaml file "([^"]*)" should contain:$`, sc.yamlFileShouldContain)
Expand Down Expand Up @@ -129,6 +130,14 @@ func (sc *ScenarioContext) yamlFileKeyShouldNotBeEmpty(path, key string) error {
return nil
}

func (sc *ScenarioContext) yamlFileShouldHaveNonEmptyKeys(path string, table *godog.Table) error {
keys, err := tableToSingleColumn(table, "key")
if err != nil {
return err
}
return dsl.RequireNonEmptyYAMLKeys(sc.resolvePath(dsl.Interpolate(path)), keys)
}

func (sc *ScenarioContext) yamlFileShouldMatch(path string, doc *godog.DocString) error {
return dsl.MatchYAMLSubtree(sc.resolvePath(dsl.Interpolate(path)), "", doc.Content, dsl.MatchExact)
}
Expand Down
51 changes: 51 additions & 0 deletions tests/bdd/steps/steps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -803,11 +803,62 @@ controlPlane:
if err := sc.yamlFileKeyShouldNotBeEmpty(rel, "controlPlane.clusterName"); err != nil {
t.Fatalf("not empty: %v", err)
}
nonEmptyKeys := docTable(t, [][]string{
{"key"},
{"controlPlane.clusterName"},
{"controlPlane.endpoints.inCluster.icmsURL"},
})
if err := sc.yamlFileShouldHaveNonEmptyKeys(rel, nonEmptyKeys); err != nil {
t.Fatalf("non-empty keys: %v", err)
}
if err := sc.yamlFileKeyShouldContain(rel, "controlPlane.endpoints.inCluster", &godog.DocString{Content: "icmsURL: http://api.sis:8080\n"}); err != nil {
t.Fatalf("contain: %v", err)
}
}

func TestYAMLFileShouldHaveNonEmptyKeysValidatesTable(t *testing.T) {
sc, _ := newScenarioContext(t)

tests := []struct {
name string
table *godog.Table
want string
}{
{
name: "no data rows",
table: docTable(t, [][]string{
{"key"},
}),
want: "at least one data row",
},
{
name: "wrong header",
table: docTable(t, [][]string{
{"name"},
{"clusterID"},
}),
want: `table header must be "key"`,
},
{
name: "empty key",
table: docTable(t, [][]string{
{"key"},
{""},
}),
want: "empty key value",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := sc.yamlFileShouldHaveNonEmptyKeys("registration.yaml", tc.table)
if err == nil || !strings.Contains(err.Error(), tc.want) {
t.Fatalf("err = %v, want error containing %q", err, tc.want)
}
})
}
}

func TestCommandOutputContainsAssertion(t *testing.T) {
sc, _ := newScenarioContext(t)
sc.LastResult = harness.Result{Stdout: "release deployed", Stderr: ""}
Expand Down
Loading