From e92c2c62f777802663d90714ce0d23c087c3471f Mon Sep 17 00:00:00 2001 From: Shxiao Date: Sat, 12 Sep 2026 10:00:48 +0900 Subject: [PATCH] test(wechat): skip the POSIX mode-bit half of the auth-state tests on Windows (#5366) Two tests in test_wechat_channel.py assert that the persisted WeChat auth state is owner-only via stat().st_mode & 0o777 == 0o600. Windows has no POSIX mode bits: Path.chmod() only toggles the read-only attribute, so st_mode reports 0o100666 and the assertion can never hold there (same class as the skill-permission assertions handled in #5244). Skip only the mode-bit assertion on Windows from a single-sourced reason constant, and keep every platform-independent assertion running: the QR login flow, the persisted JSON content, and the "no *.tmp residue" atomicity check. In the tightening test the mode assertion moves after those checks so the Windows run still verifies that the atomic owner-only temp-file path leaves no residue behind. Verified on Windows: both tests red on upstream main (assert (33206 & 511) == 384), skipped after the change; the file is 32 passed, 2 skipped; ruff check/format clean. --- backend/tests/test_wechat_channel.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/tests/test_wechat_channel.py b/backend/tests/test_wechat_channel.py index 07c926509..451221dd4 100644 --- a/backend/tests/test_wechat_channel.py +++ b/backend/tests/test_wechat_channel.py @@ -6,13 +6,18 @@ import asyncio import base64 import json import logging +import os from pathlib import Path from typing import Any from unittest import mock from unittest.mock import AsyncMock +import pytest + from app.channels.message_bus import InboundMessageType, MessageBus, OutboundMessage +_POSIX_MODE_BITS_REASON = "Windows chmod only toggles the read-only bit, so the 0o600 mode asserted here is never observable" + def _run(coro): loop = asyncio.new_event_loop() @@ -1478,6 +1483,8 @@ def test_qrcode_login_binds_and_persists_auth_state(monkeypatch, tmp_path: Path) assert auth_state["status"] == "confirmed" assert auth_state["bot_token"] == "bound-token" assert auth_state["ilink_bot_id"] == "bot-99" + if os.name == "nt": + pytest.skip(_POSIX_MODE_BITS_REASON) assert ((state_dir / "wechat-auth.json").stat().st_mode & 0o777) == 0o600 _run(go()) @@ -1506,10 +1513,14 @@ def test_save_auth_state_tightens_preexisting_loose_file(tmp_path: Path): ) channel._save_auth_state(status="confirmed", bot_token="bound-token", ilink_bot_id="bot-1") - assert (auth_path.stat().st_mode & 0o777) == 0o600 assert json.loads(auth_path.read_text(encoding="utf-8"))["bot_token"] == "bound-token" # Atomic write leaves no temp-file residue behind. assert list(state_dir.glob("*.tmp")) == [] + # Keep the platform-independent half running on Windows; only the mode-bit + # half of the "owner-only inode" contract is unobservable there. + if os.name == "nt": + pytest.skip(_POSIX_MODE_BITS_REASON) + assert (auth_path.stat().st_mode & 0o777) == 0o600 def test_save_auth_state_chmod_failure_is_logged_not_warned(tmp_path: Path, caplog):