From fb9bc2d1edb1d6377d4f5b2016ec9fd91fb1b846 Mon Sep 17 00:00:00 2001 From: starmen1265 Date: Tue, 17 Mar 2026 17:50:26 +0800 Subject: [PATCH] test(07-01): add failing tests for ZhihuLogin QR code login TDD RED phase - tests expect ZhihuLogin class that doesn't exist yet: - test_login_page_access: verify correct login URL - test_qrcode_screenshot: verify QR code capture and save - test_cookie_detection_with_z_c0: verify login detection - test_cookie_detection_without_z_c0: verify no login when missing z_c0 - test_cookie_extraction: verify cookie string format - test_login_timeout: verify timeout after 60s polling - test_result_model_success: verify success result model - test_result_model_failure: verify failure result model Co-Authored-By: Claude Opus 4.6 --- tests/test_auth/test_qrcode_login.py | 147 +++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 tests/test_auth/test_qrcode_login.py diff --git a/tests/test_auth/test_qrcode_login.py b/tests/test_auth/test_qrcode_login.py new file mode 100644 index 0000000..bbb56d3 --- /dev/null +++ b/tests/test_auth/test_qrcode_login.py @@ -0,0 +1,147 @@ +"""Tests for ZhihuLogin QR code login functionality + +Tests the core login flow: +1. Access login page +2. Capture QR code screenshot +3. Poll for login cookie (z_c0) +4. Extract cookie string on success +5. Handle timeout +""" + +import pytest +from unittest.mock import MagicMock, patch, AsyncMock +from pathlib import Path + + +class TestZhihuLogin: + """Test suite for ZhihuLogin class.""" + + def test_login_page_access(self, mock_browser): + """Test that login page is accessed with correct URL.""" + from kb_workflow.auth import ZhihuLogin + + login = ZhihuLogin(intermediate_path="/tmp/test") + login.browser = mock_browser + + # Simulate calling goto + login.browser.goto("https://www.zhihu.com/signin") + + # Verify goto was called with correct URL + mock_browser.goto.assert_called_once_with("https://www.zhihu.com/signin") + + def test_qrcode_screenshot(self, mock_browser, mock_canvas, intermediate_path): + """Test QR code screenshot is captured and saved correctly.""" + from kb_workflow.auth import ZhihuLogin + + login = ZhihuLogin(intermediate_path=intermediate_path) + login.browser = mock_browser + + # Mock the canvas screenshot + mock_browser.query_selector.return_value = mock_canvas + mock_canvas.screenshot.return_value = b"fake_png_bytes" + + # Call _save_qrcode_image + screenshot_bytes = b"fake_png_bytes" + qrcode_path = login._save_qrcode_image(screenshot_bytes) + + # Verify file was created + assert qrcode_path.exists() + assert qrcode_path.suffix == ".png" + assert "zhihu_qrcode" in qrcode_path.name + + def test_cookie_detection_with_z_c0(self, mock_browser, sample_cookies): + """Test that login is detected when z_c0 cookie exists.""" + from kb_workflow.auth import ZhihuLogin + + login = ZhihuLogin(intermediate_path="/tmp/test") + login.browser = mock_browser + + # Mock cookies to include z_c0 + mock_browser.context.cookies.return_value = sample_cookies + + # Check login cookie + is_logged_in = login.check_login_cookie() + + assert is_logged_in is True + + def test_cookie_detection_without_z_c0(self, mock_browser, sample_cookies_no_login): + """Test that login is NOT detected when z_c0 cookie is missing.""" + from kb_workflow.auth import ZhihuLogin + + login = ZhihuLogin(intermediate_path="/tmp/test") + login.browser = mock_browser + + # Mock cookies without z_c0 + mock_browser.context.cookies.return_value = sample_cookies_no_login + + # Check login cookie + is_logged_in = login.check_login_cookie() + + assert is_logged_in is False + + def test_cookie_extraction(self, mock_browser, sample_cookies): + """Test cookie string extraction after successful login.""" + from kb_workflow.auth import ZhihuLogin + + login = ZhihuLogin(intermediate_path="/tmp/test") + login.browser = mock_browser + + # Mock cookies + mock_browser.context.cookies.return_value = sample_cookies + + # Get cookie string + cookie_str = login.get_cookies_str() + + # Verify format: "name1=value1; name2=value2" + assert "z_c0=test_z_c0_value" in cookie_str + assert "_xsrf=test_xsrf_value" in cookie_str + assert ";" in cookie_str + + def test_login_timeout(self, mock_browser, sample_cookies_no_login): + """Test that TimeoutError is raised after 60 seconds of polling.""" + from kb_workflow.auth import ZhihuLogin + from tenacity import RetryError + + login = ZhihuLogin(intermediate_path="/tmp/test", timeout=2) # Short timeout for test + login.browser = mock_browser + + # Mock cookies to always return no login + mock_browser.context.cookies.return_value = sample_cookies_no_login + + # Should raise RetryError (wrapped by tenacity) or TimeoutError + with pytest.raises((TimeoutError, RetryError)): + login._wait_for_login() + + +class TestZhihuLoginResult: + """Test suite for ZhihuLoginResult model.""" + + def test_result_model_success(self): + """Test ZhihuLoginResult model with successful login.""" + from kb_workflow.auth import ZhihuLoginResult + + result = ZhihuLoginResult( + success=True, + cookie_str="z_c0=abc123; _xsrf=xyz789", + qrcode_path=Path("/tmp/qrcode.png"), + error_message=None + ) + + assert result.success is True + assert "z_c0" in result.cookie_str + assert result.error_message is None + + def test_result_model_failure(self): + """Test ZhihuLoginResult model with failed login.""" + from kb_workflow.auth import ZhihuLoginResult + + result = ZhihuLoginResult( + success=False, + cookie_str="", + qrcode_path=Path("/tmp/qrcode.png"), + error_message="Login timeout after 60 seconds" + ) + + assert result.success is False + assert result.cookie_str == "" + assert "timeout" in result.error_message.lower()