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  }