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 }