Accept both old and new cert error strings on MacOS in test assertions

Used this as an opportunity to refactor how some tests were
making assertions about error strings.

New test helpers make it easy for an error string to be expected as an
exact string, as a string built using sprintf, as a regexp, or as a
string built to include the platform-specific x509 error string.

All of these helpers can be used in a single `wantErr` field of a test
table. They can be used for both unit tests and integration tests.

Co-authored-by: Benjamin A. Petersen <ben@benjaminapetersen.me>
This commit is contained in:
Ryan Richard
2023-01-20 15:01:36 -08:00
co-authored by Benjamin A. Petersen
parent 044cbd0325
commit c6e4133c5e
9 changed files with 278 additions and 244 deletions
@@ -1,4 +1,4 @@
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package jwtcachefiller
@@ -188,7 +188,7 @@ func TestController(t *testing.T) {
syncKey controllerlib.Key
jwtAuthenticators []runtime.Object
wantClose bool
wantErr string
wantErr testutil.RequireErrorStringFunc
wantLogs []string
wantCacheEntries int
wantUsernameClaim string
@@ -350,7 +350,7 @@ func TestController(t *testing.T) {
Spec: *missingTLSJWTAuthenticatorSpec,
},
},
wantErr: `failed to build jwt authenticator: could not initialize provider: Get "` + goodIssuer + `/.well-known/openid-configuration": ` + testutil.X509UntrustedCertError("Acme Co"),
wantErr: testutil.WantX509UntrustedCertErrorString(`failed to build jwt authenticator: could not initialize provider: Get "`+goodIssuer+`/.well-known/openid-configuration": %s`, "Acme Co"),
},
{
name: "invalid jwt authenticator CA",
@@ -363,7 +363,7 @@ func TestController(t *testing.T) {
Spec: *invalidTLSJWTAuthenticatorSpec,
},
},
wantErr: "failed to build jwt authenticator: invalid TLS configuration: illegal base64 data at input byte 7",
wantErr: testutil.WantExactErrorString("failed to build jwt authenticator: invalid TLS configuration: illegal base64 data at input byte 7"),
},
}
@@ -391,8 +391,8 @@ func TestController(t *testing.T) {
syncCtx := controllerlib.Context{Context: ctx, Key: tt.syncKey}
if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != "" {
require.EqualError(t, err, tt.wantErr)
if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != nil {
testutil.RequireErrorStringFromErr(t, err, tt.wantErr)
} else {
require.NoError(t, err)
}
@@ -490,9 +490,8 @@ func TestController(t *testing.T) {
rsp, authenticated, err = cachedAuthenticator.AuthenticateToken(context.Background(), jwt)
return !isNotInitialized(err), nil
})
if test.wantErrorRegexp != "" {
require.Error(t, err)
require.Regexp(t, test.wantErrorRegexp, err.Error())
if test.wantErr != nil {
testutil.RequireErrorStringFromErr(t, err, test.wantErr)
} else {
require.NoError(t, err)
require.Equal(t, test.wantResponse, rsp)
@@ -528,7 +527,7 @@ func testTableForAuthenticateTokenTests(
jwtSignature func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string)
wantResponse *authenticator.Response
wantAuthenticated bool
wantErrorRegexp string
wantErr testutil.RequireErrorStringFunc
distributedGroupsClaimURL string
} {
tests := []struct {
@@ -537,7 +536,7 @@ func testTableForAuthenticateTokenTests(
jwtSignature func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string)
wantResponse *authenticator.Response
wantAuthenticated bool
wantErrorRegexp string
wantErr testutil.RequireErrorStringFunc
distributedGroupsClaimURL string
}{
{
@@ -594,14 +593,14 @@ func testTableForAuthenticateTokenTests(
jwtClaims: func(claims *jwt.Claims, groups *interface{}, username *string) {
},
distributedGroupsClaimURL: issuer + "/not_found_claim_source",
wantErrorRegexp: `oidc: could not expand distributed claims: while getting distributed claim "` + expectedGroupsClaim + `": error while getting distributed claim JWT: 404 Not Found`,
wantErr: testutil.WantMatchingErrorString(`oidc: could not expand distributed claims: while getting distributed claim "` + expectedGroupsClaim + `": error while getting distributed claim JWT: 404 Not Found`),
},
{
name: "distributed groups doesn't return the right claim",
jwtClaims: func(claims *jwt.Claims, groups *interface{}, username *string) {
},
distributedGroupsClaimURL: issuer + "/wrong_claim_source",
wantErrorRegexp: `oidc: could not expand distributed claims: jwt returned by distributed claim endpoint "` + issuer + `/wrong_claim_source" did not contain claim: `,
wantErr: testutil.WantMatchingErrorString(`oidc: could not expand distributed claims: jwt returned by distributed claim endpoint "` + issuer + `/wrong_claim_source" did not contain claim: `),
},
{
name: "good token with groups as string",
@@ -633,7 +632,7 @@ func testTableForAuthenticateTokenTests(
jwtClaims: func(_ *jwt.Claims, groups *interface{}, username *string) {
*groups = map[string]string{"not an array": "or a string"}
},
wantErrorRegexp: "oidc: parse groups claim \"" + expectedGroupsClaim + "\": json: cannot unmarshal object into Go value of type string",
wantErr: testutil.WantMatchingErrorString("oidc: parse groups claim \"" + expectedGroupsClaim + "\": json: cannot unmarshal object into Go value of type string"),
},
{
name: "bad token with wrong issuer",
@@ -648,42 +647,42 @@ func testTableForAuthenticateTokenTests(
jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
claims.Audience = nil
},
wantErrorRegexp: `oidc: verify token: oidc: expected audience "some-audience" got \[\]`,
wantErr: testutil.WantMatchingErrorString(`oidc: verify token: oidc: expected audience "some-audience" got \[\]`),
},
{
name: "bad token with wrong audience",
jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
claims.Audience = []string{"wrong-audience"}
},
wantErrorRegexp: `oidc: verify token: oidc: expected audience "some-audience" got \["wrong-audience"\]`,
wantErr: testutil.WantMatchingErrorString(`oidc: verify token: oidc: expected audience "some-audience" got \["wrong-audience"\]`),
},
{
name: "bad token with nbf in the future",
jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
claims.NotBefore = jwt.NewNumericDate(time.Date(3020, 2, 3, 4, 5, 6, 7, time.UTC))
},
wantErrorRegexp: `oidc: verify token: oidc: current time .* before the nbf \(not before\) time: 3020-.*`,
wantErr: testutil.WantMatchingErrorString(`oidc: verify token: oidc: current time .* before the nbf \(not before\) time: 3020-.*`),
},
{
name: "bad token with exp in past",
jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
claims.Expiry = jwt.NewNumericDate(time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC))
},
wantErrorRegexp: `oidc: verify token: oidc: token is expired \(Token Expiry: .+`,
wantErr: testutil.WantMatchingErrorString(`oidc: verify token: oidc: token is expired \(Token Expiry: .+`),
},
{
name: "bad token without exp",
jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
claims.Expiry = nil
},
wantErrorRegexp: `oidc: verify token: oidc: token is expired \(Token Expiry: .+`,
wantErr: testutil.WantMatchingErrorString(`oidc: verify token: oidc: token is expired \(Token Expiry: .+`),
},
{
name: "token does not have username claim",
jwtClaims: func(claims *jwt.Claims, _ *interface{}, username *string) {
*username = ""
},
wantErrorRegexp: `oidc: parse username claims "` + expectedUsernameClaim + `": claim not present`,
wantErr: testutil.WantMatchingErrorString(`oidc: parse username claims "` + expectedUsernameClaim + `": claim not present`),
},
{
name: "signing key is wrong",
@@ -693,7 +692,7 @@ func testTableForAuthenticateTokenTests(
require.NoError(t, err)
*algo = jose.ES256
},
wantErrorRegexp: `oidc: verify token: failed to verify signature: failed to verify id token signature`,
wantErr: testutil.WantMatchingErrorString(`oidc: verify token: failed to verify signature: failed to verify id token signature`),
},
{
name: "signing algo is unsupported",
@@ -703,7 +702,7 @@ func testTableForAuthenticateTokenTests(
require.NoError(t, err)
*algo = jose.ES384
},
wantErrorRegexp: `oidc: verify token: oidc: id token signed with unsupported algorithm, expected \["RS256" "ES256"\] got "ES384"`,
wantErr: testutil.WantMatchingErrorString(`oidc: verify token: oidc: id token signed with unsupported algorithm, expected \["RS256" "ES256"\] got "ES384"`),
},
}
+62 -1
View File
@@ -1,10 +1,11 @@
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package testutil
import (
"context"
"fmt"
"mime"
"net/http/httptest"
"testing"
@@ -125,3 +126,63 @@ func requireSecurityHeaders(t *testing.T, response *httptest.ResponseRecorder) {
// This check is more relaxed since Fosite can override the base header we set.
require.Contains(t, response.Header().Get("Cache-Control"), "no-store")
}
type RequireErrorStringFunc func(t *testing.T, actualErrorStr string)
// RequireErrorStringFromErr can be used to make assertions about errors in tests.
func RequireErrorStringFromErr(t *testing.T, actualError error, requireFunc RequireErrorStringFunc) {
require.Error(t, actualError)
requireFunc(t, actualError.Error())
}
// RequireErrorString can be used to make assertions about error strings in tests.
func RequireErrorString(t *testing.T, actualErrorStr string, requireFunc RequireErrorStringFunc) {
requireFunc(t, actualErrorStr)
}
// WantExactErrorString can be used to set up an expected value for an error string in a test table.
// Use when you want to express that the expected string must be an exact match.
func WantExactErrorString(wantErrStr string) RequireErrorStringFunc {
return func(t *testing.T, actualErrorStr string) {
require.Equal(t, wantErrStr, actualErrorStr)
}
}
// WantSprintfErrorString can be used to set up an expected value for an error string in a test table.
// Use when you want to express that an expected string built using fmt.Sprintf semantics must be an exact match.
func WantSprintfErrorString(wantErrSprintfSpecifier string, a ...interface{}) RequireErrorStringFunc {
wantErrStr := fmt.Sprintf(wantErrSprintfSpecifier, a...)
return func(t *testing.T, actualErrorStr string) {
require.Equal(t, wantErrStr, actualErrorStr)
}
}
// WantMatchingErrorString can be used to set up an expected value for an error string in a test table.
// Use when you want to express that the expected regexp must be a match.
func WantMatchingErrorString(wantErrRegexp string) RequireErrorStringFunc {
return func(t *testing.T, actualErrorStr string) {
require.Regexp(t, wantErrRegexp, actualErrorStr)
}
}
// WantX509UntrustedCertErrorString can be used to set up an expected value for an error string in a test table.
// expectedErrorFormatString must contain exactly one formatting verb, which should usually be %s, which will
// be replaced by the platform-specific X509 untrusted certs error string and then compared against expectedCommonName.
func WantX509UntrustedCertErrorString(expectedErrorFormatSpecifier string, expectedCommonName string) RequireErrorStringFunc {
// Starting in Go 1.18.1, and until it was fixed in Go 1.19.5, Go on MacOS had an incorrect error string.
// We don't care which error string was returned, as long as it is either the normal error string from
// the Go x509 library, or the error string that was accidentally returned from the Go x509 library in
// those versions of Go on MacOS which had the bug.
return func(t *testing.T, actualErrorStr string) {
// This is the MacOS error string starting in Go 1.18.1, and until it was fixed in Go 1.19.5.
macOSErr := fmt.Sprintf(`x509: “%s” certificate is not trusted`, expectedCommonName)
// This is the normal Go x509 library error string.
standardErr := `x509: certificate signed by unknown authority`
allowedErrorStrings := []string{
fmt.Sprintf(expectedErrorFormatSpecifier, macOSErr),
fmt.Sprintf(expectedErrorFormatSpecifier, standardErr),
}
// Allow either.
require.Contains(t, allowedErrorStrings, actualErrorStr)
}
}
-19
View File
@@ -1,19 +0,0 @@
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package testutil
import (
"fmt"
"runtime"
)
func X509UntrustedCertError(commonName string) string {
if runtime.GOOS == "darwin" {
// Golang use's macos' x509 verification APIs on darwin.
// This output slightly different error messages than golang's
// own x509 verification.
return fmt.Sprintf(`x509: “%s” certificate is not trusted`, commonName)
}
return `x509: certificate signed by unknown authority`
}
+44 -44
View File
@@ -1,4 +1,4 @@
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package upstreamldap
@@ -179,7 +179,7 @@ func TestEndUserAuthentication(t *testing.T) {
searchMocks func(conn *mockldapconn.MockConn)
bindEndUserMocks func(conn *mockldapconn.MockConn)
dialError error
wantError string
wantError testutil.RequireErrorStringFunc
wantToSkipDial bool
wantAuthResponse *authenticators.Response
wantUnauthenticated bool
@@ -711,7 +711,7 @@ func TestEndUserAuthentication(t *testing.T) {
Return(exampleGroupSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: "found 0 values for attribute \"some-attribute-to-check-during-refresh\" while searching for user \"some-upstream-username\", but expected 1 result",
wantError: testutil.WantExactErrorString("found 0 values for attribute \"some-attribute-to-check-during-refresh\" while searching for user \"some-upstream-username\", but expected 1 result"),
},
{
name: "when dial fails",
@@ -719,7 +719,7 @@ func TestEndUserAuthentication(t *testing.T) {
password: testUpstreamPassword,
providerConfig: providerConfig(nil),
dialError: errors.New("some dial error"),
wantError: fmt.Sprintf(`error dialing host "%s": some dial error`, testHost),
wantError: testutil.WantSprintfErrorString(`error dialing host "%s": some dial error`, testHost),
},
{
name: "when the UsernameAttribute is dn and there is not a user search filter provided",
@@ -730,7 +730,7 @@ func TestEndUserAuthentication(t *testing.T) {
p.UserSearch.Filter = ""
}),
wantToSkipDial: true,
wantError: `must specify UserSearch Filter when UserSearch UsernameAttribute is "dn"`,
wantError: testutil.WantExactErrorString(`must specify UserSearch Filter when UserSearch UsernameAttribute is "dn"`),
},
{
name: "when binding as the bind user returns an error",
@@ -741,7 +741,7 @@ func TestEndUserAuthentication(t *testing.T) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Return(errors.New("some bind error")).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(`error binding as "%s" before user search: some bind error`, testBindUsername),
wantError: testutil.WantSprintfErrorString(`error binding as "%s" before user search: some bind error`, testBindUsername),
},
{
name: "when searching for the user returns an error",
@@ -753,7 +753,7 @@ func TestEndUserAuthentication(t *testing.T) {
conn.EXPECT().Search(expectedUserSearch(nil)).Return(nil, errors.New("some user search error")).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: `error searching for user: some user search error`,
wantError: testutil.WantExactErrorString(`error searching for user: some user search error`),
},
{
name: "when searching for the user's groups returns an error",
@@ -767,7 +767,7 @@ func TestEndUserAuthentication(t *testing.T) {
Return(nil, errors.New("some group search error")).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(`error searching for group memberships for user with DN "%s": some group search error`, testUserSearchResultDNValue),
wantError: testutil.WantSprintfErrorString(`error searching for group memberships for user with DN "%s": some group search error`, testUserSearchResultDNValue),
},
{
name: "when searching for the user returns no results",
@@ -798,7 +798,7 @@ func TestEndUserAuthentication(t *testing.T) {
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(`searching for user "%s" resulted in 2 search results, but expected 1 result`, testUpstreamUsername),
wantError: testutil.WantSprintfErrorString(`searching for user "%s" resulted in 2 search results, but expected 1 result`, testUpstreamUsername),
},
{
name: "when searching for the user returns a user without a DN",
@@ -814,7 +814,7 @@ func TestEndUserAuthentication(t *testing.T) {
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(`searching for user "%s" resulted in search result without DN`, testUpstreamUsername),
wantError: testutil.WantSprintfErrorString(`searching for user "%s" resulted in search result without DN`, testUpstreamUsername),
},
{
name: "when searching for the user's groups returns a group without a DN",
@@ -845,7 +845,7 @@ func TestEndUserAuthentication(t *testing.T) {
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(
wantError: testutil.WantSprintfErrorString(
`searching for group memberships for user with DN "%s" resulted in search result without DN`,
testUserSearchResultDNValue),
},
@@ -868,7 +868,7 @@ func TestEndUserAuthentication(t *testing.T) {
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(
wantError: testutil.WantSprintfErrorString(
`found 0 values for attribute "%s" while searching for user "%s", but expected 1 result`,
testUserSearchUsernameAttribute, testUpstreamUsername),
},
@@ -901,7 +901,7 @@ func TestEndUserAuthentication(t *testing.T) {
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(
wantError: testutil.WantSprintfErrorString(
`error searching for group memberships for user with DN "%s": found 0 values for attribute "%s" while searching for user "%s", but expected 1 result`,
testUserSearchResultDNValue, testGroupSearchGroupNameAttribute, testUserSearchResultDNValue),
},
@@ -928,7 +928,7 @@ func TestEndUserAuthentication(t *testing.T) {
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(
wantError: testutil.WantSprintfErrorString(
`found 2 values for attribute "%s" while searching for user "%s", but expected 1 result`,
testUserSearchUsernameAttribute, testUpstreamUsername),
},
@@ -964,7 +964,7 @@ func TestEndUserAuthentication(t *testing.T) {
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(
wantError: testutil.WantSprintfErrorString(
`error searching for group memberships for user with DN "%s": found 2 values for attribute "%s" while searching for user "%s", but expected 1 result`,
testUserSearchResultDNValue, testGroupSearchGroupNameAttribute, testUserSearchResultDNValue),
},
@@ -988,7 +988,7 @@ func TestEndUserAuthentication(t *testing.T) {
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(
wantError: testutil.WantSprintfErrorString(
`found empty value for attribute "%s" while searching for user "%s", but expected value to be non-empty`,
testUserSearchUsernameAttribute, testUpstreamUsername),
},
@@ -1021,7 +1021,7 @@ func TestEndUserAuthentication(t *testing.T) {
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(
wantError: testutil.WantSprintfErrorString(
`error searching for group memberships for user with DN "%s": found empty value for attribute "%s" while searching for user "%s", but expected value to be non-empty`,
testUserSearchResultDNValue, testGroupSearchGroupNameAttribute, testUserSearchResultDNValue),
},
@@ -1044,7 +1044,7 @@ func TestEndUserAuthentication(t *testing.T) {
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(`found 0 values for attribute "%s" while searching for user "%s", but expected 1 result`, testUserSearchUIDAttribute, testUpstreamUsername),
wantError: testutil.WantSprintfErrorString(`found 0 values for attribute "%s" while searching for user "%s", but expected 1 result`, testUserSearchUIDAttribute, testUpstreamUsername),
},
{
name: "when searching for the user returns a user with too many values for the expected UID attribute",
@@ -1069,7 +1069,7 @@ func TestEndUserAuthentication(t *testing.T) {
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(`found 2 values for attribute "%s" while searching for user "%s", but expected 1 result`, testUserSearchUIDAttribute, testUpstreamUsername),
wantError: testutil.WantSprintfErrorString(`found 2 values for attribute "%s" while searching for user "%s", but expected 1 result`, testUserSearchUIDAttribute, testUpstreamUsername),
},
{
name: "when searching for the user returns a user with an empty value for the expected UID attribute",
@@ -1091,7 +1091,7 @@ func TestEndUserAuthentication(t *testing.T) {
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(`found empty value for attribute "%s" while searching for user "%s", but expected value to be non-empty`, testUserSearchUIDAttribute, testUpstreamUsername),
wantError: testutil.WantSprintfErrorString(`found empty value for attribute "%s" while searching for user "%s", but expected value to be non-empty`, testUserSearchUIDAttribute, testUpstreamUsername),
},
{
name: "when the group search has an override func that errors",
@@ -1109,7 +1109,7 @@ func TestEndUserAuthentication(t *testing.T) {
Return(exampleGroupSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf("error finding groups for user %s: some error", testUserSearchResultDNValue),
wantError: testutil.WantSprintfErrorString("error finding groups for user %s: some error", testUserSearchResultDNValue),
},
{
name: "when binding as the found user returns an error",
@@ -1127,7 +1127,7 @@ func TestEndUserAuthentication(t *testing.T) {
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Return(errors.New("some bind error")).Times(1)
},
skipDryRunAuthenticateUser: true,
wantError: fmt.Sprintf(`error binding for user "%s" using provided password against DN "%s": some bind error`, testUpstreamUsername, testUserSearchResultDNValue),
wantError: testutil.WantSprintfErrorString(`error binding for user "%s" using provided password against DN "%s": some bind error`, testUpstreamUsername, testUserSearchResultDNValue),
},
{
name: "when binding as the found user returns a specific invalid credentials error",
@@ -1194,8 +1194,8 @@ func TestEndUserAuthentication(t *testing.T) {
authResponse, authenticated, err := ldapProvider.AuthenticateUser(context.Background(), tt.username, tt.password, tt.grantedScopes)
require.Equal(t, !tt.wantToSkipDial, dialWasAttempted)
switch {
case tt.wantError != "":
require.EqualError(t, err, tt.wantError)
case tt.wantError != nil:
testutil.RequireErrorStringFromErr(t, err, tt.wantError)
require.False(t, authenticated)
require.Nil(t, authResponse)
case tt.wantUnauthenticated:
@@ -1226,8 +1226,8 @@ func TestEndUserAuthentication(t *testing.T) {
authResponse, authenticated, err = ldapProvider.DryRunAuthenticateUser(context.Background(), tt.username, tt.grantedScopes)
require.Equal(t, !tt.wantToSkipDial, dialWasAttempted)
switch {
case tt.wantError != "":
require.EqualError(t, err, tt.wantError)
case tt.wantError != nil:
testutil.RequireErrorStringFromErr(t, err, tt.wantError)
require.False(t, authenticated)
require.Nil(t, authResponse)
case tt.wantUnauthenticated:
@@ -1852,7 +1852,7 @@ func TestTestConnection(t *testing.T) {
providerConfig *ProviderConfig
setupMocks func(conn *mockldapconn.MockConn)
dialError error
wantError string
wantError testutil.RequireErrorStringFunc
wantToSkipDial bool
}{
{
@@ -1867,7 +1867,7 @@ func TestTestConnection(t *testing.T) {
name: "when dial fails",
providerConfig: providerConfig(nil),
dialError: errors.New("some dial error"),
wantError: fmt.Sprintf(`error dialing host "%s": some dial error`, testHost),
wantError: testutil.WantSprintfErrorString(`error dialing host "%s": some dial error`, testHost),
},
{
name: "when binding as the bind user returns an error",
@@ -1876,7 +1876,7 @@ func TestTestConnection(t *testing.T) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Return(errors.New("some bind error")).Times(1)
conn.EXPECT().Close().Times(1)
},
wantError: fmt.Sprintf(`error binding as "%s": some bind error`, testBindUsername),
wantError: testutil.WantSprintfErrorString(`error binding as "%s": some bind error`, testBindUsername),
},
{
name: "when the config is invalid",
@@ -1886,7 +1886,7 @@ func TestTestConnection(t *testing.T) {
p.UserSearch.Filter = ""
}),
wantToSkipDial: true,
wantError: `must specify UserSearch Filter when UserSearch UsernameAttribute is "dn"`,
wantError: testutil.WantExactErrorString(`must specify UserSearch Filter when UserSearch UsernameAttribute is "dn"`),
},
}
@@ -1917,8 +1917,8 @@ func TestTestConnection(t *testing.T) {
require.Equal(t, !tt.wantToSkipDial, dialWasAttempted)
switch {
case tt.wantError != "":
require.EqualError(t, err, tt.wantError)
case tt.wantError != nil:
testutil.RequireErrorStringFromErr(t, err, tt.wantError)
default:
require.NoError(t, err)
}
@@ -2010,7 +2010,7 @@ func TestRealTLSDialing(t *testing.T) {
connProto LDAPConnectionProtocol
caBundle []byte
context context.Context
wantError string
wantError testutil.RequireErrorStringFunc
}{
{
name: "happy path",
@@ -2025,7 +2025,7 @@ func TestRealTLSDialing(t *testing.T) {
caBundle: caForTestServerWithBadCertName.Bundle(),
connProto: TLS,
context: context.Background(),
wantError: `LDAP Result Code 200 "Network Error": x509: certificate is valid for 10.2.3.4, not 127.0.0.1`,
wantError: testutil.WantExactErrorString(`LDAP Result Code 200 "Network Error": x509: certificate is valid for 10.2.3.4, not 127.0.0.1`),
},
{
name: "invalid CA bundle with TLS",
@@ -2033,7 +2033,7 @@ func TestRealTLSDialing(t *testing.T) {
caBundle: []byte("not a ca bundle"),
connProto: TLS,
context: context.Background(),
wantError: `LDAP Result Code 200 "Network Error": could not parse CA bundle`,
wantError: testutil.WantExactErrorString(`LDAP Result Code 200 "Network Error": could not parse CA bundle`),
},
{
name: "invalid CA bundle with StartTLS",
@@ -2041,7 +2041,7 @@ func TestRealTLSDialing(t *testing.T) {
caBundle: []byte("not a ca bundle"),
connProto: StartTLS,
context: context.Background(),
wantError: `LDAP Result Code 200 "Network Error": could not parse CA bundle`,
wantError: testutil.WantExactErrorString(`LDAP Result Code 200 "Network Error": could not parse CA bundle`),
},
{
name: "invalid host with TLS",
@@ -2049,7 +2049,7 @@ func TestRealTLSDialing(t *testing.T) {
caBundle: testServerCABundle,
connProto: TLS,
context: context.Background(),
wantError: `LDAP Result Code 200 "Network Error": host "this:is:not:a:valid:hostname" is not a valid hostname or IP address`,
wantError: testutil.WantExactErrorString(`LDAP Result Code 200 "Network Error": host "this:is:not:a:valid:hostname" is not a valid hostname or IP address`),
},
{
name: "invalid host with StartTLS",
@@ -2057,7 +2057,7 @@ func TestRealTLSDialing(t *testing.T) {
caBundle: testServerCABundle,
connProto: StartTLS,
context: context.Background(),
wantError: `LDAP Result Code 200 "Network Error": host "this:is:not:a:valid:hostname" is not a valid hostname or IP address`,
wantError: testutil.WantExactErrorString(`LDAP Result Code 200 "Network Error": host "this:is:not:a:valid:hostname" is not a valid hostname or IP address`),
},
{
name: "missing CA bundle when it is required because the host is not using a trusted CA",
@@ -2065,7 +2065,7 @@ func TestRealTLSDialing(t *testing.T) {
caBundle: nil,
connProto: TLS,
context: context.Background(),
wantError: fmt.Sprintf(`LDAP Result Code 200 "Network Error": %s`, testutil.X509UntrustedCertError("Acme Co")),
wantError: testutil.WantX509UntrustedCertErrorString(`LDAP Result Code 200 "Network Error": %s`, "Acme Co"),
},
{
name: "cannot connect to host",
@@ -2074,7 +2074,7 @@ func TestRealTLSDialing(t *testing.T) {
caBundle: testServerCABundle,
connProto: TLS,
context: context.Background(),
wantError: fmt.Sprintf(`LDAP Result Code 200 "Network Error": dial tcp %s: connect: connection refused`, recentlyClaimedHostAndPort),
wantError: testutil.WantSprintfErrorString(`LDAP Result Code 200 "Network Error": dial tcp %s: connect: connection refused`, recentlyClaimedHostAndPort),
},
{
name: "pays attention to the passed context",
@@ -2082,7 +2082,7 @@ func TestRealTLSDialing(t *testing.T) {
caBundle: testServerCABundle,
connProto: TLS,
context: alreadyCancelledContext,
wantError: fmt.Sprintf(`LDAP Result Code 200 "Network Error": dial tcp %s: operation was canceled`, testServerHostAndPort),
wantError: testutil.WantSprintfErrorString(`LDAP Result Code 200 "Network Error": dial tcp %s: operation was canceled`, testServerHostAndPort),
},
{
name: "unsupported connection protocol",
@@ -2090,7 +2090,7 @@ func TestRealTLSDialing(t *testing.T) {
caBundle: testServerCABundle,
connProto: "bad usage of this type",
context: alreadyCancelledContext,
wantError: `LDAP Result Code 200 "Network Error": did not specify valid ConnectionProtocol`,
wantError: testutil.WantExactErrorString(`LDAP Result Code 200 "Network Error": did not specify valid ConnectionProtocol`),
},
}
for _, test := range tests {
@@ -2106,9 +2106,9 @@ func TestRealTLSDialing(t *testing.T) {
if conn != nil {
defer conn.Close()
}
if tt.wantError != "" {
if tt.wantError != nil {
require.Nil(t, conn)
require.EqualError(t, err, tt.wantError)
testutil.RequireErrorStringFromErr(t, err, tt.wantError)
} else {
require.NoError(t, err)
require.NotNil(t, conn)
+15 -21
View File
@@ -487,9 +487,8 @@ func TestProviderConfig(t *testing.T) {
unreachableServer bool
returnStatusCodes []int
returnErrBodies []string
wantErr string
wantErrRegexp string // use either wantErr or wantErrRegexp
wantRetryableErrType bool // additionally assert error type when wantErr is non-empty
wantErr testutil.RequireErrorStringFunc
wantRetryableErrType bool // additionally assert error type when wantErr is non-empty
wantNumRequests int
wantTokenTypeHint string
}{
@@ -542,7 +541,7 @@ func TestProviderConfig(t *testing.T) {
tokenType: provider.RefreshTokenType,
returnStatusCodes: []int{http.StatusBadRequest, http.StatusBadRequest},
returnErrBodies: []string{`{ "error":"invalid_client", "error_description":"unhappy" }`, `{ "error":"anything", "error_description":"unhappy" }`},
wantErr: `server responded with status 400 with body: { "error":"anything", "error_description":"unhappy" }`,
wantErr: testutil.WantExactErrorString(`server responded with status 400 with body: { "error":"anything", "error_description":"unhappy" }`),
wantRetryableErrType: false,
wantNumRequests: 2,
wantTokenTypeHint: "refresh_token",
@@ -552,7 +551,7 @@ func TestProviderConfig(t *testing.T) {
tokenType: provider.RefreshTokenType,
returnStatusCodes: []int{http.StatusBadRequest},
returnErrBodies: []string{`invalid JSON body`},
wantErr: `error parsing response body "invalid JSON body" on response with status code 400: invalid character 'i' looking for beginning of value`,
wantErr: testutil.WantExactErrorString(`error parsing response body "invalid JSON body" on response with status code 400: invalid character 'i' looking for beginning of value`),
wantRetryableErrType: false,
wantNumRequests: 1,
wantTokenTypeHint: "refresh_token",
@@ -562,7 +561,7 @@ func TestProviderConfig(t *testing.T) {
tokenType: provider.RefreshTokenType,
returnStatusCodes: []int{http.StatusBadRequest},
returnErrBodies: []string{``},
wantErr: `error parsing response body "" on response with status code 400: unexpected end of JSON input`,
wantErr: testutil.WantExactErrorString(`error parsing response body "" on response with status code 400: unexpected end of JSON input`),
wantRetryableErrType: false,
wantNumRequests: 1,
wantTokenTypeHint: "refresh_token",
@@ -572,7 +571,7 @@ func TestProviderConfig(t *testing.T) {
tokenType: provider.RefreshTokenType,
returnStatusCodes: []int{http.StatusBadRequest, http.StatusForbidden},
returnErrBodies: []string{`{ "error":"invalid_client", "error_description":"unhappy" }`, ""},
wantErr: "server responded with status 403",
wantErr: testutil.WantExactErrorString("server responded with status 403"),
wantRetryableErrType: false,
wantNumRequests: 2,
wantTokenTypeHint: "refresh_token",
@@ -582,7 +581,7 @@ func TestProviderConfig(t *testing.T) {
tokenType: provider.RefreshTokenType,
returnStatusCodes: []int{http.StatusBadRequest},
returnErrBodies: []string{`{ "error":"anything_else", "error_description":"unhappy" }`},
wantErr: `server responded with status 400 with body: { "error":"anything_else", "error_description":"unhappy" }`,
wantErr: testutil.WantExactErrorString(`server responded with status 400 with body: { "error":"anything_else", "error_description":"unhappy" }`),
wantRetryableErrType: false,
wantNumRequests: 1,
wantTokenTypeHint: "refresh_token",
@@ -592,7 +591,7 @@ func TestProviderConfig(t *testing.T) {
tokenType: provider.RefreshTokenType,
returnStatusCodes: []int{http.StatusForbidden},
returnErrBodies: []string{""},
wantErr: "server responded with status 403",
wantErr: testutil.WantExactErrorString("server responded with status 403"),
wantRetryableErrType: false,
wantNumRequests: 1,
wantTokenTypeHint: "refresh_token",
@@ -602,7 +601,7 @@ func TestProviderConfig(t *testing.T) {
tokenType: provider.RefreshTokenType,
returnStatusCodes: []int{http.StatusServiceUnavailable}, // 503
returnErrBodies: []string{""},
wantErr: "retryable revocation error: server responded with status 503",
wantErr: testutil.WantExactErrorString("retryable revocation error: server responded with status 503"),
wantRetryableErrType: true,
wantNumRequests: 1,
wantTokenTypeHint: "refresh_token",
@@ -612,7 +611,7 @@ func TestProviderConfig(t *testing.T) {
tokenType: provider.AccessTokenType,
returnStatusCodes: []int{http.StatusBadRequest, http.StatusServiceUnavailable}, // 400, 503
returnErrBodies: []string{`{ "error":"invalid_client", "error_description":"unhappy" }`, ""},
wantErr: "retryable revocation error: server responded with status 503",
wantErr: testutil.WantExactErrorString("retryable revocation error: server responded with status 503"),
wantRetryableErrType: true,
wantNumRequests: 2,
wantTokenTypeHint: "access_token",
@@ -622,7 +621,7 @@ func TestProviderConfig(t *testing.T) {
tokenType: provider.RefreshTokenType,
returnStatusCodes: []int{http.StatusInternalServerError}, // 500
returnErrBodies: []string{""},
wantErr: "retryable revocation error: server responded with status 500",
wantErr: testutil.WantExactErrorString("retryable revocation error: server responded with status 500"),
wantRetryableErrType: true,
wantNumRequests: 1,
wantTokenTypeHint: "refresh_token",
@@ -632,7 +631,7 @@ func TestProviderConfig(t *testing.T) {
tokenType: provider.RefreshTokenType,
returnStatusCodes: []int{599}, // not defined by an RFC, but sometimes considered Network Connect Timeout Error
returnErrBodies: []string{""},
wantErr: "retryable revocation error: server responded with status 599",
wantErr: testutil.WantExactErrorString("retryable revocation error: server responded with status 599"),
wantRetryableErrType: true,
wantNumRequests: 1,
wantTokenTypeHint: "refresh_token",
@@ -641,7 +640,7 @@ func TestProviderConfig(t *testing.T) {
name: "retryable error when the server cannot be reached",
tokenType: provider.AccessTokenType,
unreachableServer: true,
wantErrRegexp: "^retryable revocation error: Post .*: dial tcp .*: connect: connection refused$",
wantErr: testutil.WantMatchingErrorString("^retryable revocation error: Post .*: dial tcp .*: connect: connection refused$"),
wantRetryableErrType: true,
wantNumRequests: 0,
},
@@ -709,13 +708,8 @@ func TestProviderConfig(t *testing.T) {
require.Equal(t, tt.wantNumRequests, numRequests,
"did not make expected number of requests to revocation endpoint")
if tt.wantErr != "" || tt.wantErrRegexp != "" { //nolint:nestif
if tt.wantErr != "" {
require.EqualError(t, err, tt.wantErr)
} else {
require.Error(t, err)
require.Regexp(t, tt.wantErrRegexp, err.Error())
}
if tt.wantErr != nil {
testutil.RequireErrorStringFromErr(t, err, tt.wantErr)
if tt.wantRetryableErrType {
require.ErrorAs(t, err, &provider.RetryableRevocationError{})