From c899e073e59bd2360577f28649d6840a411a87c1 Mon Sep 17 00:00:00 2001 From: suifri Date: Fri, 31 Jul 2026 18:31:18 +0300 Subject: [PATCH] [WTEL-9628]refactor(call): createdAt filter guard to avoid using zero UNIX time value --- grpc_api/call.go | 7 +-- model/call.go | 10 ++++ model/search_list.go | 25 +++++++++ model/search_list_test.go | 103 +++++++++++++++++++++++++++++++++++++- 4 files changed, 137 insertions(+), 8 deletions(-) diff --git a/grpc_api/call.go b/grpc_api/call.go index 454c600b..c049603d 100644 --- a/grpc_api/call.go +++ b/grpc_api/call.go @@ -146,12 +146,7 @@ func (api *call) searchHistoryCall(ctx context.Context, in *engine.SearchHistory } } - if in.GetCreatedAt() != nil { - req.CreatedAt = &model.FilterBetween{ - From: in.GetCreatedAt().GetFrom(), - To: in.GetCreatedAt().GetTo(), - } - } + req.UseCreatedAtFilter(in.GetCreatedAt()) if in.GetStoredAt() != nil { req.StoredAt = &model.FilterBetween{ diff --git a/model/call.go b/model/call.go index 01246f27..4f4661a1 100644 --- a/model/call.go +++ b/model/call.go @@ -712,6 +712,16 @@ type SearchHistoryCall struct { Timeline *bool } +func (s *SearchHistoryCall) UseCreatedAtFilter(filter FilterBetweenProvider) *SearchHistoryCall { + if filter == nil { + return s + } + + s.CreatedAt = NewFilterBetweenFromProvider(filter) + + return s +} + type CallEventInfo struct { Id string `json:"id" db:"id"` Event string `json:"event" db:"-"` diff --git a/model/search_list.go b/model/search_list.go index 40849c78..3b46f99b 100644 --- a/model/search_list.go +++ b/model/search_list.go @@ -1,6 +1,7 @@ package model import ( + "cmp" "fmt" "reflect" "strings" @@ -34,6 +35,30 @@ type FilterBetween struct { To int64 } +func NewFilterBetweenFromProvider(provider FilterBetweenProvider) *FilterBetween { + return NewFilterBetween(provider.GetFrom(), provider.GetTo()) +} + +// if provided from value equals zero - then used value equals +// to now - 7 days +// if provided to value equals zero - then used value equals now +func NewFilterBetween(from, to int64) *FilterBetween { + now := time.Now().UTC() + + toVal := cmp.Or(to, now.UnixMilli()) + toTime := time.UnixMilli(toVal) + + fromTime := time.UnixMilli(cmp.Or(from, toTime.AddDate(0, 0, -7).UnixMilli())) + if fromTime.After(toTime) { + fromTime = toTime.AddDate(0, 0, -7) + } + + return &FilterBetween{ + From: fromTime.UnixMilli(), + To: toTime.UnixMilli(), + } +} + func GetBetweenFromTime(src *FilterBetween) *time.Time { if src == nil || src.From == 0 { return nil diff --git a/model/search_list_test.go b/model/search_list_test.go index 90135794..f09f311d 100644 --- a/model/search_list_test.go +++ b/model/search_list_test.go @@ -1,14 +1,20 @@ -package model +package model_test import ( "reflect" "testing" + "time" + + "github.com/webitel/engine/model" ) func TestParseRegexp(t *testing.T) { type args struct { q string } + + t.Parallel() + tests := []struct { name string args args @@ -89,9 +95,12 @@ func TestParseRegexp(t *testing.T) { wantFound: true, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotS, gotFound := ParseRegexp(tt.args.q) + t.Parallel() + + gotS, gotFound := model.ParseRegexp(tt.args.q) if !reflect.DeepEqual(*gotS, *tt.wantS) { t.Errorf("ParseRegexp() gotS = %v, want %v", *gotS, *tt.wantS) } @@ -101,3 +110,93 @@ func TestParseRegexp(t *testing.T) { }) } } + +func TestNewFilterBetween(t *testing.T) { + now := time.Now().UTC() + nowMs := now.UnixMilli() + weekAgoMs := now.AddDate(0, 0, -7).UnixMilli() + + fixedToMs := time.Date(2026, time.May, 20, 12, 0, 0, 0, time.UTC).UnixMilli() + fixedFromMs := time.Date(2026, time.May, 10, 12, 0, 0, 0, time.UTC).UnixMilli() + fixedToMinus7Ms := time.Date(2026, time.May, 13, 12, 0, 0, 0, time.UTC).UnixMilli() + + t.Parallel() + + tests := []struct { + name string + from int64 + to int64 + wantFrom int64 + wantTo int64 + deltaMs int64 + }{ + { + name: "Both from and to are zero -> defaults to now-7d and now", + from: 0, + to: 0, + wantFrom: weekAgoMs, + wantTo: nowMs, + deltaMs: 50, + }, + { + name: "To is set, From is zero -> From defaults to To - 7 days", + from: 0, + to: fixedToMs, + wantFrom: fixedToMinus7Ms, + wantTo: fixedToMs, + deltaMs: 0, + }, + { + name: "From is set, To is zero -> To defaults to now", + from: fixedFromMs, + to: 0, + wantFrom: fixedFromMs, + wantTo: nowMs, + deltaMs: 50, + }, + { + name: "Both from and to are set validly -> keeps values as is", + from: fixedFromMs, + to: fixedToMs, + wantFrom: fixedFromMs, + wantTo: fixedToMs, + deltaMs: 0, + }, + { + name: "Invalid range: From > To -> corrects From to To - 7 days", + from: fixedToMs, + to: fixedFromMs, + wantFrom: time.Date(2026, time.May, 3, 12, 0, 0, 0, time.UTC).UnixMilli(), + wantTo: fixedFromMs, + deltaMs: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got := model.NewFilterBetween(tt.from, tt.to) + + if got == nil { + t.Fatalf("NewFilterBetween() returned nil") + } + + if !withinDelta(got.From, tt.wantFrom, tt.deltaMs) { + t.Errorf("NewFilterBetween().From = %v, want %v (±%d ms)", got.From, tt.wantFrom, tt.deltaMs) + } + + if !withinDelta(got.To, tt.wantTo, tt.deltaMs) { + t.Errorf("NewFilterBetween().To = %v, want %v (±%d ms)", got.To, tt.wantTo, tt.deltaMs) + } + }) + } +} + +func withinDelta(got, want, delta int64) bool { + diff := got - want + if diff < 0 { + diff = -diff + } + return diff <= delta +}