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 }