github.com/datastax/go-cassandra-native-protocol@v0.0.0-20220706104457-5e8aad05cf90/datacodec/timestamp_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  	timestampNegUTC, _     = time.Parse("2006-01-02 15:04:05 MST", "1951-06-24 23:00:00.999 UTC")       // -584499599001
    30  	timestampNegZoned, _   = time.Parse("2006-01-02 15:04:05 -07:00", "1951-06-24 16:00:00.999 -07:00") // -584499599001
    31  	timestampPosUTC, _     = time.Parse("2006-01-02 15:04:05 MST", "2021-10-11 23:00:00.999 UTC")       // 1633993200999
    32  	timestampPosZoned, _   = time.Parse("2006-01-02 15:04:05 -07:00", "2021-10-12 00:00:00.999 +01:00") // 1633993200999
    33  	timestampEpoch         = time.Unix(0, 0).UTC()
    34  	timestampEpochZoned, _ = time.Parse("2006-01-02 15:04:05 -07:00", "1969-12-31 23:00:00 -01:00")
    35  	timestampOufOfRangeNeg = time.Date(-292275055, time.May, 16, 16, 47, 04, 191_000_000, time.UTC)   // -292275055-05-16T16:47:04.191Z
    36  	timestampOufOfRangePos = time.Date(292278994, time.August, 17, 07, 12, 55, 808_000_000, time.UTC) // +292278994-08-17T07:12:55.808Z
    37  	paris, _               = time.LoadLocation("Europe/Paris")
    38  	timestampPosBytes      = encodeUint64(0x0000017c71959567)
    39  )
    40  
    41  func TestConvertTimeToEpochMillis(t *testing.T) {
    42  	tests := []struct {
    43  		name     string
    44  		input    time.Time
    45  		expected int64
    46  		err      string
    47  	}{
    48  		{"epoch", timestampEpoch, 0, ""},
    49  		{"epoch zoned", timestampEpochZoned, 0, ""},
    50  		{"negative", timestampNegUTC, -584499599001, ""},
    51  		{"negative zoned", timestampNegZoned, -584499599001, ""},
    52  		{"positive", timestampPosUTC, 1633993200999, ""},
    53  		{"positive zoned", timestampPosZoned, 1633993200999, ""},
    54  		{"min", TimestampMin, math.MinInt64, ""},
    55  		{"max", TimestampMax, math.MaxInt64, ""},
    56  		{"out of range negative", timestampOufOfRangeNeg, 0, "value out of range: -292275055-05-16 16:47:04.191 +0000 UTC"},
    57  		{"out of range positive", timestampOufOfRangePos, 0, "value out of range: 292278994-08-17 07:12:55.808 +0000 UTC"},
    58  	}
    59  	for _, tt := range tests {
    60  		t.Run(tt.name, func(t *testing.T) {
    61  			actual, err := ConvertTimeToEpochMillis(tt.input)
    62  			assert.Equal(t, tt.expected, actual)
    63  			assertErrorMessage(t, tt.err, err)
    64  		})
    65  	}
    66  }
    67  
    68  func TestConvertEpochMillisToTime(t *testing.T) {
    69  	tests := []struct {
    70  		name     string
    71  		input    int64
    72  		expected time.Time
    73  	}{
    74  		{"epoch", 0, timestampEpoch},
    75  		{"negative", -584499599001, timestampNegUTC},
    76  		{"positive", 1633993200999, timestampPosUTC},
    77  		{"min", math.MinInt64, TimestampMin},
    78  		{"max", math.MaxInt64, TimestampMax},
    79  	}
    80  	for _, tt := range tests {
    81  		t.Run(tt.name, func(t *testing.T) {
    82  			actual := ConvertEpochMillisToTime(tt.input)
    83  			assert.True(t, tt.expected.Equal(actual))
    84  		})
    85  	}
    86  }
    87  
    88  func Test_timestampCodec_Encode(t *testing.T) {
    89  	for _, version := range primitive.SupportedProtocolVersions() {
    90  		t.Run(version.String(), func(t *testing.T) {
    91  			tests := []struct {
    92  				name     string
    93  				source   interface{}
    94  				expected []byte
    95  				err      string
    96  			}{
    97  				{"nil", nil, nil, ""},
    98  				{"nil pointer", timeNilPtr(), nil, ""},
    99  				{"non nil", timestampPosUTC, timestampPosBytes, ""},
   100  				{"conversion failed", timestampOufOfRangePos, nil, fmt.Sprintf("cannot encode time.Time as CQL timestamp with %v: cannot convert from time.Time to int64: value out of range: 292278994-08-17 07:12:55.808 +0000 UTC", version)},
   101  			}
   102  			for _, tt := range tests {
   103  				t.Run(tt.name, func(t *testing.T) {
   104  					actual, err := Timestamp.Encode(tt.source, version)
   105  					assert.Equal(t, tt.expected, actual)
   106  					assertErrorMessage(t, tt.err, err)
   107  				})
   108  			}
   109  		})
   110  	}
   111  }
   112  
   113  func Test_timestampCodec_Decode(t *testing.T) {
   114  	for _, version := range primitive.SupportedProtocolVersions() {
   115  		t.Run(version.String(), func(t *testing.T) {
   116  			tests := []struct {
   117  				name     string
   118  				source   []byte
   119  				dest     interface{}
   120  				expected interface{}
   121  				wasNull  bool
   122  				err      string
   123  			}{
   124  				{"null", nil, new(int64), new(int64), true, ""},
   125  				{"non null", timestampPosBytes, new(time.Time), &timestampPosUTC, false, ""},
   126  				{"non null interface", timestampPosBytes, new(interface{}), interfacePtr(timestampPosUTC), false, ""},
   127  				{"read failed", []byte{1}, new(int64), new(int64), false, fmt.Sprintf("cannot decode CQL timestamp as *int64 with %v: cannot read int64: expected 8 bytes but got: 1", version)},
   128  				{"conversion failed", timestampPosBytes, new(float64), new(float64), false, fmt.Sprintf("cannot decode CQL timestamp as *float64 with %v: cannot convert from int64 to *float64: conversion not supported", version)},
   129  			}
   130  			for _, tt := range tests {
   131  				t.Run(tt.name, func(t *testing.T) {
   132  					wasNull, err := Timestamp.Decode(tt.source, tt.dest, version)
   133  					assert.Equal(t, tt.expected, tt.dest)
   134  					assert.Equal(t, tt.wasNull, wasNull)
   135  					assertErrorMessage(t, tt.err, err)
   136  				})
   137  			}
   138  		})
   139  	}
   140  }
   141  
   142  func Test_convertToInt64Timestamp(t *testing.T) {
   143  	for _, layout := range []string{TimestampLayoutDefault, "2006-01-02 15:04:05.999 MST"} {
   144  		t.Run(layout, func(t *testing.T) {
   145  			for _, location := range []*time.Location{time.UTC, paris} {
   146  				t.Run(location.String(), func(t *testing.T) {
   147  					ts := timestampPosUTC.In(location)
   148  					tests := []struct {
   149  						name       string
   150  						source     interface{}
   151  						wantVal    int64
   152  						wantWasNil bool
   153  						wantErr    string
   154  					}{
   155  						{"from time", ts, 1633993200999, false, ""},
   156  						{"from time out of range", timestampOufOfRangePos, 0, false, "cannot convert from time.Time to int64: value out of range: 292278994-08-17 07:12:55.808 +0000 UTC"},
   157  						{"from *time nil", timeNilPtr(), 0, true, ""},
   158  						{"from *time non nil", &ts, 1633993200999, false, ""},
   159  						{"from *time out of range", &timestampOufOfRangePos, 0, false, "cannot convert from *time.Time to int64: value out of range: 292278994-08-17 07:12:55.808 +0000 UTC"},
   160  						{"from string", ts.Format(layout), 1633993200999, false, ""},
   161  						{"from string malformed", "not a timestamp", 0, false, "cannot convert from string to int64: parsing time \"not a timestamp\" as \"" + layout + "\""},
   162  						{"from *string nil", stringNilPtr(), 0, true, ""},
   163  						{"from *string non nil", stringPtr(ts.Format(layout)), 1633993200999, false, ""},
   164  						{"from *string malformed", stringPtr("not a timestamp"), 0, false, "cannot convert from *string to int64: parsing time \"not a timestamp\" as \"" + layout + "\""},
   165  						{"from untyped nil", nil, 0, true, ""},
   166  						{"from numeric", 1633993200999, 1633993200999, false, ""},
   167  					}
   168  					for _, tt := range tests {
   169  						t.Run(tt.name, func(t *testing.T) {
   170  							gotVal, gotWasNil, gotErr := convertToInt64Timestamp(tt.source, layout, location)
   171  							assert.Equal(t, tt.wantVal, gotVal)
   172  							assert.Equal(t, tt.wantWasNil, gotWasNil)
   173  							assertErrorMessage(t, tt.wantErr, gotErr)
   174  						})
   175  					}
   176  				})
   177  			}
   178  		})
   179  	}
   180  }
   181  
   182  func Test_convertFromInt64Timestamp(t *testing.T) {
   183  	for _, layout := range []string{TimestampLayoutDefault, "2006-01-02 15:04:05 MST"} {
   184  		t.Run(layout, func(t *testing.T) {
   185  			for _, location := range []*time.Location{time.UTC, paris} {
   186  				t.Run(location.String(), func(t *testing.T) {
   187  					ts := timestampPosUTC.In(location)
   188  					tests := []struct {
   189  						name     string
   190  						val      int64
   191  						wasNull  bool
   192  						dest     interface{}
   193  						expected interface{}
   194  						wantErr  string
   195  					}{
   196  						{"to *interface{} nil dest", 1, false, interfaceNilPtr(), interfaceNilPtr(), "cannot convert from int64 to *interface {}: destination is nil"},
   197  						{"to *interface{} nil source", 0, true, new(interface{}), new(interface{}), ""},
   198  						{"to *interface{} non nil", 1633993200999, false, new(interface{}), interfacePtr(ts), ""},
   199  						{"to *time nil dest", 1, false, timeNilPtr(), timeNilPtr(), "cannot convert from int64 to *time.Time: destination is nil"},
   200  						{"to *time nil source", 0, true, new(time.Time), new(time.Time), ""},
   201  						{"to *time", 1633993200999, false, new(time.Time), &ts, ""},
   202  						{"to *string nil dest", 1, false, stringNilPtr(), stringNilPtr(), "cannot convert from int64 to *string: destination is nil"},
   203  						{"to *string nil source", 0, true, new(string), new(string), ""},
   204  						{"to *string", 1633993200999, false, new(string), stringPtr(ts.Format(layout)), ""},
   205  						{"to numeric", 1234, false, new(int32), int32Ptr(1234), ""},
   206  					}
   207  					for _, tt := range tests {
   208  						t.Run(tt.name, func(t *testing.T) {
   209  							gotErr := convertFromInt64Timestamp(tt.val, tt.wasNull, tt.dest, layout, location)
   210  							assert.Equal(t, tt.expected, tt.dest)
   211  							assertErrorMessage(t, tt.wantErr, gotErr)
   212  						})
   213  					}
   214  				})
   215  			}
   216  		})
   217  	}
   218  }