github.com/datastax/go-cassandra-native-protocol@v0.0.0-20220706104457-5e8aad05cf90/datacodec/time_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  	"testing"
    20  	"time"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  
    24  	"github.com/datastax/go-cassandra-native-protocol/primitive"
    25  )
    26  
    27  var (
    28  	timeSimple, _ = time.Parse("15:04:05.999999999", "12:34:56.123456789") // 45296123456789
    29  	timeMax, _    = time.Parse("15:04:05.999999999", "23:59:59.999999999")
    30  	timeMin, _    = time.Parse("15:04:05", "00:00:00")
    31  
    32  	durationSimple, _     = time.ParseDuration("12h34m56s123456789ns")
    33  	durationMax           = TimeMaxDuration
    34  	durationMin           = time.Duration(0)
    35  	durationOutOfRangePos = TimeMaxDuration + 1
    36  	durationOutOfRangeNeg = time.Duration(-1)
    37  
    38  	timeSimpleBytes = encodeUint64(0x0000293253592d15)
    39  )
    40  
    41  func TestTimeToNanosOfDay(t *testing.T) {
    42  	tests := []struct {
    43  		name     string
    44  		input    time.Time
    45  		expected int64
    46  	}{
    47  		{"zero", time.Time{}, 0},
    48  		{"simple", timeSimple, 45296123456789},
    49  		{"min", timeMin, 0},
    50  		{"max", timeMax, int64(TimeMaxDuration)},
    51  	}
    52  	for _, tt := range tests {
    53  		t.Run(tt.name, func(t *testing.T) {
    54  			actual := ConvertTimeToNanosOfDay(tt.input)
    55  			assert.Equal(t, tt.expected, actual)
    56  		})
    57  	}
    58  }
    59  
    60  func TestDurationToNanosOfDay(t *testing.T) {
    61  	tests := []struct {
    62  		name     string
    63  		input    time.Duration
    64  		expected int64
    65  		err      string
    66  	}{
    67  		{"zero", 0, 0, ""},
    68  		{"simple", durationSimple, 45296123456789, ""},
    69  		{"min", durationMin, 0, ""},
    70  		{"max", durationMax, int64(TimeMaxDuration), ""},
    71  		{"out of range positive", durationOutOfRangePos, 0, "value out of range: 24h0m0s"},
    72  		{"out of range negative", durationOutOfRangeNeg, 0, "value out of range: -1ns"},
    73  	}
    74  	for _, tt := range tests {
    75  		t.Run(tt.name, func(t *testing.T) {
    76  			actual, err := ConvertDurationToNanosOfDay(tt.input)
    77  			assert.Equal(t, tt.expected, actual)
    78  			assertErrorMessage(t, tt.err, err)
    79  		})
    80  	}
    81  }
    82  
    83  func TestConvertNanosOfDayToTime(t *testing.T) {
    84  	tests := []struct {
    85  		name     string
    86  		input    int64
    87  		expected time.Time
    88  		err      string
    89  	}{
    90  		{"simple", 45296123456789, timeSimple, ""},
    91  		{"min", 0, time.Date(0, time.January, 1, 0, 0, 0, 0, time.UTC), ""},
    92  		{"max", int64(TimeMaxDuration), time.Date(0, time.January, 1, 0, 0, 0, 0, time.UTC).Add(TimeMaxDuration), ""},
    93  		{"out of range positive", int64(TimeMaxDuration) + 1, time.Time{}, "value out of range: 24h0m0s"},
    94  		{"out of range negative", -1, time.Time{}, "value out of range: -1ns"},
    95  	}
    96  	for _, tt := range tests {
    97  		t.Run(tt.name, func(t *testing.T) {
    98  			actual, err := ConvertNanosOfDayToTime(tt.input)
    99  			assert.Equal(t, tt.expected, actual)
   100  			assertErrorMessage(t, tt.err, err)
   101  		})
   102  	}
   103  }
   104  
   105  func TestConvertNanosOfDayToDuration(t *testing.T) {
   106  	tests := []struct {
   107  		name     string
   108  		input    int64
   109  		expected time.Duration
   110  		err      string
   111  	}{
   112  		{"simple", 45296123456789, durationSimple, ""},
   113  		{"min", 0, 0, ""},
   114  		{"max", int64(TimeMaxDuration), TimeMaxDuration, ""},
   115  		{"out of range positive", int64(TimeMaxDuration) + 1, 0, "value out of range: 24h0m0s"},
   116  		{"out of range negative", -1, 0, "value out of range: -1ns"},
   117  	}
   118  	for _, tt := range tests {
   119  		t.Run(tt.name, func(t *testing.T) {
   120  			actual, err := ConvertNanosOfDayToDuration(tt.input)
   121  			assert.Equal(t, tt.expected, actual)
   122  			assertErrorMessage(t, tt.err, err)
   123  		})
   124  	}
   125  }
   126  
   127  func Test_timeCodec_Encode(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   interface{}
   133  				expected []byte
   134  				err      string
   135  			}{
   136  				{"nil", nil, nil, ""},
   137  				{"nil pointer", timeNilPtr(), nil, ""},
   138  				{"non nil", timeSimple, timeSimpleBytes, ""},
   139  				{"conversion failed", TimeMaxDuration + 1, nil, fmt.Sprintf("cannot encode time.Duration as CQL time with %v: cannot convert from time.Duration to int64: value out of range: 24h0m0s", version)},
   140  			}
   141  			for _, tt := range tests {
   142  				t.Run(tt.name, func(t *testing.T) {
   143  					actual, err := Time.Encode(tt.source, version)
   144  					assert.Equal(t, tt.expected, actual)
   145  					assertErrorMessage(t, tt.err, err)
   146  				})
   147  			}
   148  		})
   149  	}
   150  	for _, version := range primitive.SupportedProtocolVersionsLesserThan(primitive.ProtocolVersion4) {
   151  		t.Run(version.String(), func(t *testing.T) {
   152  			tests := []struct {
   153  				name     string
   154  				source   interface{}
   155  				expected []byte
   156  				err      string
   157  			}{
   158  				{"nil", nil, nil, "data type time not supported"},
   159  				{"non nil", timeSimple, nil, "data type time not supported"},
   160  				{"conversion failed", TimeMaxDuration + 1, nil, "data type time not supported"},
   161  			}
   162  			for _, tt := range tests {
   163  				t.Run(tt.name, func(t *testing.T) {
   164  					actual, err := Time.Encode(tt.source, version)
   165  					assert.Equal(t, tt.expected, actual)
   166  					assertErrorMessage(t, tt.err, err)
   167  				})
   168  			}
   169  		})
   170  	}
   171  }
   172  
   173  func Test_timeCodec_Decode(t *testing.T) {
   174  	for _, version := range primitive.SupportedProtocolVersionsGreaterThanOrEqualTo(primitive.ProtocolVersion4) {
   175  		t.Run(version.String(), func(t *testing.T) {
   176  			tests := []struct {
   177  				name     string
   178  				source   []byte
   179  				dest     interface{}
   180  				expected interface{}
   181  				wasNull  bool
   182  				err      string
   183  			}{
   184  				{"null", nil, new(int64), new(int64), true, ""},
   185  				{"non null", timeSimpleBytes, new(time.Time), &timeSimple, false, ""},
   186  				{"non null interface", timeSimpleBytes, new(interface{}), interfacePtr(durationSimple), false, ""},
   187  				{"read failed", []byte{1}, new(int64), new(int64), false, fmt.Sprintf("cannot decode CQL time as *int64 with %v: cannot read int64: expected 8 bytes but got: 1", version)},
   188  				{"conversion failed", timeSimpleBytes, new(float64), new(float64), false, fmt.Sprintf("cannot decode CQL time as *float64 with %v: cannot convert from int64 to *float64: conversion not supported", version)},
   189  			}
   190  			for _, tt := range tests {
   191  				t.Run(tt.name, func(t *testing.T) {
   192  					wasNull, err := Time.Decode(tt.source, tt.dest, version)
   193  					assert.Equal(t, tt.expected, tt.dest)
   194  					assert.Equal(t, tt.wasNull, wasNull)
   195  					assertErrorMessage(t, tt.err, err)
   196  				})
   197  			}
   198  		})
   199  	}
   200  	for _, version := range primitive.SupportedProtocolVersionsLesserThan(primitive.ProtocolVersion4) {
   201  		t.Run(version.String(), func(t *testing.T) {
   202  			tests := []struct {
   203  				name     string
   204  				source   []byte
   205  				dest     interface{}
   206  				expected interface{}
   207  				wasNull  bool
   208  				err      string
   209  			}{
   210  				{"null", nil, new(int64), new(int64), true, "data type time not supported"},
   211  				{"non null", timeSimpleBytes, new(time.Time), new(time.Time), false, "data type time not supported"},
   212  			}
   213  			for _, tt := range tests {
   214  				t.Run(tt.name, func(t *testing.T) {
   215  					wasNull, err := Time.Decode(tt.source, tt.dest, version)
   216  					assert.Equal(t, tt.expected, tt.dest)
   217  					assert.Equal(t, tt.wasNull, wasNull)
   218  					assertErrorMessage(t, tt.err, err)
   219  				})
   220  			}
   221  		})
   222  	}
   223  }
   224  
   225  func Test_convertToInt64Time(t *testing.T) {
   226  	for _, layout := range []string{TimeLayoutDefault, "150405.999999999"} {
   227  		t.Run(layout, func(t *testing.T) {
   228  			tests := []struct {
   229  				name       string
   230  				source     interface{}
   231  				wantVal    int64
   232  				wantWasNil bool
   233  				wantErr    string
   234  			}{
   235  				{"from duration", durationSimple, 45296123456789, false, ""},
   236  				{"from duration out of range", durationOutOfRangePos, 0, false, "cannot convert from time.Duration to int64: value out of range: 24h0m0s"},
   237  				{"from *duration nil", durationNilPtr(), 0, true, ""},
   238  				{"from *duration non nil", &durationSimple, 45296123456789, false, ""},
   239  				{"from *duration out of range", &durationOutOfRangePos, 0, false, "cannot convert from *time.Duration to int64: value out of range: 24h0m0s"},
   240  				{"from time", timeSimple, 45296123456789, false, ""},
   241  				{"from *time nil", timeNilPtr(), 0, true, ""},
   242  				{"from *time non nil", &timeSimple, 45296123456789, false, ""},
   243  				{"from string", timeSimple.Format(layout), 45296123456789, false, ""},
   244  				{"from string malformed", "not a time", 0, false, "cannot convert from string to int64: parsing time \"not a time\" as \"" + layout + "\""},
   245  				{"from *string nil", stringNilPtr(), 0, true, ""},
   246  				{"from *string non nil", stringPtr(timeSimple.Format(layout)), 45296123456789, false, ""},
   247  				{"from *string malformed", stringPtr("not a time"), 0, false, "cannot convert from *string to int64: parsing time \"not a time\" as \"" + layout + "\""},
   248  				{"from untyped nil", nil, 0, true, ""},
   249  				{"from numeric", 1234, 1234, false, ""},
   250  			}
   251  			for _, tt := range tests {
   252  				t.Run(tt.name, func(t *testing.T) {
   253  					gotVal, gotWasNil, gotErr := convertToInt64Time(tt.source, layout)
   254  					assert.Equal(t, tt.wantVal, gotVal)
   255  					assert.Equal(t, tt.wantWasNil, gotWasNil)
   256  					assertErrorMessage(t, tt.wantErr, gotErr)
   257  				})
   258  			}
   259  		})
   260  	}
   261  }
   262  
   263  func Test_convertFromInt64Time(t *testing.T) {
   264  	for _, layout := range []string{TimeLayoutDefault, "150405"} {
   265  		t.Run(layout, func(t *testing.T) {
   266  			tests := []struct {
   267  				name     string
   268  				val      int64
   269  				wasNull  bool
   270  				dest     interface{}
   271  				expected interface{}
   272  				wantErr  string
   273  			}{
   274  				{"to *interface{} nil dest", 1, false, interfaceNilPtr(), interfaceNilPtr(), "cannot convert from int64 to *interface {}: destination is nil"},
   275  				{"to *interface{} nil source", 0, true, new(interface{}), new(interface{}), ""},
   276  				{"to *interface{} non nil", 45296123456789, false, new(interface{}), interfacePtr(durationSimple), ""},
   277  				{"to *duration nil dest", 1, false, durationNilPtr(), durationNilPtr(), "cannot convert from int64 to *time.Duration: destination is nil"},
   278  				{"to *duration nil source", 0, true, new(time.Duration), new(time.Duration), ""},
   279  				{"to *duration", 45296123456789, false, new(time.Duration), &durationSimple, ""},
   280  				{"to *duration out of range", int64(TimeMaxDuration + 1), false, new(time.Duration), new(time.Duration), "cannot convert from int64 to *time.Duration: value out of range: 24h0m0s"},
   281  				{"to *time nil dest", 1, false, timeNilPtr(), timeNilPtr(), "cannot convert from int64 to *time.Time: destination is nil"},
   282  				{"to *time nil source", 0, true, new(time.Time), new(time.Time), ""},
   283  				{"to *time", 45296123456789, false, new(time.Time), &timeSimple, ""},
   284  				{"to *time out of range", int64(TimeMaxDuration + 1), false, new(time.Time), new(time.Time), "cannot convert from int64 to *time.Time: value out of range: 24h0m0s"},
   285  				{"to *string nil dest", 1, false, stringNilPtr(), stringNilPtr(), "cannot convert from int64 to *string: destination is nil"},
   286  				{"to *string nil source", 0, true, new(string), new(string), ""},
   287  				{"to *string", 45296123456789, false, new(string), stringPtr(timeSimple.Format(layout)), ""},
   288  				{"to *string out of range", int64(TimeMaxDuration + 1), false, new(string), new(string), "cannot convert from int64 to *string: value out of range: 24h0m0s"},
   289  				{"to numeric", 1234, false, new(int32), int32Ptr(1234), ""},
   290  			}
   291  			for _, tt := range tests {
   292  				t.Run(tt.name, func(t *testing.T) {
   293  					gotErr := convertFromInt64Time(tt.val, tt.wasNull, tt.dest, layout)
   294  					assert.Equal(t, tt.expected, tt.dest)
   295  					assertErrorMessage(t, tt.wantErr, gotErr)
   296  				})
   297  			}
   298  		})
   299  	}
   300  }