| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package terraform |
| |
| import ( |
| "testing" |
| |
| "github.com/zclconf/go-cty/cty" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/checks" |
| "github.com/hashicorp/terraform/internal/configs/configschema" |
| "github.com/hashicorp/terraform/internal/plans" |
| "github.com/hashicorp/terraform/internal/providers" |
| "github.com/hashicorp/terraform/internal/states" |
| "github.com/hashicorp/terraform/internal/tfdiags" |
| ) |
| |
| // This file contains 'integration' tests for the Terraform check blocks. |
| // |
| // These tests could live in context_apply_test or context_apply2_test but given |
| // the size of those files, it makes sense to keep these check related tests |
| // grouped together. |
| |
| type checksTestingStatus struct { |
| status checks.Status |
| messages []string |
| } |
| |
| func TestContextChecks(t *testing.T) { |
| tests := map[string]struct { |
| configs map[string]string |
| plan map[string]checksTestingStatus |
| planError string |
| planWarning string |
| apply map[string]checksTestingStatus |
| applyError string |
| applyWarning string |
| state *states.State |
| provider *MockProvider |
| providerHook func(*MockProvider) |
| }{ |
| "passing": { |
| configs: map[string]string{ |
| "main.tf": ` |
| provider "checks" {} |
| |
| check "passing" { |
| data "checks_object" "positive" {} |
| |
| assert { |
| condition = data.checks_object.positive.number >= 0 |
| error_message = "negative number" |
| } |
| } |
| `, |
| }, |
| plan: map[string]checksTestingStatus{ |
| "passing": { |
| status: checks.StatusPass, |
| }, |
| }, |
| apply: map[string]checksTestingStatus{ |
| "passing": { |
| status: checks.StatusPass, |
| }, |
| }, |
| provider: &MockProvider{ |
| Meta: "checks", |
| GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ |
| DataSources: map[string]providers.Schema{ |
| "checks_object": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "number": { |
| Type: cty.Number, |
| Computed: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| ReadDataSourceFn: func(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| return providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "number": cty.NumberIntVal(0), |
| }), |
| } |
| }, |
| }, |
| }, |
| "failing": { |
| configs: map[string]string{ |
| "main.tf": ` |
| provider "checks" {} |
| |
| check "failing" { |
| data "checks_object" "positive" {} |
| |
| assert { |
| condition = data.checks_object.positive.number >= 0 |
| error_message = "negative number" |
| } |
| } |
| `, |
| }, |
| plan: map[string]checksTestingStatus{ |
| "failing": { |
| status: checks.StatusFail, |
| messages: []string{"negative number"}, |
| }, |
| }, |
| planWarning: "Check block assertion failed: negative number", |
| apply: map[string]checksTestingStatus{ |
| "failing": { |
| status: checks.StatusFail, |
| messages: []string{"negative number"}, |
| }, |
| }, |
| applyWarning: "Check block assertion failed: negative number", |
| provider: &MockProvider{ |
| Meta: "checks", |
| GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ |
| DataSources: map[string]providers.Schema{ |
| "checks_object": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "number": { |
| Type: cty.Number, |
| Computed: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| ReadDataSourceFn: func(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| return providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "number": cty.NumberIntVal(-1), |
| }), |
| } |
| }, |
| }, |
| }, |
| "mixed": { |
| configs: map[string]string{ |
| "main.tf": ` |
| provider "checks" {} |
| |
| check "failing" { |
| data "checks_object" "neutral" {} |
| |
| assert { |
| condition = data.checks_object.neutral.number >= 0 |
| error_message = "negative number" |
| } |
| |
| assert { |
| condition = data.checks_object.neutral.number < 0 |
| error_message = "positive number" |
| } |
| } |
| `, |
| }, |
| plan: map[string]checksTestingStatus{ |
| "failing": { |
| status: checks.StatusFail, |
| messages: []string{"positive number"}, |
| }, |
| }, |
| planWarning: "Check block assertion failed: positive number", |
| apply: map[string]checksTestingStatus{ |
| "failing": { |
| status: checks.StatusFail, |
| messages: []string{"positive number"}, |
| }, |
| }, |
| applyWarning: "Check block assertion failed: positive number", |
| provider: &MockProvider{ |
| Meta: "checks", |
| GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ |
| DataSources: map[string]providers.Schema{ |
| "checks_object": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "number": { |
| Type: cty.Number, |
| Computed: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| ReadDataSourceFn: func(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| return providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "number": cty.NumberIntVal(0), |
| }), |
| } |
| }, |
| }, |
| }, |
| "nested data blocks reload during apply": { |
| configs: map[string]string{ |
| "main.tf": ` |
| provider "checks" {} |
| |
| data "checks_object" "data_block" {} |
| |
| check "data_block" { |
| assert { |
| condition = data.checks_object.data_block.number >= 0 |
| error_message = "negative number" |
| } |
| } |
| |
| check "nested_data_block" { |
| data "checks_object" "nested_data_block" {} |
| |
| assert { |
| condition = data.checks_object.nested_data_block.number >= 0 |
| error_message = "negative number" |
| } |
| } |
| `, |
| }, |
| plan: map[string]checksTestingStatus{ |
| "nested_data_block": { |
| status: checks.StatusFail, |
| messages: []string{"negative number"}, |
| }, |
| "data_block": { |
| status: checks.StatusFail, |
| messages: []string{"negative number"}, |
| }, |
| }, |
| planWarning: "2 warnings:\n\n- Check block assertion failed: negative number\n- Check block assertion failed: negative number", |
| apply: map[string]checksTestingStatus{ |
| "nested_data_block": { |
| status: checks.StatusPass, |
| }, |
| "data_block": { |
| status: checks.StatusFail, |
| messages: []string{"negative number"}, |
| }, |
| }, |
| applyWarning: "Check block assertion failed: negative number", |
| provider: &MockProvider{ |
| Meta: "checks", |
| GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ |
| DataSources: map[string]providers.Schema{ |
| "checks_object": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "number": { |
| Type: cty.Number, |
| Computed: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| ReadDataSourceFn: func(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| return providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "number": cty.NumberIntVal(-1), |
| }), |
| } |
| }, |
| }, |
| providerHook: func(provider *MockProvider) { |
| provider.ReadDataSourceFn = func(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| // The data returned by the data sources are changing |
| // between the plan and apply stage. The nested data block |
| // will update to reflect this while the normal data block |
| // will not detect the change. |
| return providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "number": cty.NumberIntVal(0), |
| }), |
| } |
| } |
| }, |
| }, |
| "returns unknown for unknown config": { |
| configs: map[string]string{ |
| "main.tf": ` |
| provider "checks" {} |
| |
| resource "checks_object" "resource_block" {} |
| |
| check "resource_block" { |
| data "checks_object" "data_block" { |
| id = checks_object.resource_block.id |
| } |
| |
| assert { |
| condition = data.checks_object.data_block.number >= 0 |
| error_message = "negative number" |
| } |
| } |
| `, |
| }, |
| plan: map[string]checksTestingStatus{ |
| "resource_block": { |
| status: checks.StatusUnknown, |
| }, |
| }, |
| planWarning: "Check block assertion known after apply: The condition could not be evaluated at this time, a result will be known when this plan is applied.", |
| apply: map[string]checksTestingStatus{ |
| "resource_block": { |
| status: checks.StatusPass, |
| }, |
| }, |
| provider: &MockProvider{ |
| Meta: "checks", |
| GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ |
| ResourceTypes: map[string]providers.Schema{ |
| "checks_object": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": { |
| Type: cty.String, |
| Computed: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| DataSources: map[string]providers.Schema{ |
| "checks_object": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": { |
| Type: cty.String, |
| Required: true, |
| }, |
| "number": { |
| Type: cty.Number, |
| Computed: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| PlanResourceChangeFn: func(request providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { |
| return providers.PlanResourceChangeResponse{ |
| PlannedState: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| }), |
| } |
| }, |
| ApplyResourceChangeFn: func(request providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { |
| return providers.ApplyResourceChangeResponse{ |
| NewState: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("7A9F887D-44C7-4281-80E5-578E41F99DFC"), |
| }), |
| } |
| }, |
| ReadDataSourceFn: func(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| values := request.Config.AsValueMap() |
| if id, ok := values["id"]; ok { |
| if id.IsKnown() && id.AsString() == "7A9F887D-44C7-4281-80E5-578E41F99DFC" { |
| return providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.StringVal("7A9F887D-44C7-4281-80E5-578E41F99DFC"), |
| "number": cty.NumberIntVal(0), |
| }), |
| } |
| } |
| } |
| |
| return providers.ReadDataSourceResponse{ |
| Diagnostics: tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "shouldn't make it here", "really shouldn't make it here")}, |
| } |
| }, |
| }, |
| }, |
| "failing nested data source doesn't block the plan": { |
| configs: map[string]string{ |
| "main.tf": ` |
| provider "checks" {} |
| |
| check "error" { |
| data "checks_object" "data_block" {} |
| |
| assert { |
| condition = data.checks_object.data_block.number >= 0 |
| error_message = "negative number" |
| } |
| } |
| `, |
| }, |
| plan: map[string]checksTestingStatus{ |
| "error": { |
| status: checks.StatusFail, |
| messages: []string{ |
| "data source read failed: something bad happened and the provider couldn't read the data source", |
| }, |
| }, |
| }, |
| planWarning: "data source read failed: something bad happened and the provider couldn't read the data source", |
| apply: map[string]checksTestingStatus{ |
| "error": { |
| status: checks.StatusFail, |
| messages: []string{ |
| "data source read failed: something bad happened and the provider couldn't read the data source", |
| }, |
| }, |
| }, |
| applyWarning: "data source read failed: something bad happened and the provider couldn't read the data source", |
| provider: &MockProvider{ |
| Meta: "checks", |
| GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ |
| DataSources: map[string]providers.Schema{ |
| "checks_object": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "number": { |
| Type: cty.Number, |
| Computed: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| ReadDataSourceFn: func(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| return providers.ReadDataSourceResponse{ |
| Diagnostics: tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "data source read failed", "something bad happened and the provider couldn't read the data source")}, |
| } |
| }, |
| }, |
| }, "failing nested data source should prevent checks from executing": { |
| configs: map[string]string{ |
| "main.tf": ` |
| provider "checks" {} |
| |
| resource "checks_object" "resource_block" { |
| number = -1 |
| } |
| |
| check "error" { |
| data "checks_object" "data_block" {} |
| |
| assert { |
| condition = checks_object.resource_block.number >= 0 |
| error_message = "negative number" |
| } |
| } |
| `, |
| }, |
| state: states.BuildState(func(state *states.SyncState) { |
| state.SetResourceInstanceCurrent( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "checks_object", |
| Name: "resource_block", |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"number": -1}`), |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("test"), |
| Module: addrs.RootModule, |
| }) |
| }), |
| plan: map[string]checksTestingStatus{ |
| "error": { |
| status: checks.StatusFail, |
| messages: []string{ |
| "data source read failed: something bad happened and the provider couldn't read the data source", |
| }, |
| }, |
| }, |
| planWarning: "data source read failed: something bad happened and the provider couldn't read the data source", |
| apply: map[string]checksTestingStatus{ |
| "error": { |
| status: checks.StatusFail, |
| messages: []string{ |
| "data source read failed: something bad happened and the provider couldn't read the data source", |
| }, |
| }, |
| }, |
| applyWarning: "data source read failed: something bad happened and the provider couldn't read the data source", |
| provider: &MockProvider{ |
| Meta: "checks", |
| GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ |
| ResourceTypes: map[string]providers.Schema{ |
| "checks_object": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "number": { |
| Type: cty.Number, |
| Required: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| DataSources: map[string]providers.Schema{ |
| "checks_object": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "number": { |
| Type: cty.Number, |
| Computed: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| PlanResourceChangeFn: func(request providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { |
| return providers.PlanResourceChangeResponse{ |
| PlannedState: cty.ObjectVal(map[string]cty.Value{ |
| "number": cty.NumberIntVal(-1), |
| }), |
| } |
| }, |
| ApplyResourceChangeFn: func(request providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { |
| return providers.ApplyResourceChangeResponse{ |
| NewState: cty.ObjectVal(map[string]cty.Value{ |
| "number": cty.NumberIntVal(-1), |
| }), |
| } |
| }, |
| ReadDataSourceFn: func(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| return providers.ReadDataSourceResponse{ |
| Diagnostics: tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "data source read failed", "something bad happened and the provider couldn't read the data source")}, |
| } |
| }, |
| }, |
| }, |
| "check failing in state and passing after plan and apply": { |
| configs: map[string]string{ |
| "main.tf": ` |
| provider "checks" {} |
| |
| resource "checks_object" "resource" { |
| number = 0 |
| } |
| |
| check "passing" { |
| assert { |
| condition = checks_object.resource.number >= 0 |
| error_message = "negative number" |
| } |
| } |
| `, |
| }, |
| state: states.BuildState(func(state *states.SyncState) { |
| state.SetResourceInstanceCurrent( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "checks_object", |
| Name: "resource", |
| }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| AttrsJSON: []byte(`{"number": -1}`), |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("test"), |
| Module: addrs.RootModule, |
| }) |
| }), |
| plan: map[string]checksTestingStatus{ |
| "passing": { |
| status: checks.StatusPass, |
| }, |
| }, |
| apply: map[string]checksTestingStatus{ |
| "passing": { |
| status: checks.StatusPass, |
| }, |
| }, |
| provider: &MockProvider{ |
| Meta: "checks", |
| GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ |
| ResourceTypes: map[string]providers.Schema{ |
| "checks_object": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "number": { |
| Type: cty.Number, |
| Required: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| PlanResourceChangeFn: func(request providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { |
| return providers.PlanResourceChangeResponse{ |
| PlannedState: cty.ObjectVal(map[string]cty.Value{ |
| "number": cty.NumberIntVal(0), |
| }), |
| } |
| }, |
| ApplyResourceChangeFn: func(request providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { |
| return providers.ApplyResourceChangeResponse{ |
| NewState: cty.ObjectVal(map[string]cty.Value{ |
| "number": cty.NumberIntVal(0), |
| }), |
| } |
| }, |
| }, |
| }, |
| "failing data source does block the plan": { |
| configs: map[string]string{ |
| "main.tf": ` |
| provider "checks" {} |
| |
| data "checks_object" "data_block" {} |
| |
| check "error" { |
| assert { |
| condition = data.checks_object.data_block.number >= 0 |
| error_message = "negative number" |
| } |
| } |
| `, |
| }, |
| planError: "data source read failed: something bad happened and the provider couldn't read the data source", |
| provider: &MockProvider{ |
| Meta: "checks", |
| GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ |
| DataSources: map[string]providers.Schema{ |
| "checks_object": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "number": { |
| Type: cty.Number, |
| Computed: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| ReadDataSourceFn: func(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| return providers.ReadDataSourceResponse{ |
| Diagnostics: tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "data source read failed", "something bad happened and the provider couldn't read the data source")}, |
| } |
| }, |
| }, |
| }, |
| "invalid reference into check block": { |
| configs: map[string]string{ |
| "main.tf": ` |
| provider "checks" {} |
| |
| data "checks_object" "data_block" { |
| id = data.checks_object.nested_data_block.id |
| } |
| |
| check "error" { |
| data "checks_object" "nested_data_block" {} |
| |
| assert { |
| condition = data.checks_object.data_block.number >= 0 |
| error_message = "negative number" |
| } |
| } |
| `, |
| }, |
| planError: "Reference to scoped resource: The referenced data resource \"checks_object\" \"nested_data_block\" is not available from this context.", |
| provider: &MockProvider{ |
| Meta: "checks", |
| GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ |
| DataSources: map[string]providers.Schema{ |
| "checks_object": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": { |
| Type: cty.String, |
| Computed: true, |
| Optional: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| ReadDataSourceFn: func(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { |
| input := request.Config.AsValueMap() |
| if _, ok := input["id"]; ok { |
| return providers.ReadDataSourceResponse{ |
| State: request.Config, |
| } |
| } |
| |
| return providers.ReadDataSourceResponse{ |
| State: cty.ObjectVal(map[string]cty.Value{ |
| "id": cty.UnknownVal(cty.String), |
| }), |
| } |
| }, |
| }, |
| }, |
| } |
| for name, test := range tests { |
| t.Run(name, func(t *testing.T) { |
| configs := testModuleInline(t, test.configs) |
| ctx := testContext2(t, &ContextOpts{ |
| Providers: map[addrs.Provider]providers.Factory{ |
| addrs.NewDefaultProvider(test.provider.Meta.(string)): testProviderFuncFixed(test.provider), |
| }, |
| }) |
| |
| initialState := states.NewState() |
| if test.state != nil { |
| initialState = test.state |
| } |
| |
| plan, diags := ctx.Plan(configs, initialState, &PlanOpts{ |
| Mode: plans.NormalMode, |
| }) |
| if validateCheckDiagnostics(t, "planning", test.planWarning, test.planError, diags) { |
| return |
| } |
| validateCheckResults(t, "planning", test.plan, plan.Checks) |
| |
| if test.providerHook != nil { |
| // This gives an opportunity to change the behaviour of the |
| // provider between the plan and apply stages. |
| test.providerHook(test.provider) |
| } |
| |
| state, diags := ctx.Apply(plan, configs) |
| if validateCheckDiagnostics(t, "apply", test.applyWarning, test.applyError, diags) { |
| return |
| } |
| validateCheckResults(t, "apply", test.apply, state.CheckResults) |
| }) |
| } |
| } |
| |
| func validateCheckDiagnostics(t *testing.T, stage string, expectedWarning, expectedError string, actual tfdiags.Diagnostics) bool { |
| if expectedError != "" { |
| if !actual.HasErrors() { |
| t.Errorf("expected %s to error with \"%s\", but no errors were returned", stage, expectedError) |
| } else if expectedError != actual.Err().Error() { |
| t.Errorf("expected %s to error with \"%s\" but found \"%s\"", stage, expectedError, actual.Err()) |
| } |
| |
| // If we expected an error then we won't finish the rest of the test. |
| return true |
| } |
| |
| if expectedWarning != "" { |
| warnings := actual.ErrWithWarnings() |
| if actual.ErrWithWarnings() == nil { |
| t.Errorf("expected %s to warn with \"%s\", but no errors were returned", stage, expectedWarning) |
| } else if expectedWarning != warnings.Error() { |
| t.Errorf("expected %s to warn with \"%s\" but found \"%s\"", stage, expectedWarning, warnings) |
| } |
| } else { |
| if actual.ErrWithWarnings() != nil { |
| t.Errorf("expected %s to produce no diagnostics but found \"%s\"", stage, actual.ErrWithWarnings()) |
| } |
| } |
| |
| assertNoErrors(t, actual) |
| return false |
| } |
| |
| func validateCheckResults(t *testing.T, stage string, expected map[string]checksTestingStatus, actual *states.CheckResults) { |
| |
| // Just a quick sanity check that the plan or apply process didn't create |
| // some non-existent checks. |
| if len(expected) != len(actual.ConfigResults.Keys()) { |
| t.Errorf("expected %d check results but found %d after %s", len(expected), len(actual.ConfigResults.Keys()), stage) |
| } |
| |
| // Now, lets make sure the checks all match what we expect. |
| for check, want := range expected { |
| results := actual.GetObjectResult(addrs.Check{ |
| Name: check, |
| }.Absolute(addrs.RootModuleInstance)) |
| |
| if results.Status != want.status { |
| t.Errorf("%s: wanted %s but got %s after %s", check, want.status, results.Status, stage) |
| } |
| |
| if len(want.messages) != len(results.FailureMessages) { |
| t.Errorf("%s: expected %d failure messages but had %d after %s", check, len(want.messages), len(results.FailureMessages), stage) |
| } |
| |
| max := len(want.messages) |
| if len(results.FailureMessages) > max { |
| max = len(results.FailureMessages) |
| } |
| |
| for ix := 0; ix < max; ix++ { |
| var expected, actual string |
| if ix < len(want.messages) { |
| expected = want.messages[ix] |
| } |
| if ix < len(results.FailureMessages) { |
| actual = results.FailureMessages[ix] |
| } |
| |
| // Order matters! |
| if actual != expected { |
| t.Errorf("%s: expected failure message at %d to be \"%s\" but was \"%s\" after %s", check, ix, expected, actual, stage) |
| } |
| } |
| |
| } |
| } |