github.com/opentofu/opentofu@v1.7.1/internal/backend/unparsed_value_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package backend 7 8 import ( 9 "strings" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/hashicorp/hcl/v2" 14 "github.com/zclconf/go-cty/cty" 15 16 "github.com/opentofu/opentofu/internal/configs" 17 "github.com/opentofu/opentofu/internal/tfdiags" 18 "github.com/opentofu/opentofu/internal/tofu" 19 ) 20 21 func TestUnparsedValue(t *testing.T) { 22 vv := map[string]UnparsedVariableValue{ 23 "undeclared0": testUnparsedVariableValue("0"), 24 "undeclared1": testUnparsedVariableValue("1"), 25 "undeclared2": testUnparsedVariableValue("2"), 26 "undeclared3": testUnparsedVariableValue("3"), 27 "undeclared4": testUnparsedVariableValue("4"), 28 "declared1": testUnparsedVariableValue("5"), 29 } 30 decls := map[string]*configs.Variable{ 31 "declared1": { 32 Name: "declared1", 33 Type: cty.String, 34 ConstraintType: cty.String, 35 ParsingMode: configs.VariableParseLiteral, 36 DeclRange: hcl.Range{ 37 Filename: "fake.tf", 38 Start: hcl.Pos{Line: 2, Column: 1, Byte: 0}, 39 End: hcl.Pos{Line: 2, Column: 1, Byte: 0}, 40 }, 41 }, 42 "missing1": { 43 Name: "missing1", 44 Type: cty.String, 45 ConstraintType: cty.String, 46 ParsingMode: configs.VariableParseLiteral, 47 DeclRange: hcl.Range{ 48 Filename: "fake.tf", 49 Start: hcl.Pos{Line: 3, Column: 1, Byte: 0}, 50 End: hcl.Pos{Line: 3, Column: 1, Byte: 0}, 51 }, 52 }, 53 "missing2": { 54 Name: "missing1", 55 Type: cty.String, 56 ConstraintType: cty.String, 57 ParsingMode: configs.VariableParseLiteral, 58 Default: cty.StringVal("default for missing2"), 59 DeclRange: hcl.Range{ 60 Filename: "fake.tf", 61 Start: hcl.Pos{Line: 4, Column: 1, Byte: 0}, 62 End: hcl.Pos{Line: 4, Column: 1, Byte: 0}, 63 }, 64 }, 65 } 66 67 const undeclSingular = `Value for undeclared variable` 68 const undeclPlural = `Values for undeclared variables` 69 70 t.Run("ParseDeclaredVariableValues", func(t *testing.T) { 71 gotVals, diags := ParseDeclaredVariableValues(vv, decls) 72 73 if got, want := len(diags), 0; got != want { 74 t.Fatalf("wrong number of diagnostics %d; want %d", got, want) 75 } 76 77 wantVals := tofu.InputValues{ 78 "declared1": { 79 Value: cty.StringVal("5"), 80 SourceType: tofu.ValueFromNamedFile, 81 SourceRange: tfdiags.SourceRange{ 82 Filename: "fake.tfvars", 83 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 84 End: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 85 }, 86 }, 87 } 88 89 if diff := cmp.Diff(wantVals, gotVals, cmp.Comparer(cty.Value.RawEquals)); diff != "" { 90 t.Errorf("wrong result\n%s", diff) 91 } 92 }) 93 94 t.Run("ParseUndeclaredVariableValues", func(t *testing.T) { 95 gotVals, diags := ParseUndeclaredVariableValues(vv, decls) 96 97 if got, want := len(diags), 3; got != want { 98 t.Fatalf("wrong number of diagnostics %d; want %d", got, want) 99 } 100 101 if got, want := diags[0].Description().Summary, undeclSingular; got != want { 102 t.Errorf("wrong summary for diagnostic 0\ngot: %s\nwant: %s", got, want) 103 } 104 105 if got, want := diags[1].Description().Summary, undeclSingular; got != want { 106 t.Errorf("wrong summary for diagnostic 1\ngot: %s\nwant: %s", got, want) 107 } 108 109 if got, want := diags[2].Description().Summary, undeclPlural; got != want { 110 t.Errorf("wrong summary for diagnostic 2\ngot: %s\nwant: %s", got, want) 111 } 112 113 wantVals := tofu.InputValues{ 114 "undeclared0": { 115 Value: cty.StringVal("0"), 116 SourceType: tofu.ValueFromNamedFile, 117 SourceRange: tfdiags.SourceRange{ 118 Filename: "fake.tfvars", 119 Start: tfdiags.SourcePos{Line: 1, Column: 1}, 120 End: tfdiags.SourcePos{Line: 1, Column: 1}, 121 }, 122 }, 123 "undeclared1": { 124 Value: cty.StringVal("1"), 125 SourceType: tofu.ValueFromNamedFile, 126 SourceRange: tfdiags.SourceRange{ 127 Filename: "fake.tfvars", 128 Start: tfdiags.SourcePos{Line: 1, Column: 1}, 129 End: tfdiags.SourcePos{Line: 1, Column: 1}, 130 }, 131 }, 132 "undeclared2": { 133 Value: cty.StringVal("2"), 134 SourceType: tofu.ValueFromNamedFile, 135 SourceRange: tfdiags.SourceRange{ 136 Filename: "fake.tfvars", 137 Start: tfdiags.SourcePos{Line: 1, Column: 1}, 138 End: tfdiags.SourcePos{Line: 1, Column: 1}, 139 }, 140 }, 141 "undeclared3": { 142 Value: cty.StringVal("3"), 143 SourceType: tofu.ValueFromNamedFile, 144 SourceRange: tfdiags.SourceRange{ 145 Filename: "fake.tfvars", 146 Start: tfdiags.SourcePos{Line: 1, Column: 1}, 147 End: tfdiags.SourcePos{Line: 1, Column: 1}, 148 }, 149 }, 150 "undeclared4": { 151 Value: cty.StringVal("4"), 152 SourceType: tofu.ValueFromNamedFile, 153 SourceRange: tfdiags.SourceRange{ 154 Filename: "fake.tfvars", 155 Start: tfdiags.SourcePos{Line: 1, Column: 1}, 156 End: tfdiags.SourcePos{Line: 1, Column: 1}, 157 }, 158 }, 159 } 160 if diff := cmp.Diff(wantVals, gotVals, cmp.Comparer(cty.Value.RawEquals)); diff != "" { 161 t.Errorf("wrong result\n%s", diff) 162 } 163 }) 164 165 t.Run("ParseVariableValues", func(t *testing.T) { 166 gotVals, diags := ParseVariableValues(vv, decls) 167 for _, diag := range diags { 168 t.Logf("%s: %s", diag.Description().Summary, diag.Description().Detail) 169 } 170 if got, want := len(diags), 4; got != want { 171 t.Fatalf("wrong number of diagnostics %d; want %d", got, want) 172 } 173 174 const missingRequired = `No value for required variable` 175 176 if got, want := diags[0].Description().Summary, undeclSingular; got != want { 177 t.Errorf("wrong summary for diagnostic 0\ngot: %s\nwant: %s", got, want) 178 } 179 if got, want := diags[1].Description().Summary, undeclSingular; got != want { 180 t.Errorf("wrong summary for diagnostic 1\ngot: %s\nwant: %s", got, want) 181 } 182 if got, want := diags[2].Description().Summary, undeclPlural; got != want { 183 t.Errorf("wrong summary for diagnostic 2\ngot: %s\nwant: %s", got, want) 184 } 185 if got, want := diags[2].Description().Detail, "3 other variable(s)"; !strings.Contains(got, want) { 186 t.Errorf("wrong detail for diagnostic 2\ngot: %s\nmust contain: %s", got, want) 187 } 188 if got, want := diags[3].Description().Summary, missingRequired; got != want { 189 t.Errorf("wrong summary for diagnostic 3\ngot: %s\nwant: %s", got, want) 190 } 191 192 wantVals := tofu.InputValues{ 193 "declared1": { 194 Value: cty.StringVal("5"), 195 SourceType: tofu.ValueFromNamedFile, 196 SourceRange: tfdiags.SourceRange{ 197 Filename: "fake.tfvars", 198 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 199 End: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 200 }, 201 }, 202 "missing1": { 203 Value: cty.DynamicVal, 204 SourceType: tofu.ValueFromConfig, 205 SourceRange: tfdiags.SourceRange{ 206 Filename: "fake.tf", 207 Start: tfdiags.SourcePos{Line: 3, Column: 1, Byte: 0}, 208 End: tfdiags.SourcePos{Line: 3, Column: 1, Byte: 0}, 209 }, 210 }, 211 "missing2": { 212 Value: cty.NilVal, // OpenTofu Core handles substituting the default 213 SourceType: tofu.ValueFromConfig, 214 SourceRange: tfdiags.SourceRange{ 215 Filename: "fake.tf", 216 Start: tfdiags.SourcePos{Line: 4, Column: 1, Byte: 0}, 217 End: tfdiags.SourcePos{Line: 4, Column: 1, Byte: 0}, 218 }, 219 }, 220 } 221 if diff := cmp.Diff(wantVals, gotVals, cmp.Comparer(cty.Value.RawEquals)); diff != "" { 222 t.Errorf("wrong result\n%s", diff) 223 } 224 }) 225 } 226 227 type testUnparsedVariableValue string 228 229 func (v testUnparsedVariableValue) ParseVariableValue(mode configs.VariableParsingMode) (*tofu.InputValue, tfdiags.Diagnostics) { 230 return &tofu.InputValue{ 231 Value: cty.StringVal(string(v)), 232 SourceType: tofu.ValueFromNamedFile, 233 SourceRange: tfdiags.SourceRange{ 234 Filename: "fake.tfvars", 235 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 236 End: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 237 }, 238 }, nil 239 }