github.com/mineiros-io/terradoc@v0.0.9-0.20220711062319-018bd4ae81f5/internal/parsers/hclparser/hclattribute_test.go (about) 1 package hclparser 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7 "testing" 8 9 "github.com/hashicorp/hcl/v2" 10 "github.com/hashicorp/hcl/v2/ext/customdecode" 11 "github.com/hashicorp/hcl/v2/hcltest" 12 "github.com/madlambda/spells/assert" 13 "github.com/mineiros-io/terradoc/internal/types" 14 "github.com/zclconf/go-cty/cty" 15 ) 16 17 func TestAttributeToString(t *testing.T) { 18 attrName := "a-string" 19 20 t.Run("when value is a cty.String", func(t *testing.T) { 21 wantString := "test" 22 23 exprValue := cty.StringVal(wantString) 24 25 attr := newMockAttribute(attrName, exprValue) 26 27 res, err := attr.String() 28 assert.NoError(t, err) 29 assert.EqualStrings(t, wantString, res) 30 }) 31 32 t.Run("when value is not convertable to string", func(t *testing.T) { 33 // test that it doesn't trigger cty's panic calls 34 wantErrorMSGContains := fmt.Sprintf("could not convert %q to string", attrName) 35 exprValue := customdecode.ExpressionVal(&fakeHCLExpression{}) 36 37 attr := newMockAttribute(attrName, exprValue) 38 39 res, err := attr.String() 40 assert.Error(t, err) 41 42 if !strings.Contains(err.Error(), wantErrorMSGContains) { 43 t.Errorf("Expected error to contain %q but got %q instead", wantErrorMSGContains, err.Error()) 44 } 45 46 if res != "" { 47 t.Errorf("Expected result to be empty. Got %q instead", res) 48 } 49 }) 50 } 51 52 func TestAttributeToBool(t *testing.T) { 53 attrName := "a-bool" 54 t.Run("when value is a cty.Bool", func(t *testing.T) { 55 wantBool := true 56 57 exprValue := cty.BoolVal(wantBool) 58 59 attr := newMockAttribute(attrName, exprValue) 60 61 res, err := attr.Bool() 62 assert.NoError(t, err) 63 64 if res != wantBool { 65 t.Errorf("Expected returned value to be %t but got %t instead", wantBool, res) 66 } 67 }) 68 69 t.Run("when value is not convertable to bool", func(t *testing.T) { 70 // test that it doesn't trigger cty's panic calls 71 wantErrorMSGContains := fmt.Sprintf("could not convert %q to bool", attrName) 72 exprValue := customdecode.ExpressionVal(&fakeHCLExpression{}) 73 74 attr := newMockAttribute(attrName, exprValue) 75 76 res, err := attr.Bool() 77 assert.Error(t, err) 78 79 if !strings.Contains(err.Error(), wantErrorMSGContains) { 80 t.Errorf("Expected error to contain %q but got %q instead", wantErrorMSGContains, err.Error()) 81 } 82 83 if res != false { 84 t.Errorf("Expected result to be false. Got %t instead", res) 85 } 86 }) 87 } 88 89 func TestAttributeToJSONValue(t *testing.T) { 90 for _, tt := range []struct { 91 desc string 92 value string 93 }{ 94 { 95 desc: "when value is a list", 96 value: `[1, 2, "c", [3, "a", "b"]]`, 97 }, 98 { 99 desc: "when value is a number", 100 value: "123", 101 }, 102 { 103 desc: "when value is a string", 104 value: `"foo"`, 105 }, 106 { 107 desc: "when value is a map", 108 value: `{a=123, b="foo"}`, 109 }, 110 } { 111 t.Run(tt.desc, func(t *testing.T) { 112 // test that the returned value is not an escaped json string 113 expr := hcltest.MockExprLiteral(cty.StringVal(tt.value)) 114 attr := &HCLAttribute{&hcl.Attribute{Expr: expr}} 115 116 res, err := attr.RawJSON() 117 assert.NoError(t, err) 118 119 var strRes string 120 err = json.Unmarshal(res, &strRes) 121 122 assert.NoError(t, err) 123 assert.EqualStrings(t, tt.value, strRes) 124 }) 125 } 126 } 127 128 func TestAttributeToTypeValidPrimaryType(t *testing.T) { 129 for _, tt := range []struct { 130 desc string 131 exprValue string 132 expectedTerraformType types.TerraformType 133 }{ 134 { 135 desc: "when type is bool", 136 exprValue: "bool", 137 expectedTerraformType: types.TerraformBool, 138 }, 139 { 140 desc: "when type is string", 141 exprValue: "string", 142 expectedTerraformType: types.TerraformString, 143 }, 144 { 145 desc: "when type is number", 146 exprValue: "number", 147 expectedTerraformType: types.TerraformNumber, 148 }, 149 } { 150 t.Run(tt.desc, func(t *testing.T) { 151 attr := newTypeAttribute(tt.exprValue, tt.exprValue) 152 153 res, err := attr.VarType() 154 assert.NoError(t, err) 155 156 assert.EqualInts(t, int(tt.expectedTerraformType), int(res.TFType)) 157 assert.EqualStrings(t, "", res.Label) 158 159 // ensure that type does not have a nested type 160 if res.HasNestedType() { 161 t.Errorf("type %q should not have a nested type", tt.expectedTerraformType) 162 } 163 }) 164 } 165 } 166 167 func TestAttributeToTypeInvalidTypes(t *testing.T) { 168 for _, tt := range []struct { 169 desc string 170 exprValue string 171 expectedErrorMSG string 172 }{ 173 { 174 desc: "when an invalid primary type is given", 175 exprValue: "foo", 176 expectedErrorMSG: "type \"foo\" is invalid", 177 }, 178 { 179 desc: "when type is a list without arguments", 180 exprValue: "list", 181 expectedErrorMSG: "type \"list\" needs an argument", 182 }, 183 { 184 desc: "when type is an object without definition", 185 exprValue: "object", 186 expectedErrorMSG: "type \"object\" needs an argument", 187 }, 188 { 189 desc: "when type is a tuple without definition", 190 exprValue: "tuple", 191 expectedErrorMSG: "type \"tuple\" needs an argument", 192 }, 193 { 194 desc: "when type is a map without definition", 195 exprValue: "map", 196 expectedErrorMSG: "type \"map\" needs an argument", 197 }, 198 } { 199 t.Run(tt.desc, func(t *testing.T) { 200 attr := newTypeAttribute(tt.exprValue, tt.exprValue) 201 202 res, err := attr.VarType() 203 assert.Error(t, err) 204 205 if !strings.Contains(err.Error(), tt.expectedErrorMSG) { 206 t.Errorf("Expected error to contain %q. Got %q instead", tt.expectedErrorMSG, err.Error()) 207 } 208 209 assert.EqualInts(t, int(types.TerraformEmptyType), int(res.TFType)) 210 assert.EqualStrings(t, "", res.Label) 211 212 if res.HasNestedType() { 213 t.Error("empty type cannot have a nested type") 214 } 215 }) 216 } 217 } 218 219 func TestAttributeToTerraformTypeValidComplexType(t *testing.T) { 220 t.Skip("I'm not sure how tf I'll test this") 221 } 222 223 type fakeHCLExpression struct { 224 value cty.Value 225 } 226 227 func (expr fakeHCLExpression) Variables() []hcl.Traversal { 228 return nil 229 } 230 231 func (expr fakeHCLExpression) Range() hcl.Range { 232 return hcl.Range{} 233 } 234 235 func (expr fakeHCLExpression) StartRange() hcl.Range { 236 return hcl.Range{} 237 } 238 239 func (expr fakeHCLExpression) Value(_ *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 240 return expr.value, nil 241 } 242 243 func newMockAttribute(name string, returnValue cty.Value) *HCLAttribute { 244 fakeExpr := &fakeHCLExpression{ 245 value: returnValue, 246 } 247 attr := &hcl.Attribute{Name: name, Expr: fakeExpr} 248 249 return &HCLAttribute{attr} 250 } 251 252 func newTypeAttribute(name, typeStr string) *HCLAttribute { 253 expr := hcltest.MockExprVariable(typeStr) 254 attr := &hcl.Attribute{Name: name, Expr: expr} 255 256 return &HCLAttribute{attr} 257 }