diff --git a/CHANGELOG.md b/CHANGELOG.md index c325cb447f..a416e72fe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ BUG FIXES: * Provider functions will now handle partially unknown arguments per the tfplugin spec ([#2127](https://github.com/opentofu/opentofu/pull/2127)) * `tofu init` will no longer return a spurious "Backend configuration changed" error when re-initializing a working directory with existing initialization of a backend whose configuration schema has required arguments. This was a regression caused by the similar fix in the v1.8.4 release. ([#2135](https://github.com/opentofu/opentofu/pull/2135)) +* The `tofu test` command doesn't try to validate mock provider definition by its underlying provider schema now. ([#2140](https://github.com/opentofu/opentofu/pull/2140)) ## 1.8.4 diff --git a/internal/command/test_test.go b/internal/command/test_test.go index a3908e0693..99e69e4d18 100644 --- a/internal/command/test_test.go +++ b/internal/command/test_test.go @@ -17,6 +17,7 @@ import ( "github.com/opentofu/opentofu/internal/addrs" testing_command "github.com/opentofu/opentofu/internal/command/testing" "github.com/opentofu/opentofu/internal/command/views" + "github.com/opentofu/opentofu/internal/configs/configschema" "github.com/opentofu/opentofu/internal/providers" "github.com/opentofu/opentofu/internal/terminal" ) @@ -1300,3 +1301,66 @@ digits, underscores, and dashes. }) } } + +// TestTest_MockProviderValidation checks if tofu test runs proper validation for +// mock_provider. Even if provider schema has required fields, tofu test should +// ignore it completely, because the provider is mocked. +func TestTest_MockProviderValidation(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("test/mock_provider_validation"), td) + defer testChdir(t, td)() + + provider := testing_command.NewProvider(nil) + providerSource, closePS := newMockProviderSource(t, map[string][]string{ + "test": {"1.0.0"}, + }) + defer closePS() + + provider.Provider.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ + Provider: providers.Schema{ + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "required_field": { + Type: cty.String, + Required: true, + }, + }, + }, + }, + ResourceTypes: map[string]providers.Schema{ + "test_resource": { + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "value": { + Type: cty.String, + Optional: true, + }, + "computed_value": { + Type: cty.String, + Computed: true, + }, + }, + }, + }, + }, + } + + streams, _ := terminal.StreamsForTesting(t) + view := views.NewView(streams) + ui := new(cli.MockUi) + meta := Meta{ + testingOverrides: metaOverridesForProvider(provider.Provider), + Ui: ui, + View: view, + Streams: streams, + ProviderSource: providerSource, + } + + testCmd := &TestCommand{ + Meta: meta, + } + + if code := testCmd.Run(nil); code != 0 { + t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter) + } +} diff --git a/internal/command/testdata/test/mock_provider_validation/main.tf b/internal/command/testdata/test/mock_provider_validation/main.tf new file mode 100644 index 0000000000..2da6a3cfd0 --- /dev/null +++ b/internal/command/testdata/test/mock_provider_validation/main.tf @@ -0,0 +1,5 @@ +provider "test" {} + +resource "test_resource" "primary" { + value = "foo" +} diff --git a/internal/command/testdata/test/mock_provider_validation/main.tftest.hcl b/internal/command/testdata/test/mock_provider_validation/main.tftest.hcl new file mode 100644 index 0000000000..d787ca3634 --- /dev/null +++ b/internal/command/testdata/test/mock_provider_validation/main.tftest.hcl @@ -0,0 +1,14 @@ +mock_provider "test" { + mock_resource "test_resource" { + defaults = { + computed_value = "bar" + } + } +} + +run "test" { + assert { + condition = test_resource.primary.computed_value == "bar" + error_message = "Unexpected computed value" + } +} diff --git a/internal/tofu/provider_for_test_framework.go b/internal/tofu/provider_for_test_framework.go index 2a6b3510d5..0ffcc8ac67 100644 --- a/internal/tofu/provider_for_test_framework.go +++ b/internal/tofu/provider_for_test_framework.go @@ -103,28 +103,22 @@ func (p *providerForTest) ReadDataSource(r providers.ReadDataSourceRequest) prov return resp } -// Calling the internal provider ensures providerForTest has the same behaviour as if -// it wasn't overridden or mocked. The only exception is ImportResourceState, which panics -// if called via providerForTest because importing is not supported in testing framework. +// ValidateProviderConfig is irrelevant when provider is mocked or overridden. +func (p *providerForTest) ValidateProviderConfig(_ providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse { + return providers.ValidateProviderConfigResponse{} +} +// GetProviderSchema is also used to perform additional validation outside of the provider +// implementation. We are excluding parts of the schema related to provider since it is +// irrelevant in the scope of mocking / overriding. When running `tofu test` configuration +// is being transformed for testing framework and original provider configuration is not +// accessible so it is safe to wipe metadata as well. See Config.transformProviderConfigsForTest +// for more details. func (p *providerForTest) GetProviderSchema() providers.GetProviderSchemaResponse { - return p.internal.GetProviderSchema() -} - -func (p *providerForTest) ValidateProviderConfig(r providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse { - return p.internal.ValidateProviderConfig(r) -} - -func (p *providerForTest) ValidateResourceConfig(r providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { - return p.internal.ValidateResourceConfig(r) -} - -func (p *providerForTest) ValidateDataResourceConfig(r providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse { - return p.internal.ValidateDataResourceConfig(r) -} - -func (p *providerForTest) UpgradeResourceState(r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse { - return p.internal.UpgradeResourceState(r) + providerSchema := p.internal.GetProviderSchema() + providerSchema.Provider = providers.Schema{} + providerSchema.ProviderMeta = providers.Schema{} + return providerSchema } // providerForTest doesn't configure its internal provider because it is mocked. @@ -132,22 +126,6 @@ func (p *providerForTest) ConfigureProvider(_ providers.ConfigureProviderRequest return providers.ConfigureProviderResponse{} } -func (p *providerForTest) Stop() error { - return p.internal.Stop() -} - -func (p *providerForTest) GetFunctions() providers.GetFunctionsResponse { - return p.internal.GetFunctions() -} - -func (p *providerForTest) CallFunction(r providers.CallFunctionRequest) providers.CallFunctionResponse { - return p.internal.CallFunction(r) -} - -func (p *providerForTest) Close() error { - return p.internal.Close() -} - func (p *providerForTest) ImportResourceState(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse { panic("Importing is not supported in testing context. providerForTest must not be used to call ImportResourceState") } @@ -190,6 +168,38 @@ func (p *providerForTest) addMockResources(mockResources []*configs.MockResource } } +// Calling the internal provider ensures providerForTest has the same behaviour as if +// it wasn't overridden or mocked. The only exception is ImportResourceState, which panics +// if called via providerForTest because importing is not supported in testing framework. + +func (p *providerForTest) ValidateResourceConfig(r providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse { + return p.internal.ValidateResourceConfig(r) +} + +func (p *providerForTest) ValidateDataResourceConfig(r providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse { + return p.internal.ValidateDataResourceConfig(r) +} + +func (p *providerForTest) UpgradeResourceState(r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse { + return p.internal.UpgradeResourceState(r) +} + +func (p *providerForTest) Stop() error { + return p.internal.Stop() +} + +func (p *providerForTest) GetFunctions() providers.GetFunctionsResponse { + return p.internal.GetFunctions() +} + +func (p *providerForTest) CallFunction(r providers.CallFunctionRequest) providers.CallFunctionResponse { + return p.internal.CallFunction(r) +} + +func (p *providerForTest) Close() error { + return p.internal.Close() +} + type resourceForTest struct { overrideValues map[string]cty.Value }