github.com/datastax/go-cassandra-native-protocol@v0.0.0-20220706104457-5e8aad05cf90/datacodec/date_test.go (about)

     1  // Copyright 2021 DataStax
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package datacodec
    16  
    17  import (
    18  	"fmt"
    19  	"math"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/stretchr/testify/assert"
    24  
    25  	"github.com/datastax/go-cassandra-native-protocol/primitive"
    26  )
    27  
    28  var (
    29  	datePos, _     = time.Parse(DateLayoutDefault, "2021-10-12") // 18912
    30  	dateNeg, _     = time.Parse(DateLayoutDefault, "1951-06-24") // -6766
    31  	dateOutOfRange = time.Unix((math.MaxInt32+1)*86400, 0).UTC()
    32  
    33  	datePosBytes = encodeUint32(0x800049e0)
    34  )
    35  
    36  func TestConvertTimeToEpochDays(t *testing.T) {
    37  	tests := []struct {
    38  		name     string
    39  		input    time.Time
    40  		expected int32
    41  		err      string
    42  	}{
    43  		{"epoch", time.Unix(0, 0), 0, ""},
    44  		{"negative", dateNeg, -6766, ""},
    45  		{"positive", datePos, 18912, ""},
    46  		{"negative truncation", time.Date(1951, time.June, 24, 23, 59, 59, 999999999, time.UTC), -6766, ""},
    47  		{"positive truncation", time.Date(2021, time.October, 12, 23, 59, 59, 999999999, time.UTC), 18912, ""},
    48  		{"min", DateMin, math.MinInt32, ""},
    49  		{"max", DateMax, math.MaxInt32, ""},
    50  		{"out of range negative", time.Date(-5877641, time.June, 22, 23, 59, 59, 999999999, time.UTC), 0, "value out of range: -5877641-06-22 23:59:59.999999999 +0000 UTC"},
    51  		{"out of range positive", time.Date(5881580, time.July, 12, 0, 0, 0, 0, time.UTC), 0, "value out of range: 5881580-07-12 00:00:00 +0000 UTC"},
    52  	}
    53  	for _, tt := range tests {
    54  		t.Run(tt.name, func(t *testing.T) {
    55  			actual, err := ConvertTimeToEpochDays(tt.input)
    56  			assert.Equal(t, tt.expected, actual)
    57  			assertErrorMessage(t, tt.err, err)
    58  		})
    59  	}
    60  }
    61  
    62  func TestConvertEpochDaysToTime(t *testing.T) {
    63  	tests := []struct {
    64  		name     string
    65  		input    int32
    66  		expected time.Time
    67  	}{
    68  		{"epoch", 0, time.Unix(0, 0)},
    69  		{"negative", -6766, dateNeg},
    70  		{"positive", 18912, datePos},
    71  		{"min", math.MinInt32, DateMin},
    72  		{"max", math.MaxInt32, DateMax},
    73  	}
    74  	for _, tt := range tests {
    75  		t.Run(tt.name, func(t *testing.T) {
    76  			actual := ConvertEpochDaysToTime(tt.input)
    77  			assert.True(t, tt.expected.Equal(actual))
    78  		})
    79  	}
    80  }
    81  
    82  func Test_dateCodec_Encode(t *testing.T) {
    83  	for _, version := range primitive.SupportedProtocolVersionsGreaterThanOrEqualTo(primitive.ProtocolVersion4) {
    84  		t.Run(version.String(), func(t *testing.T) {
    85  			tests := []struct {
    86  				name     string
    87  				source   interface{}
    88  				expected []byte
    89  				err      string
    90  			}{
    91  				{"nil", nil, nil, ""},
    92  				{"nil pointer", timeNilPtr(), nil, ""},
    93  				{"non nil", datePos, datePosBytes, ""},
    94  				{"conversion failed", dateOutOfRange, nil, fmt.Sprintf("cannot encode time.Time as CQL date with %v: cannot convert from time.Time to int32: value out of range: 5881580-07-12 00:00:00 +0000 UTC", version)},
    95  			}
    96  			for _, tt := range tests {
    97  				t.Run(tt.name, func(t *testing.T) {
    98  					actual, err := Date.Encode(tt.source, version)
    99  					assert.Equal(t, tt.expected, actual)
   100  					assertErrorMessage(t, tt.err, err)
   101  				})
   102  			}
   103  		})
   104  	}
   105  	for _, version := range primitive.SupportedProtocolVersionsLesserThan(primitive.ProtocolVersion4) {
   106  		t.Run(version.String(), func(t *testing.T) {
   107  			tests := []struct {
   108  				name     string
   109  				source   interface{}
   110  				expected []byte
   111  				err      string
   112  			}{
   113  				{"nil", nil, nil, "data type date not supported"},
   114  				{"non nil", datePos, nil, "data type date not supported"},
   115  			}
   116  			for _, tt := range tests {
   117  				t.Run(tt.name, func(t *testing.T) {
   118  					actual, err := Date.Encode(tt.source, version)
   119  					assert.Equal(t, tt.expected, actual)
   120  					assertErrorMessage(t, tt.err, err)
   121  				})
   122  			}
   123  		})
   124  	}
   125  }
   126  
   127  func Test_dateCodec_Decode(t *testing.T) {
   128  	for _, version := range primitive.SupportedProtocolVersionsGreaterThanOrEqualTo(primitive.ProtocolVersion4) {
   129  		t.Run(version.String(), func(t *testing.T) {
   130  			tests := []struct {
   131  				name     string
   132  				source   []byte
   133  				dest     interface{}
   134  				expected interface{}
   135  				wasNull  bool
   136  				err      string
   137  			}{
   138  				{"null", nil, new(int32), new(int32), true, ""},
   139  				{"non null", datePosBytes, new(time.Time), &datePos, false, ""},
   140  				{"non null interface", datePosBytes, new(interface{}), interfacePtr(datePos), false, ""},
   141  				{"read failed", []byte{1}, new(int32), new(int32), false, fmt.Sprintf("cannot decode CQL date as *int32 with %v: cannot read int32: expected 4 bytes but got: 1", version)},
   142  				{"conversion failed", datePosBytes, new(float64), new(float64), false, fmt.Sprintf("cannot decode CQL date as *float64 with %v: cannot convert from int32 to *float64: conversion not supported", version)},
   143  			}
   144  			for _, tt := range tests {
   145  				t.Run(tt.name, func(t *testing.T) {
   146  					wasNull, err := Date.Decode(tt.source, tt.dest, version)
   147  					assert.Equal(t, tt.expected, tt.dest)
   148  					assert.Equal(t, tt.wasNull, wasNull)
   149  					assertErrorMessage(t, tt.err, err)
   150  				})
   151  			}
   152  		})
   153  	}
   154  	for _, version := range primitive.SupportedProtocolVersionsLesserThan(primitive.ProtocolVersion4) {
   155  		t.Run(version.String(), func(t *testing.T) {
   156  			tests := []struct {
   157  				name     string
   158  				source   []byte
   159  				dest     interface{}
   160  				expected interface{}
   161  				wasNull  bool
   162  				err      string
   163  			}{
   164  				{"null", nil, new(int32), new(int32), true, "data type date not supported"},
   165  				{"non null", datePosBytes, new(time.Time), new(time.Time), false, "data type date not supported"},
   166  			}
   167  			for _, tt := range tests {
   168  				t.Run(tt.name, func(t *testing.T) {
   169  					wasNull, err := Date.Decode(tt.source, tt.dest, version)
   170  					assert.Equal(t, tt.expected, tt.dest)
   171  					assert.Equal(t, tt.wasNull, wasNull)
   172  					assertErrorMessage(t, tt.err, err)
   173  				})
   174  			}
   175  		})
   176  	}
   177  }
   178  
   179  func Test_convertToInt32Date(t *testing.T) {
   180  	for _, layout := range []string{DateLayoutDefault, "Jan 02 2006"} {
   181  		t.Run(layout, func(t *testing.T) {
   182  			tests := []struct {
   183  				name       string
   184  				source     interface{}
   185  				wantVal    int32
   186  				wantWasNil bool
   187  				wantErr    string
   188  			}{
   189  				{"from time", datePos, 18912, false, ""},
   190  				{"from time out of range", dateOutOfRange, 0, false, "cannot convert from time.Time to int32: value out of range: 5881580-07-12 00:00:00 +0000 UTC"},
   191  				{"from *time nil", timeNilPtr(), 0, true, ""},
   192  				{"from *time non nil", &datePos, 18912, false, ""},
   193  				{"from *time out of range", &dateOutOfRange, 0, false, "cannot convert from *time.Time to int32: value out of range: 5881580-07-12 00:00:00 +0000 UTC"},
   194  				{"from string", datePos.Format(layout), 18912, false, ""},
   195  				{"from string malformed", "not a date", 0, false, "cannot convert from string to int32: parsing time \"not a date\" as \"" + layout + "\""},
   196  				{"from *string nil", stringNilPtr(), 0, true, ""},
   197  				{"from *string non nil", stringPtr(datePos.Format(layout)), 18912, false, ""},
   198  				{"from *string malformed", stringPtr("not a date"), 0, false, "cannot convert from *string to int32: parsing time \"not a date\" as \"" + layout + "\""},
   199  				{"from untyped nil", nil, 0, true, ""},
   200  				{"from numeric", 1234, 1234, false, ""},
   201  			}
   202  			for _, tt := range tests {
   203  				t.Run(tt.name, func(t *testing.T) {
   204  					gotVal, gotWasNil, gotErr := convertToInt32Date(tt.source, layout)
   205  					assert.Equal(t, tt.wantVal, gotVal)
   206  					assert.Equal(t, tt.wantWasNil, gotWasNil)
   207  					assertErrorMessage(t, tt.wantErr, gotErr)
   208  				})
   209  			}
   210  		})
   211  	}
   212  }
   213  
   214  func Test_convertFromInt32Date(t *testing.T) {
   215  	for _, layout := range []string{DateLayoutDefault, "Jan 02 2006"} {
   216  		t.Run(layout, func(t *testing.T) {
   217  			tests := []struct {
   218  				name     string
   219  				val      int32
   220  				wasNull  bool
   221  				dest     interface{}
   222  				expected interface{}
   223  				err      string
   224  			}{
   225  				{"to *interface{} nil dest", 1, false, interfaceNilPtr(), interfaceNilPtr(), "cannot convert from int32 to *interface {}: destination is nil"},
   226  				{"to *interface{} nil source", 0, true, new(interface{}), new(interface{}), ""},
   227  				{"to *interface{} non nil", 18912, false, new(interface{}), interfacePtr(datePos), ""},
   228  				{"to *time nil dest", 1, false, timeNilPtr(), timeNilPtr(), "cannot convert from int32 to *time.Time: destination is nil"},
   229  				{"to *time nil source", 0, true, new(time.Time), new(time.Time), ""},
   230  				{"to *time", 18912, false, new(time.Time), &datePos, ""},
   231  				{"to *string nil dest", 1, false, stringNilPtr(), stringNilPtr(), "cannot convert from int32 to *string: destination is nil"},
   232  				{"to *string nil source", 0, true, new(string), new(string), ""},
   233  				{"to *string", 18912, false, new(string), stringPtr(datePos.Format(layout)), ""},
   234  				{"to numeric", 1234, false, new(int32), int32Ptr(1234), ""},
   235  			}
   236  			for _, tt := range tests {
   237  				t.Run(tt.name, func(t *testing.T) {
   238  					err := convertFromInt32Date(tt.val, tt.wasNull, layout, tt.dest)
   239  					assert.Equal(t, tt.expected, tt.dest)
   240  					assertErrorMessage(t, tt.err, err)
   241  				})
   242  			}
   243  		})
   244  	}
   245  }