184 lines
5.9 KiB
Python
184 lines
5.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Tests for pineagents.utils.platform helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import sys
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from pineagents.utils.platform import (
|
|
is_windows_admin,
|
|
warn_unelevated_sandbox,
|
|
)
|
|
|
|
|
|
@pytest.fixture()
|
|
def _platform_caplog(caplog: pytest.LogCaptureFixture):
|
|
"""Capture logs from pineagents.utils.platform.
|
|
|
|
The project logger sets propagate=False on the qwenpaw namespace,
|
|
so caplog (which hooks the root logger) won't see records unless we
|
|
temporarily re-enable propagation.
|
|
"""
|
|
target = logging.getLogger("qwenpaw")
|
|
old_propagate = target.propagate
|
|
target.propagate = True
|
|
with caplog.at_level("WARNING", logger="pineagents.utils.platform"):
|
|
yield
|
|
target.propagate = old_propagate
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# is_windows_admin
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestIsWindowsAdmin:
|
|
"""Tests for is_windows_admin()."""
|
|
|
|
def test_non_windows_returns_true(self) -> None:
|
|
"""Non-Windows platforms return True (no-op)."""
|
|
with patch.object(sys, "platform", "linux"):
|
|
assert is_windows_admin() is True
|
|
|
|
def test_non_windows_macos_returns_true(self) -> None:
|
|
"""On macOS, is_windows_admin() returns True (no-op)."""
|
|
with patch.object(sys, "platform", "darwin"):
|
|
assert is_windows_admin() is True
|
|
|
|
def test_windows_admin_returns_true(self) -> None:
|
|
"""When Windows IsUserAnAdmin() returns 1 (admin), returns True."""
|
|
mock_ctypes = MagicMock()
|
|
mock_ctypes.windll.shell32.IsUserAnAdmin.return_value = 1
|
|
with (
|
|
patch.object(sys, "platform", "win32"),
|
|
patch.dict(sys.modules, {"ctypes": mock_ctypes}),
|
|
):
|
|
assert is_windows_admin() is True
|
|
|
|
def test_windows_non_admin_returns_false(self) -> None:
|
|
"""IsUserAnAdmin() returns 0 (non-admin) -> False."""
|
|
mock_ctypes = MagicMock()
|
|
mock_ctypes.windll.shell32.IsUserAnAdmin.return_value = 0
|
|
with (
|
|
patch.object(sys, "platform", "win32"),
|
|
patch.dict(sys.modules, {"ctypes": mock_ctypes}),
|
|
):
|
|
assert is_windows_admin() is False
|
|
|
|
def test_windows_ctypes_exception_returns_false(self) -> None:
|
|
"""When ctypes raises an exception, returns False (conservative)."""
|
|
mock_ctypes = MagicMock()
|
|
mock_ctypes.windll.shell32.IsUserAnAdmin.side_effect = OSError(
|
|
"ctypes failed",
|
|
)
|
|
with (
|
|
patch.object(sys, "platform", "win32"),
|
|
patch.dict(sys.modules, {"ctypes": mock_ctypes}),
|
|
):
|
|
assert is_windows_admin() is False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# warn_unelevated_sandbox
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestWarnUnelevatedSandbox:
|
|
"""Tests for warn_unelevated_sandbox()."""
|
|
|
|
def test_non_windows_is_noop(self) -> None:
|
|
"""On non-Windows platforms, function returns immediately."""
|
|
with patch.object(sys, "platform", "linux"):
|
|
warn_unelevated_sandbox()
|
|
|
|
def test_windows_admin_is_noop(self) -> None:
|
|
"""Admin on Windows: no warning logged."""
|
|
with (
|
|
patch.object(sys, "platform", "win32"),
|
|
patch(
|
|
"pineagents.utils.platform.is_windows_admin",
|
|
return_value=True,
|
|
),
|
|
):
|
|
warn_unelevated_sandbox()
|
|
|
|
def test_windows_non_admin_sandbox_disabled_logs_warning(
|
|
self,
|
|
_platform_caplog,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""When non-admin and sandbox_enabled=True, logs a warning."""
|
|
mock_config = MagicMock()
|
|
mock_config.security.sandbox_enabled = True
|
|
|
|
with (
|
|
patch.object(sys, "platform", "win32"),
|
|
patch(
|
|
"pineagents.utils.platform.is_windows_admin",
|
|
return_value=False,
|
|
),
|
|
patch(
|
|
"pineagents.config.load_config",
|
|
return_value=mock_config,
|
|
),
|
|
):
|
|
warn_unelevated_sandbox()
|
|
|
|
assert any(
|
|
"sandbox downgraded" in record.message for record in caplog.records
|
|
)
|
|
|
|
def test_windows_non_admin_sandbox_already_off_no_warning(
|
|
self,
|
|
_platform_caplog,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""When non-admin and sandbox_enabled=False, no warning is logged."""
|
|
mock_config = MagicMock()
|
|
mock_config.security.sandbox_enabled = False
|
|
|
|
with (
|
|
patch.object(sys, "platform", "win32"),
|
|
patch(
|
|
"pineagents.utils.platform.is_windows_admin",
|
|
return_value=False,
|
|
),
|
|
patch(
|
|
"pineagents.config.load_config",
|
|
return_value=mock_config,
|
|
),
|
|
):
|
|
warn_unelevated_sandbox()
|
|
|
|
assert not any(
|
|
"sandbox downgraded" in record.message for record in caplog.records
|
|
)
|
|
|
|
def test_windows_non_admin_config_load_failure_logs_warning(
|
|
self,
|
|
_platform_caplog,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""When load_config raises, logs a warning but does not crash."""
|
|
with (
|
|
patch.object(sys, "platform", "win32"),
|
|
patch(
|
|
"pineagents.utils.platform.is_windows_admin",
|
|
return_value=False,
|
|
),
|
|
patch(
|
|
"pineagents.config.load_config",
|
|
side_effect=RuntimeError("config broken"),
|
|
),
|
|
):
|
|
warn_unelevated_sandbox()
|
|
|
|
assert any(
|
|
"unelevated sandbox check failed" in record.message
|
|
for record in caplog.records
|
|
)
|