diff --git a/service/sharddistributor/store/etcd/etcdtypes/state_test.go b/service/sharddistributor/store/etcd/etcdtypes/state_test.go index 767878bb5db..208922cb929 100644 --- a/service/sharddistributor/store/etcd/etcdtypes/state_test.go +++ b/service/sharddistributor/store/etcd/etcdtypes/state_test.go @@ -223,6 +223,29 @@ func TestAssignedState_JSONMarshalling(t *testing.T) { } } +func TestAssignedState_JSONUnmarshalWithZeroLastUpdated(t *testing.T) { + tests := map[string]struct { + jsonStr string + }{ + "last_updated is string zero": { + jsonStr: `{"assigned_shards":{"1":{"status":"AssignmentStatusREADY"}},"last_updated":"0"}`, + }, + "last_updated is numeric zero": { + jsonStr: `{"assigned_shards":{"1":{"status":"AssignmentStatusREADY"}},"last_updated":0}`, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + var unmarshalled AssignedState + err := json.Unmarshal([]byte(tc.jsonStr), &unmarshalled) + require.NoError(t, err) + require.Equal(t, types.AssignmentStatusREADY, unmarshalled.AssignedShards["1"].Status) + require.True(t, time.Time(unmarshalled.LastUpdated).IsZero()) + }) + } +} + func TestShardStatistics_FieldNumberMatched(t *testing.T) { require.Equal(t, reflect.TypeOf(ShardStatistics{}).NumField(), diff --git a/service/sharddistributor/store/etcd/etcdtypes/time.go b/service/sharddistributor/store/etcd/etcdtypes/time.go index e7985f293b5..bbd6e3d7ab2 100644 --- a/service/sharddistributor/store/etcd/etcdtypes/time.go +++ b/service/sharddistributor/store/etcd/etcdtypes/time.go @@ -39,11 +39,17 @@ func (t Time) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the json.Unmarshaler interface. // It decodes the time from time.RFC3339Nano format. +// It also handles the special case where the value is "0" or 0 (zero), +// which is treated as the zero value of time.Time. func (t *Time) UnmarshalJSON(data []byte) error { str := string(data) if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' { str = str[1 : len(str)-1] } + if str == "0" { + *t = Time(time.Time{}) + return nil + } parsed, err := ParseTime(str) if err != nil { return err diff --git a/service/sharddistributor/store/etcd/etcdtypes/time_test.go b/service/sharddistributor/store/etcd/etcdtypes/time_test.go index eb0b1df8ae9..0a47dd9d139 100644 --- a/service/sharddistributor/store/etcd/etcdtypes/time_test.go +++ b/service/sharddistributor/store/etcd/etcdtypes/time_test.go @@ -89,6 +89,16 @@ func TestTimeUnmarshalJSON(t *testing.T) { expectTime: Time(time.Date(2025, 11, 17, 23, 2, 3, 456789012, time.UTC)), expectErr: "", }, + "zero string": { + input: []byte(`"0"`), + expectTime: Time(time.Time{}), + expectErr: "", + }, + "zero number": { + input: []byte(`0`), + expectTime: Time(time.Time{}), + expectErr: "", + }, "invalid format": { input: []byte(`"not-a-time"`), expectTime: Time{},