github.com/opentofu/opentofu@v1.7.1/internal/lang/funcs/conversion_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 funcs
     7  
     8  import (
     9  	"fmt"
    10  	"testing"
    11  
    12  	"github.com/opentofu/opentofu/internal/lang/marks"
    13  	"github.com/zclconf/go-cty/cty"
    14  )
    15  
    16  func TestTo(t *testing.T) {
    17  	tests := []struct {
    18  		Value    cty.Value
    19  		TargetTy cty.Type
    20  		Want     cty.Value
    21  		Err      string
    22  	}{
    23  		{
    24  			cty.StringVal("a"),
    25  			cty.String,
    26  			cty.StringVal("a"),
    27  			``,
    28  		},
    29  		{
    30  			cty.UnknownVal(cty.String),
    31  			cty.String,
    32  			cty.UnknownVal(cty.String),
    33  			``,
    34  		},
    35  		{
    36  			cty.NullVal(cty.String),
    37  			cty.String,
    38  			cty.NullVal(cty.String),
    39  			``,
    40  		},
    41  		{
    42  			// This test case represents evaluating the expression tostring(null)
    43  			// from HCL, since null in HCL is cty.NullVal(cty.DynamicPseudoType).
    44  			// The result in that case should still be null, but a null specifically
    45  			// of type string.
    46  			cty.NullVal(cty.DynamicPseudoType),
    47  			cty.String,
    48  			cty.NullVal(cty.String),
    49  			``,
    50  		},
    51  		{
    52  			cty.StringVal("a").Mark("boop"),
    53  			cty.String,
    54  			cty.StringVal("a").Mark("boop"),
    55  			``,
    56  		},
    57  		{
    58  			cty.NullVal(cty.String).Mark("boop"),
    59  			cty.String,
    60  			cty.NullVal(cty.String).Mark("boop"),
    61  			``,
    62  		},
    63  		{
    64  			cty.True,
    65  			cty.String,
    66  			cty.StringVal("true"),
    67  			``,
    68  		},
    69  		{
    70  			cty.StringVal("a"),
    71  			cty.Bool,
    72  			cty.DynamicVal,
    73  			`cannot convert "a" to bool; only the strings "true" or "false" are allowed`,
    74  		},
    75  		{
    76  			cty.StringVal("a").Mark("boop"),
    77  			cty.Bool,
    78  			cty.DynamicVal,
    79  			`cannot convert "a" to bool; only the strings "true" or "false" are allowed`,
    80  		},
    81  		{
    82  			cty.StringVal("a").Mark(marks.Sensitive),
    83  			cty.Bool,
    84  			cty.DynamicVal,
    85  			`cannot convert this sensitive string to bool`,
    86  		},
    87  		{
    88  			cty.StringVal("a"),
    89  			cty.Number,
    90  			cty.DynamicVal,
    91  			`cannot convert "a" to number; given string must be a decimal representation of a number`,
    92  		},
    93  		{
    94  			cty.StringVal("a").Mark("boop"),
    95  			cty.Number,
    96  			cty.DynamicVal,
    97  			`cannot convert "a" to number; given string must be a decimal representation of a number`,
    98  		},
    99  		{
   100  			cty.StringVal("a").Mark(marks.Sensitive),
   101  			cty.Number,
   102  			cty.DynamicVal,
   103  			`cannot convert this sensitive string to number`,
   104  		},
   105  		{
   106  			cty.NullVal(cty.String),
   107  			cty.Number,
   108  			cty.NullVal(cty.Number),
   109  			``,
   110  		},
   111  		{
   112  			cty.UnknownVal(cty.Bool),
   113  			cty.String,
   114  			cty.UnknownVal(cty.String),
   115  			``,
   116  		},
   117  		{
   118  			cty.UnknownVal(cty.String),
   119  			cty.Bool,
   120  			cty.UnknownVal(cty.Bool), // conversion is optimistic
   121  			``,
   122  		},
   123  		{
   124  			cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.True}),
   125  			cty.List(cty.String),
   126  			cty.ListVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("true")}),
   127  			``,
   128  		},
   129  		{
   130  			cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.True}),
   131  			cty.Set(cty.String),
   132  			cty.SetVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("true")}),
   133  			``,
   134  		},
   135  		{
   136  			cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.True}),
   137  			cty.Map(cty.String),
   138  			cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.StringVal("true")}),
   139  			``,
   140  		},
   141  		{
   142  			cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.StringVal("world").Mark("boop")}),
   143  			cty.Map(cty.String),
   144  			cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.StringVal("world").Mark("boop")}),
   145  			``,
   146  		},
   147  		{
   148  			cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.StringVal("world")}).Mark("boop"),
   149  			cty.Map(cty.String),
   150  			cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.StringVal("world")}).Mark("boop"),
   151  			``,
   152  		},
   153  		{
   154  			cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world").Mark("boop")}),
   155  			cty.List(cty.String),
   156  			cty.ListVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world").Mark("boop")}),
   157  			``,
   158  		},
   159  		{
   160  			cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world")}).Mark("boop"),
   161  			cty.List(cty.String),
   162  			cty.ListVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world")}).Mark("boop"),
   163  			``,
   164  		},
   165  		{
   166  			cty.EmptyTupleVal,
   167  			cty.String,
   168  			cty.DynamicVal,
   169  			`cannot convert tuple to string`,
   170  		},
   171  		{
   172  			cty.UnknownVal(cty.EmptyTuple),
   173  			cty.String,
   174  			cty.DynamicVal,
   175  			`cannot convert tuple to string`,
   176  		},
   177  		{
   178  			cty.EmptyObjectVal,
   179  			cty.Object(map[string]cty.Type{"foo": cty.String}),
   180  			cty.DynamicVal,
   181  			`incompatible object type for conversion: attribute "foo" is required`,
   182  		},
   183  	}
   184  
   185  	for _, test := range tests {
   186  		t.Run(fmt.Sprintf("to %s(%#v)", test.TargetTy.FriendlyNameForConstraint(), test.Value), func(t *testing.T) {
   187  			f := MakeToFunc(test.TargetTy)
   188  			got, err := f.Call([]cty.Value{test.Value})
   189  
   190  			if test.Err != "" {
   191  				if err == nil {
   192  					t.Fatal("succeeded; want error")
   193  				}
   194  				if got, want := err.Error(), test.Err; got != want {
   195  					t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
   196  				}
   197  				return
   198  			} else if err != nil {
   199  				t.Fatalf("unexpected error: %s", err)
   200  			}
   201  
   202  			if !got.RawEquals(test.Want) {
   203  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   204  			}
   205  		})
   206  	}
   207  }