github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/caveats/types/types_test.go (about)

     1  package types
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"google.golang.org/protobuf/types/known/structpb"
     8  
     9  	"github.com/stretchr/testify/require"
    10  )
    11  
    12  func TestConversion(t *testing.T) {
    13  	tcs := []struct {
    14  		name          string
    15  		vtype         VariableType
    16  		inputValue    any
    17  		expectedValue any
    18  		expectedErr   string
    19  	}{
    20  		{
    21  			name:          "bool to bool",
    22  			vtype:         BooleanType,
    23  			inputValue:    true,
    24  			expectedValue: true,
    25  			expectedErr:   "",
    26  		},
    27  		{
    28  			name:          "string to bool",
    29  			vtype:         BooleanType,
    30  			inputValue:    "hiya",
    31  			expectedValue: nil,
    32  			expectedErr:   "for bool: a bool value is required, but found string `hiya`",
    33  		},
    34  		{
    35  			name:          "number to string",
    36  			vtype:         StringType,
    37  			inputValue:    42,
    38  			expectedValue: nil,
    39  			expectedErr:   "for string: a string value is required, but found int `42`",
    40  		},
    41  		{
    42  			name:          "float to float",
    43  			vtype:         DoubleType,
    44  			inputValue:    42.0,
    45  			expectedValue: 42.0,
    46  			expectedErr:   "",
    47  		},
    48  		{
    49  			name:          "string to float",
    50  			vtype:         DoubleType,
    51  			inputValue:    "42.0",
    52  			expectedValue: 42.0,
    53  			expectedErr:   "",
    54  		},
    55  		{
    56  			name:          "float to uint",
    57  			vtype:         UIntType,
    58  			inputValue:    42.0,
    59  			expectedValue: uint64(42),
    60  			expectedErr:   "",
    61  		},
    62  		{
    63  			name:          "invalid float to uint",
    64  			vtype:         UIntType,
    65  			inputValue:    42.1,
    66  			expectedValue: uint64(42),
    67  			expectedErr:   "for uint: a uint value is required, but found numeric value `42.1`",
    68  		},
    69  		{
    70  			name:          "negative float to uint",
    71  			vtype:         UIntType,
    72  			inputValue:    -42.0,
    73  			expectedValue: nil,
    74  			expectedErr:   "for uint: a uint value is required, but found int64 value `-42`",
    75  		},
    76  		{
    77  			name:          "float to int",
    78  			vtype:         IntType,
    79  			inputValue:    42.0,
    80  			expectedValue: int64(42),
    81  			expectedErr:   "",
    82  		},
    83  		{
    84  			name:          "string to int",
    85  			vtype:         IntType,
    86  			inputValue:    "42",
    87  			expectedValue: int64(42),
    88  			expectedErr:   "",
    89  		},
    90  		{
    91  			name:          "string to uint",
    92  			vtype:         UIntType,
    93  			inputValue:    "42",
    94  			expectedValue: uint64(42),
    95  			expectedErr:   "",
    96  		},
    97  		{
    98  			name:          "invalid float to int",
    99  			vtype:         IntType,
   100  			inputValue:    42.1,
   101  			expectedValue: int64(42),
   102  			expectedErr:   "for int: a int value is required, but found numeric value `42.1`",
   103  		},
   104  		{
   105  			name:          "negative float to int",
   106  			vtype:         IntType,
   107  			inputValue:    -42.0,
   108  			expectedValue: int64(-42),
   109  			expectedErr:   "",
   110  		},
   111  		{
   112  			name:          "valid list<uint>",
   113  			vtype:         MustListType(UIntType),
   114  			inputValue:    []any{uint64(1)},
   115  			expectedValue: []any{uint64(1)},
   116  			expectedErr:   "",
   117  		},
   118  		{
   119  			name:          "valid list<uint> with conversion",
   120  			vtype:         MustListType(UIntType),
   121  			inputValue:    []any{42.0},
   122  			expectedValue: []any{uint64(42)},
   123  			expectedErr:   "",
   124  		},
   125  		{
   126  			name:          "valid list<uint> with string conversion",
   127  			vtype:         MustListType(UIntType),
   128  			inputValue:    []any{"42"},
   129  			expectedValue: []any{uint64(42)},
   130  			expectedErr:   "",
   131  		},
   132  		{
   133  			name:          "valid list<uint> with string conversion of max int",
   134  			vtype:         MustListType(UIntType),
   135  			inputValue:    []any{"9223372036854775807"},
   136  			expectedValue: []any{uint64(9223372036854775807)},
   137  			expectedErr:   "",
   138  		},
   139  		{
   140  			name:          "map with invalid value type",
   141  			vtype:         MustMapType(UIntType),
   142  			inputValue:    map[string]any{"foo": "a1"},
   143  			expectedValue: nil,
   144  			expectedErr:   "for map<uint>: found an invalid value for key `foo`: for uint: a uint64 value is required, but found invalid string value `a1`",
   145  		},
   146  		{
   147  			name:          "map with invalid value type in different key",
   148  			vtype:         MustMapType(UIntType),
   149  			inputValue:    map[string]any{"foo": "1", "bar": "a1"},
   150  			expectedValue: nil,
   151  			expectedErr:   "for map<uint>: found an invalid value for key `bar`: for uint: a uint64 value is required, but found invalid string value `a1`",
   152  		},
   153  		{
   154  			name:          "map with valid converted value type",
   155  			vtype:         MustMapType(UIntType),
   156  			inputValue:    map[string]any{"foo": "1"},
   157  			expectedValue: map[string]any{"foo": uint64(1)},
   158  			expectedErr:   "",
   159  		},
   160  		{
   161  			name:          "map with invalid nested value in list",
   162  			vtype:         MustMapType(MustListType(UIntType)),
   163  			inputValue:    map[string]any{"foo": []any{"-1"}},
   164  			expectedValue: nil,
   165  			expectedErr:   "for map<list<uint>>: found an invalid value for key `foo`: for list<uint>: found an invalid value for item at index 0: for uint: a uint value is required, but found int64 value `-1`",
   166  		},
   167  		{
   168  			name:          "map with valid nested value in list",
   169  			vtype:         MustMapType(MustListType(UIntType)),
   170  			inputValue:    map[string]any{"foo": []any{"1"}},
   171  			expectedValue: map[string]any{"foo": []any{uint64(1)}},
   172  			expectedErr:   "",
   173  		},
   174  		{
   175  			name:          "map with invalid nested map value",
   176  			vtype:         MustMapType(MustMapType(StringType)),
   177  			inputValue:    map[string]any{"foo": map[string]any{"bar": 42.0}},
   178  			expectedValue: nil,
   179  			expectedErr:   "for map<map<string>>: found an invalid value for key `foo`: for map<string>: found an invalid value for key `bar`: for string: a string value is required, but found float64 `42`",
   180  		},
   181  		{
   182  			name:          "map with valid nested map value",
   183  			vtype:         MustMapType(MustMapType(StringType)),
   184  			inputValue:    map[string]any{"foo": map[string]any{"bar": "hiya"}},
   185  			expectedValue: map[string]any{"foo": map[string]any{"bar": "hiya"}},
   186  			expectedErr:   "",
   187  		},
   188  		{
   189  			name:  "valid bytes",
   190  			vtype: BytesType,
   191  			inputValue: func() string {
   192  				v, _ := structpb.NewValue([]byte{1, 2, 3, 42})
   193  				return v.GetStringValue()
   194  			}(),
   195  			expectedValue: []byte{1, 2, 3, 42},
   196  			expectedErr:   "",
   197  		},
   198  		{
   199  			name:          "invalid type for bytes",
   200  			vtype:         BytesType,
   201  			inputValue:    42.0,
   202  			expectedValue: nil,
   203  			expectedErr:   "for bytes: bytes requires a base64 unicode string, found: float64 `42`",
   204  		},
   205  		{
   206  			name:          "invalid bytes",
   207  			vtype:         BytesType,
   208  			inputValue:    "testing123",
   209  			expectedValue: nil,
   210  			expectedErr:   "for bytes: bytes requires a base64 encoded string: illegal base64 data at input byte 8",
   211  		},
   212  		{
   213  			name:          "valid duration",
   214  			vtype:         DurationType,
   215  			inputValue:    "10h4m",
   216  			expectedValue: 604 * time.Minute,
   217  			expectedErr:   "",
   218  		},
   219  		{
   220  			name:          "invalid duration",
   221  			vtype:         DurationType,
   222  			inputValue:    "10hm",
   223  			expectedValue: nil,
   224  			expectedErr:   "for duration: could not parse duration string `10hm`: time: unknown unit \"hm\" in duration \"10hm\"",
   225  		},
   226  		{
   227  			name:          "valid timestamp",
   228  			vtype:         TimestampType,
   229  			inputValue:    "2023-01-22T01:13:00Z",
   230  			expectedValue: time.Date(2023, time.January, 22, 1, 13, 0, 0, time.UTC),
   231  			expectedErr:   "",
   232  		},
   233  		{
   234  			name:          "invalid timestamp",
   235  			vtype:         TimestampType,
   236  			inputValue:    "2023-0122",
   237  			expectedValue: nil,
   238  			expectedErr:   "for timestamp: could not parse RFC 3339 formatted timestamp string `2023-0122`: parsing time \"2023-0122\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"22\" as \"-\"",
   239  		},
   240  		{
   241  			name:          "invalid timestamp value",
   242  			vtype:         TimestampType,
   243  			inputValue:    42.0,
   244  			expectedValue: nil,
   245  			expectedErr:   "for timestamp: timestamps requires a RFC 3339 formatted timestamp string, found: float64 `42`",
   246  		},
   247  		{
   248  			name:          "valid ipaddress",
   249  			vtype:         IPAddressType,
   250  			inputValue:    "1.2.3.4",
   251  			expectedValue: MustParseIPAddress("1.2.3.4"),
   252  			expectedErr:   "",
   253  		},
   254  		{
   255  			name:          "invalid ipaddress",
   256  			vtype:         IPAddressType,
   257  			inputValue:    "1...23.4",
   258  			expectedValue: nil,
   259  			expectedErr:   "for ipaddress: could not parse ip address string `1...23.4`: ParseAddr(\"1...23.4\"): IPv4 field must have at least one digit (at \"..23.4\")",
   260  		},
   261  		{
   262  			name:          "invalid ipaddress type",
   263  			vtype:         IPAddressType,
   264  			inputValue:    42.0,
   265  			expectedValue: nil,
   266  			expectedErr:   "for ipaddress: ipaddress requires an ipaddress string, found: float64 `42`",
   267  		},
   268  		{
   269  			name:          "invalid ipaddress in list",
   270  			vtype:         MustListType(IPAddressType),
   271  			inputValue:    []any{"1...23.4"},
   272  			expectedValue: nil,
   273  			expectedErr:   "for list<ipaddress>: found an invalid value for item at index 0: for ipaddress: could not parse ip address string `1...23.4`: ParseAddr(\"1...23.4\"): IPv4 field must have at least one digit (at \"..23.4\")",
   274  		},
   275  		{
   276  			name:          "ipaddress in list",
   277  			vtype:         MustListType(IPAddressType),
   278  			inputValue:    []any{"1.2.3.4", "4.5.6.7"},
   279  			expectedValue: []any{MustParseIPAddress("1.2.3.4"), MustParseIPAddress("4.5.6.7")},
   280  			expectedErr:   "",
   281  		},
   282  	}
   283  
   284  	for _, tc := range tcs {
   285  		tc := tc
   286  		t.Run(tc.name, func(t *testing.T) {
   287  			result, err := tc.vtype.ConvertValue(tc.inputValue)
   288  			if err != nil {
   289  				require.Equal(t, tc.expectedErr, err.Error())
   290  			} else {
   291  				require.NoError(t, err)
   292  				require.Equal(t, tc.expectedValue, result)
   293  			}
   294  		})
   295  	}
   296  }