2023-09-27 20:00:36 -04:00
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
2023-07-12 20:16:16 -04:00
package stackruntime
import (
"context"
2024-02-21 05:07:51 -05:00
"path/filepath"
2023-07-12 20:16:16 -04:00
"testing"
2023-07-14 14:45:04 -04:00
"github.com/hashicorp/hcl/v2"
2024-02-26 05:36:19 -05:00
"github.com/zclconf/go-cty/cty"
2024-02-21 04:58:44 -05:00
"github.com/hashicorp/terraform/internal/addrs"
2024-06-27 10:08:08 -04:00
"github.com/hashicorp/terraform/internal/depsfile"
"github.com/hashicorp/terraform/internal/getproviders/providerreqs"
2024-02-21 04:58:44 -05:00
"github.com/hashicorp/terraform/internal/providers"
stacks_testing_provider "github.com/hashicorp/terraform/internal/stacks/stackruntime/testing"
2023-07-14 14:45:04 -04:00
"github.com/hashicorp/terraform/internal/tfdiags"
2023-07-12 20:16:16 -04:00
)
2024-02-26 04:42:14 -05:00
type validateTestInput struct {
2024-02-28 02:24:53 -05:00
// skip lets us write tests for behaviour we want to add in the future. Set
// this to true for any tests that are not yet implemented.
skip bool
// diags is a function that returns the expected diagnostics for the
// test.
2024-02-26 04:42:14 -05:00
diags func ( ) tfdiags . Diagnostics
2024-02-26 05:36:19 -05:00
// planInputVars is used only in the plan tests to provide a set of input
// variables to use for the plan request. Validate operates statically so
// does not need any input variables.
planInputVars map [ string ] cty . Value
2024-02-26 04:42:14 -05:00
}
var (
// validConfigurations are shared between the validate and plan tests.
validConfigurations = map [ string ] validateTestInput {
2024-02-21 05:07:51 -05:00
"empty" : { } ,
2024-03-07 15:49:39 -05:00
"plan-variable-defaults" : { } ,
2024-02-21 05:07:51 -05:00
"variable-output-roundtrip" : { } ,
"variable-output-roundtrip-nested" : { } ,
2024-07-26 05:36:08 -04:00
"aliased-provider" : { } ,
2026-02-02 10:38:27 -05:00
"planning-action-lifecycle" : { } ,
2024-02-26 05:36:19 -05:00
filepath . Join ( "with-single-input" , "input-from-component" ) : { } ,
filepath . Join ( "with-single-input" , "input-from-component-list" ) : {
planInputVars : map [ string ] cty . Value {
"components" : cty . SetVal ( [ ] cty . Value {
cty . StringVal ( "one" ) ,
cty . StringVal ( "two" ) ,
cty . StringVal ( "three" ) ,
} ) ,
} ,
} ,
2024-03-04 16:20:17 -05:00
filepath . Join ( "with-single-input" , "provider-name-clash" ) : {
planInputVars : map [ string ] cty . Value {
"input" : cty . StringVal ( "input" ) ,
} ,
} ,
filepath . Join ( "with-single-input" , "valid" ) : {
planInputVars : map [ string ] cty . Value {
"input" : cty . StringVal ( "input" ) ,
2024-05-16 07:06:13 -04:00
} ,
} ,
filepath . Join ( "with-single-input" , "provider-for-each" ) : {
planInputVars : map [ string ] cty . Value {
"input" : cty . StringVal ( "input" ) ,
2024-03-04 16:20:17 -05:00
} ,
} ,
2023-07-12 20:16:16 -04:00
}
2024-02-26 04:42:14 -05:00
// invalidConfigurations are shared between the validate and plan tests.
invalidConfigurations = map [ string ] validateTestInput {
2026-03-05 07:49:30 -05:00
"const-variable-in-component" : {
diags : func ( ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Const variable not supported in stacks" ,
Detail : "Variables with const = true are not supported in modules used as stack components. Const variables are evaluated during configuration loading, which is not supported in the stacks runtime." ,
Subject : & hcl . Range {
Filename : mainBundleSourceAddrStr ( "const-variable-in-component/const-variable-in-component.tf" ) ,
Start : hcl . Pos { Line : 10 , Column : 1 , Byte : 124 } ,
End : hcl . Pos { Line : 10 , Column : 17 , Byte : 140 } ,
} ,
} )
return diags
} ,
} ,
2024-02-21 04:58:44 -05:00
"validate-undeclared-variable" : {
diags : func ( ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reference to undeclared input variable" ,
Detail : ` There is no variable "a" block declared in this stack. ` ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "validate-undeclared-variable/validate-undeclared-variable.tfcomponent.hcl" ) ,
2024-02-21 04:58:44 -05:00
Start : hcl . Pos { Line : 3 , Column : 11 , Byte : 40 } ,
End : hcl . Pos { Line : 3 , Column : 16 , Byte : 45 } ,
} ,
} )
return diags
} ,
} ,
"invalid-configuration" : {
diags : func ( ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Unsupported argument" ,
Detail : "An argument named \"invalid\" is not expected here." ,
Subject : & hcl . Range {
2024-02-23 13:18:17 -05:00
Filename : mainBundleSourceAddrStr ( "invalid-configuration/invalid-configuration.tf" ) ,
2024-02-21 04:58:44 -05:00
Start : hcl . Pos { Line : 11 , Column : 3 , Byte : 163 } ,
End : hcl . Pos { Line : 11 , Column : 10 , Byte : 170 } ,
} ,
} )
return diags
} ,
2023-07-14 14:45:04 -04:00
} ,
2024-02-21 05:07:51 -05:00
filepath . Join ( "with-single-input" , "undeclared-provider" ) : {
diags : func ( ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
2024-07-08 04:31:52 -04:00
Summary : "Reference to undeclared provider configuration" ,
Detail : "There is no provider \"testing\" \"default\" block declared in this stack." ,
2024-02-21 05:07:51 -05:00
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/undeclared-provider/undeclared-provider.tfcomponent.hcl" ) ,
2024-07-08 04:31:52 -04:00
Start : hcl . Pos { Line : 10 , Column : 15 , Byte : 163 } ,
End : hcl . Pos { Line : 10 , Column : 39 , Byte : 187 } ,
2024-02-21 05:07:51 -05:00
} ,
} )
2024-09-07 09:04:14 -04:00
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reference to undeclared provider configuration" ,
Detail : "There is no provider \"testing\" \"default\" block declared in this stack." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/undeclared-provider/undeclared-provider.tfcomponent.hcl" ) ,
2024-09-07 09:04:14 -04:00
Start : hcl . Pos { Line : 25 , Column : 15 , Byte : 379 } ,
End : hcl . Pos { Line : 25 , Column : 39 , Byte : 403 } ,
} ,
} )
2024-02-21 05:07:51 -05:00
return diags
} ,
} ,
filepath . Join ( "with-single-input" , "missing-provider" ) : {
diags : func ( ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
2024-09-07 09:04:14 -04:00
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Missing required provider configuration" ,
Detail : "The root module for component.removed requires a provider configuration named \"testing\" for provider \"hashicorp/testing\", which is not assigned in the block's \"providers\" argument." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/missing-provider/missing-provider.tfcomponent.hcl" ) ,
2024-09-07 09:04:14 -04:00
Start : hcl . Pos { Line : 25 , Column : 1 , Byte : 337 } ,
End : hcl . Pos { Line : 25 , Column : 8 , Byte : 344 } ,
} ,
} )
2024-02-21 05:07:51 -05:00
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Missing required provider configuration" ,
2024-09-05 07:15:53 -04:00
Detail : "The root module for component.self requires a provider configuration named \"testing\" for provider \"hashicorp/testing\", which is not assigned in the block's \"providers\" argument." ,
2024-02-21 05:07:51 -05:00
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/missing-provider/missing-provider.tfcomponent.hcl" ) ,
2024-02-21 05:07:51 -05:00
Start : hcl . Pos { Line : 14 , Column : 1 , Byte : 169 } ,
End : hcl . Pos { Line : 14 , Column : 17 , Byte : 185 } ,
} ,
} )
return diags
} ,
} ,
2024-02-27 02:43:58 -05:00
filepath . Join ( "with-single-input" , "invalid-provider-type" ) : {
2024-02-28 02:24:53 -05:00
diags : func ( ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid provider configuration" ,
2024-07-08 04:31:52 -04:00
Detail : "The provider configuration slot \"testing\" requires a configuration for provider \"registry.terraform.io/hashicorp/testing\", not for provider \"terraform.io/builtin/testing\"." ,
2024-02-28 02:24:53 -05:00
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/invalid-provider-type/invalid-provider-type.tfcomponent.hcl" ) ,
2024-02-28 02:24:53 -05:00
Start : hcl . Pos { Line : 22 , Column : 15 , Byte : 378 } ,
End : hcl . Pos { Line : 22 , Column : 39 , Byte : 402 } ,
} ,
} )
2024-09-07 09:04:14 -04:00
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid provider configuration" ,
Detail : "The provider configuration slot \"testing\" requires a configuration for provider \"registry.terraform.io/hashicorp/testing\", not for provider \"terraform.io/builtin/testing\"." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/invalid-provider-type/invalid-provider-type.tfcomponent.hcl" ) ,
2024-09-07 09:04:14 -04:00
Start : hcl . Pos { Line : 37 , Column : 15 , Byte : 614 } ,
End : hcl . Pos { Line : 37 , Column : 39 , Byte : 638 } ,
} ,
} )
2024-02-28 02:24:53 -05:00
return diags
} ,
2024-02-21 05:07:51 -05:00
} ,
2024-02-27 02:43:58 -05:00
filepath . Join ( "with-single-input" , "invalid-provider-config" ) : {
diags : func ( ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Unsupported argument" ,
Detail : "An argument named \"imaginary\" is not expected here." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/invalid-provider-config/invalid-provider-config.tfcomponent.hcl" ) ,
2024-02-27 02:43:58 -05:00
Start : hcl . Pos { Line : 11 , Column : 5 , Byte : 218 } ,
End : hcl . Pos { Line : 11 , Column : 14 , Byte : 227 } ,
} ,
} )
return diags
} ,
} ,
2024-02-26 05:36:19 -05:00
filepath . Join ( "with-single-input" , "undeclared-variable" ) : {
diags : func ( ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reference to undeclared input variable" ,
Detail : ` There is no variable "input" block declared in this stack. ` ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/undeclared-variable/undeclared-variable.tfcomponent.hcl" ) ,
2024-02-26 05:36:19 -05:00
Start : hcl . Pos { Line : 19 , Column : 13 , Byte : 284 } ,
End : hcl . Pos { Line : 19 , Column : 22 , Byte : 293 } ,
} ,
} )
return diags
} ,
} ,
filepath . Join ( "with-single-input" , "missing-variable" ) : {
diags : func ( ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid inputs for component" ,
Detail : "Invalid input variable definition object: attribute \"input\" is required." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/missing-variable/missing-variable.tfcomponent.hcl" ) ,
2024-02-26 05:36:19 -05:00
Start : hcl . Pos { Line : 22 , Column : 12 , Byte : 338 } ,
End : hcl . Pos { Line : 22 , Column : 14 , Byte : 340 } ,
} ,
} )
return diags
} ,
} ,
filepath . Join ( "with-single-input" , "input-from-missing-component" ) : {
diags : func ( ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reference to undeclared component" ,
Detail : "There is no component \"output\" block declared in this stack." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/input-from-missing-component/input-from-missing-component.tfcomponent.hcl" ) ,
2024-02-26 05:36:19 -05:00
Start : hcl . Pos { Line : 19 , Column : 13 , Byte : 314 } ,
End : hcl . Pos { Line : 19 , Column : 29 , Byte : 330 } ,
} ,
} )
return diags
} ,
} ,
filepath . Join ( "with-single-input" , "input-from-provider" ) : {
diags : func ( ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid inputs for component" ,
2025-07-09 07:05:18 -04:00
Detail : "Invalid input variable definition object: attribute \"input\": string required, but have configuration for hashicorp/testing provider." ,
2024-02-26 05:36:19 -05:00
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/input-from-provider/input-from-provider.tfcomponent.hcl" ) ,
2024-02-26 05:36:19 -05:00
Start : hcl . Pos { Line : 17 , Column : 12 , Byte : 239 } ,
End : hcl . Pos { Line : 20 , Column : 4 , Byte : 339 } ,
} ,
} )
return diags
} ,
} ,
2024-08-12 09:02:36 -04:00
filepath . Join ( "with-single-input" , "depends-on-invalid" ) : {
diags : func ( ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid depends_on target" ,
Detail : "The depends_on argument must refer to an embedded stack or component, but this reference refers to \"var.input\"." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/depends-on-invalid/depends-on-invalid.tfcomponent.hcl" ) ,
2024-08-12 09:02:36 -04:00
Start : hcl . Pos { Line : 22 , Column : 17 , Byte : 293 } ,
End : hcl . Pos { Line : 22 , Column : 26 , Byte : 302 } ,
} ,
} )
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid depends_on target" ,
Detail : "The depends_on argument must refer to an embedded stack or component, but this reference refers to \"var.input\"." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/depends-on-invalid/depends-on-invalid.tfcomponent.hcl" ) ,
2024-08-12 09:02:36 -04:00
Start : hcl . Pos { Line : 37 , Column : 17 , Byte : 509 } ,
End : hcl . Pos { Line : 37 , Column : 26 , Byte : 518 } ,
} ,
} )
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid depends_on target" ,
Detail : "The depends_on reference \"component.missing\" does not exist." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/depends-on-invalid/depends-on-invalid.tfcomponent.hcl" ) ,
2024-08-12 09:02:36 -04:00
Start : hcl . Pos { Line : 22 , Column : 28 , Byte : 304 } ,
End : hcl . Pos { Line : 22 , Column : 45 , Byte : 321 } ,
} ,
} )
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid depends_on target" ,
Detail : "The depends_on reference \"stack.missing\" does not exist." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/depends-on-invalid/depends-on-invalid.tfcomponent.hcl" ) ,
2024-08-12 09:02:36 -04:00
Start : hcl . Pos { Line : 37 , Column : 28 , Byte : 520 } ,
End : hcl . Pos { Line : 37 , Column : 41 , Byte : 533 } ,
} ,
} )
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagWarning ,
Summary : "Non-valid depends_on target" ,
Detail : "The depends_on argument should refer directly to an embedded stack or component in configuration, but this reference is too deep.\n\n" +
"Terraform Stacks has simplified the reference to the nearest valid target, \"component.first\". To remove this warning, update the configuration to the same target." ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "with-single-input/depends-on-invalid/depends-on-invalid.tfcomponent.hcl" ) ,
2024-08-12 09:02:36 -04:00
Start : hcl . Pos { Line : 52 , Column : 17 , Byte : 722 } ,
End : hcl . Pos { Line : 52 , Column : 32 , Byte : 737 } ,
} ,
} )
return diags
} ,
} ,
2024-02-21 04:58:44 -05:00
}
2024-02-26 04:42:14 -05:00
)
2024-02-21 04:58:44 -05:00
2024-02-26 04:42:14 -05:00
// TestValidate_valid tests that a variety of configurations under the main
// test source bundle each generate no diagnostics at all, as a
// relatively-simple way to detect accidental regressions.
//
// Any stack configuration directory that we expect should be valid can
// potentially be included in here unless it depends on provider plugins
// to complete validation, since this test cannot supply provider plugins.
func TestValidate_valid ( t * testing . T ) {
for name , tc := range validConfigurations {
t . Run ( name , func ( t * testing . T ) {
if tc . skip {
// We've added this test before the implementation was ready.
t . SkipNow ( )
}
ctx := context . Background ( )
2024-09-12 10:16:15 -04:00
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-07-26 05:36:08 -04:00
lock . SetProvider (
addrs . NewDefaultProvider ( "other" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-02-26 04:42:14 -05:00
2024-09-12 10:16:15 -04:00
testContext := TestContext {
config : loadMainBundleConfigForTest ( t , name ) ,
providers : map [ addrs . Provider ] providers . Factory {
2024-02-28 02:24:53 -05:00
// We support both hashicorp/testing and
// terraform.io/builtin/testing as providers. This lets us
// test the provider aliasing feature. Both providers
// support the same set of resources and data sources.
2024-02-26 05:36:19 -05:00
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-02-26 05:36:19 -05:00
} ,
2024-02-28 02:24:53 -05:00
addrs . NewBuiltInProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-02-28 02:24:53 -05:00
} ,
2024-07-26 05:36:08 -04:00
// We also support an "other" provider out of the box to
// test the provider aliasing feature.
addrs . NewDefaultProvider ( "other" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-07-26 05:36:08 -04:00
} ,
2024-02-26 05:36:19 -05:00
} ,
2024-09-12 10:16:15 -04:00
dependencyLocks : * lock ,
2024-02-26 04:42:14 -05:00
}
2024-09-12 10:16:15 -04:00
cycle := TestCycle { } // empty, as we expect no diagnostics
testContext . Validate ( t , ctx , cycle )
2024-02-26 04:42:14 -05:00
} )
}
}
func TestValidate_invalid ( t * testing . T ) {
for name , tc := range invalidConfigurations {
2024-02-21 04:58:44 -05:00
t . Run ( name , func ( t * testing . T ) {
2024-02-21 05:07:51 -05:00
if tc . skip {
// We've added this test before the implementation was ready.
t . SkipNow ( )
}
2024-02-21 04:58:44 -05:00
ctx := context . Background ( )
2024-06-27 10:08:08 -04:00
lock := depsfile . NewLocks ( )
lock . SetProvider (
addrs . NewDefaultProvider ( "testing" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-09-12 10:16:15 -04:00
lock . SetProvider (
addrs . NewDefaultProvider ( "other" ) ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
2024-06-27 10:08:08 -04:00
2024-09-12 10:16:15 -04:00
testContext := TestContext {
config : loadMainBundleConfigForTest ( t , name ) ,
providers : map [ addrs . Provider ] providers . Factory {
2024-02-28 02:24:53 -05:00
// We support both hashicorp/testing and
// terraform.io/builtin/testing as providers. This lets us
// test the provider aliasing feature. Both providers
// support the same set of resources and data sources.
2024-02-21 04:58:44 -05:00
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-02-21 04:58:44 -05:00
} ,
2024-02-28 02:24:53 -05:00
addrs . NewBuiltInProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-02-28 02:24:53 -05:00
} ,
2024-09-12 10:16:15 -04:00
// We also support an "other" provider out of the box to
// test the provider aliasing feature.
addrs . NewDefaultProvider ( "other" ) : func ( ) ( providers . Interface , error ) {
return stacks_testing_provider . NewProvider ( t ) , nil
} ,
2024-02-21 04:58:44 -05:00
} ,
2024-09-12 10:16:15 -04:00
dependencyLocks : * lock ,
2024-02-21 04:58:44 -05:00
}
2024-09-12 10:16:15 -04:00
testContext . Validate ( t , ctx , TestCycle {
wantValidateDiags : tc . diags ( ) ,
} )
2024-02-21 04:58:44 -05:00
} )
2023-07-14 14:45:04 -04:00
}
}
2024-09-12 10:16:15 -04:00
func TestValidate ( t * testing . T ) {
tcs := map [ string ] struct {
path string
providers map [ addrs . Provider ] providers . Factory
locks * depsfile . Locks
wantDiags tfdiags . Diagnostics
} {
"embedded-stack-selfref" : {
path : "validate-embedded-stack-selfref" ,
wantDiags : initDiags ( func ( diags tfdiags . Diagnostics ) tfdiags . Diagnostics {
return diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
"Self-dependent items in configuration" ,
` The following items in your configuration form a circular dependency chain through their references :
2023-07-14 14:45:04 -04:00
- stack . a collected outputs
2025-03-26 08:25:45 -04:00
- stack . a . output . a
2023-07-14 14:45:04 -04:00
- stack . a inputs
2025-03-19 05:39:23 -04:00
Terraform uses references to decide a suitable order for performing operations , so configuration items may not refer to their own results either directly or indirectly . ` ,
) )
} ) ,
} ,
"cyclic-component-dependency" : {
path : "validate-cyclic-dependency" ,
wantDiags : initDiags ( func ( diags tfdiags . Diagnostics ) tfdiags . Diagnostics {
return diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
"Self-dependent items in configuration" ,
` The following items in your configuration form a circular dependency chain through their references :
- component . boundary
- component . vault - config
2023-07-14 14:45:04 -04:00
Terraform uses references to decide a suitable order for performing operations , so configuration items may not refer to their own results either directly or indirectly . ` ,
2024-09-12 10:16:15 -04:00
) )
} ) ,
} ,
"missing-provider-from-lockfile" : {
path : filepath . Join ( "with-single-input" , "input-from-component" ) ,
providers : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
return stacks_testing_provider . NewProvider ( t ) , nil
} ,
2024-06-28 04:46:12 -04:00
} ,
2024-09-12 10:16:15 -04:00
locks : depsfile . NewLocks ( ) , // deliberately empty
wantDiags : initDiags ( func ( diags tfdiags . Diagnostics ) tfdiags . Diagnostics {
return diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Provider missing from lockfile" ,
2025-09-19 04:22:03 -04:00
Detail : "Provider \"registry.terraform.io/hashicorp/testing\" is not in the lockfile. This provider must be in the lockfile to be used in the configuration. Please run `terraform stacks providers lock` to update the lockfile and run this operation again with an updated configuration." ,
2024-09-12 10:16:15 -04:00
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : "git::https://example.com/test.git//with-single-input/input-from-component/input-from-component.tfcomponent.hcl" ,
2024-09-12 10:16:15 -04:00
Start : hcl . Pos { Line : 8 , Column : 1 , Byte : 98 } ,
End : hcl . Pos { Line : 8 , Column : 29 , Byte : 126 } ,
} ,
} )
} ) ,
2024-06-28 04:46:12 -04:00
} ,
2024-09-12 10:16:15 -04:00
"implied-provider-type-with-hashicorp-provider" : {
path : filepath . Join ( "legacy-module" , "with-hashicorp-provider" ) ,
2024-07-08 04:31:52 -04:00
providers : map [ addrs . Provider ] providers . Factory {
addrs . NewDefaultProvider ( "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-07-08 04:31:52 -04:00
} ,
} ,
} ,
2024-09-12 10:16:15 -04:00
"implied-provider-type-with-non-hashicorp-provider" : {
path : filepath . Join ( "legacy-module" , "with-non-hashicorp-provider" ) ,
2024-07-08 04:31:52 -04:00
providers : map [ addrs . Provider ] providers . Factory {
addrs . NewProvider ( addrs . DefaultProviderRegistryHost , "other" , "testing" ) : func ( ) ( providers . Interface , error ) {
2024-09-07 08:22:16 -04:00
return stacks_testing_provider . NewProvider ( t ) , nil
2024-07-08 04:31:52 -04:00
} ,
} ,
2024-09-12 10:16:15 -04:00
wantDiags : initDiags ( func ( diags tfdiags . Diagnostics ) tfdiags . Diagnostics {
return diags . Append ( & hcl . Diagnostic {
2024-07-08 04:31:52 -04:00
Severity : hcl . DiagError ,
Summary : "Invalid provider configuration" ,
Detail : "The provider configuration slot \"testing\" requires a configuration for provider \"registry.terraform.io/hashicorp/testing\", not for provider \"registry.terraform.io/other/testing\"." +
"\n\nThe module does not declare a source address for \"testing\" in its required_providers block, so Terraform assumed \"hashicorp/testing\" for backward-compatibility with older versions of Terraform" ,
Subject : & hcl . Range {
2025-05-15 02:33:13 -04:00
Filename : mainBundleSourceAddrStr ( "legacy-module/with-non-hashicorp-provider/with-non-hashicorp-provider.tfcomponent.hcl" ) ,
2024-07-08 04:31:52 -04:00
Start : hcl . Pos { Line : 21 , Column : 15 , Byte : 447 } ,
End : hcl . Pos { Line : 21 , Column : 39 , Byte : 471 } ,
} ,
} )
2024-09-12 10:16:15 -04:00
} ) ,
2024-07-08 04:31:52 -04:00
} ,
}
2024-09-12 10:16:15 -04:00
for name , tc := range tcs {
t . Run ( name , func ( t * testing . T ) {
2024-07-08 04:31:52 -04:00
ctx := context . Background ( )
2024-09-12 10:16:15 -04:00
ctx , span := tracer . Start ( ctx , name )
defer span . End ( )
locks := tc . locks
if locks == nil {
locks = depsfile . NewLocks ( )
for addr := range tc . providers {
locks . SetProvider (
addr ,
providerreqs . MustParseVersion ( "0.0.0" ) ,
providerreqs . MustParseVersionConstraints ( "=0.0.0" ) ,
providerreqs . PreferredHashes ( [ ] providerreqs . Hash { } ) ,
)
}
2024-07-08 04:31:52 -04:00
}
2024-09-12 10:16:15 -04:00
testContext := TestContext {
config : loadMainBundleConfigForTest ( t , tc . path ) ,
providers : tc . providers ,
dependencyLocks : * locks ,
2024-07-08 04:31:52 -04:00
}
2024-09-12 10:16:15 -04:00
testContext . Validate ( t , ctx , TestCycle {
wantValidateDiags : tc . wantDiags ,
} )
2024-07-08 04:31:52 -04:00
} )
}
}