diff --git a/.changes/unreleased/FEATURES-20240111-142126.yaml b/.changes/unreleased/FEATURES-20240111-142126.yaml new file mode 100644 index 000000000..99c271b7c --- /dev/null +++ b/.changes/unreleased/FEATURES-20240111-142126.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'statecheck: Introduced new `statecheck` package with interface and built-in + state check functionality' +time: 2024-01-11T14:21:26.261094Z +custom: + Issue: "273" diff --git a/.changes/unreleased/FEATURES-20240111-142223.yaml b/.changes/unreleased/FEATURES-20240111-142223.yaml new file mode 100644 index 000000000..ca6a2a386 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240111-142223.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'statecheck: Added `ExpectKnownValue` state check, which asserts that a given + resource attribute has a defined type, and value' +time: 2024-01-11T14:22:23.072321Z +custom: + Issue: "273" diff --git a/.changes/unreleased/FEATURES-20240111-142314.yaml b/.changes/unreleased/FEATURES-20240111-142314.yaml new file mode 100644 index 000000000..3d683c564 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240111-142314.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'statecheck: Added `ExpectKnownOutputValue` state check, which asserts that + a given output value has a defined type, and value' +time: 2024-01-11T14:23:14.025585Z +custom: + Issue: "273" diff --git a/.changes/unreleased/FEATURES-20240111-142353.yaml b/.changes/unreleased/FEATURES-20240111-142353.yaml new file mode 100644 index 000000000..eaa67ae05 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240111-142353.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'statecheck: Added `ExpectKnownOutputValueAtPath` plan check, which asserts + that a given output value at a specified path has a defined type, and value' +time: 2024-01-11T14:23:53.633255Z +custom: + Issue: "273" diff --git a/.changes/unreleased/FEATURES-20240111-142544.yaml b/.changes/unreleased/FEATURES-20240111-142544.yaml new file mode 100644 index 000000000..42bf8aa03 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240111-142544.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'statecheck: Added `ExpectSensitiveValue` built-in state check, which asserts + that a given attribute has a sensitive value' +time: 2024-01-11T14:25:44.598583Z +custom: + Issue: "273" diff --git a/helper/resource/state_checks.go b/helper/resource/state_checks.go new file mode 100644 index 000000000..66c850eae --- /dev/null +++ b/helper/resource/state_checks.go @@ -0,0 +1,29 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + "errors" + + tfjson "github.com/hashicorp/terraform-json" + "github.com/mitchellh/go-testing-interface" + + "github.com/hashicorp/terraform-plugin-testing/statecheck" +) + +func runStateChecks(ctx context.Context, t testing.T, state *tfjson.State, stateChecks []statecheck.StateCheck) error { + t.Helper() + + var result []error + + for _, stateCheck := range stateChecks { + resp := statecheck.CheckStateResponse{} + stateCheck.CheckState(ctx, statecheck.CheckStateRequest{State: state}, &resp) + + result = append(result, resp.Error) + } + + return errors.Join(result...) +} diff --git a/helper/resource/state_checks_test.go b/helper/resource/state_checks_test.go new file mode 100644 index 000000000..e8ab33753 --- /dev/null +++ b/helper/resource/state_checks_test.go @@ -0,0 +1,22 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-testing/statecheck" +) + +var _ statecheck.StateCheck = &stateCheckSpy{} + +type stateCheckSpy struct { + err error + called bool +} + +func (s *stateCheckSpy) CheckState(ctx context.Context, req statecheck.CheckStateRequest, resp *statecheck.CheckStateResponse) { + s.called = true + resp.Error = s.err +} diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 581152f72..5abda4eaa 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -24,6 +24,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-plugin-testing/tfversion" @@ -590,6 +591,13 @@ type TestStep struct { // [plancheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck RefreshPlanChecks RefreshPlanChecks + // ConfigStateChecks allow assertions to be made against the state file at different points of a Config (apply) test using a state check. + // Custom state checks can be created by implementing the [StateCheck] interface, or by using a StateCheck implementation from the provided [statecheck] package + // + // [StateCheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#StateCheck + // [statecheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck + ConfigStateChecks ConfigStateChecks + // PlanOnly can be set to only run `plan` with this configuration, and not // actually apply it. This is useful for ensuring config changes result in // no-op plans @@ -795,6 +803,10 @@ type RefreshPlanChecks struct { PostRefresh []plancheck.PlanCheck } +// ConfigStateChecks runs all state checks in the slice. This occurs after the apply and refresh of a Config test are run. +// All errors by state checks in this slice are aggregated, reported, and will result in a test failure. +type ConfigStateChecks []statecheck.StateCheck + // ParallelTest performs an acceptance test on a resource, allowing concurrency // with other ParallelTest. The number of concurrent tests is controlled by the // "go test" command -parallel flag. diff --git a/helper/resource/testing_new_config.go b/helper/resource/testing_new_config.go index 35e605c1d..17d3745fa 100644 --- a/helper/resource/testing_new_config.go +++ b/helper/resource/testing_new_config.go @@ -313,6 +313,26 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint } } + // Run post-apply, post-refresh state checks + if len(step.ConfigStateChecks) > 0 { + var state *tfjson.State + + err = runProviderCommand(ctx, t, func() error { + var err error + state, err = wd.State(ctx) + return err + }, wd, providers) + + if err != nil { + return fmt.Errorf("Error retrieving post-apply, post-refresh state: %w", err) + } + + err = runStateChecks(ctx, t, state, step.ConfigStateChecks) + if err != nil { + return fmt.Errorf("Post-apply refresh state check(s) failed:\n%w", err) + } + } + // check if plan is empty if !planIsEmpty(plan, helper.TerraformVersion()) && !step.ExpectNonEmptyPlan { var stdout string diff --git a/helper/resource/testing_new_config_test.go b/helper/resource/testing_new_config_test.go index b54eb4282..fe0eb707e 100644 --- a/helper/resource/testing_new_config_test.go +++ b/helper/resource/testing_new_config_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" @@ -717,3 +718,126 @@ func Test_ConfigPlanChecks_PostApplyPostRefresh_Errors(t *testing.T) { }, }) } + +func Test_ConfigStateChecks_Called(t *testing.T) { + t.Parallel() + + spy1 := &stateCheckSpy{} + spy2 := &stateCheckSpy{} + UnitTest(t, TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_0_0), // ProtoV6ProviderFactories + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource": { + CreateResponse: &resource.CreateResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "test"), + }, + ), + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []TestStep{ + { + Config: `resource "test_resource" "test" {}`, + ConfigStateChecks: ConfigStateChecks{ + spy1, + spy2, + }, + }, + }, + }) + + if !spy1.called { + t.Error("expected ConfigStateChecks spy1 to be called at least once") + } + + if !spy2.called { + t.Error("expected ConfigStateChecks spy2 to be called at least once") + } +} + +func Test_ConfigStateChecks_Errors(t *testing.T) { + t.Parallel() + + spy1 := &stateCheckSpy{} + spy2 := &stateCheckSpy{ + err: errors.New("spy2 check failed"), + } + spy3 := &stateCheckSpy{ + err: errors.New("spy3 check failed"), + } + UnitTest(t, TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_0_0), // ProtoV6ProviderFactories + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource": { + CreateResponse: &resource.CreateResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "test"), + }, + ), + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []TestStep{ + { + Config: `resource "test_resource" "test" {}`, + ConfigStateChecks: ConfigStateChecks{ + spy1, + spy2, + spy3, + }, + ExpectError: regexp.MustCompile(`.*?(spy2 check failed)\n.*?(spy3 check failed)`), + }, + }, + }) +} diff --git a/helper/resource/teststep_validate.go b/helper/resource/teststep_validate.go index d63db4dc8..8590ca466 100644 --- a/helper/resource/teststep_validate.go +++ b/helper/resource/teststep_validate.go @@ -15,7 +15,7 @@ import ( // testStepValidateRequest contains data for the (TestStep).validate() method. type testStepValidateRequest struct { // StepConfiguration contains the TestStep configuration derived from - // TestStep.Config or TestStep.ConfigDirectory. + // TestStep.Config, TestStep.ConfigDirectory, or TestStep.ConfigFile. StepConfiguration teststep.Config // StepNumber is the index of the TestStep in the TestCase.Steps. @@ -235,5 +235,11 @@ func (s TestStep) validate(ctx context.Context, req testStepValidateRequest) err return err } + if len(s.ConfigStateChecks) > 0 && req.StepConfiguration == nil { + err := fmt.Errorf("TestStep ConfigStateChecks must only be specified with Config, ConfigDirectory or ConfigFile") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + return nil } diff --git a/helper/resource/teststep_validate_test.go b/helper/resource/teststep_validate_test.go index c5f19a60e..6a326e003 100644 --- a/helper/resource/teststep_validate_test.go +++ b/helper/resource/teststep_validate_test.go @@ -466,6 +466,16 @@ func TestTestStepValidate(t *testing.T) { testStepValidateRequest: testStepValidateRequest{TestCaseHasProviders: true}, expectedError: errors.New("TestStep ConfigPlanChecks.PostApplyPostRefresh must only be specified with Config"), }, + "configstatechecks-not-config-mode": { + testStep: TestStep{ + ConfigStateChecks: ConfigStateChecks{ + &stateCheckSpy{}, + }, + RefreshState: true, + }, + testStepValidateRequest: testStepValidateRequest{TestCaseHasProviders: true}, + expectedError: errors.New("TestStep ConfigStateChecks must only be specified with Config"), + }, "refreshplanchecks-postrefresh-not-refresh-mode": { testStep: TestStep{ RefreshPlanChecks: RefreshPlanChecks{ diff --git a/knownvalue/null.go b/knownvalue/null.go new file mode 100644 index 000000000..f10c5a3f5 --- /dev/null +++ b/knownvalue/null.go @@ -0,0 +1,32 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" +) + +var _ Check = nullExact{} + +type nullExact struct{} + +// CheckValue determines whether the passed value is of nil. +func (v nullExact) CheckValue(other any) error { + if other != nil { + return fmt.Errorf("expected value nil for NullExact check, got: %T", other) + } + + return nil +} + +// String returns the string representation of nil. +func (v nullExact) String() string { + return "nil" +} + +// NullExact returns a Check for asserting equality nil +// and the value passed to the CheckValue method. +func NullExact() nullExact { + return nullExact{} +} diff --git a/knownvalue/null_test.go b/knownvalue/null_test.go new file mode 100644 index 000000000..94682ed7b --- /dev/null +++ b/knownvalue/null_test.go @@ -0,0 +1,64 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue_test + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" +) + +func TestNullValue_CheckValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + self knownvalue.Check + other any + expectedError error + }{ + "zero-nil": { + self: knownvalue.NullExact(), + }, + "zero-other": { + self: knownvalue.NullExact(), + other: nil, // checking against the underlying value field zero-value + }, + "not-nil": { + self: knownvalue.NullExact(), + other: false, + expectedError: fmt.Errorf("expected value nil for NullExact check, got: bool"), + }, + "equal": { + self: knownvalue.NullExact(), + other: nil, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.self.CheckValue(testCase.other) + + if diff := cmp.Diff(got, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestNullValue_String(t *testing.T) { + t.Parallel() + + got := knownvalue.NullExact().String() + + if diff := cmp.Diff(got, "nil"); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } +} diff --git a/statecheck/doc.go b/statecheck/doc.go new file mode 100644 index 000000000..eba32447d --- /dev/null +++ b/statecheck/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package statecheck contains the state check interface, request/response structs, and common state check implementations. +package statecheck diff --git a/statecheck/expect_known_output_value.go b/statecheck/expect_known_output_value.go new file mode 100644 index 000000000..64c47f819 --- /dev/null +++ b/statecheck/expect_known_output_value.go @@ -0,0 +1,72 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource State Check +var _ StateCheck = expectKnownOutputValue{} + +type expectKnownOutputValue struct { + outputAddress string + knownValue knownvalue.Check +} + +// CheckState implements the state check logic. +func (e expectKnownOutputValue) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) { + var output *tfjson.StateOutput + + if req.State == nil { + resp.Error = fmt.Errorf("state is nil") + } + + if req.State.Values == nil { + resp.Error = fmt.Errorf("state does not contain any state values") + } + + for address, oc := range req.State.Values.Outputs { + if e.outputAddress == address { + output = oc + + break + } + } + + if output == nil { + resp.Error = fmt.Errorf("%s - Output not found in state", e.outputAddress) + + return + } + + result, err := tfjsonpath.Traverse(output.Value, tfjsonpath.Path{}) + + if err != nil { + resp.Error = err + + return + } + + if err := e.knownValue.CheckValue(result); err != nil { + resp.Error = fmt.Errorf("error checking value for output at path: %s, err: %s", e.outputAddress, err) + + return + } +} + +// ExpectKnownOutputValue returns a state check that asserts that the specified value +// has a known type, and value. +func ExpectKnownOutputValue(outputAddress string, knownValue knownvalue.Check) StateCheck { + return expectKnownOutputValue{ + outputAddress: outputAddress, + knownValue: knownValue, + } +} diff --git a/statecheck/expect_known_output_value_at_path.go b/statecheck/expect_known_output_value_at_path.go new file mode 100644 index 000000000..9fb8cc4f1 --- /dev/null +++ b/statecheck/expect_known_output_value_at_path.go @@ -0,0 +1,74 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource State Check +var _ StateCheck = expectKnownOutputValueAtPath{} + +type expectKnownOutputValueAtPath struct { + outputAddress string + outputPath tfjsonpath.Path + knownValue knownvalue.Check +} + +// CheckState implements the state check logic. +func (e expectKnownOutputValueAtPath) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) { + var output *tfjson.StateOutput + + if req.State == nil { + resp.Error = fmt.Errorf("state is nil") + } + + if req.State.Values == nil { + resp.Error = fmt.Errorf("state does not contain any state values") + } + + for address, oc := range req.State.Values.Outputs { + if e.outputAddress == address { + output = oc + + break + } + } + + if output == nil { + resp.Error = fmt.Errorf("%s - Output not found in state", e.outputAddress) + + return + } + + result, err := tfjsonpath.Traverse(output.Value, e.outputPath) + + if err != nil { + resp.Error = err + + return + } + + if err := e.knownValue.CheckValue(result); err != nil { + resp.Error = fmt.Errorf("error checking value for output at path: %s.%s, err: %s", e.outputAddress, e.outputPath.String(), err) + + return + } +} + +// ExpectKnownOutputValueAtPath returns a state check that asserts that the specified output at the given path +// has a known type and value. +func ExpectKnownOutputValueAtPath(outputAddress string, outputPath tfjsonpath.Path, knownValue knownvalue.Check) StateCheck { + return expectKnownOutputValueAtPath{ + outputAddress: outputAddress, + outputPath: outputPath, + knownValue: knownValue, + } +} diff --git a/statecheck/expect_known_output_value_at_path_test.go b/statecheck/expect_known_output_value_at_path_test.go new file mode 100644 index 000000000..078daee04 --- /dev/null +++ b/statecheck/expect_known_output_value_at_path_test.go @@ -0,0 +1,1475 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck_test + +import ( + "context" + "fmt" + "math/big" + "regexp" + "testing" + + "github.com/google/go-cmp/cmp" + tfjson "github.com/hashicorp/terraform-json" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestExpectKnownOutputValueAtPath_CheckState_ResourceNotFound(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_two_output", + tfjsonpath.New("bool_attribute"), + knownvalue.BoolExact(true), + ), + }, + ExpectError: regexp.MustCompile("test_resource_two_output - Output not found in state"), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_AttributeValueNull(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" {} + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("bool_attribute"), + knownvalue.NullExact(), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Bool(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("bool_attribute"), + knownvalue.BoolExact(true), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Bool_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("bool_attribute"), + knownvalue.Float64Exact(1.23), + ), + }, + ExpectError: regexp.MustCompile(`expected json\.Number value for Float64Exact check, got: bool`), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Bool_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("bool_attribute"), + knownvalue.BoolExact(false), + ), + }, + ExpectError: regexp.MustCompile("expected value false for BoolExact check, got: true"), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Float64(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + float_attribute = 1.23 + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("float_attribute"), + knownvalue.Float64Exact(1.23), + ), + }, + }, + }, + }) +} + +// We do not need equivalent tests for Int64 and Number as they all test the same logic. +func TestExpectKnownOutputValueAtPath_CheckState_Float64_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + float_attribute = 1.23 + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("float_attribute"), + knownvalue.StringExact("str"), + ), + }, + ExpectError: regexp.MustCompile(`expected string value for StringExact check, got: json\.Number`), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Float64_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + float_attribute = 1.23 + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("float_attribute"), + knownvalue.Float64Exact(3.21), + ), + }, + ExpectError: regexp.MustCompile("expected value 3.21 for Float64Exact check, got: 1.23"), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Int64(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + int_attribute = 123 + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("int_attribute"), + knownvalue.Int64Exact(123), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Int64_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + int_attribute = 123 + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("int_attribute"), + knownvalue.Int64Exact(321), + ), + }, + ExpectError: regexp.MustCompile("expected value 321 for Int64Exact check, got: 123"), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_List(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("list_attribute"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_List_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("list_attribute"), + knownvalue.MapExact(map[string]knownvalue.Check{}), + ), + }, + ExpectError: regexp.MustCompile(`expected map\[string\]any value for MapExact check, got: \[\]interface {}`), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_List_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("list_attribute"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("value3"), + knownvalue.StringExact("value4"), + }), + ), + }, + ExpectError: regexp.MustCompile(`list element index 0: expected value value3 for StringExact check, got: value1`), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_ListPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("list_attribute"), + knownvalue.ListPartial(map[int]knownvalue.Check{ + 0: knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }) +} + +// No need to check KnownValueWrongType for ListPartial as all lists, and sets are []any in +// tfjson.State. +func TestExpectKnownOutputValueAtPath_CheckState_ListPartial_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("list_attribute"), + knownvalue.ListPartial(map[int]knownvalue.Check{ + 0: knownvalue.StringExact("value3"), + }), + ), + }, + ExpectError: regexp.MustCompile(`list element 0: expected value value3 for StringExact check, got: value1`), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_ListElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("list_attribute"), + knownvalue.ListSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_ListElements_WrongNum(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("list_attribute"), + knownvalue.ListSizeExact(3), + ), + }, + ExpectError: regexp.MustCompile("expected 3 elements for ListSizeExact check, got 2 elements"), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_ListNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_nested_block { + list_nested_block_attribute = "str" + } + list_nested_block { + list_nested_block_attribute = "rts" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("list_nested_block"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.MapExact(map[string]knownvalue.Check{ + "list_nested_block_attribute": knownvalue.StringExact("str"), + }), + knownvalue.MapExact(map[string]knownvalue.Check{ + "list_nested_block_attribute": knownvalue.StringExact("rts"), + }), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_ListNestedBlockPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_nested_block { + list_nested_block_attribute = "str" + } + list_nested_block { + list_nested_block_attribute = "rts" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("list_nested_block"), + knownvalue.ListPartial(map[int]knownvalue.Check{ + 1: knownvalue.MapExact(map[string]knownvalue.Check{ + "list_nested_block_attribute": knownvalue.StringExact("rts"), + }), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_ListNestedBlockElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_nested_block { + list_nested_block_attribute = "str" + } + list_nested_block { + list_nested_block_attribute = "rts" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("list_nested_block"), + knownvalue.ListSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Map(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("map_attribute"), + knownvalue.MapExact(map[string]knownvalue.Check{ + "key1": knownvalue.StringExact("value1"), + "key2": knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Map_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("map_attribute"), + knownvalue.ListExact([]knownvalue.Check{}), + ), + }, + ExpectError: regexp.MustCompile(`expected \[\]any value for ListExact check, got: map\[string\]interface {}`), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Map_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("map_attribute"), + knownvalue.MapExact(map[string]knownvalue.Check{ + "key3": knownvalue.StringExact("value3"), + "key4": knownvalue.StringExact("value4"), + }), + ), + }, + ExpectError: regexp.MustCompile(`missing element key3 for MapExact check`), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_MapPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("map_attribute"), + knownvalue.MapPartial(map[string]knownvalue.Check{ + "key1": knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_MapPartial_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("map_attribute"), + knownvalue.MapPartial(map[string]knownvalue.Check{ + "key3": knownvalue.StringExact("value1"), + }), + ), + }, + ExpectError: regexp.MustCompile(`missing element key3 for MapPartial check`), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_MapElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("map_attribute"), + knownvalue.MapSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_MapElements_WrongNum(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("map_attribute"), + knownvalue.MapSizeExact(3), + ), + }, + ExpectError: regexp.MustCompile("expected 3 elements for MapSizeExact check, got 2 elements"), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Number(t *testing.T) { + t.Parallel() + + f, _, err := big.ParseFloat("123", 10, 512, big.ToNearestEven) + + if err != nil { + t.Errorf("%s", err) + } + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + int_attribute = 123 + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("int_attribute"), + knownvalue.NumberExact(f), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Number_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + f, _, err := big.ParseFloat("321", 10, 512, big.ToNearestEven) + + if err != nil { + t.Errorf("%s", err) + } + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + int_attribute = 123 + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("int_attribute"), + knownvalue.NumberExact(f), + ), + }, + ExpectError: regexp.MustCompile("expected value 321 for NumberExact check, got: 123"), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Set(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("set_attribute"), + knownvalue.SetExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_Set_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("set_attribute"), + knownvalue.SetExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value3"), + }), + ), + }, + ExpectError: regexp.MustCompile(`missing value value3 for SetExact check`), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_SetPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("set_attribute"), + knownvalue.SetPartial([]knownvalue.Check{ + knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_SetPartial_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("set_attribute"), + knownvalue.SetPartial([]knownvalue.Check{ + knownvalue.StringExact("value3"), + }), + ), + }, + ExpectError: regexp.MustCompile(`missing value value3 for SetPartial check`), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_SetElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("set_attribute"), + knownvalue.SetSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_SetNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_block { + set_nested_block_attribute = "str" + } + set_nested_block { + set_nested_block_attribute = "rts" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("set_nested_block"), + knownvalue.SetExact([]knownvalue.Check{ + knownvalue.MapExact(map[string]knownvalue.Check{ + "set_nested_block_attribute": knownvalue.StringExact("str"), + }), + knownvalue.MapExact(map[string]knownvalue.Check{ + "set_nested_block_attribute": knownvalue.StringExact("rts"), + }), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_SetNestedBlockPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_block { + set_nested_block_attribute = "str" + } + set_nested_block { + set_nested_block_attribute = "rts" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("set_nested_block"), + knownvalue.SetPartial([]knownvalue.Check{ + knownvalue.MapExact(map[string]knownvalue.Check{ + "set_nested_block_attribute": knownvalue.StringExact("rts"), + }), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_SetNestedBlockElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_block { + set_nested_block_attribute = "str" + } + set_nested_block { + set_nested_block_attribute = "rts" + } + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("set_nested_block"), + knownvalue.SetSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_String(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("string_attribute"), + knownvalue.StringExact("str")), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_String_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("string_attribute"), + knownvalue.BoolExact(true)), + }, + ExpectError: regexp.MustCompile("expected bool value for BoolExact check, got: string"), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_String_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("string_attribute"), + knownvalue.StringExact("rts")), + }, + ExpectError: regexp.MustCompile("expected value rts for StringExact check, got: str"), + }, + }, + }) +} + +func TestExpectKnownOutputValueAtPath_CheckState_UnknownAttributeType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + knownValue knownvalue.Check + req statecheck.CheckStateRequest + expectedErr error + }{ + "unrecognised-type": { + knownValue: knownvalue.Int64Exact(123), + req: statecheck.CheckStateRequest{ + State: &tfjson.State{ + Values: &tfjson.StateValues{ + Outputs: map[string]*tfjson.StateOutput{ + "obj": { + Value: map[string]any{ + "float32_output": float32(123), + }, + }, + }, + }, + }, + }, + expectedErr: fmt.Errorf("error checking value for output at path: obj.float32_output, err: expected json.Number value for Int64Exact check, got: float32"), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + e := statecheck.ExpectKnownOutputValueAtPath("obj", tfjsonpath.New("float32_output"), testCase.knownValue) + + resp := statecheck.CheckStateResponse{} + + e.CheckState(context.Background(), testCase.req, &resp) + + if diff := cmp.Diff(resp.Error, testCase.expectedErr, equateErrorMessage); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/statecheck/expect_known_output_value_test.go b/statecheck/expect_known_output_value_test.go new file mode 100644 index 000000000..b3f5e2ccf --- /dev/null +++ b/statecheck/expect_known_output_value_test.go @@ -0,0 +1,1436 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck_test + +import ( + "context" + "fmt" + "math/big" + "regexp" + "testing" + + "github.com/google/go-cmp/cmp" + tfjson "github.com/hashicorp/terraform-json" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" +) + +func TestExpectKnownOutputValue_CheckState_OutputNotFound(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + + output bool_output { + value = test_resource.one.bool_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "bool_not_found", + knownvalue.BoolExact(true), + ), + }, + ExpectError: regexp.MustCompile("bool_not_found - Output not found in state"), + }, + }, + }) +} + +// TestExpectKnownOutputValue_CheckState_AttributeValueNull shows that outputs that reference +// null values do not appear in state. Indicating that there is no way to discriminate +// between null outputs and non-existent outputs. +// Reference: https://github.com/hashicorp/terraform/issues/34080 +func TestExpectKnownOutputValue_CheckState_AttributeValueNull(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" {} + output bool_output { + value = test_resource.one.bool_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "bool_output", + knownvalue.BoolExact(true), + ), + }, + ExpectError: regexp.MustCompile("bool_output - Output not found in state"), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Bool(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + + output bool_output { + value = test_resource.one.bool_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "bool_output", + knownvalue.BoolExact(true), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Bool_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + + output bool_output { + value = test_resource.one.bool_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "bool_output", + knownvalue.Float64Exact(1.23), + ), + }, + ExpectError: regexp.MustCompile(`expected json\.Number value for Float64Exact check, got: bool`), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Bool_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + + output bool_output { + value = test_resource.one.bool_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "bool_output", + knownvalue.BoolExact(false), + ), + }, + ExpectError: regexp.MustCompile("expected value false for BoolExact check, got: true"), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Float64(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + float_attribute = 1.23 + } + + output float64_output { + value = test_resource.one.float_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "float64_output", + knownvalue.Float64Exact(1.23), + ), + }, + }, + }, + }) +} + +// We do not need equivalent tests for Int64 and Number as they all test the same logic. +func TestExpectKnownOutputValue_CheckState_Float64_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + float_attribute = 1.23 + } + + output float64_output { + value = test_resource.one.float_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "float64_output", + knownvalue.StringExact("str"), + ), + }, + ExpectError: regexp.MustCompile(`expected string value for StringExact check, got: json\.Number`), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Float64_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + float_attribute = 1.23 + } + + output float64_output { + value = test_resource.one.float_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "float64_output", + knownvalue.Float64Exact(3.21), + ), + }, + ExpectError: regexp.MustCompile("expected value 3.21 for Float64Exact check, got: 1.23"), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Int64(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + int_attribute = 123 + } + + output int64_output { + value = test_resource.one.int_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "int64_output", + knownvalue.Int64Exact(123), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Int64_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + int_attribute = 123 + } + + output int64_output { + value = test_resource.one.int_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "int64_output", + knownvalue.Int64Exact(321), + ), + }, + ExpectError: regexp.MustCompile("expected value 321 for Int64Exact check, got: 123"), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_List(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output list_output { + value = test_resource.one.list_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "list_output", + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_List_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output list_output { + value = test_resource.one.list_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "list_output", + knownvalue.MapExact(map[string]knownvalue.Check{}), + ), + }, + ExpectError: regexp.MustCompile(`expected map\[string\]any value for MapExact check, got: \[\]interface {}`), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_List_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output list_output { + value = test_resource.one.list_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "list_output", + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("value3"), + knownvalue.StringExact("value4"), + }), + ), + }, + ExpectError: regexp.MustCompile(`list element index 0: expected value value3 for StringExact check, got: value1`), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_ListPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output list_output { + value = test_resource.one.list_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "list_output", + knownvalue.ListPartial(map[int]knownvalue.Check{ + 0: knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }) +} + +// No need to check KnownValueWrongType for ListPartial as all lists, and sets are []any in +// tfjson.State. +func TestExpectKnownOutputValue_CheckState_ListPartial_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output list_output { + value = test_resource.one.list_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "list_output", + knownvalue.ListPartial(map[int]knownvalue.Check{ + 0: knownvalue.StringExact("value3"), + }), + ), + }, + ExpectError: regexp.MustCompile(`list element 0: expected value value3 for StringExact check, got: value1`), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_ListElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output list_output { + value = test_resource.one.list_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "list_output", + knownvalue.ListSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_ListElements_WrongNum(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + + output list_output { + value = test_resource.one.list_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "list_output", + knownvalue.ListSizeExact(3), + ), + }, + ExpectError: regexp.MustCompile("expected 3 elements for ListSizeExact check, got 2 elements"), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_ListNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_nested_block { + list_nested_block_attribute = "str" + } + list_nested_block { + list_nested_block_attribute = "rts" + } + } + + output list_nested_block_output { + value = test_resource.one.list_nested_block + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "list_nested_block_output", + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.MapExact(map[string]knownvalue.Check{ + "list_nested_block_attribute": knownvalue.StringExact("str"), + }), + knownvalue.MapExact(map[string]knownvalue.Check{ + "list_nested_block_attribute": knownvalue.StringExact("rts"), + }), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_ListNestedBlockPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_nested_block { + list_nested_block_attribute = "str" + } + list_nested_block { + list_nested_block_attribute = "rts" + } + } + + output list_nested_block_output { + value = test_resource.one.list_nested_block + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "list_nested_block_output", + knownvalue.ListPartial(map[int]knownvalue.Check{ + 1: knownvalue.MapExact(map[string]knownvalue.Check{ + "list_nested_block_attribute": knownvalue.StringExact("rts"), + }), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_ListNestedBlockElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_nested_block { + list_nested_block_attribute = "str" + } + list_nested_block { + list_nested_block_attribute = "rts" + } + } + + output list_nested_block_output { + value = test_resource.one.list_nested_block + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "list_nested_block_output", + knownvalue.ListSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Map(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output map_output { + value = test_resource.one.map_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "map_output", + knownvalue.MapExact(map[string]knownvalue.Check{ + "key1": knownvalue.StringExact("value1"), + "key2": knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Map_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output map_output { + value = test_resource.one.map_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "map_output", + knownvalue.ListExact([]knownvalue.Check{}), + ), + }, + ExpectError: regexp.MustCompile(`expected \[\]any value for ListExact check, got: map\[string\]interface {}`), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Map_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output map_output { + value = test_resource.one.map_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "map_output", + knownvalue.MapExact(map[string]knownvalue.Check{ + "key3": knownvalue.StringExact("value3"), + "key4": knownvalue.StringExact("value4"), + }), + ), + }, + ExpectError: regexp.MustCompile(`missing element key3 for MapExact check`), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_MapPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output map_output { + value = test_resource.one.map_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "map_output", + knownvalue.MapPartial(map[string]knownvalue.Check{ + "key1": knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_MapPartial_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output map_output { + value = test_resource.one.map_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "map_output", + knownvalue.MapPartial(map[string]knownvalue.Check{ + "key3": knownvalue.StringExact("value1"), + }), + ), + }, + ExpectError: regexp.MustCompile(`missing element key3 for MapPartial check`), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_MapElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output map_output { + value = test_resource.one.map_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "map_output", + knownvalue.MapSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_MapElements_WrongNum(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + + output map_output { + value = test_resource.one.map_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "map_output", + knownvalue.MapSizeExact(3), + ), + }, + ExpectError: regexp.MustCompile("expected 3 elements for MapSizeExact check, got 2 elements"), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Number(t *testing.T) { + t.Parallel() + + f, _, err := big.ParseFloat("123", 10, 512, big.ToNearestEven) + + if err != nil { + t.Errorf("%s", err) + } + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + int_attribute = 123 + } + + output int64_output { + value = test_resource.one.int_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "int64_output", + knownvalue.NumberExact(f), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Number_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + f, _, err := big.ParseFloat("321", 10, 512, big.ToNearestEven) + + if err != nil { + t.Errorf("%s", err) + } + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + int_attribute = 123 + } + + output int64_output { + value = test_resource.one.int_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "int64_output", + knownvalue.NumberExact(f), + ), + }, + ExpectError: regexp.MustCompile("expected value 321 for NumberExact check, got: 123"), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Set(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + + output set_output { + value = test_resource.one.set_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "set_output", + knownvalue.SetExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_Set_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + + output set_output { + value = test_resource.one.set_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "set_output", + knownvalue.SetExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value3"), + }), + ), + }, + ExpectError: regexp.MustCompile(`missing value value3 for SetExact check`), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_SetPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + + output set_output { + value = test_resource.one.set_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "set_output", + knownvalue.SetPartial([]knownvalue.Check{ + knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_SetPartial_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + + output set_output { + value = test_resource.one.set_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "set_output", + knownvalue.SetPartial([]knownvalue.Check{ + knownvalue.StringExact("value3"), + }), + ), + }, + ExpectError: regexp.MustCompile(`missing value value3 for SetPartial check`), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_SetElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + + output set_output { + value = test_resource.one.set_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "set_output", + knownvalue.SetSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_SetNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_block { + set_nested_block_attribute = "str" + } + set_nested_block { + set_nested_block_attribute = "rts" + } + } + + output set_nested_block_output { + value = test_resource.one.set_nested_block + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "set_nested_block_output", + knownvalue.SetExact([]knownvalue.Check{ + knownvalue.MapExact(map[string]knownvalue.Check{ + "set_nested_block_attribute": knownvalue.StringExact("str"), + }), + knownvalue.MapExact(map[string]knownvalue.Check{ + "set_nested_block_attribute": knownvalue.StringExact("rts"), + }), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_SetNestedBlockPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_block { + set_nested_block_attribute = "str" + } + set_nested_block { + set_nested_block_attribute = "rts" + } + } + + output set_nested_block_output { + value = test_resource.one.set_nested_block + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "set_nested_block_output", + knownvalue.SetPartial([]knownvalue.Check{ + knownvalue.MapExact(map[string]knownvalue.Check{ + "set_nested_block_attribute": knownvalue.StringExact("rts"), + }), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_SetNestedBlockElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_block { + set_nested_block_attribute = "str" + } + set_nested_block { + set_nested_block_attribute = "rts" + } + } + + output set_nested_block_output { + value = test_resource.one.set_nested_block + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "set_nested_block_output", + knownvalue.SetSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_String(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + output string_output { + value = test_resource.one.string_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "string_output", + knownvalue.StringExact("str")), + }, + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_String_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + output string_output { + value = test_resource.one.string_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "string_output", + knownvalue.BoolExact(true)), + }, + ExpectError: regexp.MustCompile("expected bool value for BoolExact check, got: string"), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_String_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + output string_output { + value = test_resource.one.string_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "string_output", + knownvalue.StringExact("rts")), + }, + ExpectError: regexp.MustCompile("expected value rts for StringExact check, got: str"), + }, + }, + }) +} + +func TestExpectKnownOutputValue_CheckState_UnknownAttributeType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + knownValue knownvalue.Check + req statecheck.CheckStateRequest + expectedErr error + }{ + "unrecognised-type": { + knownValue: knownvalue.Int64Exact(123), + req: statecheck.CheckStateRequest{ + State: &tfjson.State{ + Values: &tfjson.StateValues{ + Outputs: map[string]*tfjson.StateOutput{ + "float32_output": { + Value: float32(123), + }, + }, + }, + }, + }, + expectedErr: fmt.Errorf("error checking value for output at path: float32_output, err: expected json.Number value for Int64Exact check, got: float32"), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + e := statecheck.ExpectKnownOutputValue("float32_output", testCase.knownValue) + + resp := statecheck.CheckStateResponse{} + + e.CheckState(context.Background(), testCase.req, &resp) + + if diff := cmp.Diff(resp.Error, testCase.expectedErr, equateErrorMessage); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/statecheck/expect_known_value.go b/statecheck/expect_known_value.go new file mode 100644 index 000000000..96662c32a --- /dev/null +++ b/statecheck/expect_known_value.go @@ -0,0 +1,76 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource State Check +var _ StateCheck = expectKnownValue{} + +type expectKnownValue struct { + resourceAddress string + attributePath tfjsonpath.Path + knownValue knownvalue.Check +} + +// CheckState implements the state check logic. +func (e expectKnownValue) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) { + var rc *tfjson.StateResource + + if req.State == nil { + resp.Error = fmt.Errorf("state is nil") + } + + if req.State.Values == nil { + resp.Error = fmt.Errorf("state does not contain any state values") + } + + if req.State.Values.RootModule == nil { + resp.Error = fmt.Errorf("state does not contain a root module") + } + + for _, resourceChange := range req.State.Values.RootModule.Resources { + if e.resourceAddress == resourceChange.Address { + rc = resourceChange + + break + } + } + + if rc == nil { + resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddress) + + return + } + + result, err := tfjsonpath.Traverse(rc.AttributeValues, e.attributePath) + + if err != nil { + resp.Error = err + + return + } + + if err := e.knownValue.CheckValue(result); err != nil { + resp.Error = fmt.Errorf("error checking value for attribute at path: %s.%s, err: %s", e.resourceAddress, e.attributePath.String(), err) + } +} + +// ExpectKnownValue returns a state check that asserts that the specified attribute at the given resource +// has a known type and value. +func ExpectKnownValue(resourceAddress string, attributePath tfjsonpath.Path, knownValue knownvalue.Check) StateCheck { + return expectKnownValue{ + resourceAddress: resourceAddress, + attributePath: attributePath, + knownValue: knownValue, + } +} diff --git a/statecheck/expect_known_value_test.go b/statecheck/expect_known_value_test.go new file mode 100644 index 000000000..9625ec089 --- /dev/null +++ b/statecheck/expect_known_value_test.go @@ -0,0 +1,1411 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck_test + +import ( + "context" + "fmt" + "math/big" + "regexp" + "testing" + + "github.com/google/go-cmp/cmp" + tfjson "github.com/hashicorp/terraform-json" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestExpectKnownValue_CheckState_ResourceNotFound(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.two", + tfjsonpath.New("bool_attribute"), + knownvalue.BoolExact(true), + ), + }, + ExpectError: regexp.MustCompile("test_resource.two - Resource not found in state"), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_AttributeValueNull(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + knownvalue.NullExact(), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Bool(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + knownvalue.BoolExact(true), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Bool_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + knownvalue.Float64Exact(1.23), + ), + }, + ExpectError: regexp.MustCompile(`expected json\.Number value for Float64Exact check, got: bool`), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Bool_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + knownvalue.BoolExact(false), + ), + }, + ExpectError: regexp.MustCompile("expected value false for BoolExact check, got: true"), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Float64(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + float_attribute = 1.23 + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("float_attribute"), + knownvalue.Float64Exact(1.23), + ), + }, + }, + }, + }) +} + +// We do not need equivalent tests for Int64 and Number as they all test the same logic. +func TestExpectKnownValue_CheckState_Float64_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + float_attribute = 1.23 + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("float_attribute"), + knownvalue.StringExact("str"), + ), + }, + ExpectError: regexp.MustCompile(`expected string value for StringExact check, got: json\.Number`), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Float64_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + float_attribute = 1.23 + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("float_attribute"), + knownvalue.Float64Exact(3.21), + ), + }, + ExpectError: regexp.MustCompile("expected value 3.21 for Float64Exact check, got: 1.23"), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Int64(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + int_attribute = 123 + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("int_attribute"), + knownvalue.Int64Exact(123), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Int64_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + int_attribute = 123 + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("int_attribute"), + knownvalue.Int64Exact(321), + ), + }, + ExpectError: regexp.MustCompile("expected value 321 for Int64Exact check, got: 123"), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_List(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("list_attribute"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_List_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("list_attribute"), + knownvalue.MapExact(map[string]knownvalue.Check{}), + ), + }, + ExpectError: regexp.MustCompile(`expected map\[string\]any value for MapExact check, got: \[\]interface {}`), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_List_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("list_attribute"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("value3"), + knownvalue.StringExact("value4"), + }), + ), + }, + ExpectError: regexp.MustCompile(`list element index 0: expected value value3 for StringExact check, got: value1`), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_ListPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("list_attribute"), + knownvalue.ListPartial(map[int]knownvalue.Check{ + 0: knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }) +} + +// No need to check KnownValueWrongType for ListPartial as all lists, and sets are []any in +// tfjson.State. +func TestExpectKnownValue_CheckState_ListPartial_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("list_attribute"), + knownvalue.ListPartial(map[int]knownvalue.Check{ + 0: knownvalue.StringExact("value3"), + }), + ), + }, + ExpectError: regexp.MustCompile(`list element 0: expected value value3 for StringExact check, got: value1`), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_ListElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("list_attribute"), + knownvalue.ListSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_ListElements_WrongNum(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_attribute = [ + "value1", + "value2" + ] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("list_attribute"), + knownvalue.ListSizeExact(3), + ), + }, + ExpectError: regexp.MustCompile("expected 3 elements for ListSizeExact check, got 2 elements"), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_ListNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_nested_block { + list_nested_block_attribute = "str" + } + list_nested_block { + list_nested_block_attribute = "rts" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("list_nested_block"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.MapExact(map[string]knownvalue.Check{ + "list_nested_block_attribute": knownvalue.StringExact("str"), + }), + knownvalue.MapExact(map[string]knownvalue.Check{ + "list_nested_block_attribute": knownvalue.StringExact("rts"), + }), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_ListNestedBlockPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_nested_block { + list_nested_block_attribute = "str" + } + list_nested_block { + list_nested_block_attribute = "rts" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("list_nested_block"), + knownvalue.ListPartial(map[int]knownvalue.Check{ + 1: knownvalue.MapExact(map[string]knownvalue.Check{ + "list_nested_block_attribute": knownvalue.StringExact("rts"), + }), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_ListNestedBlockElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + list_nested_block { + list_nested_block_attribute = "str" + } + list_nested_block { + list_nested_block_attribute = "rts" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("list_nested_block"), + knownvalue.ListSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Map(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("map_attribute"), + knownvalue.MapExact(map[string]knownvalue.Check{ + "key1": knownvalue.StringExact("value1"), + "key2": knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Map_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("map_attribute"), + knownvalue.ListExact([]knownvalue.Check{}), + ), + }, + ExpectError: regexp.MustCompile(`expected \[\]any value for ListExact check, got: map\[string\]interface {}`), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Map_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("map_attribute"), + knownvalue.MapExact(map[string]knownvalue.Check{ + "key3": knownvalue.StringExact("value3"), + "key4": knownvalue.StringExact("value4"), + }), + ), + }, + ExpectError: regexp.MustCompile(`missing element key3 for MapExact check`), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_MapPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("map_attribute"), + knownvalue.MapPartial(map[string]knownvalue.Check{ + "key1": knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_MapPartial_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("map_attribute"), + knownvalue.MapPartial(map[string]knownvalue.Check{ + "key3": knownvalue.StringExact("value1"), + }), + ), + }, + ExpectError: regexp.MustCompile(`missing element key3 for MapPartial check`), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_MapElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("map_attribute"), + knownvalue.MapSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_MapElements_WrongNum(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + map_attribute = { + key1 = "value1" + key2 = "value2" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("map_attribute"), + knownvalue.MapSizeExact(3), + ), + }, + ExpectError: regexp.MustCompile("expected 3 elements for MapSizeExact check, got 2 elements"), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Number(t *testing.T) { + t.Parallel() + + f, _, err := big.ParseFloat("123", 10, 512, big.ToNearestEven) + + if err != nil { + t.Errorf("%s", err) + } + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + int_attribute = 123 + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("int_attribute"), + knownvalue.NumberExact(f), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Number_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + f, _, err := big.ParseFloat("321", 10, 512, big.ToNearestEven) + + if err != nil { + t.Errorf("%s", err) + } + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + int_attribute = 123 + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("int_attribute"), + knownvalue.NumberExact(f), + ), + }, + ExpectError: regexp.MustCompile("expected value 321 for NumberExact check, got: 123"), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Set(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("set_attribute"), + knownvalue.SetExact([]knownvalue.Check{ + knownvalue.StringExact("value2"), + knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_Set_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("set_attribute"), + knownvalue.SetExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value3"), + }), + ), + }, + ExpectError: regexp.MustCompile(`missing value value3 for SetExact check`), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_SetPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("set_attribute"), + knownvalue.SetPartial([]knownvalue.Check{ + knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_SetPartial_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("set_attribute"), + knownvalue.SetPartial([]knownvalue.Check{ + knownvalue.StringExact("value3"), + }), + ), + }, + ExpectError: regexp.MustCompile(`missing value value3 for SetPartial check`), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_SetElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_attribute = [ + "value1", + "value2" + ] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("set_attribute"), + knownvalue.SetSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_SetNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_block { + set_nested_block_attribute = "str" + } + set_nested_block { + set_nested_block_attribute = "rts" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("set_nested_block"), + knownvalue.SetExact([]knownvalue.Check{ + knownvalue.MapExact(map[string]knownvalue.Check{ + "set_nested_block_attribute": knownvalue.StringExact("str"), + }), + knownvalue.MapExact(map[string]knownvalue.Check{ + "set_nested_block_attribute": knownvalue.StringExact("rts"), + }), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_SetNestedBlockPartial(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_block { + set_nested_block_attribute = "str" + } + set_nested_block { + set_nested_block_attribute = "rts" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("set_nested_block"), + knownvalue.SetPartial([]knownvalue.Check{ + knownvalue.MapExact(map[string]knownvalue.Check{ + "set_nested_block_attribute": knownvalue.StringExact("rts"), + }), + }), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_SetNestedBlockElements(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_block { + set_nested_block_attribute = "str" + } + set_nested_block { + set_nested_block_attribute = "rts" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("set_nested_block"), + knownvalue.SetSizeExact(2), + ), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_String(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("string_attribute"), + knownvalue.StringExact("str")), + }, + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_String_KnownValueWrongType(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("string_attribute"), + knownvalue.BoolExact(true)), + }, + ExpectError: regexp.MustCompile("expected bool value for BoolExact check, got: string"), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_String_KnownValueWrongValue(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("string_attribute"), + knownvalue.StringExact("rts")), + }, + ExpectError: regexp.MustCompile("expected value rts for StringExact check, got: str"), + }, + }, + }) +} + +func TestExpectKnownValue_CheckState_UnknownAttributeType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + knownValue knownvalue.Check + req statecheck.CheckStateRequest + expectedErr error + }{ + "unrecognised-type": { + knownValue: knownvalue.Int64Exact(123), + req: statecheck.CheckStateRequest{ + State: &tfjson.State{ + Values: &tfjson.StateValues{ + RootModule: &tfjson.StateModule{ + Resources: []*tfjson.StateResource{ + { + Address: "example_resource.test", + AttributeValues: map[string]any{ + "attribute": float32(123), + }, + }, + }, + }, + }, + }, + }, + expectedErr: fmt.Errorf("error checking value for attribute at path: example_resource.test.attribute, err: expected json.Number value for Int64Exact check, got: float32"), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + e := statecheck.ExpectKnownValue("example_resource.test", tfjsonpath.New("attribute"), testCase.knownValue) + + resp := statecheck.CheckStateResponse{} + + e.CheckState(context.Background(), testCase.req, &resp) + + if diff := cmp.Diff(resp.Error, testCase.expectedErr, equateErrorMessage); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +var equateErrorMessage = cmp.Comparer(func(x, y error) bool { + if x == nil || y == nil { + return x == nil && y == nil + } + + return x.Error() == y.Error() +}) + +func testProvider() *schema.Provider { + return &schema.Provider{ + ResourcesMap: map[string]*schema.Resource{ + "test_resource": { + CreateContext: func(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics { + d.SetId("test") + return nil + }, + UpdateContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + return nil + }, + DeleteContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + return nil + }, + ReadContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + return nil + }, + Schema: map[string]*schema.Schema{ + "bool_attribute": { + Optional: true, + Type: schema.TypeBool, + }, + "float_attribute": { + Optional: true, + Type: schema.TypeFloat, + }, + "int_attribute": { + Optional: true, + Type: schema.TypeInt, + }, + "list_attribute": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "list_nested_block": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "list_nested_block_attribute": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "map_attribute": { + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "set_attribute": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "set_nested_block": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "set_nested_block_attribute": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "string_attribute": { + Optional: true, + Type: schema.TypeString, + }, + }, + }, + }, + } +} diff --git a/statecheck/expect_sensitive_value.go b/statecheck/expect_sensitive_value.go new file mode 100644 index 000000000..447a2cb27 --- /dev/null +++ b/statecheck/expect_sensitive_value.go @@ -0,0 +1,93 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "encoding/json" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ StateCheck = expectSensitiveValue{} + +type expectSensitiveValue struct { + resourceAddress string + attributePath tfjsonpath.Path +} + +// CheckState implements the state check logic. +func (e expectSensitiveValue) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) { + var rc *tfjson.StateResource + + if req.State == nil { + resp.Error = fmt.Errorf("state is nil") + } + + if req.State.Values == nil { + resp.Error = fmt.Errorf("state does not contain any state values") + } + + if req.State.Values.RootModule == nil { + resp.Error = fmt.Errorf("state does not contain a root module") + } + + for _, resourceChange := range req.State.Values.RootModule.Resources { + if e.resourceAddress == resourceChange.Address { + rc = resourceChange + + break + } + } + + if rc == nil { + resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddress) + + return + } + + var data map[string]any + + err := json.Unmarshal(rc.SensitiveValues, &data) + + if err != nil { + resp.Error = fmt.Errorf("could not unmarshal SensitiveValues: %s", err) + + return + } + + result, err := tfjsonpath.Traverse(data, e.attributePath) + + if err != nil { + resp.Error = err + + return + } + + isSensitive, ok := result.(bool) + if !ok { + resp.Error = fmt.Errorf("invalid path: the path value cannot be asserted as bool") + return + } + + if !isSensitive { + resp.Error = fmt.Errorf("attribute at path is not sensitive") + return + } +} + +// ExpectSensitiveValue returns a state check that asserts that the specified attribute at the given resource has a sensitive value. +// +// Due to implementation differences between the terraform-plugin-sdk and the terraform-plugin-framework, representation of sensitive +// values may differ. For example, terraform-plugin-sdk based providers may have less precise representations of sensitive values, such +// as marking whole maps as sensitive rather than individual element values. +func ExpectSensitiveValue(resourceAddress string, attributePath tfjsonpath.Path) StateCheck { + return expectSensitiveValue{ + resourceAddress: resourceAddress, + attributePath: attributePath, + } +} diff --git a/statecheck/expect_sensitive_value_test.go b/statecheck/expect_sensitive_value_test.go new file mode 100644 index 000000000..5723d73b4 --- /dev/null +++ b/statecheck/expect_sensitive_value_test.go @@ -0,0 +1,308 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck_test + +import ( + "context" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_ExpectSensitiveValue_SensitiveStringAttribute(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_4_6), // StateResource.SensitiveValues + }, + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProviderSensitive(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: ` + resource "test_resource" "one" { + sensitive_string_attribute = "test" + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectSensitiveValue("test_resource.one", + tfjsonpath.New("sensitive_string_attribute")), + }, + }, + }, + }) +} + +func Test_ExpectSensitiveValue_SensitiveListAttribute(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_4_6), // StateResource.SensitiveValues + }, + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProviderSensitive(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: ` + resource "test_resource" "one" { + sensitive_list_attribute = ["value1"] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectSensitiveValue("test_resource.one", + tfjsonpath.New("sensitive_list_attribute")), + }, + }, + }, + }) +} + +func Test_ExpectSensitiveValue_SensitiveSetAttribute(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_4_6), // StateResource.SensitiveValues + }, + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProviderSensitive(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: ` + resource "test_resource" "one" { + sensitive_set_attribute = ["value1"] + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectSensitiveValue("test_resource.one", + tfjsonpath.New("sensitive_set_attribute")), + }, + }, + }, + }) +} + +func Test_ExpectSensitiveValue_SensitiveMapAttribute(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_4_6), // StateResource.SensitiveValues + }, + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProviderSensitive(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: ` + resource "test_resource" "one" { + sensitive_map_attribute = { + key1 = "value1", + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectSensitiveValue("test_resource.one", + tfjsonpath.New("sensitive_map_attribute")), + }, + }, + }, + }) +} + +func Test_ExpectSensitiveValue_ListNestedBlock_SensitiveAttribute(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_4_6), // StateResource.SensitiveValues + }, + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProviderSensitive(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: ` + resource "test_resource" "one" { + list_nested_block_sensitive_attribute { + sensitive_list_nested_block_attribute = "sensitive-test" + list_nested_block_attribute = "test" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectSensitiveValue("test_resource.one", + tfjsonpath.New("list_nested_block_sensitive_attribute").AtSliceIndex(0). + AtMapKey("sensitive_list_nested_block_attribute")), + }, + }, + }, + }) +} + +func Test_ExpectSensitiveValue_SetNestedBlock_SensitiveAttribute(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_4_6), // StateResource.SensitiveValues + }, + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProviderSensitive(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: ` + resource "test_resource" "one" { + set_nested_block_sensitive_attribute { + sensitive_set_nested_block_attribute = "sensitive-test" + set_nested_block_attribute = "test" + } + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectSensitiveValue("test_resource.one", + tfjsonpath.New("set_nested_block_sensitive_attribute")), + }, + }, + }, + }) +} + +func Test_ExpectSensitiveValue_ExpectError_ResourceNotFound(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_4_6), // StateResource.SensitiveValues + }, + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProviderSensitive(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: ` + resource "test_resource" "one" {} + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectSensitiveValue("test_resource.two", tfjsonpath.New("set_attribute")), + }, + ExpectError: regexp.MustCompile(`test_resource.two - Resource not found in state`), + }, + }, + }) +} + +func testProviderSensitive() *schema.Provider { + return &schema.Provider{ + ResourcesMap: map[string]*schema.Resource{ + "test_resource": { + CreateContext: func(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics { + d.SetId("test") + return nil + }, + UpdateContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + return nil + }, + DeleteContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + return nil + }, + ReadContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + return nil + }, + Schema: map[string]*schema.Schema{ + "sensitive_string_attribute": { + Sensitive: true, + Optional: true, + Type: schema.TypeString, + }, + "sensitive_list_attribute": { + Sensitive: true, + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "sensitive_set_attribute": { + Sensitive: true, + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "sensitive_map_attribute": { + Sensitive: true, + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "list_nested_block_sensitive_attribute": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "list_nested_block_attribute": { + Type: schema.TypeString, + Optional: true, + }, + "sensitive_list_nested_block_attribute": { + Sensitive: true, + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "set_nested_block_sensitive_attribute": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "set_nested_block_attribute": { + Type: schema.TypeString, + Optional: true, + }, + "sensitive_set_nested_block_attribute": { + Sensitive: true, + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + } +} diff --git a/statecheck/state_check.go b/statecheck/state_check.go new file mode 100644 index 000000000..cfd2da6b1 --- /dev/null +++ b/statecheck/state_check.go @@ -0,0 +1,30 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + + tfjson "github.com/hashicorp/terraform-json" +) + +// StateCheck defines an interface for implementing test logic that checks a state file and then returns an error +// if the state file does not match what is expected. +type StateCheck interface { + // CheckState should perform the state check. + CheckState(context.Context, CheckStateRequest, *CheckStateResponse) +} + +// CheckStateRequest is a request for an invoke of the CheckState function. +type CheckStateRequest struct { + // State represents a parsed state file, retrieved via the `terraform show -json` command. + State *tfjson.State +} + +// CheckStateResponse is a response to an invoke of the CheckState function. +type CheckStateResponse struct { + // Error is used to report the failure of a state check assertion and is combined with other StateCheck errors + // to be reported as a test failure. + Error error +} diff --git a/tfversion/versions.go b/tfversion/versions.go index 77f384b5e..f405bd766 100644 --- a/tfversion/versions.go +++ b/tfversion/versions.go @@ -28,6 +28,7 @@ var ( Version1_2_0 *version.Version = version.Must(version.NewVersion("1.2.0")) Version1_3_0 *version.Version = version.Must(version.NewVersion("1.3.0")) Version1_4_0 *version.Version = version.Must(version.NewVersion("1.4.0")) + Version1_4_6 *version.Version = version.Must(version.NewVersion("1.4.6")) Version1_5_0 *version.Version = version.Must(version.NewVersion("1.5.0")) Version1_6_0 *version.Version = version.Must(version.NewVersion("1.6.0")) Version1_7_0 *version.Version = version.Must(version.NewVersion("1.7.0")) diff --git a/website/data/plugin-testing-nav-data.json b/website/data/plugin-testing-nav-data.json index 7e581b6c3..a5a5487dc 100644 --- a/website/data/plugin-testing-nav-data.json +++ b/website/data/plugin-testing-nav-data.json @@ -50,6 +50,27 @@ } ] }, + { + "title": "State Checks", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests/state-checks" + }, + { + "title": "Resource State Checks", + "path": "acceptance-tests/state-checks/resource" + }, + { + "title": "Output Plan Checks", + "path": "acceptance-tests/state-checks/output" + }, + { + "title": "Custom Plan Checks", + "path": "acceptance-tests/state-checks/custom" + } + ] + }, { "title": "Known Value Checks", "routes": [ @@ -81,6 +102,10 @@ "title": "Map", "path": "acceptance-tests/known-value-checks/map" }, + { + "title": "Null", + "path": "acceptance-tests/known-value-checks/null" + }, { "title": "Number", "path": "acceptance-tests/known-value-checks/number" diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/bool.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/bool.mdx index 039bc347b..1a5c09a53 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/bool.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/bool.mdx @@ -8,13 +8,13 @@ description: >- The known value checks that are available for bool values are: -* [BoolValueExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/bool#boolvalueexact-check) +* [BoolExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/bool#boolexact-check) -## `BoolValueExact` Check +## `BoolExact` Check -The [BoolValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#BoolValueExact) check tests that a resource attribute, or output value has an exactly matching bool value. +The [BoolExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#BoolExact) check tests that a resource attribute, or output value has an exactly matching bool value. -Example usage of [BoolValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#BoolValueExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [BoolExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#BoolExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. ```go func TestExpectKnownValue_CheckPlan_Bool(t *testing.T) { @@ -33,7 +33,7 @@ func TestExpectKnownValue_CheckPlan_Bool(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("bool_attribute"), - knownvalue.BoolValueExact(true), + knownvalue.BoolExact(true), ), }, }, diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/float64.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/float64.mdx index d4bacb127..ebaa17d39 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/float64.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/float64.mdx @@ -8,13 +8,13 @@ description: >- The known value checks that are available for float64 values are: -* [Float64ValueExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/float64#float64valueexact-check) +* [Float64Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/float64#float64exact-check) -## `Float64ValueExact` Check +## `Float64Exact` Check -The [Float64ValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64ValueExact) check tests that a resource attribute, or output value has an exactly matching float64 value. +The [Float64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Exact) check tests that a resource attribute, or output value has an exactly matching float64 value. -Example usage of [Float64ValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64ValueExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [Float64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Exact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. ```go func TestExpectKnownValue_CheckPlan_Float64(t *testing.T) { @@ -33,7 +33,7 @@ func TestExpectKnownValue_CheckPlan_Float64(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("float_attribute"), - knownvalue.Float64ValueExact(1.23), + knownvalue.Float64Exact(1.23), ), }, }, diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx index dae04a89d..bc22d96e5 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx @@ -20,15 +20,15 @@ Example uses in the testing module include: The known value check types are implemented within the `terraform-plugin-testing` module in the [`knownvalue` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue). Known value checks are instantiated by calling the relevant constructor function. ```go -knownvalue.BoolValueExact(true) +knownvalue.BoolExact(true) ``` For known value checks that represent collections, or objects, nesting of known value checks can be used to define a "composite" known value check for use in asserting against a resource attribute, or output value that contains other values. ```go -knownvalue.ListValueExact([]knownvalue.Check{ - knownvalue.StringValueExact("value1"), - knownvalue.StringValueExact("value2"), +knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value2"), }) ``` diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx index 9954cd7ec..214fcef25 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx @@ -8,13 +8,13 @@ description: >- The known value checks that are available for int64 values are: -* [Int64ValueExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/float64#int64valueexact-check) +* [Int64Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/float64#int64exact-check) -## `Int64ValueExact` Check +## `Int64Exact` Check -The [Int64ValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64ValueExact) check tests that a resource attribute, or output value has an exactly matching int64 value. +The [Int64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Exact) check tests that a resource attribute, or output value has an exactly matching int64 value. -Example usage of [Int64ValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64ValueExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [Int64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Exact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. ```go func TestExpectKnownValue_CheckPlan_Int64(t *testing.T) { @@ -33,7 +33,7 @@ func TestExpectKnownValue_CheckPlan_Int64(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("int_attribute"), - knownvalue.Int64ValueExact(123), + knownvalue.Int64Exact(123), ), }, }, diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/list.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/list.mdx index 774cedc02..e491e58b6 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/list.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/list.mdx @@ -8,22 +8,22 @@ description: >- The known value checks that are available for list values are: -* [ListElementsExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/list#listelementsexact-check) -* [ListValueExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/list#listvalueexact-check) -* [ListValuePartialMatch](/terraform/plugin/testing/acceptance-tests/known-value-checks/list#listvaluepartialmatch-check) +* [ListExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/list#listexact-check) +* [ListPartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/list#listpartial-check) +* [ListSizeExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/list#listsizeexact-check) -## `ListElementsExact` Check +## `ListExact` Check -The [ListElementsExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListElementsExact) check tests that a resource attribute, or output value contains the specified number of elements. +The [ListExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListExact) check tests that a resource attribute, or output value has an order-dependent, matching collection of element values. -Example usage of [ListElementsExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListElementsExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [ListExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. ```go -func TestExpectKnownValue_CheckPlan_ListElements(t *testing.T) { +func TestExpectKnownValue_CheckPlan_List(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - // Provider definition omitted. + // Provider definition omitted. Steps: []resource.TestStep{ { Config: `resource "test_resource" "one" { @@ -38,7 +38,10 @@ func TestExpectKnownValue_CheckPlan_ListElements(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("list_attribute"), - knownvalue.ListElementsExact(2), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value2"), + }), ), }, }, @@ -48,18 +51,18 @@ func TestExpectKnownValue_CheckPlan_ListElements(t *testing.T) { } ``` -## `ListValueExact` Check +## `ListPartial` Check -The [ListValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListValueExact) check tests that a resource attribute, or output value has an order-dependent, matching collection of element values. +The [ListPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListPartial) check tests that a resource attribute, or output value has matching element values for the specified collection indices. -Example usage of [ListValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListValueExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [ListPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListPartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. In this example, only the first element within the list, the element defined at index `0`, is checked. ```go -func TestExpectKnownValue_CheckPlan_List(t *testing.T) { +func TestExpectKnownValue_CheckPlan_ListPartial(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - // Provider definition omitted. + // Provider definition omitted. Steps: []resource.TestStep{ { Config: `resource "test_resource" "one" { @@ -74,9 +77,8 @@ func TestExpectKnownValue_CheckPlan_List(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("list_attribute"), - knownvalue.ListValueExact([]knownvalue.Check{ - knownvalue.StringValueExact("value1"), - knownvalue.StringValueExact("value2"), + knownvalue.ListPartial(map[int]knownvalue.Check{ + 0: knownvalue.StringExact("value1"), }), ), }, @@ -87,14 +89,14 @@ func TestExpectKnownValue_CheckPlan_List(t *testing.T) { } ``` -## `ListValuePartialMatch` Check +## `ListSizeExact` Check -The [ListValuePartialMatch](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListValuePartialMatch) check tests that a resource attribute, or output value has matching element values for the specified collection indices. +The [ListSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListSizeExact) check tests that a resource attribute, or output value contains the specified number of elements. -Example usage of [ListValuePartialMatch](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListValuePartialMatch) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. In this example, only the first element within the list, the element defined at index `0`, is checked. +Example usage of [ListSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListSizeExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. ```go -func TestExpectKnownValue_CheckPlan_ListPartial(t *testing.T) { +func TestExpectKnownValue_CheckPlan_ListElements(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ @@ -113,9 +115,7 @@ func TestExpectKnownValue_CheckPlan_ListPartial(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("list_attribute"), - knownvalue.ListValuePartialMatch(map[int]knownvalue.Check{ - 0: knownvalue.StringValueExact("value1"), - }), + knownvalue.ListSizeExact(2), ), }, }, diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/map.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/map.mdx index ea1a5c252..fc04c2f80 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/map.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/map.mdx @@ -8,18 +8,18 @@ description: >- The known value checks that are available for map values are: -* [MapElementsExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/map#mapelementsexact-check) -* [MapValueExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/map#mapvalueexact-check) -* [MapValuePartialMatch](/terraform/plugin/testing/acceptance-tests/known-value-checks/map#mapvaluepartialmatch-check) +* [MapExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/map#mapexact-check) +* [MapPartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/map#mappartial-check) +* [MapSizeExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/map#mapsizeexact-check) -## `MapElementsExact` Check +## `MapExact` Check -The [MapElementsExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapElementsExact) check tests that a resource attribute, or output value contains the specified number of elements. +The [MapExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapExact) check tests that a resource attribute, or output value has a key-specified, matching collection of element values. -Example usage of [MapElementsExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapElementsExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [MapExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. ```go -func TestExpectKnownValue_CheckPlan_MapElements(t *testing.T) { +func TestExpectKnownValue_CheckPlan_Map(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ @@ -38,7 +38,10 @@ func TestExpectKnownValue_CheckPlan_MapElements(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("map_attribute"), - knownvalue.MapElementsExact(2), + knownvalue.MapExact(map[string]knownvalue.Check{ + "key1": knownvalue.StringExact("value1"), + "key2": knownvalue.StringExact("value2"), + }), ), }, }, @@ -48,14 +51,16 @@ func TestExpectKnownValue_CheckPlan_MapElements(t *testing.T) { } ``` -## `MapValueExact` Check +## `MapPartial` Check -The [MapValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapValueExact) check tests that a resource attribute, or output value has a key-specified, matching collection of element values. +The [MapPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapPartial) check tests that a resource attribute, or output value has matching element values for the specified keys. -Example usage of [MapValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapValueExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [MapPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapPartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +In this example, only the element associated with `key1` within the map is checked. ```go -func TestExpectKnownValue_CheckPlan_Map(t *testing.T) { +func TestExpectKnownValue_CheckPlan_MapPartial(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ @@ -74,9 +79,8 @@ func TestExpectKnownValue_CheckPlan_Map(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("map_attribute"), - knownvalue.MapValueExact(map[string]knownvalue.Check{ - "key1": knownvalue.StringValueExact("value1"), - "key2": knownvalue.StringValueExact("value2"), + knownvalue.MapPartial(map[string]knownvalue.Check{ + "key1": knownvalue.StringExact("value1"), }), ), }, @@ -87,16 +91,14 @@ func TestExpectKnownValue_CheckPlan_Map(t *testing.T) { } ``` -## `MapValuePartialMatch` Check +## `MapSizeExact` Check -The [MapValuePartialMatch](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapValuePartialMatch) check tests that a resource attribute, or output value has matching element values for the specified keys. +The [MapSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapSizeExact) check tests that a resource attribute, or output value contains the specified number of elements. -Example usage of [MapValuePartialMatch](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapValuePartialMatch) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. - -In this example, only the element associated with `key1` within the map is checked. +Example usage of [MapSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapSizeExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. ```go -func TestExpectKnownValue_CheckPlan_MapPartial(t *testing.T) { +func TestExpectKnownValue_CheckPlan_MapElements(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ @@ -115,9 +117,7 @@ func TestExpectKnownValue_CheckPlan_MapPartial(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("map_attribute"), - knownvalue.MapValuePartialMatch(map[string]knownvalue.Check{ - "key1": knownvalue.StringValueExact("value1"), - }), + knownvalue.MapSizeExact(2), ), }, }, @@ -125,4 +125,4 @@ func TestExpectKnownValue_CheckPlan_MapPartial(t *testing.T) { }, }) } -``` +``` \ No newline at end of file diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/null.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/null.mdx new file mode 100644 index 000000000..58d95ce8d --- /dev/null +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/null.mdx @@ -0,0 +1,39 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Null Value Checks for use with Plan Checks or State Checks. +--- + +# Null Known Value Checks + +The known value checks that are available for null values are: + +* [NullExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/null#nullexact-check) + +## `NullExact` Check + +The [NullExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NullExact) check tests that a resource attribute, or output value has an exactly matching null value. + +Example usage of [NullExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NullExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_AttributeValueNull(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + // Provider definition omitted. + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + knownvalue.NullExact(), + ), + }, + }, + }, + }) +} +``` diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/number.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/number.mdx index 784146c06..fe5345727 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/number.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/number.mdx @@ -8,13 +8,13 @@ description: >- The known value checks that are available for number values are: -* [NumberValueExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/number#numbervalueexact-check) +* [NumberExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/number#numberexact-check) -## `NumberValueExact` Check +## `NumberExact` Check -The [NumberValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberValueExact) check tests that a resource attribute, or output value has an exactly matching number value. +The [NumberExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberExact) check tests that a resource attribute, or output value has an exactly matching number value. -Example usage of [NumberValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberValueExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [NumberExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. ```go func TestExpectKnownValue_CheckPlan_Number(t *testing.T) { @@ -39,7 +39,7 @@ func TestExpectKnownValue_CheckPlan_Number(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("number_attribute"), - knownvalue.NumberValueExact(num), + knownvalue.NumberExact(num), ), }, }, diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/object.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/object.mdx index 55e4b923b..86e62a786 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/object.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/object.mdx @@ -8,51 +8,14 @@ description: >- The known value checks that are available for object values are: -* [ObjectElementsExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/object#objectelementsexact-check) -* [ObjectValueExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/object#objectvalueexact-check) -* [ObjectValuePartialMatch](/terraform/plugin/testing/acceptance-tests/known-value-checks/object#objectvaluepartialmatch-check) +* [ObjectExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/object#objectexact-check) +* [ObjectPartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/object#objectpartial-check) -## `ObjectElementsExact` Check +## `ObjectExact` Check -The [ObjectElementsExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectElementsExact) check tests that a resource attribute, or output value contains the specified number of attributes. +The [ObjectExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectExact) check tests that a resource attribute, or output value has a matching collection of attribute name, and attribute values. -Example usage of [ObjectElementsExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectElementsExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. - -```go -func TestExpectKnownValue_CheckPlan_ObjectElements(t *testing.T) { - t.Parallel() - - resource.Test(t, resource.TestCase{ - // Provider definition omitted. - Steps: []resource.TestStep{ - { - Config: `resource "test_resource" "one" { - object_attribute = { - key1 = "value1" - key2 = "value2" - } - } - `, - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{ - plancheck.ExpectKnownValue( - "test_resource.one", - tfjsonpath.New("object_attribute"), - knownvalue.ObjectElementsExact(2), - ), - }, - }, - }, - }, - }) -} -``` - -## `ObjectValueExact` Check - -The [ObjectValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectValueExact) check tests that a resource attribute, or output value has a matching collection of attribute name, and attribute values. - -Example usage of [ObjectValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectValueExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [ObjectExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. ```go func TestExpectKnownValue_CheckPlan_Object(t *testing.T) { @@ -74,9 +37,9 @@ func TestExpectKnownValue_CheckPlan_Object(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("object_attribute"), - knownvalue.ObjectValueExact(map[string]knownvalue.Check{ - "attr1": knownvalue.StringValueExact("value1"), - "attr2": knownvalue.StringValueExact("value2"), + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "attr1": knownvalue.StringExact("value1"), + "attr2": knownvalue.StringExact("value2"), }), ), }, @@ -87,11 +50,11 @@ func TestExpectKnownValue_CheckPlan_Object(t *testing.T) { } ``` -## `ObjectValuePartialMatch` Check +## `ObjectPartial` Check -The [ObjectValuePartialMatch](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectValuePartialMatch) check tests that a resource attribute, or output value has matching attribute values for the specified attribute names. +The [ObjectPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectPartial) check tests that a resource attribute, or output value has matching attribute values for the specified attribute names. -Example usage of [ObjectValuePartialMatch](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectValuePartialMatch) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [ObjectPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectPartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. In this example, only the attribute value associated with the attribute name `attr1` within the object is checked. @@ -115,8 +78,8 @@ func TestExpectKnownValue_CheckPlan_ObjectPartial(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("object_attribute"), - knownvalue.ObjectValuePartialMatch(map[string]knownvalue.Check{ - "attr1": knownvalue.StringValueExact("value1"), + knownvalue.ObjectPartial(map[string]knownvalue.Check{ + "attr1": knownvalue.StringExact("value1"), }), ), }, diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/set.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/set.mdx index 64d646a2b..f4bf5730f 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/set.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/set.mdx @@ -8,22 +8,22 @@ description: >- The known value checks that are available for set values are: -* [SetElementsExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/set#setelementsexact-check) -* [SetValueExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/set#setvalueexact-check) -* [SetValuePartialMatch](/terraform/plugin/testing/acceptance-tests/known-value-checks/set#setvaluepartialmatch-check) +* [SetExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/set#setexact-check) +* [SetPartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/set#setpartial-check) +* [SetSizeExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/set#setsizeexact-check) -## `SetElementsExact` Check +## `SetExact` Check -The [SetElementsExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetElementsExact) check tests that a resource attribute, or output value contains the specified number of elements. +The [SetExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetExact) check tests that a resource attribute, or output value has an order-independent, matching collection of element values. -Example usage of [SetElementsExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetElementsExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [SetExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. ```go -func TestExpectKnownValue_CheckPlan_SetElements(t *testing.T) { +func TestExpectKnownValue_CheckPlan_Set(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - // Provider definition omitted. + // Provider definition omitted. Steps: []resource.TestStep{ { Config: `resource "test_resource" "one" { @@ -38,7 +38,10 @@ func TestExpectKnownValue_CheckPlan_SetElements(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("set_attribute"), - knownvalue.SetElementsExact(2), + knownvalue.SetExact([]knownvalue.Check{ + knownvalue.StringExact("value2"), + knownvalue.StringExact("value1"), + }), ), }, }, @@ -48,18 +51,18 @@ func TestExpectKnownValue_CheckPlan_SetElements(t *testing.T) { } ``` -## `SetValueExact` Check +## `SetPartial` Check -The [SetValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetValueExact) check tests that a resource attribute, or output value has an order-independent, matching collection of element values. +The [SetPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetPartial) check tests that a resource attribute, or output value contains matching element values. -Example usage of [SetValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetValueExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [SetPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetPartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. In this example, only the one element within the set is checked. ```go -func TestExpectKnownValue_CheckPlan_Set(t *testing.T) { +func TestExpectKnownValue_CheckPlan_SetPartial(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - // Provider definition omitted. + // Provider definition omitted. Steps: []resource.TestStep{ { Config: `resource "test_resource" "one" { @@ -74,9 +77,8 @@ func TestExpectKnownValue_CheckPlan_Set(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("set_attribute"), - knownvalue.SetValueExact([]knownvalue.Check{ - knownvalue.StringValueExact("value2"), - knownvalue.StringValueExact("value1"), + knownvalue.SetPartial([]knownvalue.Check{ + knownvalue.StringExact("value2"), }), ), }, @@ -87,14 +89,14 @@ func TestExpectKnownValue_CheckPlan_Set(t *testing.T) { } ``` -## `SetValuePartialMatch` Check +## `SetSizeExact` Check -The [SetValuePartialMatch](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetValuePartialMatch) check tests that a resource attribute, or output value contains matching element values. +The [SetSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetSizeExact) check tests that a resource attribute, or output value contains the specified number of elements. -Example usage of [SetValuePartialMatch](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetValuePartialMatch) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. In this example, only the one element within the set is checked. +Example usage of [SetSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetSizeExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. ```go -func TestExpectKnownValue_CheckPlan_SetPartial(t *testing.T) { +func TestExpectKnownValue_CheckPlan_SetElements(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ @@ -113,9 +115,7 @@ func TestExpectKnownValue_CheckPlan_SetPartial(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("set_attribute"), - knownvalue.SetValuePartialMatch([]knownvalue.Check{ - knownvalue.StringValueExact("value2"), - }), + knownvalue.SetSizeExact(2), ), }, }, diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/string.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/string.mdx index 2d771964b..f25a66907 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/string.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/string.mdx @@ -8,13 +8,13 @@ description: >- The known value checks that are available for string values are: -* [StringValueExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/string#stringvalueexact-check) +* [StringExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/string#stringexact-check) -## `StringValueExact` Check +## `StringExact` Check -The [StringValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringValueExact) check tests that a resource attribute, or output value has an exactly matching string value. +The [StringExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringExact) check tests that a resource attribute, or output value has an exactly matching string value. -Example usage of [StringValueExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringValueExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. +Example usage of [StringExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. ```go func TestExpectKnownValue_CheckPlan_String(t *testing.T) { @@ -33,7 +33,7 @@ func TestExpectKnownValue_CheckPlan_String(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("string_attribute"), - knownvalue.StringValueExact("str")), + knownvalue.StringExact("str")), }, }, }, diff --git a/website/docs/plugin/testing/acceptance-tests/plan-checks/output.mdx b/website/docs/plugin/testing/acceptance-tests/plan-checks/output.mdx index 146ea2de7..6dd2121cd 100644 --- a/website/docs/plugin/testing/acceptance-tests/plan-checks/output.mdx +++ b/website/docs/plugin/testing/acceptance-tests/plan-checks/output.mdx @@ -44,7 +44,7 @@ func TestExpectKnownOutputValue_CheckPlan_Bool(t *testing.T) { PreApply: []plancheck.PlanCheck{ plancheck.ExpectKnownOutputValue( "bool_output", - knownvalue.BoolValueExact(true), + knownvalue.BoolExact(true), ), }, }, @@ -80,7 +80,7 @@ func TestExpectKnownOutputValue_CheckPlan_Bool(t *testing.T) { PreApply: []plancheck.PlanCheck{ plancheck.ExpectKnownOutputValue( "bool_output", - knownvalue.BoolValueExact(true), + knownvalue.BoolExact(true), ), }, }, diff --git a/website/docs/plugin/testing/acceptance-tests/plan-checks/resource.mdx b/website/docs/plugin/testing/acceptance-tests/plan-checks/resource.mdx index c0f93b6e7..2ef10e073 100644 --- a/website/docs/plugin/testing/acceptance-tests/plan-checks/resource.mdx +++ b/website/docs/plugin/testing/acceptance-tests/plan-checks/resource.mdx @@ -39,7 +39,7 @@ func TestExpectKnownValue_CheckPlan_String(t *testing.T) { plancheck.ExpectKnownValue( "test_resource.one", tfjsonpath.New("string_attribute"), - knownvalue.StringValueExact("str")), + knownvalue.StringExact("str")), }, }, }, diff --git a/website/docs/plugin/testing/acceptance-tests/state-checks/custom.mdx b/website/docs/plugin/testing/acceptance-tests/state-checks/custom.mdx new file mode 100644 index 000000000..817be30fb --- /dev/null +++ b/website/docs/plugin/testing/acceptance-tests/state-checks/custom.mdx @@ -0,0 +1,119 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: State Checks' +description: >- + State Checks are test assertions that can inspect state during a TestStep. Custom State Checks can be implemented. +--- + +# Custom State Checks + +The package [`statecheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck) also provides the [`StateCheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#StateCheck) interface, which can be implemented for a custom state check. + +The [`statecheck.CheckStateRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#CheckStateRequest) contains the current state file, parsed by the [terraform-json package](https://pkg.go.dev/github.com/hashicorp/terraform-json#State). + +Here is an example implementation of a state check that asserts that a specific resource attribute has a known type and value: + +```go +package example_test + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-testing/statecheck" +) + +var _ StateCheck = expectKnownValue{} + +type expectKnownValue struct { + resourceAddress string + attributePath tfjsonpath.Path + knownValue knownvalue.Check +} + +func (e expectKnownValue) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) { + var rc *tfjson.StateResource + + if req.State == nil { + resp.Error = fmt.Errorf("state is nil") + } + + if req.State.Values == nil { + resp.Error = fmt.Errorf("state does not contain any state values") + } + + if req.State.Values.RootModule == nil { + resp.Error = fmt.Errorf("state does not contain a root module") + } + + for _, resourceChange := range req.State.Values.RootModule.Resources { + if e.resourceAddress == resourceChange.Address { + rc = resourceChange + + break + } + } + + if rc == nil { + resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddress) + + return + } + + result, err := tfjsonpath.Traverse(rc.AttributeValues, e.attributePath) + + if err != nil { + resp.Error = err + + return + } + + if err := e.knownValue.CheckValue(result); err != nil { + resp.Error = err + } +} + +func ExpectKnownValue(resourceAddress string, attributePath tfjsonpath.Path, knownValue knownvalue.Check) StateCheck { + return expectKnownValue{ + resourceAddress: resourceAddress, + attributePath: attributePath, + knownValue: knownValue, + } +} +``` + +And example usage: +```go +package example_test + +import ( + "testing" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestExpectKnownValue_CheckState_Bool(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + // Provider definition omitted. + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + knownvalue.BoolExact(true), + ), + }, + }, + }, + }) +} +``` diff --git a/website/docs/plugin/testing/acceptance-tests/state-checks/index.mdx b/website/docs/plugin/testing/acceptance-tests/state-checks/index.mdx new file mode 100644 index 000000000..36304e388 --- /dev/null +++ b/website/docs/plugin/testing/acceptance-tests/state-checks/index.mdx @@ -0,0 +1,20 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: State Checks' +description: >- + State Checks are test assertions that can inspect state during a TestStep. The testing module + provides built-in State Checks for common use-cases, and custom State Checks can also be implemented. +--- + +# State Checks + +During the **Lifecycle (config)** [mode](/terraform/plugin/testing/acceptance-tests/teststep#test-modes) of a `TestStep`, the testing framework will run `terraform apply`. + +The execution of `terraform apply` results in a [state file](/terraform/language/state), and can be represented by this [JSON format](/terraform/internals/json-format#state-representation). + +A **state check** is a test assertion that inspects the state file. Multiple state checks can be run, all assertion errors returned are aggregated, reported as a test failure, and all test cleanup logic is executed. + +Refer to: + +- [Resource State Checks](/terraform/plugin/testing/acceptance-tests/state-checks/resource) for built-in managed resource and data source state checks. +- [Output State Checks](/terraform/plugin/testing/acceptance-tests/state-checks/output) for built-in output-related state checks. +- [Custom State Checks](/terraform/plugin/testing/acceptance-tests/state-checks/custom) for defining bespoke state checks. diff --git a/website/docs/plugin/testing/acceptance-tests/state-checks/output.mdx b/website/docs/plugin/testing/acceptance-tests/state-checks/output.mdx new file mode 100644 index 000000000..b1e02da1f --- /dev/null +++ b/website/docs/plugin/testing/acceptance-tests/state-checks/output.mdx @@ -0,0 +1,84 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: State Checks' +description: >- + State Checks are test assertions that can inspect state during a TestStep. The testing module + provides built-in Output Value State Checks for common use-cases. +--- + +# Output State Checks + +The `terraform-plugin-testing` module provides a package [`statecheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck) with built-in output value state checks for common use-cases: + +| Check | Description | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------| +| [`statecheck.ExpectKnownOutputValue(address, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectKnownOutputValue) | Asserts the output at the specified address has the specified type, and value. | +| [`statecheck.ExpectKnownOutputValueAtPath(address, path, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectKnownOutputValueAtPath) | Asserts the output at the specified address, and path has the specified type, and value. | + +## Example using `statecheck.ExpectKnownOutputValue` + +The [`statecheck.ExpectKnownOutputValue(address, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectKnownOutputValue) state check verifies that a specific output value has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownOutputValue` state check. + +```go +func TestExpectKnownOutputValue_CheckState_Bool(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + // Provider definition omitted. + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + + output bool_output { + value = test_resource.one.bool_attribute + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValue( + "bool_output", + knownvalue.BoolExact(true), + ), + }, + }, + }, + }) +} +``` + +## Example using `statecheck.ExpectKnownOutputValueAtPath` + +The [`statecheck.ExpectKnownOutputValueAtPath(address, path, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectKnownOutputValueAtPath) state check verifies that a specific output value at a defined path has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownOutputValueAtPath` state check. + +```go +func TestExpectKnownOutputValueAtPath_CheckState_Bool(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + // Provider definition omitted. + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + + output test_resource_one_output { + value = test_resource.one + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("bool_attribute"), + knownvalue.BoolExact(true), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/website/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx b/website/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx new file mode 100644 index 000000000..582afbcdf --- /dev/null +++ b/website/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx @@ -0,0 +1,78 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: State Checks' +description: >- + State Checks are test assertions that can inspect state during a TestStep. The testing module + provides built-in Managed Resource and Data Source State Checks for common use-cases. +--- + +# Resource State Checks + +The `terraform-plugin-testing` module provides a package [`statecheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck) with built-in managed resource, and data source state checks for common use-cases: + +| Check | Description | +|------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| +| [`statecheck.ExpectKnownValue(address, path, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectKnownValue) | Asserts the specified attribute at the given managed resource, or data source, has the specified type, and value. | +| [`statecheck.ExpectSensitiveValue(address, path)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectSensitiveValue) | Asserts the specified attribute at the given managed resource, or data source, has a sensitive value. | + +## Example using `statecheck.ExpectKnownValue` + +The [`statecheck.ExpectKnownValue(address, path, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectKnownValue) state check provides a basis for asserting that a specific resource attribute has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownValue` state check. + +```go +func TestExpectKnownValue_CheckState_Bool(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + // Provider definition omitted. + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + knownvalue.BoolExact(true), + ), + }, + }, + }, + }) +} +``` + +## Example using `statecheck.ExpectSensitiveValue` + +The [`statecheck.ExpectSensitiveValue(address, path)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectSensitiveValue) state check provides a basis for asserting that a specific resource attribute is marked as sensitive. + +-> **Note:** In this example, a [TerraformVersionCheck](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#TerraformVersionCheck) is being used to prevent execution of this test prior to Terraform version `1.4.6` (refer to the release notes for Terraform [v1.4.6](https://github.com/hashicorp/terraform/releases/tag/v1.4.6)). + +```go +func Test_ExpectSensitiveValue_SensitiveStringAttribute(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_4_6), // StateResource.SensitiveValues + }, + // Provider definition omitted. + Steps: []r.TestStep{ + { + Config: ` + resource "test_resource" "one" { + sensitive_string_attribute = "test" + } + `, + ConfigStateChecks: r.ConfigStateChecks{ + statecheck.ExpectSensitiveValue("test_resource.one", + tfjsonpath.New("sensitive_string_attribute")), + }, + }, + }, + }) +} +```