diff --git a/internal/pkg/platform/activity.go b/internal/pkg/platform/activity.go index 582acc32..655b3f49 100644 --- a/internal/pkg/platform/activity.go +++ b/internal/pkg/platform/activity.go @@ -108,9 +108,13 @@ func Activity( // Try to grab new logs using the last logs timestamp activityRequest.MinimumDateCreated = latestCreatedTimestamp + 1 - // Avoid exit on error from the activity API when tailing logs + // Avoid exit on error from the activity API when tailing logs, unless + // the token can no longer be used and every later request would fail newLatestCreatedTimestamp, count, err := printLatestActivity(ctx, clients, token, activityRequest, token) if err != nil { + if isAuthError(err) { + return err + } clients.IO.PrintDebug(ctx, "%s\n", err) } @@ -132,6 +136,18 @@ func Activity( } } +// isAuthError returns true if the error means the token is no longer valid +func isAuthError(err error) bool { + switch slackerror.ToSlackError(err).Code { + case slackerror.ErrInvalidAuth, + slackerror.ErrNotAuthed, + slackerror.ErrTokenExpired, + slackerror.ErrTokenRevoked: + return true + } + return false +} + func printLatestActivity(ctx context.Context, clients *shared.ClientFactory, token string, args types.ActivityRequest, xoxpToken string) (latestCreated int64, num int, e error) { var span opentracing.Span span, ctx = opentracing.StartSpanFromContext(ctx, "getLatestActivity") diff --git a/internal/pkg/platform/activity_test.go b/internal/pkg/platform/activity_test.go index e8542309..63fdfe67 100644 --- a/internal/pkg/platform/activity_test.go +++ b/internal/pkg/platform/activity_test.go @@ -246,6 +246,27 @@ func TestPlatformActivity_StreamingLogs(t *testing.T) { cm.API.AssertNumberOfCalls(t, "Activity", 1) }, }, + "should return error if TailArg is set and activity request fails with an auth error while polling": { + Args: types.ActivityArgs{ + TailArg: true, + IdleTimeoutM: 1, + PollingIntervalMS: 20, // poll activity every 20 ms + }, + Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) context.Context { + cm.API.On("Activity", mock.Anything, mock.Anything, mock.Anything).Return(api.ActivityResult{}, slackerror.New(slackerror.ErrTokenRevoked)) + ctx, cancel := context.WithCancel(ctx) + go func() { + time.Sleep(time.Millisecond * 50) // cancel activity in 50 ms + cancel() + }() + return ctx + }, + ExpectedError: slackerror.New(slackerror.ErrTokenRevoked), + ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { + // the initial request and the first polling request, then polling stops + cm.API.AssertNumberOfCalls(t, "Activity", 2) + }, + }, "should return nil if TailArg is set and activity request fails while polling": { Args: types.ActivityArgs{ TailArg: true, @@ -909,6 +930,25 @@ func Test_functionExecutionStartedToString(t *testing.T) { assert.Contains(t, result, "Trace=trace1") } +func Test_isAuthError(t *testing.T) { + tests := map[string]struct { + err error + expected bool + }{ + "invalid auth": {err: slackerror.New(slackerror.ErrInvalidAuth), expected: true}, + "not authed": {err: slackerror.New(slackerror.ErrNotAuthed), expected: true}, + "token expired": {err: slackerror.New(slackerror.ErrTokenExpired), expected: true}, + "token revoked": {err: slackerror.New(slackerror.ErrTokenRevoked), expected: true}, + "internal error": {err: slackerror.New(slackerror.ErrInternal), expected: false}, + "unknown error": {err: slackerror.New("mock_broken_logs"), expected: false}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.expected, isAuthError(tc.err)) + }) + } +} + func Test_triggerPayloadReceivedOutputToString(t *testing.T) { activity := api.Activity{ Level: types.INFO,