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  }