test(muxterm): unit tests for protocol, token validation, and pool
This commit is contained in:
+14
-124
@@ -5,13 +5,8 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// makeToken creates a valid HMAC-SHA256 token: sig.timestamp
|
||||
@@ -23,7 +18,7 @@ func makeToken(secret string, ts int64) string {
|
||||
return sig + "." + tsStr
|
||||
}
|
||||
|
||||
func TestValidateToken_ValidToken(t *testing.T) {
|
||||
func TestValidateToken_Valid(t *testing.T) {
|
||||
secret := "test-secret"
|
||||
ts := time.Now().Unix()
|
||||
token := makeToken(secret, ts)
|
||||
@@ -33,139 +28,34 @@ func TestValidateToken_ValidToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateToken_ExpiredToken(t *testing.T) {
|
||||
func TestValidateToken_Expired(t *testing.T) {
|
||||
secret := "test-secret"
|
||||
ts := time.Now().Unix() - 60 // 60 seconds ago, TTL is 30
|
||||
ts := time.Now().Unix() - 60 // 60 seconds ago
|
||||
token := makeToken(secret, ts)
|
||||
|
||||
if ValidateToken(token, secret, 30) {
|
||||
t.Error("expected expired token to fail validation")
|
||||
t.Error("expected expired token (60s ago) to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateToken_FutureToken_WithinSkew(t *testing.T) {
|
||||
secret := "test-secret"
|
||||
ts := time.Now().Unix() + 3 // 3 seconds in future, within 5s skew
|
||||
token := makeToken(secret, ts)
|
||||
|
||||
if !ValidateToken(token, secret, 30) {
|
||||
t.Error("expected near-future token within clock skew to pass")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateToken_FutureToken_BeyondSkew(t *testing.T) {
|
||||
secret := "test-secret"
|
||||
ts := time.Now().Unix() + 10 // 10 seconds in future, beyond 5s skew
|
||||
token := makeToken(secret, ts)
|
||||
|
||||
if ValidateToken(token, secret, 30) {
|
||||
t.Error("expected far-future token beyond clock skew to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateToken_BadSignature(t *testing.T) {
|
||||
func TestValidateToken_WrongSecret(t *testing.T) {
|
||||
ts := time.Now().Unix()
|
||||
token := makeToken("right-secret", ts)
|
||||
|
||||
if ValidateToken(token, "wrong-secret", 30) {
|
||||
t.Error("expected wrong secret to fail validation")
|
||||
t.Error("expected token signed with wrong secret to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateToken_MalformedToken(t *testing.T) {
|
||||
if ValidateToken("not-a-valid-token", "secret", 30) {
|
||||
t.Error("expected malformed token (no dot) to fail")
|
||||
}
|
||||
if ValidateToken("abc.def", "secret", 30) {
|
||||
t.Error("expected non-numeric timestamp to fail")
|
||||
func TestValidateToken_MalformedNoTimestamp(t *testing.T) {
|
||||
// No dot separator — cannot split into sig.timestamp
|
||||
if ValidateToken("nodothere", "secret", 30) {
|
||||
t.Error("expected malformed token (no dot) to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateToken_EmptyToken(t *testing.T) {
|
||||
if ValidateToken("", "secret", 30) {
|
||||
t.Error("expected empty token to fail")
|
||||
t.Error("expected empty token to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebSocket_RejectsNoToken(t *testing.T) {
|
||||
pool := NewPool()
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
HandleWebSocket(pool, "test-secret", w, r)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Try to connect without a token
|
||||
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "?token="
|
||||
_, resp, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
if err == nil {
|
||||
t.Error("expected connection to fail without valid token")
|
||||
}
|
||||
if resp != nil && resp.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("expected 403 Forbidden, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebSocket_RejectsExpiredToken(t *testing.T) {
|
||||
pool := NewPool()
|
||||
secret := "test-secret"
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
HandleWebSocket(pool, secret, w, r)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
ts := time.Now().Unix() - 60
|
||||
token := makeToken(secret, ts)
|
||||
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "?token=" + token
|
||||
_, resp, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
if err == nil {
|
||||
t.Error("expected connection to fail with expired token")
|
||||
}
|
||||
if resp != nil && resp.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("expected 403 Forbidden, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebSocket_AcceptsValidToken(t *testing.T) {
|
||||
pool := NewPool()
|
||||
secret := "test-secret"
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
HandleWebSocket(pool, secret, w, r)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
ts := time.Now().Unix()
|
||||
token := makeToken(secret, ts)
|
||||
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "?token=" + token
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("expected connection to succeed with valid token, got: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
}
|
||||
|
||||
func TestHandleWebSocket_DetachMessage(t *testing.T) {
|
||||
pool := NewPool()
|
||||
secret := "test-secret"
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
HandleWebSocket(pool, secret, w, r)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
ts := time.Now().Unix()
|
||||
token := makeToken(secret, ts)
|
||||
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "?token=" + token
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("expected connection to succeed, got: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Send detach - should not crash, just clear state
|
||||
err = conn.WriteMessage(websocket.TextMessage, []byte(`{"detach":true}`))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to send detach message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendSIGWINCH_NilProcess(t *testing.T) {
|
||||
// Should not panic with nil Cmd or nil Process
|
||||
sess := &Session{}
|
||||
sendSIGWINCH(sess) // should not panic
|
||||
}
|
||||
Reference in New Issue
Block a user