mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-27 00:46:25 -06:00
bd6ba6cf99
Importing to the same target address twice or importing the same ID to multiple different resources of the same type is not allowed.
294 lines
9.1 KiB
Go
294 lines
9.1 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package command
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
"github.com/hashicorp/terraform/internal/terminal"
|
|
)
|
|
|
|
func setupTest(t *testing.T, fixturepath string, args ...string) (*terminal.TestOutput, int) {
|
|
view, done := testView(t)
|
|
p := testProvider()
|
|
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
|
ResourceTypes: map[string]providers.Schema{
|
|
"test_instance": {
|
|
Block: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"ami": {Type: cty.String, Optional: true},
|
|
},
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
"network_interface": {
|
|
Nesting: configschema.NestingList,
|
|
Block: configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"device_index": {Type: cty.String, Optional: true},
|
|
"description": {Type: cty.String, Optional: true},
|
|
"name": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
c := &ValidateCommand{
|
|
Meta: Meta{
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
View: view,
|
|
},
|
|
}
|
|
|
|
args = append(args, "-no-color")
|
|
args = append(args, testFixturePath(fixturepath))
|
|
|
|
code := c.Run(args)
|
|
return done(t), code
|
|
}
|
|
|
|
func TestValidateCommand(t *testing.T) {
|
|
if output, code := setupTest(t, "validate-valid"); code != 0 {
|
|
t.Fatalf("unexpected non-successful exit code %d\n\n%s", code, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestValidateCommandWithTfvarsFile(t *testing.T) {
|
|
// Create a temporary working directory that is empty because this test
|
|
// requires scanning the current working directory by validate command.
|
|
td := t.TempDir()
|
|
testCopyDir(t, testFixturePath("validate-valid/with-tfvars-file"), td)
|
|
defer testChdir(t, td)()
|
|
|
|
view, done := testView(t)
|
|
c := &ValidateCommand{
|
|
Meta: Meta{
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
View: view,
|
|
},
|
|
}
|
|
|
|
args := []string{}
|
|
code := c.Run(args)
|
|
output := done(t)
|
|
if code != 0 {
|
|
t.Fatalf("bad %d\n\n%s", code, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestValidateFailingCommand(t *testing.T) {
|
|
if output, code := setupTest(t, "validate-invalid"); code != 1 {
|
|
t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestValidateFailingCommandMissingQuote(t *testing.T) {
|
|
output, code := setupTest(t, "validate-invalid/missing_quote")
|
|
|
|
if code != 1 {
|
|
t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
wantError := "Error: Invalid reference"
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestValidateFailingCommandMissingVariable(t *testing.T) {
|
|
output, code := setupTest(t, "validate-invalid/missing_var")
|
|
if code != 1 {
|
|
t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
wantError := "Error: Reference to undeclared input variable"
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestSameProviderMutipleTimesShouldFail(t *testing.T) {
|
|
output, code := setupTest(t, "validate-invalid/multiple_providers")
|
|
if code != 1 {
|
|
t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
wantError := "Error: Duplicate provider configuration"
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestSameModuleMultipleTimesShouldFail(t *testing.T) {
|
|
output, code := setupTest(t, "validate-invalid/multiple_modules")
|
|
if code != 1 {
|
|
t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
wantError := "Error: Duplicate module call"
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestSameResourceMultipleTimesShouldFail(t *testing.T) {
|
|
output, code := setupTest(t, "validate-invalid/multiple_resources")
|
|
if code != 1 {
|
|
t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
wantError := `Error: Duplicate resource "aws_instance" configuration`
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestSameImportTargetMultipleTimesShouldFail(t *testing.T) {
|
|
output, code := setupTest(t, "validate-invalid/duplicate_import_targets")
|
|
if code != 1 {
|
|
t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
wantError := `Error: Duplicate import configuration for "aws_instance.web"`
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestSameImportIDMultipleTimesShouldFail(t *testing.T) {
|
|
output, code := setupTest(t, "validate-invalid/duplicate_import_ids")
|
|
if code != 1 {
|
|
t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
wantError := `Error: Duplicate import for ID "test"`
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestOutputWithoutValueShouldFail(t *testing.T) {
|
|
output, code := setupTest(t, "validate-invalid/outputs")
|
|
if code != 1 {
|
|
t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
wantError := `The argument "value" is required, but no definition was found.`
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
wantError = `An argument named "values" is not expected here. Did you mean "value"?`
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestModuleWithIncorrectNameShouldFail(t *testing.T) {
|
|
output, code := setupTest(t, "validate-invalid/incorrectmodulename")
|
|
if code != 1 {
|
|
t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
|
|
wantError := `Error: Invalid module instance name`
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
wantError = `Error: Variables not allowed`
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestWronglyUsedInterpolationShouldFail(t *testing.T) {
|
|
output, code := setupTest(t, "validate-invalid/interpolation")
|
|
if code != 1 {
|
|
t.Fatalf("Should have failed: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
|
|
wantError := `Error: Variables not allowed`
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
wantError = `A single static variable reference is required`
|
|
if !strings.Contains(output.Stderr(), wantError) {
|
|
t.Fatalf("Missing error string %q\n\n'%s'", wantError, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestMissingDefinedVar(t *testing.T) {
|
|
output, code := setupTest(t, "validate-invalid/missing_defined_var")
|
|
// This is allowed because validate tests only that variables are referenced
|
|
// correctly, not that they all have defined values.
|
|
if code != 0 {
|
|
t.Fatalf("Should have passed: %d\n\n%s", code, output.Stderr())
|
|
}
|
|
}
|
|
|
|
func TestValidate_json(t *testing.T) {
|
|
tests := []struct {
|
|
path string
|
|
valid bool
|
|
}{
|
|
{"validate-valid", true},
|
|
{"validate-invalid", false},
|
|
{"validate-invalid/missing_quote", false},
|
|
{"validate-invalid/missing_var", false},
|
|
{"validate-invalid/multiple_providers", false},
|
|
{"validate-invalid/multiple_modules", false},
|
|
{"validate-invalid/multiple_resources", false},
|
|
{"validate-invalid/duplicate_import_targets", false},
|
|
{"validate-invalid/duplicate_import_ids", false},
|
|
{"validate-invalid/outputs", false},
|
|
{"validate-invalid/incorrectmodulename", false},
|
|
{"validate-invalid/interpolation", false},
|
|
{"validate-invalid/missing_defined_var", true},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.path, func(t *testing.T) {
|
|
var want, got map[string]interface{}
|
|
|
|
wantFile, err := os.Open(path.Join(testFixturePath(tc.path), "output.json"))
|
|
if err != nil {
|
|
t.Fatalf("failed to open output file: %s", err)
|
|
}
|
|
defer wantFile.Close()
|
|
wantBytes, err := ioutil.ReadAll(wantFile)
|
|
if err != nil {
|
|
t.Fatalf("failed to read output file: %s", err)
|
|
}
|
|
err = json.Unmarshal([]byte(wantBytes), &want)
|
|
if err != nil {
|
|
t.Fatalf("failed to unmarshal expected JSON: %s", err)
|
|
}
|
|
|
|
output, code := setupTest(t, tc.path, "-json")
|
|
|
|
gotString := output.Stdout()
|
|
err = json.Unmarshal([]byte(gotString), &got)
|
|
if err != nil {
|
|
t.Fatalf("failed to unmarshal actual JSON: %s", err)
|
|
}
|
|
|
|
if !cmp.Equal(got, want) {
|
|
t.Errorf("wrong output:\n %v\n", cmp.Diff(got, want))
|
|
t.Errorf("raw output:\n%s\n", gotString)
|
|
}
|
|
|
|
if tc.valid && code != 0 {
|
|
t.Errorf("wrong exit code: want 0, got %d", code)
|
|
} else if !tc.valid && code != 1 {
|
|
t.Errorf("wrong exit code: want 1, got %d", code)
|
|
}
|
|
|
|
if errorOutput := output.Stderr(); errorOutput != "" {
|
|
t.Errorf("unexpected error output:\n%s", errorOutput)
|
|
}
|
|
})
|
|
}
|
|
}
|