Files
worker/internal/workercluster/transport_test.go
Gleb Tv 2c7a0236da feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
2026-07-13 17:55:14 +03:00

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())
}