github.com/blend/go-sdk@v1.20220411.3/status/tracked_action_test.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package status 9 10 import ( 11 "context" 12 "fmt" 13 "testing" 14 "time" 15 16 "github.com/blend/go-sdk/assert" 17 "github.com/blend/go-sdk/ex" 18 ) 19 20 func Test_TrackedAction_Wrap(t *testing.T) { 21 t.Parallel() 22 its := assert.New(t) 23 24 var shouldError, shouldPanic bool 25 action := ActionerFunc(func(_ context.Context, _ interface{}) (interface{}, error) { 26 if shouldPanic { 27 panic(fmt.Errorf("this is a panic")) 28 } 29 if shouldError { 30 return nil, fmt.Errorf("this is an error") 31 } 32 return "ok!", nil 33 }) 34 ta := NewTrackedAction("test-service") 35 tracked := ta.Intercept(action) 36 37 // should yield ok! 38 res, err := tracked.Action(context.Background(), "test-resource") 39 its.Nil(err) 40 its.Equal("ok!", res) 41 its.Len(ta.errors, 0) 42 its.Len(ta.requests, 1) 43 44 shouldError = true 45 res, err = tracked.Action(context.Background(), "test-resource") 46 its.Equal("this is an error", ex.ErrClass(err).Error()) 47 its.Nil(res) 48 its.Len(ta.errors, 1) 49 its.Len(ta.requests, 1) 50 51 shouldPanic = true 52 shouldError = false 53 res, err = tracked.Action(context.Background(), "test-resource") 54 its.Equal("this is a panic", ex.ErrClass(err).Error()) 55 its.Nil(res) 56 its.Len(ta.errors, 2) 57 its.Len(ta.requests, 1) 58 59 // push "now" forward by the expiration, the next wrapped call should clear the history 60 shouldPanic = false 61 shouldError = false 62 ta.nowProvider = func() time.Time { return time.Now().UTC().Add(ta.ExpirationOrDefault()) } 63 res, err = tracked.Action(context.Background(), "test-resource") 64 its.Nil(err) 65 its.Equal("ok!", res) 66 its.Len(ta.errors, 0) 67 its.Len(ta.requests, 1) 68 } 69 70 func Test_TrackedAction_GetStatus(t *testing.T) { 71 t.Parallel() 72 its := assert.New(t) 73 74 now := time.Now().UTC() 75 ta := &TrackedAction{ 76 ServiceName: "test-tracked-action", 77 nowProvider: func() time.Time { return now }, 78 TrackedActionConfig: TrackedActionConfig{ 79 Expiration: 5 * time.Second, 80 }, 81 requests: []RequestInfo{ 82 {now}, 83 {now.Add(-1 * time.Second)}, 84 {now.Add(-2 * time.Second)}, 85 {now.Add(-3 * time.Second)}, 86 {now.Add(-4 * time.Second)}, 87 {now.Add(-5 * time.Second)}, 88 {now.Add(-6 * time.Second)}, 89 {now.Add(-7 * time.Second)}, 90 }, 91 errors: []ErrorInfo{ 92 {Args: "test-resource-0", RequestInfo: RequestInfo{now.Add(-2 * time.Second)}}, 93 {Args: "test-resource-1", RequestInfo: RequestInfo{now.Add(-3 * time.Second)}}, 94 {Args: "test-resource-0", RequestInfo: RequestInfo{now.Add(-4 * time.Second)}}, 95 {Args: "test-resource-1", RequestInfo: RequestInfo{now.Add(-5 * time.Second)}}, 96 {Args: "test-resource-0", RequestInfo: RequestInfo{now.Add(-6 * time.Second)}}, 97 {Args: "test-resource-1", RequestInfo: RequestInfo{now.Add(-7 * time.Second)}}, 98 {Args: "test-resource-1", RequestInfo: RequestInfo{now.Add(-8 * time.Second)}}, 99 }, 100 } 101 102 status := ta.GetStatus() 103 its.Equal("test-tracked-action", status.Name) 104 its.Equal(SignalRed, status.Status) 105 its.Equal(5, status.Details.RequestCount) 106 its.Equal(3, status.Details.ErrorCount) 107 its.Len(status.Details.ErrorBreakdown, 2) 108 its.Equal(2, status.Details.ErrorBreakdown["test-resource-0"]) 109 its.Equal(1, status.Details.ErrorBreakdown["test-resource-1"]) 110 } 111 112 func Test_TrackedAction_getStatusSignalUnsafe(t *testing.T) { 113 t.Parallel() 114 its := assert.New(t) 115 116 testCases := [...]struct { 117 RequestCount int 118 ErrorCount int 119 Expected Signal 120 Message string 121 }{ 122 {0, 0, SignalGreen, "should return green with no requests"}, 123 {0, 10, SignalYellow, "should return yellow when 10 requests fail"}, 124 {0, 50, SignalRed, "should return red when 50 requests fail"}, 125 {2200, 10, SignalGreen, "should use percentages when count is high enough"}, 126 } 127 for _, tc := range testCases { 128 its.Equal(tc.Expected, trackedActionDefaultsWithCounts(tc.RequestCount, tc.ErrorCount).getStatusSignalUnsafe(), tc.Message) 129 } 130 } 131 132 func Test_TrackedAction_cleanOldRequestsUnsafe(t *testing.T) { 133 t.Parallel() 134 its := assert.New(t) 135 136 now := time.Now().UTC() 137 ta := &TrackedAction{ 138 nowProvider: func() time.Time { return now }, 139 TrackedActionConfig: TrackedActionConfig{ 140 Expiration: 5 * time.Second, 141 }, 142 requests: []RequestInfo{ 143 {now}, 144 {now.Add(-1 * time.Second)}, 145 {now.Add(-2 * time.Second)}, 146 {now.Add(-3 * time.Second)}, 147 {now.Add(-4 * time.Second)}, 148 {now.Add(-5 * time.Second)}, 149 {now.Add(-6 * time.Second)}, 150 {now.Add(-7 * time.Second)}, 151 }, 152 errors: []ErrorInfo{ 153 {RequestInfo: RequestInfo{now.Add(-3 * time.Second)}}, 154 {RequestInfo: RequestInfo{now.Add(-4 * time.Second)}}, 155 {RequestInfo: RequestInfo{now.Add(-5 * time.Second)}}, 156 {RequestInfo: RequestInfo{now.Add(-6 * time.Second)}}, 157 {RequestInfo: RequestInfo{now.Add(-7 * time.Second)}}, 158 {RequestInfo: RequestInfo{now.Add(-8 * time.Second)}}, 159 }, 160 } 161 162 ta.cleanOldRequestsUnsafe() 163 its.Len(ta.requests, 5) 164 its.Len(ta.errors, 2) 165 } 166 167 func Test_TrackedAction_redErrorCount(t *testing.T) { 168 t.Parallel() 169 its := assert.New(t) 170 171 lessThanRequestCount, _ := lessThanPercentage(DefaultRedRequestCount, DefaultRedRequestPercentage) 172 moreThanRequestCount, moreThanExpected := moreThanPercentage(DefaultRedRequestCount, DefaultRedRequestPercentage) 173 testCases := [...]struct { 174 RequestCount int 175 Expected float64 176 }{ 177 {lessThanRequestCount, DefaultRedRequestCount}, 178 {moreThanRequestCount, moreThanExpected}, 179 } 180 181 for _, tc := range testCases { 182 its.Equal(tc.Expected, trackedActionDefaults().redErrorCount(tc.RequestCount), fmt.Sprintf("requestCount: %d", tc.RequestCount)) 183 } 184 } 185 186 func Test_TrackedAction_yellowErrorCount(t *testing.T) { 187 t.Parallel() 188 its := assert.New(t) 189 190 lessThanRequestCount, _ := lessThanPercentage(DefaultYellowRequestCount, DefaultYellowRequestPercentage) 191 moreThanRequestCount, moreThanExpected := moreThanPercentage(DefaultYellowRequestCount, DefaultYellowRequestPercentage) 192 testCases := [...]struct { 193 RequestCount int 194 Expected float64 195 }{ 196 {lessThanRequestCount, DefaultYellowRequestCount}, 197 {moreThanRequestCount, moreThanExpected}, 198 } 199 200 for _, tc := range testCases { 201 its.Equal(tc.Expected, trackedActionDefaults().yellowErrorCount(tc.RequestCount), fmt.Sprintf("requestCount: %d", tc.RequestCount)) 202 } 203 } 204 205 func Test_TrackedAction_now(t *testing.T) { 206 t.Parallel() 207 its := assert.New(t) 208 209 now := time.Date(2021, 06, 13, 11, 50, 0, 0, time.UTC) 210 withProvider := TrackedAction{ 211 nowProvider: func() time.Time { 212 return now 213 }, 214 } 215 216 its.Equal(now, withProvider.now()) 217 its.NotEqual(now, new(TrackedAction).now()) 218 }