Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions ui/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,51 @@
# Global flag to track initialization
_initialized = False

# Isolation: the tests write QSettings (~/.config/opensnitch/settings.conf),
# delete settings.conf, and toggle the autostart .desktop file. Redirect HOME
# and the XDG dirs to a throwaway directory *before* opensnitch or Qt cache
# any path, so a test run never touches an installed OpenSnitch UI.
import hashlib
import os
import pwd
import shutil
import sys
import tempfile

if any(m == "opensnitch" or m.startswith("opensnitch.") for m in sys.modules):
raise RuntimeError("opensnitch was imported before tests/conftest.py could isolate HOME; refusing to run")

_REAL_HOME = pwd.getpwuid(os.getuid()).pw_dir
_SANDBOX = tempfile.mkdtemp(prefix="opensnitch-ui-tests-")
for _var, _sub in (
("HOME", ""),
("XDG_CONFIG_HOME", ".config"),
("XDG_DATA_HOME", ".local/share"),
("XDG_CACHE_HOME", ".cache"),
("XDG_STATE_HOME", ".local/state"),
("XDG_RUNTIME_DIR", "run")):
_path = os.path.join(_SANDBOX, _sub)
os.makedirs(_path, mode=0o700, exist_ok=True)
os.environ[_var] = _path
# no desktop notifications or other D-Bus traffic to the real session
os.environ.pop("DBUS_SESSION_BUS_ADDRESS", None)

# files of a real installation that a test run must never modify
_REAL_FILES = [
os.path.join(_REAL_HOME, ".config/opensnitch/settings.conf"),
os.path.join(_REAL_HOME, ".config/autostart/opensnitch_ui.desktop"),
]

def _snapshot_real_files():
snap = {}
for path in _REAL_FILES:
try:
with open(path, "rb") as f:
snap[path] = hashlib.sha256(f.read()).hexdigest()
except FileNotFoundError:
snap[path] = None
return snap

def init_test_environment():
"""Initialize database and config after QApplication exists."""
global _initialized
Expand All @@ -35,6 +80,27 @@ def init_test_environment():

_initialized = True

@pytest.fixture(scope="session", autouse=True)
def isolated_home():
"""Fail loudly if anything resolves outside the sandbox, or if the
real installation's files changed during the run."""
try:
from PyQt6 import QtCore
from opensnitch.utils import xdg
except ImportError:
# tests that run without Qt: only the check of the real files applies
pass
else:
settings_file = QtCore.QSettings("opensnitch", "settings").fileName()
for path in (settings_file, xdg.xdg_config_home, xdg.xdg_runtime_dir, xdg.Autostart().userAutostart):
assert path.startswith(_SANDBOX + os.sep), f"not isolated: {path} is outside {_SANDBOX}"

before = _snapshot_real_files()
yield _SANDBOX
after = _snapshot_real_files()
shutil.rmtree(_SANDBOX, ignore_errors=True)
assert before == after, f"test run modified the real installation: {before} -> {after}"

@pytest.fixture(scope="session")
def qapp():
"""Create QApplication for the entire test session."""
Expand Down
21 changes: 11 additions & 10 deletions ui/tests/dialogs/test_preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def test_save_ui_settings(self, qtbot):
self.run(qtbot)

assert self.prefs.cfgMgr.getBool(self.prefs.cfgMgr.DEFAULT_IGNORE_RULES) == True and self.prefs.cfgMgr.getInt(self.prefs.cfgMgr.DEFAULT_IGNORE_TEMPORARY_RULES) == 1
cols = self.prefs.cfgMgr.getSettings(Config.STATS_SHOW_COLUMNS)
cols = self.prefs.cfgMgr.getSettings(Config.STATS_SHOW_COLUMNS + "_connections")
# Column indices changed since original test - just verify columns are saved
assert cols is not None and len(cols) > 0

Expand All @@ -76,8 +76,8 @@ def test_save_node_settings(self, qtbot, capsys):
self.prefs.checkNodeLogUTC.setChecked(False)
self.prefs.checkNodeLogMicro.setChecked(True)
self.prefs.checkInterceptUnknown.setChecked(True)
self.prefs.tabWidget.setCurrentIndex(self.prefs.TAB_NODES)
self.prefs._node_needs_update = True
self.prefs.stackedWidget.setCurrentIndex(self.prefs.TAB_NODES)
self.prefs.node_needs_update = True

self.run(qtbot)

Expand Down Expand Up @@ -161,8 +161,9 @@ def test_cancel_discards_changes(self, qtbot):
original_action = self.prefs.cfgMgr.getInt(self.prefs.cfgMgr.DEFAULT_ACTION_KEY)
original_timeout = self.prefs.cfgMgr.getInt(self.prefs.cfgMgr.DEFAULT_TIMEOUT_KEY, 15)

# Make changes
self.prefs.comboUIAction.setCurrentIndex(Config.ACTION_DENY_IDX)
# Make changes (to an action other than the saved one)
new_action = Config.ACTION_REJECT_IDX if original_action != Config.ACTION_REJECT_IDX else Config.ACTION_ALLOW_IDX
self.prefs.comboUIAction.setCurrentIndex(new_action)
self.prefs.spinUITimeout.setValue(99)

# This test needs exec() to verify cancel closes dialog without saving.
Expand Down Expand Up @@ -348,16 +349,16 @@ def test_settings_persistence_reopen(self, qtbot):
assert prefs2.comboUIAction.currentIndex() == Config.ACTION_ALLOW_IDX
assert prefs2.spinUITimeout.value() == 45

def test_action_deny_setting(self, qtbot):
"""Test saving deny action."""
def test_action_drop_setting(self, qtbot):
"""Test saving drop action."""
qtbot.addWidget(self.prefs)

self.prefs.comboUIAction.setCurrentIndex(Config.ACTION_DENY_IDX)
self.prefs.comboUIAction.setCurrentIndex(Config.ACTION_DROP_IDX)

self.run(qtbot)

assert self.prefs.cfgMgr.getInt(self.prefs.cfgMgr.DEFAULT_ACTION_KEY) == Config.ACTION_DENY_IDX
assert self.prefs.comboUIAction.currentText() == Config.ACTION_DENY
assert self.prefs.cfgMgr.getInt(self.prefs.cfgMgr.DEFAULT_ACTION_KEY) == Config.ACTION_DROP_IDX
assert self.prefs.comboUIAction.currentText() == Config.ACTION_DROP

def test_action_reject_setting(self, qtbot):
"""Test saving reject action."""
Expand Down