package main import ( "crypto/hmac" "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 func makeToken(secret string, ts int64) string { tsStr := fmt.Sprintf("%d", ts) mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(tsStr)) sig := hex.EncodeToString(mac.Sum(nil)) return sig + "." + tsStr } func TestValidateToken_ValidToken(t *testing.T) { secret := "test-secret" ts := time.Now().Unix() token := makeToken(secret, ts) if !ValidateToken(token, secret, 30) { t.Error("expected valid token to pass validation") } } func TestValidateToken_ExpiredToken(t *testing.T) { secret := "test-secret" ts := time.Now().Unix() - 60 // 60 seconds ago, TTL is 30 token := makeToken(secret, ts) if ValidateToken(token, secret, 30) { t.Error("expected expired token to fail validation") } } 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) { ts := time.Now().Unix() token := makeToken("right-secret", ts) if ValidateToken(token, "wrong-secret", 30) { t.Error("expected wrong secret to fail validation") } } 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") } if ValidateToken("", "secret", 30) { t.Error("expected empty token to fail") } } 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 }