Files
agent-radar 1a3a4ccde1
Creator App Tests / PR Spam Gate (push) Skipped
Frontend Tests / PR Spam Gate (push) Skipped
NPM Format (website & console) / PR Spam Gate (push) Skipped
Pre-commit Checks / PR Spam Gate (push) Skipped
QwenPaw Creator Plugin Release / check-version (push) Canceled after 0s
QwenPaw Creator Plugin Release / upload-oss (push) Canceled after 0s
Tests / PR Spam Gate (push) Skipped
CodeQL / CodeQL (python) (push) Canceled after 0s
CodeQL / CodeQL (typescript) (push) Canceled after 0s
Creator App Tests / Creator Backend Tests (push) Canceled after 0s
Creator App Tests / Creator UI Build & Tests (push) Canceled after 0s
E2E Smoke Tests / Playwright UI Smoke (push) Canceled after 0s
Frontend Tests / Detect console changes (push) Canceled after 0s
NPM Format (website & console) / website format check (push) Canceled after 0s
NPM Format (website & console) / console format check (push) Canceled after 0s
Pre-commit Checks / Pre-commit (push) Canceled after 0s
QwenPaw Data Plugin Release / check-version (push) Canceled after 0s
Tests / Detect code changes (push) Canceled after 0s
Frontend Tests / Vitest Unit Tests (push) Canceled after 0s
Frontend Tests / Frontend Summary (push) Canceled after 0s
QwenPaw Data Plugin Release / upload-oss (push) Canceled after 0s
Tests / Maintainer Approval (push) Canceled after 0s
Tests / Unit Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - fallback (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p2 (push) Canceled after 0s
Tests / Coverage Report (push) Canceled after 0s
Tests / Test Summary (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:01:50 +08:00

184 lines
5.9 KiB
Python

# -*- coding: utf-8 -*-
"""Tests for qwenpaw.utils.platform helpers."""
from __future__ import annotations
import logging
import sys
from unittest.mock import MagicMock, patch
import pytest
from qwenpaw.utils.platform import (
is_windows_admin,
warn_unelevated_sandbox,
)
@pytest.fixture()
def _platform_caplog(caplog: pytest.LogCaptureFixture):
"""Capture logs from qwenpaw.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="qwenpaw.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(
"qwenpaw.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(
"qwenpaw.utils.platform.is_windows_admin",
return_value=False,
),
patch(
"qwenpaw.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(
"qwenpaw.utils.platform.is_windows_admin",
return_value=False,
),
patch(
"qwenpaw.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(
"qwenpaw.utils.platform.is_windows_admin",
return_value=False,
),
patch(
"qwenpaw.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
)