mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
implement override resources for mock providers (#2168)
Signed-off-by: ollevche <ollevche@gmail.com>
This commit is contained in:
parent
cb866bf503
commit
6c8bfa2794
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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" {
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -975,6 +975,7 @@ func (c *Config) transformProviderConfigsForTest(run *TestRun, file *TestFile) (
|
||||
DeclRange: testProvider.DeclRange,
|
||||
IsMocked: testProvider.IsMocked,
|
||||
MockResources: testProvider.MockResources,
|
||||
OverrideResources: testProvider.OverrideResources,
|
||||
}
|
||||
|
||||
}
|
||||
@ -993,6 +994,7 @@ func (c *Config) transformProviderConfigsForTest(run *TestRun, file *TestFile) (
|
||||
DeclRange: mp.DeclRange,
|
||||
IsMocked: true,
|
||||
MockResources: mp.MockResources,
|
||||
OverrideResources: mp.OverrideResources,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ type Provider struct {
|
||||
// testing framework to instantiate test provider wrapper.
|
||||
IsMocked bool
|
||||
MockResources []*MockResource
|
||||
OverrideResources []*OverrideResource
|
||||
|
||||
ForEach hcl.Expression
|
||||
Instances map[addrs.InstanceKey]instances.RepetitionData
|
||||
|
@ -107,6 +107,7 @@ func (file *TestFile) getTestProviderOrMock(addr string) (*Provider, bool) {
|
||||
DeclRange: mockProvider.DeclRange,
|
||||
IsMocked: true,
|
||||
MockResources: mockProvider.MockResources,
|
||||
OverrideResources: mockProvider.OverrideResources,
|
||||
}
|
||||
|
||||
return p, true
|
||||
@ -319,6 +320,7 @@ type MockProvider struct {
|
||||
// Fields below are specific to configs.MockProvider:
|
||||
|
||||
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,37 +922,25 @@ func decodeMockProviderBlock(block *hcl.Block) (*MockProvider, hcl.Diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
managedResources = make(map[string]struct{})
|
||||
dataResources = make(map[string]struct{})
|
||||
)
|
||||
|
||||
for _, block := range content.Blocks {
|
||||
switch block.Type {
|
||||
case blockNameMockData, blockNameMockResource:
|
||||
res, resDiags := decodeMockResourceBlock(block)
|
||||
diags = append(diags, resDiags...)
|
||||
if resDiags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
|
||||
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{}{}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
||||
providerForTest.setSingleResource(n.Addr.Resource.Resource, n.Config.OverrideValues)
|
||||
|
||||
return providerForTest, schema, nil
|
||||
provider, err := newProviderForTestWithSchema(underlyingProvider, schema)
|
||||
if err != nil {
|
||||
return nil, providers.ProviderSchema{}, err
|
||||
}
|
||||
|
||||
provider = provider.
|
||||
withOverrideResource(n.Addr.ConfigResource(), n.Config.OverrideValues).
|
||||
linkWithCurrentResource(n.Addr.ConfigResource())
|
||||
|
||||
return provider, schema, nil
|
||||
}
|
||||
|
@ -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{
|
||||
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
|
||||
}
|
||||
|
||||
if schema.Diagnostics.HasErrors() {
|
||||
return providerForTest{}, fmt.Errorf("invalid provider schema for test wrapper: %w", schema.Diagnostics.Err())
|
||||
}
|
||||
|
||||
return providerForTest{
|
||||
internal: internal,
|
||||
schema: schema,
|
||||
managedResources: make(resourceForTestByType),
|
||||
dataResources: make(resourceForTestByType),
|
||||
}
|
||||
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 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())
|
||||
}
|
||||
|
||||
p := newProviderForTestWithSchema(internal, schema)
|
||||
|
||||
p.addMockResources(res)
|
||||
|
||||
return p, 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())
|
||||
}
|
||||
|
||||
type resourceForTestByType map[string]resourceForTest
|
||||
resources[res.Type] = resourceForTest{
|
||||
values: res.Defaults,
|
||||
}
|
||||
}
|
||||
|
||||
func (m resourceForTestByType) getOverrideValues(typeName string) map[string]cty.Value {
|
||||
return m[typeName].overrideValues
|
||||
return p
|
||||
}
|
||||
|
||||
func (p providerForTest) withCopiedOverrideResources() providerForTest {
|
||||
p.overrideResources = p.overrideResources.copy()
|
||||
return p
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -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:
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user