Rename oh_action to oh_user_action for clarity (#7368)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Engel Nyst 2025-03-21 23:23:15 +01:00 committed by GitHub
parent 7d0e2265f7
commit a03ad1079c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 114 additions and 7 deletions

View File

@ -1,9 +1,12 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import * as ChatSlice from "#/state/chat-slice";
import {
updateStatusWhenErrorMessagePresent,
WsClientProvider,
useWsClient,
} from "#/context/ws-client-provider";
import React from "react";
describe("Propagate error message", () => {
it("should do nothing when no message was passed from server", () => {
@ -41,3 +44,59 @@ describe("Propagate error message", () => {
});
});
});
// Create a mock for socket.io-client
const mockEmit = vi.fn();
const mockOn = vi.fn();
const mockOff = vi.fn();
const mockDisconnect = vi.fn();
vi.mock("socket.io-client", () => {
return {
io: vi.fn(() => ({
emit: mockEmit,
on: mockOn,
off: mockOff,
disconnect: mockDisconnect,
io: {
opts: {
query: {},
},
},
})),
};
});
// Mock component to test the hook
const TestComponent = () => {
const { send } = useWsClient();
React.useEffect(() => {
// Send a test event
send({ type: "test_event" });
}, [send]);
return <div>Test Component</div>;
};
describe("WsClientProvider", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("should emit oh_user_action event when send is called", async () => {
const { getByText } = render(
<WsClientProvider conversationId="test-conversation-id">
<TestComponent />
</WsClientProvider>
);
// Assert
expect(getByText("Test Component")).toBeInTheDocument();
// Wait for the emit call to happen (useEffect needs time to run)
await waitFor(() => {
expect(mockEmit).toHaveBeenCalledWith("oh_user_action", { type: "test_event" });
}, { timeout: 1000 });
});
});

View File

@ -118,7 +118,7 @@ export function WsClientProvider({
EventLogger.error("WebSocket is not connected.");
return;
}
sioRef.current.emit("oh_action", event);
sioRef.current.emit("oh_user_action", event);
}
function handleConnect() {

View File

@ -35,7 +35,7 @@ export const handlers: WebSocketHandler[] = [
);
}
io.client.on("oh_action", async (_, data) => {
io.client.on("oh_user_action", async (_, data) => {
if (isInitConfig(data)) {
io.client.emit(
"oh_event",

View File

@ -75,8 +75,15 @@ async def connect(connection_id: str, environ):
logger.info(f'Finished replaying event stream for conversation {conversation_id}')
@sio.event
async def oh_user_action(connection_id: str, data: dict):
await conversation_manager.send_to_event_stream(connection_id, data)
@sio.event
async def oh_action(connection_id: str, data: dict):
# TODO: Remove this handler once all clients are updated to use oh_user_action
# Keeping for backward compatibility with in-progress sessions
await conversation_manager.send_to_event_stream(connection_id, data)

View File

@ -8,7 +8,7 @@ interruptions are recoverable.
There are 3 main server side event handlers:
* `connect` - Invoked when a new connection to the server is established. (This may be via http or WebSocket)
* `oh_action` - Invoked when a connected client sends an event (such as a prompt for the Agent) -
* `oh_user_action` - Invoked when a connected client sends an event (such as a prompt for the Agent) -
this is distinct from the `oh_event` sent from the server to the client.
* `disconnect` - Invoked when a connected client disconnects from the server.

View File

@ -100,7 +100,6 @@ reportlab = "*"
concurrency = ["gevent"]
[tool.poetry.group.runtime.dependencies]
jupyterlab = "*"
notebook = "*"
@ -130,7 +129,6 @@ ignore = ["D1"]
convention = "google"
[tool.poetry.group.evaluation.dependencies]
streamlit = "*"
whatthepatch = "*"

View File

@ -0,0 +1,43 @@
from unittest.mock import AsyncMock, patch
import pytest
from openhands.server.listen_socket import oh_action, oh_user_action
@pytest.mark.asyncio
async def test_oh_user_action():
"""Test that oh_user_action correctly forwards data to the conversation manager."""
connection_id = 'test_connection_id'
test_data = {'action': 'test_action', 'data': 'test_data'}
# Mock the conversation_manager
with patch('openhands.server.listen_socket.conversation_manager') as mock_manager:
mock_manager.send_to_event_stream = AsyncMock()
# Call the function
await oh_user_action(connection_id, test_data)
# Verify the conversation manager was called with the correct arguments
mock_manager.send_to_event_stream.assert_called_once_with(
connection_id, test_data
)
@pytest.mark.asyncio
async def test_oh_action():
"""Test that oh_action (legacy handler) correctly forwards data to the conversation manager."""
connection_id = 'test_connection_id'
test_data = {'action': 'test_action', 'data': 'test_data'}
# Mock the conversation_manager
with patch('openhands.server.listen_socket.conversation_manager') as mock_manager:
mock_manager.send_to_event_stream = AsyncMock()
# Call the function
await oh_action(connection_id, test_data)
# Verify the conversation manager was called with the correct arguments
mock_manager.send_to_event_stream.assert_called_once_with(
connection_id, test_data
)