mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-03-05 00:12:49 +01:00
477 lines
16 KiB
Python
477 lines
16 KiB
Python
from unittest import mock
|
|
|
|
import pytest
|
|
from qtpy.QtCore import Qt
|
|
from qtpy.QtGui import QHideEvent
|
|
from qtpy.QtNetwork import QAuthenticator
|
|
|
|
from bec_widgets.widgets.editors.web_console.web_console import (
|
|
BECShell,
|
|
ConsoleMode,
|
|
WebConsole,
|
|
_web_console_registry,
|
|
)
|
|
|
|
from .client_mocks import mocked_client
|
|
|
|
|
|
@pytest.fixture
|
|
def mocked_server_startup():
|
|
"""Mock the web console server startup process."""
|
|
with mock.patch(
|
|
"bec_widgets.widgets.editors.web_console.web_console.subprocess"
|
|
) as mock_subprocess:
|
|
with mock.patch.object(_web_console_registry, "_wait_for_server_port"):
|
|
_web_console_registry._server_port = 12345
|
|
yield mock_subprocess
|
|
|
|
|
|
def static_console(qtbot, client, unique_id: str | None = None):
|
|
"""Fixture to provide a static unique_id for WebConsole tests."""
|
|
if unique_id is None:
|
|
widget = WebConsole(client=client)
|
|
else:
|
|
widget = WebConsole(client=client, unique_id=unique_id)
|
|
qtbot.addWidget(widget)
|
|
qtbot.waitExposed(widget)
|
|
return widget
|
|
|
|
|
|
@pytest.fixture
|
|
def console_widget(qtbot, mocked_client, mocked_server_startup):
|
|
"""Create a WebConsole widget with mocked server startup."""
|
|
yield static_console(qtbot, mocked_client)
|
|
|
|
|
|
@pytest.fixture
|
|
def bec_shell_widget(qtbot, mocked_client, mocked_server_startup):
|
|
"""Create a BECShell widget with mocked server startup."""
|
|
widget = BECShell(client=mocked_client)
|
|
qtbot.addWidget(widget)
|
|
qtbot.waitExposed(widget)
|
|
yield widget
|
|
|
|
|
|
@pytest.fixture
|
|
def console_widget_with_static_id(qtbot, mocked_client, mocked_server_startup):
|
|
"""Create a WebConsole widget with a static unique ID."""
|
|
yield static_console(qtbot, mocked_client, unique_id="test_console")
|
|
|
|
|
|
@pytest.fixture
|
|
def two_console_widgets_same_id(qtbot, mocked_client, mocked_server_startup):
|
|
"""Create two WebConsole widgets sharing the same unique ID."""
|
|
widget1 = static_console(qtbot, mocked_client, unique_id="shared_console")
|
|
widget2 = static_console(qtbot, mocked_client, unique_id="shared_console")
|
|
yield widget1, widget2
|
|
|
|
|
|
def test_web_console_widget_initialization(console_widget):
|
|
assert (
|
|
console_widget.page.url().toString()
|
|
== f"http://localhost:{_web_console_registry._server_port}"
|
|
)
|
|
|
|
|
|
def test_web_console_write(console_widget):
|
|
# Test the write method
|
|
with mock.patch.object(console_widget.page, "runJavaScript") as mock_run_js:
|
|
console_widget.write("Hello, World!")
|
|
|
|
assert mock.call('window.term.paste("Hello, World!");') in mock_run_js.mock_calls
|
|
|
|
|
|
def test_web_console_write_no_return(console_widget):
|
|
# Test the write method with send_return=False
|
|
with mock.patch.object(console_widget.page, "runJavaScript") as mock_run_js:
|
|
console_widget.write("Hello, World!", send_return=False)
|
|
|
|
assert mock.call('window.term.paste("Hello, World!");') in mock_run_js.mock_calls
|
|
assert mock_run_js.call_count == 1
|
|
|
|
|
|
def test_web_console_send_return(console_widget):
|
|
# Test the send_return method
|
|
with mock.patch.object(console_widget.page, "runJavaScript") as mock_run_js:
|
|
console_widget.send_return()
|
|
|
|
script = mock_run_js.call_args[0][0]
|
|
assert "new KeyboardEvent('keypress', {charCode: 13})" in script
|
|
assert mock_run_js.call_count == 1
|
|
|
|
|
|
def test_web_console_send_ctrl_c(console_widget):
|
|
# Test the send_ctrl_c method
|
|
with mock.patch.object(console_widget.page, "runJavaScript") as mock_run_js:
|
|
console_widget.send_ctrl_c()
|
|
|
|
script = mock_run_js.call_args[0][0]
|
|
assert "new KeyboardEvent('keypress', {charCode: 3})" in script
|
|
assert mock_run_js.call_count == 1
|
|
|
|
|
|
def test_web_console_authenticate(console_widget):
|
|
# Test the _authenticate method
|
|
token = _web_console_registry._token
|
|
mock_auth = mock.MagicMock(spec=QAuthenticator)
|
|
console_widget._authenticate(None, mock_auth)
|
|
mock_auth.setUser.assert_called_once_with("user")
|
|
mock_auth.setPassword.assert_called_once_with(token)
|
|
|
|
|
|
def test_web_console_registry_wait_for_server_port():
|
|
# Test the _wait_for_server_port method
|
|
with mock.patch.object(_web_console_registry, "_server_process") as mock_subprocess:
|
|
mock_subprocess.stderr.readline.side_effect = [b"Starting", b"Listening on port: 12345"]
|
|
_web_console_registry._wait_for_server_port()
|
|
assert _web_console_registry._server_port == 12345
|
|
|
|
|
|
def test_web_console_registry_wait_for_server_port_timeout():
|
|
# Test the _wait_for_server_port method with timeout
|
|
with mock.patch.object(_web_console_registry, "_server_process") as mock_subprocess:
|
|
with pytest.raises(TimeoutError):
|
|
_web_console_registry._wait_for_server_port(timeout=0.1)
|
|
|
|
|
|
def test_web_console_startup_command_execution(console_widget, qtbot):
|
|
"""Test that the startup command is triggered after successful initialization."""
|
|
# Set a custom startup command
|
|
console_widget.startup_cmd = "test startup command"
|
|
|
|
assert console_widget.startup_cmd == "test startup command"
|
|
|
|
# Generator to simulate JS initialization sequence
|
|
def js_readiness_sequence():
|
|
yield False # First call: not ready yet
|
|
while True:
|
|
yield True # Any subsequent calls: ready
|
|
|
|
readiness_gen = js_readiness_sequence()
|
|
|
|
def mock_run_js(script, callback=None):
|
|
# Check if this is the initialization check call
|
|
if "window.term !== undefined" in script and callback:
|
|
ready = next(readiness_gen)
|
|
callback(ready)
|
|
else:
|
|
# For other JavaScript calls (like paste), just call the callback
|
|
if callback:
|
|
callback(True)
|
|
|
|
with mock.patch.object(
|
|
console_widget.page, "runJavaScript", side_effect=mock_run_js
|
|
) as mock_run_js_method:
|
|
# Reset initialization state and start the timer
|
|
console_widget._is_initialized = False
|
|
console_widget._startup_timer.start()
|
|
|
|
# Wait for the initialization to complete
|
|
qtbot.waitUntil(lambda: console_widget._is_initialized, timeout=3000)
|
|
|
|
# Verify that the startup command was executed
|
|
startup_calls = [
|
|
call
|
|
for call in mock_run_js_method.call_args_list
|
|
if "test startup command" in str(call)
|
|
]
|
|
assert len(startup_calls) > 0, "Startup command should have been executed"
|
|
|
|
# Verify the initialized signal was emitted
|
|
assert console_widget._is_initialized is True
|
|
assert not console_widget._startup_timer.isActive()
|
|
|
|
|
|
def test_bec_shell_startup_contains_gui_id(bec_shell_widget):
|
|
"""Test that the BEC shell startup command includes the GUI ID."""
|
|
bec_shell = bec_shell_widget
|
|
|
|
assert bec_shell._is_bec_shell
|
|
assert bec_shell._unique_id == "bec_shell"
|
|
|
|
with mock.patch.object(bec_shell.bec_dispatcher, "cli_server", None):
|
|
assert bec_shell.startup_cmd == "bec --nogui"
|
|
|
|
with mock.patch.object(bec_shell.bec_dispatcher.cli_server, "gui_id", "test_gui_id"):
|
|
assert bec_shell.startup_cmd == "bec --gui-id test_gui_id"
|
|
|
|
|
|
def test_web_console_set_readonly(console_widget):
|
|
# Test the set_readonly method
|
|
console_widget.set_readonly(True)
|
|
assert not console_widget.isEnabled()
|
|
|
|
console_widget.set_readonly(False)
|
|
assert console_widget.isEnabled()
|
|
|
|
|
|
def test_web_console_with_unique_id(console_widget_with_static_id):
|
|
"""Test creating a WebConsole with a unique_id."""
|
|
widget = console_widget_with_static_id
|
|
|
|
assert widget._unique_id == "test_console"
|
|
assert widget._unique_id in _web_console_registry._page_registry
|
|
page_info = _web_console_registry.get_page_info("test_console")
|
|
assert page_info is not None
|
|
assert page_info.owner_gui_id == widget.gui_id
|
|
assert widget.gui_id in page_info.widget_ids
|
|
|
|
|
|
def test_web_console_page_sharing(two_console_widgets_same_id):
|
|
"""Test that two widgets can share the same page using unique_id."""
|
|
widget1, widget2 = two_console_widgets_same_id
|
|
|
|
# Both should reference the same page in the registry
|
|
page_info = _web_console_registry.get_page_info("shared_console")
|
|
assert page_info is not None
|
|
assert widget1.gui_id in page_info.widget_ids
|
|
assert widget2.gui_id in page_info.widget_ids
|
|
assert widget1.page == widget2.page
|
|
|
|
|
|
def test_web_console_has_ownership(console_widget_with_static_id):
|
|
"""Test the has_ownership method."""
|
|
widget = console_widget_with_static_id
|
|
|
|
# Widget should have ownership by default
|
|
assert widget.has_ownership()
|
|
|
|
|
|
def test_web_console_yield_ownership(console_widget_with_static_id):
|
|
"""Test yielding ownership of a page."""
|
|
widget = console_widget_with_static_id
|
|
|
|
assert widget.has_ownership()
|
|
|
|
# Yield ownership
|
|
widget.yield_ownership()
|
|
|
|
# Widget should no longer have ownership
|
|
assert not widget.has_ownership()
|
|
page_info = _web_console_registry.get_page_info("test_console")
|
|
assert page_info.owner_gui_id is None
|
|
# Overlay should be shown
|
|
assert widget._mode == ConsoleMode.INACTIVE
|
|
|
|
|
|
def test_web_console_take_page_ownership(two_console_widgets_same_id):
|
|
"""Test taking ownership of a page."""
|
|
widget1, widget2 = two_console_widgets_same_id
|
|
|
|
# Widget1 should have ownership initially
|
|
assert widget1.has_ownership()
|
|
assert not widget2.has_ownership()
|
|
|
|
# Widget2 takes ownership
|
|
widget2.take_page_ownership()
|
|
|
|
# Now widget2 should have ownership
|
|
assert not widget1.has_ownership()
|
|
assert widget2.has_ownership()
|
|
|
|
assert widget2._mode == ConsoleMode.ACTIVE
|
|
assert widget1._mode == ConsoleMode.INACTIVE
|
|
|
|
|
|
def test_web_console_hide_event_yields_ownership(qtbot, console_widget_with_static_id):
|
|
"""Test that hideEvent yields ownership."""
|
|
widget = console_widget_with_static_id
|
|
|
|
assert widget.has_ownership()
|
|
|
|
# Hide the widget. Note that we cannot call widget.hide() directly
|
|
# because it doesn't trigger the hideEvent in tests as widgets are
|
|
# not visible in the test environment.
|
|
widget.hideEvent(QHideEvent())
|
|
qtbot.wait(100) # Allow event processing
|
|
|
|
# Widget should have yielded ownership
|
|
assert not widget.has_ownership()
|
|
page_info = _web_console_registry.get_page_info("test_console")
|
|
assert page_info.owner_gui_id is None
|
|
|
|
|
|
def test_web_console_show_event_takes_ownership(console_widget_with_static_id):
|
|
"""Test that showEvent takes ownership when page has no owner."""
|
|
widget = console_widget_with_static_id
|
|
|
|
# Yield ownership
|
|
widget.yield_ownership()
|
|
assert not widget.has_ownership()
|
|
|
|
# Show the widget again
|
|
widget.show()
|
|
|
|
# Widget should have reclaimed ownership
|
|
assert widget.has_ownership()
|
|
assert widget.browser.isVisible()
|
|
assert not widget.overlay.isVisible()
|
|
|
|
|
|
def test_web_console_mouse_press_takes_ownership(qtbot, two_console_widgets_same_id):
|
|
"""Test that clicking on overlay takes ownership."""
|
|
widget1, widget2 = two_console_widgets_same_id
|
|
widget1.show()
|
|
widget2.show()
|
|
|
|
# Widget1 has ownership, widget2 doesn't
|
|
assert widget1.has_ownership()
|
|
assert not widget2.has_ownership()
|
|
assert widget1.isVisible()
|
|
assert widget1._mode == ConsoleMode.ACTIVE
|
|
assert widget2._mode == ConsoleMode.INACTIVE
|
|
|
|
qtbot.mouseClick(widget2, Qt.MouseButton.LeftButton)
|
|
|
|
# Widget2 should now have ownership
|
|
assert widget2.has_ownership()
|
|
assert not widget1.has_ownership()
|
|
|
|
|
|
def test_web_console_registry_cleanup_removes_page(console_widget_with_static_id):
|
|
"""Test that the registry cleans up pages when all widgets are removed."""
|
|
widget = console_widget_with_static_id
|
|
|
|
assert widget._unique_id in _web_console_registry._page_registry
|
|
|
|
# Cleanup the widget
|
|
widget.cleanup()
|
|
|
|
# Page should be removed from registry
|
|
assert widget._unique_id not in _web_console_registry._page_registry
|
|
|
|
|
|
def test_web_console_without_unique_id_no_page_sharing(console_widget):
|
|
"""Test that widgets without unique_id don't participate in page sharing."""
|
|
widget = console_widget
|
|
|
|
# Widget should not be in the page registry
|
|
assert widget._unique_id is None
|
|
assert not widget.has_ownership() # Should return False for non-unique widgets
|
|
|
|
|
|
def test_web_console_registry_get_page_info_nonexistent(qtbot, mocked_client):
|
|
"""Test getting page info for a non-existent page."""
|
|
page_info = _web_console_registry.get_page_info("nonexistent")
|
|
assert page_info is None
|
|
|
|
|
|
def test_web_console_take_ownership_without_unique_id(console_widget):
|
|
"""Test that take_page_ownership fails gracefully without unique_id."""
|
|
widget = console_widget
|
|
# Should not crash when taking ownership without unique_id
|
|
widget.take_page_ownership()
|
|
|
|
|
|
def test_web_console_yield_ownership_without_unique_id(console_widget):
|
|
"""Test that yield_ownership fails gracefully without unique_id."""
|
|
widget = console_widget
|
|
# Should not crash when yielding ownership without unique_id
|
|
widget.yield_ownership()
|
|
|
|
|
|
def test_registry_yield_ownership_gui_id_not_in_instances():
|
|
"""Test registry yield_ownership returns False when gui_id not in instances."""
|
|
result = _web_console_registry.yield_ownership("nonexistent_gui_id")
|
|
assert result is False
|
|
|
|
|
|
def test_registry_yield_ownership_instance_is_none(console_widget_with_static_id):
|
|
"""Test registry yield_ownership returns False when instance weakref is dead."""
|
|
widget = console_widget_with_static_id
|
|
gui_id = widget.gui_id
|
|
|
|
# Store the gui_id and simulate the weakref being dead
|
|
_web_console_registry._instances[gui_id] = lambda: None
|
|
|
|
result = _web_console_registry.yield_ownership(gui_id)
|
|
assert result is False
|
|
|
|
|
|
def test_registry_yield_ownership_unique_id_none(console_widget_with_static_id):
|
|
"""Test registry yield_ownership returns False when page info's unique_id is None."""
|
|
widget = console_widget_with_static_id
|
|
gui_id = widget.gui_id
|
|
unique_id = widget._unique_id
|
|
widget._unique_id = None
|
|
|
|
result = _web_console_registry.yield_ownership(gui_id)
|
|
assert result is False
|
|
|
|
widget._unique_id = unique_id # Restore for cleanup
|
|
|
|
|
|
def test_registry_yield_ownership_unique_id_not_in_page_registry(console_widget_with_static_id):
|
|
"""Test registry yield_ownership returns False when unique_id not in page registry."""
|
|
widget = console_widget_with_static_id
|
|
gui_id = widget.gui_id
|
|
unique_id = widget._unique_id
|
|
widget._unique_id = "nonexistent_unique_id"
|
|
|
|
result = _web_console_registry.yield_ownership(gui_id)
|
|
assert result is False
|
|
|
|
widget._unique_id = unique_id # Restore for cleanup
|
|
|
|
|
|
def test_registry_owner_is_visible_page_info_none():
|
|
"""Test owner_is_visible returns False when page info doesn't exist."""
|
|
result = _web_console_registry.owner_is_visible("nonexistent_page")
|
|
assert result is False
|
|
|
|
|
|
def test_registry_owner_is_visible_no_owner(console_widget_with_static_id):
|
|
"""Test owner_is_visible returns False when page has no owner."""
|
|
widget = console_widget_with_static_id
|
|
|
|
# Yield ownership so there's no owner
|
|
widget.yield_ownership()
|
|
page_info = _web_console_registry.get_page_info(widget._unique_id)
|
|
assert page_info.owner_gui_id is None
|
|
|
|
result = _web_console_registry.owner_is_visible(widget._unique_id)
|
|
assert result is False
|
|
|
|
|
|
def test_registry_owner_is_visible_owner_ref_none(console_widget_with_static_id):
|
|
"""Test owner_is_visible returns False when owner ref doesn't exist in instances."""
|
|
widget = console_widget_with_static_id
|
|
unique_id = widget._unique_id
|
|
|
|
# Remove owner from instances dict
|
|
del _web_console_registry._instances[widget.gui_id]
|
|
|
|
result = _web_console_registry.owner_is_visible(unique_id)
|
|
assert result is False
|
|
|
|
|
|
def test_registry_owner_is_visible_owner_instance_none(console_widget_with_static_id):
|
|
"""Test owner_is_visible returns False when owner instance weakref is dead."""
|
|
widget = console_widget_with_static_id
|
|
unique_id = widget._unique_id
|
|
gui_id = widget.gui_id
|
|
|
|
# Simulate dead weakref
|
|
_web_console_registry._instances[gui_id] = lambda: None
|
|
|
|
result = _web_console_registry.owner_is_visible(unique_id)
|
|
assert result is False
|
|
|
|
|
|
def test_registry_owner_is_visible_owner_visible(console_widget_with_static_id):
|
|
"""Test owner_is_visible returns True when owner is visible."""
|
|
widget = console_widget_with_static_id
|
|
widget.show()
|
|
|
|
result = _web_console_registry.owner_is_visible(widget._unique_id)
|
|
assert result is True
|
|
|
|
|
|
def test_registry_owner_is_visible_owner_not_visible(console_widget_with_static_id):
|
|
"""Test owner_is_visible returns False when owner is not visible."""
|
|
widget = console_widget_with_static_id
|
|
widget.hide()
|
|
|
|
result = _web_console_registry.owner_is_visible(widget._unique_id)
|
|
assert result is False
|