implement override resources for mock providers (#2168)

Signed-off-by: ollevche <ollevche@gmail.com>
This commit is contained in:
Oleksandr Levchenkov 2024-12-03 18:24:26 +02:00 committed by GitHub
parent cb866bf503
commit 6c8bfa2794
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 403 additions and 185 deletions

View File

@ -42,6 +42,7 @@ ENHANCEMENTS:
* Improved performance for large graphs with many submodules. ([#1809](https://github.com/opentofu/opentofu/pull/1809))
* Extended trace logging for HTTP backend, including request and response bodies. ([#2120](https://github.com/opentofu/opentofu/pull/2120))
* `tofu test` now throws errors instead of warnings for invalid override and mock fields. ([#2220](https://github.com/opentofu/opentofu/pull/2220))
* `tofu test` now supports `override_resource` and `override_data` blocks in the scope of a single `mock_provider`. ([#2168](https://github.com/opentofu/opentofu/pull/2168))
BUG FIXES:
* `templatefile` no longer crashes if the given filename is derived from a sensitive value. ([#1801](https://github.com/opentofu/opentofu/issues/1801))

View File

@ -76,7 +76,7 @@ func TestMocksAndOverrides(t *testing.T) {
if stderr != "" {
t.Errorf("unexpected stderr output on 'test':\n%s", stderr)
}
if !strings.Contains(stdout, "13 passed, 0 failed") {
if !strings.Contains(stdout, "15 passed, 0 failed") {
t.Errorf("output doesn't have expected success string:\n%s", stdout)
}
}

View File

@ -58,12 +58,17 @@ module "rand_count" {
source = "./rand"
}
resource "aws_s3_bucket" "test" {
bucket = "must not be used anyway"
data "http" "first" {
url = "must not be used anyway"
}
data "aws_s3_bucket" "test" {
bucket = "must not be used anyway"
provider http {
alias = "aliased"
}
data "http" "second" {
provider = http.aliased
url = "must not be used anyway"
}
provider "local" {

View File

@ -4,10 +4,16 @@ override_module {
override_resource {
target = local_file.dont_create_me
values = {
file_permission = "000"
}
}
override_resource {
target = module.first.local_file.dont_create_me
values = {
file_permission = "000"
}
}
run "check_root_overridden_res" {
@ -223,16 +229,10 @@ run "check_for_each_n_count_overridden" {
}
# ensures non-aliased provider is mocked by default
mock_provider "aws" {
mock_resource "aws_s3_bucket" {
mock_provider "http" {
mock_data "http" {
defaults = {
arn = "arn:aws:s3:::mocked"
}
}
mock_data "aws_s3_bucket" {
defaults = {
bucket_domain_name = "mocked.com"
response_body = "I am mocked!"
}
}
}
@ -241,6 +241,12 @@ mock_provider "aws" {
# and aliased one is mocked
mock_provider "local" {
alias = "aliased"
mock_resource "local_file" {
defaults = {
file_permission = "000"
}
}
}
# ensures we can use this provider in run's providers block
@ -267,13 +273,8 @@ mock_provider "random" {
run "check_mock_providers" {
assert {
condition = resource.aws_s3_bucket.test.arn == "arn:aws:s3:::mocked"
error_message = "aws s3 bucket resource doesn't have mocked values"
}
assert {
condition = data.aws_s3_bucket.test.bucket_domain_name == "mocked.com"
error_message = "aws s3 bucket data doesn't have mocked values"
condition = data.http.first.response_body == "I am mocked!"
error_message = "http data doesn't have mocked values"
}
assert {
@ -294,7 +295,8 @@ run "check_mock_providers" {
run "check_providers_block" {
providers = {
aws = aws
http = http
http.aliased = http.aliased
local.aliased = local.aliased
random = random.for_pets
}
@ -309,3 +311,76 @@ run "check_providers_block" {
error_message = "random integer should not be mocked if providers block present"
}
}
# http.aliased provider is not used directly,
# but we don't want http provider to make any
# requests.
mock_provider "http" {
alias = "aliased"
}
mock_provider "http" {
alias = "with_mock_and_override"
mock_data "http" {
defaults = {
response_body = "mocked"
}
}
override_data {
target = data.http.first
values = {
response_body = "overridden [first]"
}
}
override_data {
target = data.http.second
values = {
response_body = "overridden [second]"
}
}
}
# This test ensures override_data works
# inside a provider block and priority
# between mocks and overrides is correct.
run "check_mock_provider_override" {
providers = {
http = http.with_mock_and_override
http.aliased = http.with_mock_and_override
local.aliased = local.aliased
}
assert {
condition = data.http.first.response_body == "overridden [first]"
error_message = "HTTP response body is not mocked"
}
assert {
condition = data.http.second.response_body == "overridden [second]"
error_message = "HTTP response body is not overridden"
}
}
# This test ensures override inside a mock_provider
# doesn't apply for resources created via a different
# provider.
run "check_multiple_mock_provider_override" {
providers = {
http = http.with_mock_and_override
http.aliased = http
local.aliased = local.aliased
}
assert {
condition = data.http.first.response_body == "overridden [first]"
error_message = "HTTP response body is not mocked"
}
assert {
condition = data.http.second.response_body == "I am mocked!"
error_message = "HTTP response body is not overridden"
}
}

View File

@ -966,15 +966,16 @@ func (c *Config) transformProviderConfigsForTest(run *TestRun, file *TestFile) (
}
next[ref.InChild.String()] = &Provider{
Name: ref.InChild.Name,
NameRange: ref.InChild.NameRange,
Alias: ref.InChild.Alias,
AliasRange: ref.InChild.AliasRange,
Version: testProvider.Version,
Config: testProvider.Config,
DeclRange: testProvider.DeclRange,
IsMocked: testProvider.IsMocked,
MockResources: testProvider.MockResources,
Name: ref.InChild.Name,
NameRange: ref.InChild.NameRange,
Alias: ref.InChild.Alias,
AliasRange: ref.InChild.AliasRange,
Version: testProvider.Version,
Config: testProvider.Config,
DeclRange: testProvider.DeclRange,
IsMocked: testProvider.IsMocked,
MockResources: testProvider.MockResources,
OverrideResources: testProvider.OverrideResources,
}
}
@ -986,13 +987,14 @@ func (c *Config) transformProviderConfigsForTest(run *TestRun, file *TestFile) (
}
for _, mp := range file.MockProviders {
next[mp.moduleUniqueKey()] = &Provider{
Name: mp.Name,
NameRange: mp.NameRange,
Alias: mp.Alias,
AliasRange: mp.AliasRange,
DeclRange: mp.DeclRange,
IsMocked: true,
MockResources: mp.MockResources,
Name: mp.Name,
NameRange: mp.NameRange,
Alias: mp.Alias,
AliasRange: mp.AliasRange,
DeclRange: mp.DeclRange,
IsMocked: true,
MockResources: mp.MockResources,
OverrideResources: mp.OverrideResources,
}
}
}

View File

@ -43,8 +43,9 @@ type Provider struct {
// IsMocked indicates if this provider has been mocked. It is used in
// testing framework to instantiate test provider wrapper.
IsMocked bool
MockResources []*MockResource
IsMocked bool
MockResources []*MockResource
OverrideResources []*OverrideResource
ForEach hcl.Expression
Instances map[addrs.InstanceKey]instances.RepetitionData

View File

@ -100,13 +100,14 @@ func (file *TestFile) getTestProviderOrMock(addr string) (*Provider, bool) {
mockProvider, ok := file.MockProviders[addr]
if ok {
p := &Provider{
Name: mockProvider.Name,
NameRange: mockProvider.NameRange,
Alias: mockProvider.Alias,
AliasRange: mockProvider.AliasRange,
DeclRange: mockProvider.DeclRange,
IsMocked: true,
MockResources: mockProvider.MockResources,
Name: mockProvider.Name,
NameRange: mockProvider.NameRange,
Alias: mockProvider.Alias,
AliasRange: mockProvider.AliasRange,
DeclRange: mockProvider.DeclRange,
IsMocked: true,
MockResources: mockProvider.MockResources,
OverrideResources: mockProvider.OverrideResources,
}
return p, true
@ -318,7 +319,8 @@ type MockProvider struct {
// Fields below are specific to configs.MockProvider:
MockResources []*MockResource
MockResources []*MockResource
OverrideResources []*OverrideResource
}
// moduleUniqueKey is copied from Provider.moduleUniqueKey
@ -329,6 +331,58 @@ func (p *MockProvider) moduleUniqueKey() string {
return p.Name
}
func (p *MockProvider) validateMockResources() hcl.Diagnostics {
var diags hcl.Diagnostics
managedResources := make(map[string]struct{})
dataResources := make(map[string]struct{})
for _, res := range p.MockResources {
resources := managedResources
if res.Mode == addrs.DataResourceMode {
resources = dataResources
}
if _, ok := resources[res.Type]; ok {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Duplicated `%v` block", res.getBlockName()),
Detail: fmt.Sprintf("`%v.%v` is already defined in `mock_provider` block.", res.getBlockName(), res.Type),
Subject: p.DeclRange.Ptr(),
})
continue
}
resources[res.Type] = struct{}{}
}
return diags
}
func (p *MockProvider) validateOverrideResources() hcl.Diagnostics {
var diags hcl.Diagnostics
resources := make(map[string]struct{})
for _, res := range p.OverrideResources {
k := res.TargetParsed.String()
if _, ok := resources[k]; ok {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Duplicated `%v` block", res.getBlockName()),
Detail: fmt.Sprintf("`%v` with target `%v` is already defined in `mock_provider` block.", res.getBlockName(), k),
Subject: p.DeclRange.Ptr(),
})
continue
}
resources[k] = struct{}{}
}
return diags
}
const (
blockNameMockResource = "mock_resource"
blockNameMockData = "mock_data"
@ -402,20 +456,13 @@ func loadTestFile(body hcl.Body) (*TestFile, hcl.Diagnostics) {
tf.Providers[provider.moduleUniqueKey()] = provider
}
case blockNameOverrideResource:
overrideRes, overrideResDiags := decodeOverrideResourceBlock(block, addrs.ManagedResourceMode)
case blockNameOverrideResource, blockNameOverrideData:
overrideRes, overrideResDiags := decodeOverrideResourceBlock(block)
diags = append(diags, overrideResDiags...)
if !overrideResDiags.HasErrors() {
tf.OverrideResources = append(tf.OverrideResources, overrideRes)
}
case blockNameOverrideData:
overrideData, overrideDataDiags := decodeOverrideResourceBlock(block, addrs.DataResourceMode)
diags = append(diags, overrideDataDiags...)
if !overrideDataDiags.HasErrors() {
tf.OverrideResources = append(tf.OverrideResources, overrideData)
}
case blockNameOverrideModule:
overrideMod, overrideModDiags := decodeOverrideModuleBlock(block)
diags = append(diags, overrideModDiags...)
@ -527,20 +574,13 @@ func decodeTestRunBlock(block *hcl.Block) (*TestRun, hcl.Diagnostics) {
r.Module = module
}
case blockNameOverrideResource:
overrideRes, overrideResDiags := decodeOverrideResourceBlock(block, addrs.ManagedResourceMode)
case blockNameOverrideResource, blockNameOverrideData:
overrideRes, overrideResDiags := decodeOverrideResourceBlock(block)
diags = append(diags, overrideResDiags...)
if !overrideResDiags.HasErrors() {
r.OverrideResources = append(r.OverrideResources, overrideRes)
}
case blockNameOverrideData:
overrideData, overrideDataDiags := decodeOverrideResourceBlock(block, addrs.DataResourceMode)
diags = append(diags, overrideDataDiags...)
if !overrideDataDiags.HasErrors() {
r.OverrideResources = append(r.OverrideResources, overrideData)
}
case blockNameOverrideModule:
overrideMod, overrideModDiags := decodeOverrideModuleBlock(block)
diags = append(diags, overrideModDiags...)
@ -763,7 +803,7 @@ func decodeTestRunOptionsBlock(block *hcl.Block) (*TestRunOptions, hcl.Diagnosti
return &opts, diags
}
func decodeOverrideResourceBlock(block *hcl.Block, mode addrs.ResourceMode) (*OverrideResource, hcl.Diagnostics) {
func decodeOverrideResourceBlock(block *hcl.Block) (*OverrideResource, hcl.Diagnostics) {
parseTarget := func(attr *hcl.Attribute) (hcl.Traversal, *addrs.ConfigResource, hcl.Diagnostics) {
traversal, traversalDiags := hcl.AbsTraversalForExpr(attr.Expr)
diags := traversalDiags
@ -780,8 +820,15 @@ func decodeOverrideResourceBlock(block *hcl.Block, mode addrs.ResourceMode) (*Ov
return traversal, &configRes, diags
}
res := &OverrideResource{
Mode: mode,
res := &OverrideResource{}
switch block.Type {
case blockNameOverrideResource:
res.Mode = addrs.ManagedResourceMode
case blockNameOverrideData:
res.Mode = addrs.DataResourceMode
default:
panic("BUG: unsupported block type for override resource: " + block.Type)
}
content, diags := block.Body.Content(overrideResourceBlockSchema)
@ -875,38 +922,26 @@ func decodeMockProviderBlock(block *hcl.Block) (*MockProvider, hcl.Diagnostics)
}
}
var (
managedResources = make(map[string]struct{})
dataResources = make(map[string]struct{})
)
for _, block := range content.Blocks {
res, resDiags := decodeMockResourceBlock(block)
diags = append(diags, resDiags...)
if resDiags.HasErrors() {
continue
switch block.Type {
case blockNameMockData, blockNameMockResource:
res, resDiags := decodeMockResourceBlock(block)
diags = append(diags, resDiags...)
if !resDiags.HasErrors() {
provider.MockResources = append(provider.MockResources, res)
}
case blockNameOverrideData, blockNameOverrideResource:
res, resDiags := decodeOverrideResourceBlock(block)
diags = append(diags, resDiags...)
if !resDiags.HasErrors() {
provider.OverrideResources = append(provider.OverrideResources, res)
}
}
resources := managedResources
if res.Mode == addrs.DataResourceMode {
resources = dataResources
}
if _, ok := resources[res.Type]; ok {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Duplicated `%v` block", res.getBlockName()),
Detail: fmt.Sprintf("`%v.%v` is already defined in `mock_provider` block.", res.getBlockName(), res.Type),
Subject: provider.DeclRange.Ptr(),
})
continue
}
resources[res.Type] = struct{}{}
provider.MockResources = append(provider.MockResources, res)
}
diags = append(diags, provider.validateMockResources()...)
diags = append(diags, provider.validateOverrideResources()...)
return provider, diags
}
@ -1146,6 +1181,12 @@ var mockProviderBlockSchema = &hcl.BodySchema{
Type: blockNameMockData,
LabelNames: []string{"type"},
},
{
Type: blockNameOverrideResource,
},
{
Type: blockNameOverrideData,
},
},
}

View File

@ -153,10 +153,14 @@ func (ctx *BuiltinEvalContext) InitProvider(addr addrs.AbsProviderConfig, provid
// We cannot wrap providers.Factory itself, because factories don't support aliases.
pc, ok := ctx.Evaluator.Config.Module.GetProviderConfig(addr.Provider.Type, addr.Alias)
if ok && pc.IsMocked {
p, err = newProviderForTest(p, pc.MockResources)
testP, err := newProviderForTestWithSchema(p, p.GetProviderSchema())
if err != nil {
return nil, err
}
p = testP.
withMockResources(pc.MockResources).
withOverrideResources(pc.OverrideResources)
}
}

View File

@ -2704,12 +2704,21 @@ func (n *NodeAbstractResourceInstance) getProvider(ctx EvalContext) (providers.I
}
if n.Config == nil || !n.Config.IsOverridden {
if p, ok := underlyingProvider.(providerForTest); ok {
underlyingProvider = p.linkWithCurrentResource(n.Addr.ConfigResource())
}
return underlyingProvider, schema, nil
}
providerForTest := newProviderForTestWithSchema(underlyingProvider, schema)
provider, err := newProviderForTestWithSchema(underlyingProvider, schema)
if err != nil {
return nil, providers.ProviderSchema{}, err
}
providerForTest.setSingleResource(n.Addr.Resource.Resource, n.Config.OverrideValues)
provider = provider.
withOverrideResource(n.Addr.ConfigResource(), n.Config.OverrideValues).
linkWithCurrentResource(n.Addr.ConfigResource())
return providerForTest, schema, nil
return provider, schema, nil
}

View File

@ -26,46 +26,52 @@ type providerForTest struct {
internal providers.Interface
schema providers.ProviderSchema
managedResources resourceForTestByType
dataResources resourceForTestByType
mockResources mockResourcesForTest
overrideResources overrideResourcesForTest
currentResourceAddress string
}
func newProviderForTestWithSchema(internal providers.Interface, schema providers.ProviderSchema) *providerForTest {
return &providerForTest{
internal: internal,
schema: schema,
managedResources: make(resourceForTestByType),
dataResources: make(resourceForTestByType),
func newProviderForTestWithSchema(internal providers.Interface, schema providers.ProviderSchema) (providerForTest, error) {
if p, ok := internal.(providerForTest); ok {
// We can create a proper deep copy here, however currently
// it is only relevant for override resources, since we extend
// the override resource map in NodeAbstractResourceInstance.
return p.withCopiedOverrideResources(), nil
}
}
func newProviderForTest(internal providers.Interface, res []*configs.MockResource) (providers.Interface, error) {
schema := internal.GetProviderSchema()
if schema.Diagnostics.HasErrors() {
return nil, fmt.Errorf("getting provider schema for test wrapper: %w", schema.Diagnostics.Err())
return providerForTest{}, fmt.Errorf("invalid provider schema for test wrapper: %w", schema.Diagnostics.Err())
}
p := newProviderForTestWithSchema(internal, schema)
p.addMockResources(res)
return p, nil
return providerForTest{
internal: internal,
schema: schema,
mockResources: mockResourcesForTest{
managed: make(map[string]resourceForTest),
data: make(map[string]resourceForTest),
},
overrideResources: overrideResourcesForTest{
managed: make(map[string]resourceForTest),
data: make(map[string]resourceForTest),
},
}, nil
}
func (p *providerForTest) ReadResource(r providers.ReadResourceRequest) providers.ReadResourceResponse {
var resp providers.ReadResourceResponse
func (p providerForTest) ReadResource(r providers.ReadResourceRequest) providers.ReadResourceResponse {
resSchema, _ := p.schema.SchemaForResourceType(addrs.ManagedResourceMode, r.TypeName)
overrideValues := p.managedResources.getOverrideValues(r.TypeName)
mockValues := p.getMockValuesForManagedResource(r.TypeName)
var resp providers.ReadResourceResponse
resp.NewState, resp.Diagnostics = newMockValueComposer(r.TypeName).
ComposeBySchema(resSchema, r.ProviderMeta, overrideValues)
ComposeBySchema(resSchema, r.ProviderMeta, mockValues)
return resp
}
func (p *providerForTest) PlanResourceChange(r providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
func (p providerForTest) PlanResourceChange(r providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
if r.Config.IsNull() {
return providers.PlanResourceChangeResponse{
PlannedState: r.ProposedNewState, // null
@ -74,37 +80,37 @@ func (p *providerForTest) PlanResourceChange(r providers.PlanResourceChangeReque
resSchema, _ := p.schema.SchemaForResourceType(addrs.ManagedResourceMode, r.TypeName)
overrideValues := p.managedResources.getOverrideValues(r.TypeName)
mockValues := p.getMockValuesForManagedResource(r.TypeName)
var resp providers.PlanResourceChangeResponse
resp.PlannedState, resp.Diagnostics = newMockValueComposer(r.TypeName).
ComposeBySchema(resSchema, r.Config, overrideValues)
ComposeBySchema(resSchema, r.Config, mockValues)
return resp
}
func (p *providerForTest) ApplyResourceChange(r providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
func (p providerForTest) ApplyResourceChange(r providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
return providers.ApplyResourceChangeResponse{
NewState: r.PlannedState,
}
}
func (p *providerForTest) ReadDataSource(r providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
func (p providerForTest) ReadDataSource(r providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
resSchema, _ := p.schema.SchemaForResourceType(addrs.DataResourceMode, r.TypeName)
var resp providers.ReadDataSourceResponse
overrideValues := p.dataResources.getOverrideValues(r.TypeName)
mockValues := p.getMockValuesForDataResource(r.TypeName)
resp.State, resp.Diagnostics = newMockValueComposer(r.TypeName).
ComposeBySchema(resSchema, r.Config, overrideValues)
ComposeBySchema(resSchema, r.Config, mockValues)
return resp
}
// ValidateProviderConfig is irrelevant when provider is mocked or overridden.
func (p *providerForTest) ValidateProviderConfig(_ providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse {
func (p providerForTest) ValidateProviderConfig(_ providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse {
return providers.ValidateProviderConfigResponse{}
}
@ -114,7 +120,7 @@ func (p *providerForTest) ValidateProviderConfig(_ providers.ValidateProviderCon
// 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 {
func (p providerForTest) GetProviderSchema() providers.GetProviderSchemaResponse {
providerSchema := p.internal.GetProviderSchema()
providerSchema.Provider = providers.Schema{}
providerSchema.ProviderMeta = providers.Schema{}
@ -122,92 +128,163 @@ func (p *providerForTest) GetProviderSchema() providers.GetProviderSchemaRespons
}
// providerForTest doesn't configure its internal provider because it is mocked.
func (p *providerForTest) ConfigureProvider(_ providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
func (p providerForTest) ConfigureProvider(_ providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
return providers.ConfigureProviderResponse{}
}
func (p *providerForTest) ImportResourceState(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
func (p providerForTest) ImportResourceState(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
panic("Importing is not supported in testing context. providerForTest must not be used to call ImportResourceState")
}
func (p *providerForTest) setSingleResource(addr addrs.Resource, overrides map[string]cty.Value) {
res := resourceForTest{
overrideValues: overrides,
}
switch addr.Mode {
case addrs.ManagedResourceMode:
p.managedResources[addr.Type] = res
case addrs.DataResourceMode:
p.dataResources[addr.Type] = res
case addrs.InvalidResourceMode:
panic("BUG: invalid mock resource mode")
default:
panic("BUG: unsupported resource mode: " + addr.Mode.String())
}
}
func (p *providerForTest) addMockResources(mockResources []*configs.MockResource) {
for _, mockRes := range mockResources {
var resources resourceForTestByType
switch mockRes.Mode {
case addrs.ManagedResourceMode:
resources = p.managedResources
case addrs.DataResourceMode:
resources = p.dataResources
case addrs.InvalidResourceMode:
panic("BUG: invalid mock resource mode")
default:
panic("BUG: unsupported mock resource mode: " + mockRes.Mode.String())
}
resources[mockRes.Type] = resourceForTest{
overrideValues: mockRes.Defaults,
}
}
}
// 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 {
func (p providerForTest) ValidateResourceConfig(r providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
return p.internal.ValidateResourceConfig(r)
}
func (p *providerForTest) ValidateDataResourceConfig(r providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse {
func (p providerForTest) ValidateDataResourceConfig(r providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse {
return p.internal.ValidateDataResourceConfig(r)
}
func (p *providerForTest) UpgradeResourceState(r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
func (p providerForTest) UpgradeResourceState(r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
return p.internal.UpgradeResourceState(r)
}
func (p *providerForTest) Stop() error {
func (p providerForTest) Stop() error {
return p.internal.Stop()
}
func (p *providerForTest) GetFunctions() providers.GetFunctionsResponse {
func (p providerForTest) GetFunctions() providers.GetFunctionsResponse {
return p.internal.GetFunctions()
}
func (p *providerForTest) CallFunction(r providers.CallFunctionRequest) providers.CallFunctionResponse {
func (p providerForTest) CallFunction(r providers.CallFunctionRequest) providers.CallFunctionResponse {
return p.internal.CallFunction(r)
}
func (p *providerForTest) Close() error {
func (p providerForTest) Close() error {
return p.internal.Close()
}
type resourceForTest struct {
overrideValues map[string]cty.Value
func (p providerForTest) withMockResources(mockResources []*configs.MockResource) providerForTest {
for _, res := range mockResources {
var resources map[mockResourceType]resourceForTest
switch res.Mode {
case addrs.ManagedResourceMode:
resources = p.mockResources.managed
case addrs.DataResourceMode:
resources = p.mockResources.data
case addrs.InvalidResourceMode:
panic("BUG: invalid mock resource mode")
default:
panic("BUG: unsupported mock resource mode: " + res.Mode.String())
}
resources[res.Type] = resourceForTest{
values: res.Defaults,
}
}
return p
}
type resourceForTestByType map[string]resourceForTest
func (p providerForTest) withCopiedOverrideResources() providerForTest {
p.overrideResources = p.overrideResources.copy()
return p
}
func (m resourceForTestByType) getOverrideValues(typeName string) map[string]cty.Value {
return m[typeName].overrideValues
func (p providerForTest) withOverrideResources(overrideResources []*configs.OverrideResource) providerForTest {
for _, res := range overrideResources {
p = p.withOverrideResource(*res.TargetParsed, res.Values)
}
return p
}
func (p providerForTest) withOverrideResource(addr addrs.ConfigResource, overrides map[string]cty.Value) providerForTest {
var resources map[string]resourceForTest
switch addr.Resource.Mode {
case addrs.ManagedResourceMode:
resources = p.overrideResources.managed
case addrs.DataResourceMode:
resources = p.overrideResources.data
case addrs.InvalidResourceMode:
panic("BUG: invalid override resource mode")
default:
panic("BUG: unsupported override resource mode: " + addr.Resource.Mode.String())
}
resources[addr.String()] = resourceForTest{
values: overrides,
}
return p
}
func (p providerForTest) linkWithCurrentResource(addr addrs.ConfigResource) providerForTest {
p.currentResourceAddress = addr.String()
return p
}
type resourceForTest struct {
values map[string]cty.Value
}
type mockResourceType = string
type mockResourcesForTest struct {
managed map[mockResourceType]resourceForTest
data map[mockResourceType]resourceForTest
}
type overrideResourceAddress = string
type overrideResourcesForTest struct {
managed map[overrideResourceAddress]resourceForTest
data map[overrideResourceAddress]resourceForTest
}
func (res overrideResourcesForTest) copy() overrideResourcesForTest {
resCopy := overrideResourcesForTest{
managed: make(map[overrideResourceAddress]resourceForTest, len(res.managed)),
data: make(map[overrideResourceAddress]resourceForTest, len(res.data)),
}
for k, v := range res.managed {
resCopy.managed[k] = v
}
for k, v := range res.data {
resCopy.data[k] = v
}
return resCopy
}
func (p providerForTest) getMockValuesForManagedResource(typeName string) map[string]cty.Value {
if p.currentResourceAddress != "" {
res, ok := p.overrideResources.managed[p.currentResourceAddress]
if ok {
return res.values
}
}
return p.mockResources.managed[typeName].values
}
func (p providerForTest) getMockValuesForDataResource(typeName string) map[string]cty.Value {
if p.currentResourceAddress != "" {
res, ok := p.overrideResources.data[p.currentResourceAddress]
if ok {
return res.values
}
}
return p.mockResources.data[typeName].values
}
func newMockValueComposer(typeName string) hcl2shim.MockValueComposer {

View File

@ -158,7 +158,7 @@ A test file consists of:
* A **[`variables` block](#the-variables-and-runvariables-blocks)** (optional): define variables for all tests in the
current file.
* The **[`provider` blocks](#the-providers-block)** (optional): define the providers to be used for the tests.
* The **[`mock_provider` blocks](#the-mock_providers-blocks)** (optional): define the providers to be mocked.
* The **[`mock_provider` blocks](#the-mock_provider-blocks)** (optional): define the providers to be mocked.
* The **[`override_resource` blocks](#the-override_resource-and-override_data-blocks)** (optional): define the resources to be overridden.
* The **[`override_data` blocks](#the-override_resource-and-override_data-blocks)** (optional): define the data sources to be overridden.
* The **[`override_module` blocks](#the-override_module-block)** (optional): define the module calls to be overridden.
@ -429,6 +429,9 @@ Mock providers also support `alias` field as well as `mock_resource` and `mock_d
In some cases, you may want to use default values instead of automatically generated ones by passing them
inside `defaults` field of `mock_resource` or `mock_data` blocks.
Additionally, you can use `override_resource` and `override_data` blocks to override resources or data
sources in the scope of a single provider. Read more about overriding in [the next section](#the-override_resource-and-override_data-blocks).
In the example below, we test if the bucket name is correctly passed to the resource
without actually creating it: