feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
Этот коммит содержится в:
133
internal/workercluster/transport_test.go
Обычный файл
133
internal/workercluster/transport_test.go
Обычный файл
@@ -0,0 +1,133 @@
|
||||
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())
|
||||
}
|
||||
Ссылка в новой задаче
Block a user