134 строки
4.5 KiB
Go
134 строки
4.5 KiB
Go
package workercluster
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
rafthttp "github.com/CanonicalLtd/raft-http"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// newLocalListener opens a TCP listener on the loopback interface
|
|
// using an ephemeral port. Used by transport and e2e tests.
|
|
func newLocalListener(t *testing.T) net.Listener {
|
|
t.Helper()
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
return l
|
|
}
|
|
|
|
// TestTransport_Auth_RejectsMissingCreds verifies that a request
|
|
// without an Authorization header is rejected with 401.
|
|
func TestTransport_Auth_RejectsMissingCreds(t *testing.T) {
|
|
h := rafthttp.NewHandler()
|
|
wrapped := NewAuthHandler(h, HTTPCreds{Login: "alice", Password: "secret"}, nil)
|
|
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest("GET", "/raft", nil)
|
|
wrapped.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, rec.Code)
|
|
assert.Equal(t, `Basic realm="raft"`, rec.Header().Get("WWW-Authenticate"))
|
|
}
|
|
|
|
// TestTransport_Auth_RejectsWrongCreds verifies that a request with
|
|
// incorrect credentials is rejected with 401.
|
|
func TestTransport_Auth_RejectsWrongCreds(t *testing.T) {
|
|
h := rafthttp.NewHandler()
|
|
wrapped := NewAuthHandler(h, HTTPCreds{Login: "alice", Password: "secret"}, nil)
|
|
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest("GET", "/raft", nil)
|
|
req.SetBasicAuth("alice", "wrong")
|
|
wrapped.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, rec.Code)
|
|
}
|
|
|
|
// TestTransport_Auth_AcceptsCorrectCreds verifies that a request with
|
|
// matching credentials is forwarded to the inner rafthttp handler. The
|
|
// rafthttp GET path expects an Upgrade header; without one it returns
|
|
// 400, but the important point is that the auth wrapper does not block
|
|
// the request before reaching the inner handler.
|
|
func TestTransport_Auth_AcceptsCorrectCreds(t *testing.T) {
|
|
h := rafthttp.NewHandler()
|
|
wrapped := NewAuthHandler(h, HTTPCreds{Login: "alice", Password: "secret"}, nil)
|
|
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest("GET", "/raft", nil)
|
|
req.SetBasicAuth("alice", "secret")
|
|
wrapped.ServeHTTP(rec, req)
|
|
|
|
assert.NotEqual(t, http.StatusUnauthorized, rec.Code, "auth wrapper should not block good credentials")
|
|
assert.Equal(t, http.StatusBadRequest, rec.Code, "rafthttp expects Upgrade:raft header; with auth, 400 confirms we reached the inner handler")
|
|
}
|
|
|
|
// TestTransport_Auth_PanicsOnEmptyCreds verifies the wrapper refuses
|
|
// to construct without both login and password.
|
|
func TestTransport_Auth_PanicsOnEmptyCreds(t *testing.T) {
|
|
assert.Panics(t, func() {
|
|
NewAuthHandler(rafthttp.NewHandler(), HTTPCreds{Login: "", Password: ""}, nil)
|
|
})
|
|
}
|
|
|
|
// TestTransport_Auth_TimingSafe verifies that the wrapper uses
|
|
// crypto/subtle.ConstantTimeCompare rather than ==. We assert this
|
|
// indirectly: with wrong creds the response is always 401 regardless of
|
|
// how close the password is to the real one.
|
|
func TestTransport_Auth_TimingSafe(t *testing.T) {
|
|
h := rafthttp.NewHandler()
|
|
wrapped := NewAuthHandler(h, HTTPCreds{Login: "alice", Password: "secret"}, nil)
|
|
|
|
for _, pw := range []string{"s", "se", "sec", "secr", "secre", "secret", "secretX"} {
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest("GET", "/raft", nil)
|
|
req.SetBasicAuth("alice", pw)
|
|
wrapped.ServeHTTP(rec, req)
|
|
if pw == "secret" {
|
|
assert.NotEqual(t, http.StatusUnauthorized, rec.Code, "exact password should pass auth")
|
|
} else {
|
|
assert.Equal(t, http.StatusUnauthorized, rec.Code, "password %q should fail auth", pw)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestTransport_HTTPCreds_IsConfigured verifies the IsConfigured
|
|
// contract: both set, or neither.
|
|
func TestTransport_HTTPCreds_IsConfigured(t *testing.T) {
|
|
cases := []struct {
|
|
creds HTTPCreds
|
|
want bool
|
|
}{
|
|
{HTTPCreds{Login: "u", Password: "p"}, true},
|
|
{HTTPCreds{Login: "u"}, false},
|
|
{HTTPCreds{Password: "p"}, false},
|
|
{HTTPCreds{}, false},
|
|
}
|
|
for _, tc := range cases {
|
|
assert.Equal(t, tc.want, tc.creds.IsConfigured(), "%+v", tc.creds)
|
|
}
|
|
}
|
|
|
|
// TestTransport_NewTransportRequiresArgs guards the constructor's
|
|
// invariants.
|
|
func TestTransport_NewTransportRequiresArgs(t *testing.T) {
|
|
h := rafthttp.NewHandler()
|
|
l := newLocalListener(t)
|
|
|
|
_, _, err := NewTransport("", l, nil, nil, nil)
|
|
require.Error(t, err)
|
|
|
|
_, _, err = NewTransport("", nil, h, nil, nil)
|
|
require.Error(t, err)
|
|
|
|
layer, srv, err := NewTransport("/raft", l, h, nil, nil)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, layer)
|
|
require.NotNil(t, srv)
|
|
require.NoError(t, srv.Close())
|
|
require.NoError(t, l.Close())
|
|
}
|