v0.3.1 minor refactor, tests changed to standard library

This commit is contained in:
2026-07-18 17:09:09 -04:00
parent 74434a0c75
commit 4b04334797
15 changed files with 2051 additions and 946 deletions
+114 -45
View File
@@ -2,53 +2,122 @@ package auth
import (
"encoding/base64"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHTTPAuthParsing(t *testing.T) {
// Test Basic Auth
basicHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("user:pass"))
username, password, err := ParseBasicAuth(basicHeader)
require.NoError(t, err)
assert.Equal(t, "user", username)
assert.Equal(t, "pass", password)
// Test Bearer Token
bearerHeader := "Bearer test-token-xyz"
token, err := ParseBearerToken(bearerHeader)
require.NoError(t, err)
assert.Equal(t, "test-token-xyz", token)
// Test ExtractAuthType
assert.Equal(t, "Basic", ExtractAuthType(basicHeader))
assert.Equal(t, "Bearer", ExtractAuthType(bearerHeader))
assert.Equal(t, "Custom", ExtractAuthType("Custom somedata"))
assert.Equal(t, "", ExtractAuthType("InvalidHeader"))
// Test invalid formats
_, _, err = ParseBasicAuth("Invalid header")
assert.Error(t, err)
assert.Equal(t, ErrAuthInvalidBasicFormat, err)
_, err = ParseBearerToken("Invalid header")
assert.Error(t, err)
assert.Equal(t, ErrAuthInvalidBearerFormat, err)
// Test malformed Basic auth
_, _, err = ParseBasicAuth("Basic not-base64!")
assert.Error(t, err)
assert.Equal(t, ErrAuthInvalidBasicEncoding, err)
_, _, err = ParseBasicAuth("Basic " + base64.StdEncoding.EncodeToString([]byte("no-colon")))
assert.Error(t, err)
assert.Equal(t, ErrAuthInvalidBasicCreds, err)
// Test empty Bearer token
_, err = ParseBearerToken("Bearer ")
assert.Error(t, err)
assert.Equal(t, ErrAuthEmptyBearerToken, err)
func basicHeader(payload string) string {
return "Basic " + base64.StdEncoding.EncodeToString([]byte(payload))
}
func TestParseBasicAuth(t *testing.T) {
cases := []struct {
name, header, username, password string
want error
}{
{name: "standard", header: basicHeader("user:pass"), username: "user", password: "pass"},
{name: "empty password", header: basicHeader("user:"), username: "user"},
{name: "empty username", header: basicHeader(":pass"), password: "pass"},
{name: "colon in password", header: basicHeader("user:pa:ss"), username: "user", password: "pa:ss"},
{name: "unicode", header: basicHeader("üser:pässwörd"), username: "üser", password: "pässwörd"},
{name: "separator only", header: basicHeader(":")},
{name: "missing scheme", header: "Invalid header", want: ErrAuthInvalidBasicFormat},
{name: "no space", header: "Basic", want: ErrAuthInvalidBasicFormat},
{name: "empty header", header: "", want: ErrAuthInvalidBasicFormat},
// ☢ RFC 7235 auth-scheme is case-insensitive; matching here is not
{name: "lowercase scheme", header: "basic dXNlcjpwYXNz", want: ErrAuthInvalidBasicFormat},
{name: "bad base64", header: "Basic not-base64!", want: ErrAuthInvalidBasicEncoding},
{name: "no colon", header: basicHeader("no-colon"), want: ErrAuthInvalidBasicCreds},
{name: "empty payload", header: "Basic ", want: ErrAuthInvalidBasicCreds},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
username, password, err := ParseBasicAuth(tc.header)
if tc.want != nil {
errIs(t, err, tc.want, tc.name)
eq(t, username, "", "username on failure")
eq(t, password, "", "password on failure")
return
}
noErr(t, err, tc.name)
eq(t, username, tc.username, "username")
eq(t, password, tc.password, "password")
})
}
}
func TestParseBearerToken(t *testing.T) {
cases := []struct {
name, header, token string
want error
}{
{name: "standard", header: "Bearer test-token-xyz", token: "test-token-xyz"},
{name: "embedded space", header: "Bearer a b", token: "a b"},
{name: "trailing space", header: "Bearer x ", token: "x "},
{name: "empty token", header: "Bearer ", want: ErrAuthEmptyBearerToken},
{name: "missing scheme", header: "Invalid header", want: ErrAuthInvalidBearerFormat},
{name: "lowercase scheme", header: "bearer x", want: ErrAuthInvalidBearerFormat},
{name: "no space", header: "Bearer", want: ErrAuthInvalidBearerFormat},
{name: "empty header", header: "", want: ErrAuthInvalidBearerFormat},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
token, err := ParseBearerToken(tc.header)
if tc.want != nil {
errIs(t, err, tc.want, tc.name)
eq(t, token, "", "token on failure")
return
}
noErr(t, err, tc.name)
eq(t, token, tc.token, "token")
})
}
}
func TestExtractAuthType(t *testing.T) {
cases := map[string]string{
"Basic dXNlcjpwYXNz": "Basic",
"Bearer token": "Bearer",
"Custom somedata": "Custom",
"Basic ": "Basic",
"InvalidHeader": "",
"": "",
" Bearer token": "", // cut at the first space yields an empty scheme
}
for header, want := range cases {
eq(t, ExtractAuthType(header), want, fmt.Sprintf("header %q", header))
}
}
func FuzzParseBasicAuth(f *testing.F) {
f.Add(basicHeader("user:pass"))
f.Add("Basic ")
f.Add("")
f.Add("Basic !!!")
f.Fuzz(func(t *testing.T, header string) {
username, password, err := ParseBasicAuth(header)
if err != nil {
if username != "" || password != "" {
t.Fatal("values returned alongside error")
}
return
}
encoded, ok := strings.CutPrefix(header, "Basic ")
if !ok {
t.Fatal("accepted a header without the Basic prefix")
}
decoded, decErr := base64.StdEncoding.DecodeString(encoded)
if decErr != nil {
t.Fatal("accepted an undecodable payload")
}
if string(decoded) != username+":"+password {
t.Fatalf("lossy split: %q vs %q", decoded, username+":"+password)
}
})
}