Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions internal/pipeline/live_dogfood.go
Original file line number Diff line number Diff line change
Expand Up @@ -1982,7 +1982,7 @@ func validLiveDogfoodJSONOutput(stdout string) bool {
func liveDogfoodUnavailableForRunner(run liveDogfoodRun) bool {
output := strings.ToLower(run.stdout + run.stderr)
return strings.Contains(output, "http 403") ||
liveDogfoodAuth401Output(output) ||
liveDogfoodAuth401(run) ||
strings.Contains(output, "permission denied") ||
strings.Contains(output, "your credentials are valid but lack access")
}
Expand Down Expand Up @@ -2041,19 +2041,39 @@ func liveDogfoodRequiresTierSkipReason(annotations map[string]string, activeTier
return fmt.Sprintf("blocked-fixture: requires auth tier %q", requiredTier)
}

// liveDogfoodAuthExitCode is the typed exit code a printed CLI returns from
// authErr, so it is authoritative about the failure class regardless of how the
// vendor worded the 401 body.
const liveDogfoodAuthExitCode = 4

func liveDogfoodAuth401(run liveDogfoodRun) bool {
return liveDogfoodAuth401Output(strings.ToLower(run.stdout + run.stderr))
output := strings.ToLower(run.stdout + run.stderr)
if run.exitCode == liveDogfoodAuthExitCode && strings.Contains(output, "http 401") {
return true
}
return liveDogfoodAuth401Output(output)
}

func liveDogfoodAuth401Output(output string) bool {
if !strings.Contains(output, "http 401") {
return false
}
return strings.Contains(output, "couldn't authenticate") ||
strings.Contains(output, "could not authenticate") ||
strings.Contains(output, "login required") ||
strings.Contains(output, "request is missing required authentication credential") ||
strings.Contains(output, "not authenticated")
return containsAnyOf(output, liveDogfoodAuth401Phrases)
}

// Vendor 401 bodies are unstandardized; each entry is a lowercase substring
// observed in a real provider's unauthenticated response.
var liveDogfoodAuth401Phrases = []string{
"couldn't authenticate",
"could not authenticate",
"login required",
"request is missing required authentication credential",
"not authenticated",
"invalid access token",
"invalid token",
"expired token",
"token expired",
"unauthorized",
}

func commandSupportsDryRun(help string) bool {
Expand Down
68 changes: 68 additions & 0 deletions internal/pipeline/live_dogfood_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2337,6 +2337,7 @@ func TestLiveDogfoodUnavailableForRunnerDoesNotHideNotFound(t *testing.T) {
assert.True(t, liveDogfoodUnavailableForRunner(liveDogfoodRun{stderr: "HTTP 403 permission denied"}))
assert.True(t, liveDogfoodUnavailableForRunner(liveDogfoodRun{stderr: "your credentials are valid but lack access"}))
assert.True(t, liveDogfoodUnavailableForRunner(liveDogfoodRun{stderr: `HTTP 401: {"error":"Couldn't authenticate you"}`}))
assert.True(t, liveDogfoodUnavailableForRunner(liveDogfoodRun{stderr: `HTTP 401: {"code":124,"message":"Invalid access token."}`}))
assert.False(t, liveDogfoodUnavailableForRunner(liveDogfoodRun{stderr: "HTTP 404 NotFound"}))
}

Expand All @@ -2362,11 +2363,26 @@ func TestLiveDogfoodAuth401OutputMatchesGooglePhrases(t *testing.T) {
}`,
want: true,
},
{
name: "invalid access token",
output: `Error: GET /users/me returned HTTP 401: {"code":124,"message":"Invalid access token."}`,
want: true,
},
{
name: "bare unauthorized",
output: `Error: GET /users/me returned HTTP 401: {"error":"Unauthorized"}`,
want: true,
},
{
name: "non 401",
output: `Error: GET /widgets returned HTTP 404: {"error":"not found"}`,
want: false,
},
{
name: "404 with auth-ish wording",
output: `Error: GET /widgets returned HTTP 404: {"error":"invalid access token"}`,
want: false,
},
}

for _, tt := range cases {
Expand All @@ -2378,6 +2394,58 @@ func TestLiveDogfoodAuth401OutputMatchesGooglePhrases(t *testing.T) {
}
}

func TestLiveDogfoodAuth401TypedExitCode(t *testing.T) {
t.Parallel()

cases := []struct {
name string
run liveDogfoodRun
want bool
}{
{
name: "typed auth exit with unrecognized vendor wording",
run: liveDogfoodRun{
exitCode: liveDogfoodAuthExitCode,
stderr: `Error: GET /users/me returned HTTP 401: {"code":9999,"message":"Nope."}`,
},
want: true,
},
{
name: "typed auth exit without a 401",
run: liveDogfoodRun{
exitCode: liveDogfoodAuthExitCode,
stderr: `Error: no credentials configured`,
},
want: false,
},
{
name: "unrecognized wording on a generic failure exit",
run: liveDogfoodRun{
exitCode: 1,
stderr: `Error: GET /users/me returned HTTP 401: {"code":9999,"message":"Nope."}`,
},
want: false,
},
{
name: "recognized wording on a generic failure exit",
run: liveDogfoodRun{
exitCode: 1,
stderr: `Error: GET /users/me returned HTTP 401: {"code":124,"message":"Invalid access token."}`,
},
want: true,
},
}

for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

assert.Equal(t, tt.want, liveDogfoodAuth401(tt.run))
assert.Equal(t, tt.want, liveDogfoodUnavailableForRunner(tt.run))
})
}
}

func TestLiveDogfoodRequiredParamFixtureReason(t *testing.T) {
t.Parallel()

Expand Down
Loading