|
|
|
@@ -2,8 +2,10 @@ package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bookhoard/internal/database"
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
@@ -68,114 +70,128 @@ func TestWebSocketDeviceAuth(t *testing.T) {
|
|
|
|
|
})
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Connect to WebSocket with device token
|
|
|
|
|
// Use gorilla/websocket's RequestHeader support
|
|
|
|
|
wsURL := strings.Replace(setup.Server.URL, "http", "ws", 1) + "/ws/sync?token=device-auth-test"
|
|
|
|
|
req, _ := http.NewRequest("GET", wsURL, nil)
|
|
|
|
|
req.Header.Set("Authorization", "Bearer test-device-token-"+deviceID.String())
|
|
|
|
|
|
|
|
|
|
// We can't easily test WebSocket with custom headers using gorilla/websocket
|
|
|
|
|
// So this test just verifies the device exists
|
|
|
|
|
device, err := setup.DB.GetDeviceByAuthToken(context.Background(), "test-device-token-"+deviceID.String())
|
|
|
|
|
// Create dialer with custom headers
|
|
|
|
|
dialer := &websocket.Dialer{
|
|
|
|
|
HandshakeTimeout: 5 * time.Second,
|
|
|
|
|
}
|
|
|
|
|
headers := http.Header{}
|
|
|
|
|
headers.Set("Authorization", "Bearer test-device-token-"+deviceID.String())
|
|
|
|
|
// Connect with device token in header
|
|
|
|
|
ws, resp, err := dialer.Dial(wsURL, headers)
|
|
|
|
|
require.NoError(t, err, "WebSocket connection with device token should succeed")
|
|
|
|
|
defer ws.Close()
|
|
|
|
|
if resp != nil {
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
require.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode, "Should upgrade to WebSocket")
|
|
|
|
|
}
|
|
|
|
|
// Read initial state message
|
|
|
|
|
ws.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
|
|
|
_, msg, err := ws.ReadMessage()
|
|
|
|
|
require.NoError(t, err, "Should receive initial state message")
|
|
|
|
|
var initialMsg map[string]interface{}
|
|
|
|
|
err = json.Unmarshal(msg, &initialMsg)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "Test KOReader", device.DeviceName)
|
|
|
|
|
assert.Equal(t, "initial_state", initialMsg["type"])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestWebSocketProgressBroadcast tests that progress updates are broadcast to connected clients
|
|
|
|
|
/* func TestWebSocketProgressBroadcast(t *testing.T) {
|
|
|
|
|
setup := setupTestServer(t)
|
|
|
|
|
func TestWebSocketProgressBroadcast(t *testing.T) {
|
|
|
|
|
setup := setupTestServer(t)
|
|
|
|
|
|
|
|
|
|
// Get JWT token
|
|
|
|
|
token := setup.Token
|
|
|
|
|
// Get JWT token
|
|
|
|
|
token := setup.Token
|
|
|
|
|
|
|
|
|
|
// Create a test media item
|
|
|
|
|
userID := getTestUserID(t, setup.DB)
|
|
|
|
|
mediaID := createTestMediaItem(t, setup.DB, userID)
|
|
|
|
|
// Create a test media item
|
|
|
|
|
userID := getTestUserID(t, setup.DB)
|
|
|
|
|
mediaID := createTestMediaItem(t, setup.DB, userID)
|
|
|
|
|
|
|
|
|
|
// Connect WebSocket client
|
|
|
|
|
wsURL := strings.Replace(setup.Server.URL, "http", "ws", 1) + "/ws/sync?token=" + token
|
|
|
|
|
ws, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
defer ws.Close()
|
|
|
|
|
// Connect WebSocket client
|
|
|
|
|
wsURL := strings.Replace(setup.Server.URL, "http", "ws", 1) + "/ws/sync?token=" + token
|
|
|
|
|
ws, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
defer ws.Close()
|
|
|
|
|
|
|
|
|
|
// Read and discard initial state message
|
|
|
|
|
ws.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
|
|
|
_, _, _ = ws.ReadMessage()
|
|
|
|
|
// Read and discard initial state message
|
|
|
|
|
ws.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
|
|
|
_, _, _ = ws.ReadMessage()
|
|
|
|
|
|
|
|
|
|
// Update progress via HTTP API
|
|
|
|
|
progressReq := map[string]interface{}{
|
|
|
|
|
"source": "test",
|
|
|
|
|
"location": map[string]interface{}{
|
|
|
|
|
"percentage": 0.5,
|
|
|
|
|
},
|
|
|
|
|
"device_metadata": map[string]interface{}{
|
|
|
|
|
"device_type": "web",
|
|
|
|
|
},
|
|
|
|
|
// Update progress via HTTP API
|
|
|
|
|
progressReq := map[string]interface{}{
|
|
|
|
|
"source": "test",
|
|
|
|
|
"location": map[string]interface{}{
|
|
|
|
|
"percentage": 0.5,
|
|
|
|
|
},
|
|
|
|
|
"device_metadata": map[string]interface{}{
|
|
|
|
|
"device_type": "web",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
body, _ := json.Marshal(progressReq)
|
|
|
|
|
|
|
|
|
|
req, _ := http.NewRequest("POST", setup.Server.URL+"/api/progress/"+mediaID, strings.NewReader(string(body)))
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
|
|
|
|
|
|
client := &http.Client{}
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
|
|
|
|
|
|
// Read the broadcast message from WebSocket
|
|
|
|
|
_, msg, err := ws.ReadMessage()
|
|
|
|
|
require.NoError(t, err, "Failed to read broadcast message")
|
|
|
|
|
|
|
|
|
|
var broadcastMsg map[string]interface{}
|
|
|
|
|
err = json.Unmarshal(msg, &broadcastMsg)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "progress_update", broadcastMsg["type"])
|
|
|
|
|
|
|
|
|
|
data := broadcastMsg["data"].(map[string]interface{})
|
|
|
|
|
assert.Equal(t, mediaID, data["book_id"])
|
|
|
|
|
assert.InDelta(t, 0.5, data["percentage"], 0.01)
|
|
|
|
|
|
|
|
|
|
sourceDevice := broadcastMsg["source_device"].(map[string]interface{})
|
|
|
|
|
assert.Equal(t, "web", sourceDevice["type"])
|
|
|
|
|
}
|
|
|
|
|
body, _ := json.Marshal(progressReq)
|
|
|
|
|
|
|
|
|
|
req, _ := http.NewRequest("POST", setup.Server.URL+"/api/progress/"+mediaID, strings.NewReader(string(body)))
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
|
|
|
|
|
|
client := &http.Client{}
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
|
|
|
|
|
|
// Read the broadcast message from WebSocket
|
|
|
|
|
_, msg, err := ws.ReadMessage()
|
|
|
|
|
require.NoError(t, err, "Failed to read broadcast message")
|
|
|
|
|
|
|
|
|
|
var broadcastMsg map[string]interface{}
|
|
|
|
|
err = json.Unmarshal(msg, &broadcastMsg)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "progress_update", broadcastMsg["type"])
|
|
|
|
|
|
|
|
|
|
data := broadcastMsg["data"].(map[string]interface{})
|
|
|
|
|
assert.Equal(t, mediaID, data["book_id"])
|
|
|
|
|
assert.InDelta(t, 0.5, data["percentage"], 0.01)
|
|
|
|
|
|
|
|
|
|
sourceDevice := broadcastMsg["source_device"].(map[string]interface{})
|
|
|
|
|
assert.Equal(t, "web", sourceDevice["type"])
|
|
|
|
|
} */
|
|
|
|
|
|
|
|
|
|
// TestWebSocketPingPong tests that ping/pong messages work correctly
|
|
|
|
|
/* func TestWebSocketPingPong(t *testing.T) {
|
|
|
|
|
setup := setupTestServer(t)
|
|
|
|
|
func TestWebSocketPingPong(t *testing.T) {
|
|
|
|
|
setup := setupTestServer(t)
|
|
|
|
|
|
|
|
|
|
token := setup.Token
|
|
|
|
|
token := setup.Token
|
|
|
|
|
|
|
|
|
|
wsURL := strings.Replace(setup.Server.URL, "http", "ws", 1) + "/ws/sync?token=" + token
|
|
|
|
|
ws, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
defer ws.Close()
|
|
|
|
|
wsURL := strings.Replace(setup.Server.URL, "http", "ws", 1) + "/ws/sync?token=" + token
|
|
|
|
|
ws, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
defer ws.Close()
|
|
|
|
|
|
|
|
|
|
// Read initial message
|
|
|
|
|
ws.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
|
|
|
_, _, _ = ws.ReadMessage()
|
|
|
|
|
// Read initial message
|
|
|
|
|
ws.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
|
|
|
_, _, _ = ws.ReadMessage()
|
|
|
|
|
|
|
|
|
|
// Send a ping message (as a text message for testing)
|
|
|
|
|
err = ws.WriteMessage(websocket.TextMessage, []byte(`{"type":"ping"}`))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
// Send a ping message (as a text message for testing)
|
|
|
|
|
err = ws.WriteMessage(websocket.TextMessage, []byte(`{"type":"ping"}`))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Server should respond with pong
|
|
|
|
|
ws.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
|
|
|
_, msg, err := ws.ReadMessage()
|
|
|
|
|
if err == nil {
|
|
|
|
|
var pongMsg map[string]interface{}
|
|
|
|
|
err = json.Unmarshal(msg, &pongMsg)
|
|
|
|
|
// Server should respond with pong
|
|
|
|
|
ws.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
|
|
|
_, msg, err := ws.ReadMessage()
|
|
|
|
|
if err == nil {
|
|
|
|
|
// Server might respond with pong
|
|
|
|
|
assert.Equal(t, "pong", pongMsg["type"])
|
|
|
|
|
var pongMsg map[string]interface{}
|
|
|
|
|
err = json.Unmarshal(msg, &pongMsg)
|
|
|
|
|
if err == nil {
|
|
|
|
|
// Server might respond with pong
|
|
|
|
|
assert.Equal(t, "pong", pongMsg["type"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} */
|
|
|
|
|
|
|
|
|
|
// TestWebSocketConnectionLimit tests that the server handles multiple connections
|
|
|
|
|
/* func TestWebSocketConnectionLimit(t *testing.T) {
|
|
|
|
|
func TestWebSocketConnectionLimit(t *testing.T) {
|
|
|
|
|
setup := setupTestServer(t)
|
|
|
|
|
|
|
|
|
|
token := setup.Token
|
|
|
|
@@ -197,9 +213,9 @@ if err == nil {
|
|
|
|
|
for _, ws := range connections {
|
|
|
|
|
ws.Close()
|
|
|
|
|
}
|
|
|
|
|
} */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* // TestWebSocketInvalidToken tests that invalid tokens are rejected
|
|
|
|
|
// TestWebSocketInvalidToken tests that invalid tokens are rejected
|
|
|
|
|
func TestWebSocketInvalidToken(t *testing.T) {
|
|
|
|
|
setup := setupTestServer(t)
|
|
|
|
|
|
|
|
|
@@ -215,7 +231,7 @@ func TestWebSocketInvalidToken(t *testing.T) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
} */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper function to create a test media item
|
|
|
|
|
func createTestMediaItem(t *testing.T, db *database.Queries, userID uuid.UUID) string {
|
|
|
|
@@ -241,7 +257,7 @@ func createTestMediaItem(t *testing.T, db *database.Queries, userID uuid.UUID) s
|
|
|
|
|
return uuid.UUID(mediaID.ID.Bytes).String()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* // TestWebSocketUserScopedBroadcast tests that broadcasts only go to the user who made changes
|
|
|
|
|
// TestWebSocketUserScopedBroadcast tests that broadcasts only go to the user who made changes
|
|
|
|
|
func TestWebSocketUserScopedBroadcast(t *testing.T) {
|
|
|
|
|
setup := setupTestServer(t)
|
|
|
|
|
client := &http.Client{}
|
|
|
|
@@ -307,7 +323,7 @@ func TestWebSocketUserScopedBroadcast(t *testing.T) {
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Errorf("Regular user should not receive collection_updated message")
|
|
|
|
|
}
|
|
|
|
|
} */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper: connectWebSocketToServer establishes WebSocket connection with auth token
|
|
|
|
|
func connectWebSocketToServer(t *testing.T, serverURL string, token string) *websocket.Conn {
|
|
|
|
|