76f06c330b
- Replace ZhihuPlaywrightExtractor with ZhihuCrawler - Add comment crawling with include_comments=True by default - Implement cookie expiration detection and QR code login trigger - Add idempotency check via IdempotencyChecker - Add comment formatting with "## 热门评论" section - Convert HTML content to Markdown using html2text Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
613 lines
22 KiB
Python
613 lines
22 KiB
Python
"""Tests for ZhihuAdapter
|
|
|
|
Tests cover:
|
|
- Answer content extraction
|
|
- Zhuanlan (article) content extraction
|
|
- Cookie-based authentication
|
|
- Login page detection
|
|
- AuthError handling
|
|
|
|
Phase 09 - Pipeline Integration:
|
|
- ZhihuAdapter now uses ZhihuCrawler instead of ZhihuPlaywrightExtractor
|
|
- Comments are included by default and appended to content
|
|
- Cookie expiration triggers QR code login
|
|
- Idempotency via IdempotencyChecker
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import MagicMock, patch, PropertyMock
|
|
import asyncio
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
from kb_workflow.pipeline.platforms.zhihu import ZhihuAdapter
|
|
from kb_workflow.pipeline.errors import AuthError
|
|
|
|
# =============================================================================
|
|
# Mock Fixtures for ZhihuCrawler Integration (Phase 09)
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_crawl_answer_result():
|
|
"""Mock CrawlResult from ZhihuCrawler.crawl_answer().
|
|
|
|
Simulates successful answer crawl with metadata and content.
|
|
Used for testing ZhihuAdapter integration with new crawler.
|
|
"""
|
|
from kb_workflow.crawler.models import (
|
|
CrawlResult, AnswerData, AuthorInfo, ContentMetadata, CommentData, CommentAuthor
|
|
)
|
|
|
|
return CrawlResult(
|
|
success=True,
|
|
content_type="answer",
|
|
data=AnswerData(
|
|
id=789012,
|
|
question_id=123456,
|
|
question_title="Test Question Title for Pipeline Integration",
|
|
content="<p>This is the <strong>answer</strong> content from ZhihuCrawler.</p>",
|
|
excerpt="This is the answer content from ZhihuCrawler.",
|
|
metadata=ContentMetadata(
|
|
author=AuthorInfo(
|
|
name="Test Author",
|
|
url_token="test-author-123",
|
|
headline="Test Author Headline"
|
|
),
|
|
voteup_count=1234,
|
|
comment_count=56,
|
|
created_time=1705312800, # 2024-01-15 10:00:00 UTC
|
|
updated_time=1705399200 # 2024-01-16 10:00:00 UTC
|
|
)
|
|
),
|
|
comments=[
|
|
CommentData(
|
|
id=111,
|
|
content="Great answer! Very helpful.",
|
|
author=CommentAuthor(name="Commenter1", url_token="commenter-1"),
|
|
created_time=1705316400,
|
|
like_count=42,
|
|
child_count=3,
|
|
is_author=False
|
|
),
|
|
CommentData(
|
|
id=222,
|
|
content="Thanks for sharing!",
|
|
author=CommentAuthor(name="Commenter2", url_token="commenter-2"),
|
|
created_time=1705320000,
|
|
like_count=15,
|
|
child_count=0,
|
|
is_author=False
|
|
)
|
|
],
|
|
error=None
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_crawl_article_result():
|
|
"""Mock CrawlResult from ZhihuCrawler.crawl_article().
|
|
|
|
Simulates successful zhuanlan article crawl.
|
|
Used for testing ZhihuAdapter integration with new crawler.
|
|
"""
|
|
from kb_workflow.crawler.models import (
|
|
CrawlResult, ArticleData, AuthorInfo, ContentMetadata, CommentData, CommentAuthor
|
|
)
|
|
|
|
return CrawlResult(
|
|
success=True,
|
|
content_type="article",
|
|
data=ArticleData(
|
|
id=123456,
|
|
title="Test Article Title for Pipeline Integration",
|
|
content="<h1>Article Content</h1><p>This is the <em>article</em> content.</p>",
|
|
excerpt="Article Content This is the article content.",
|
|
metadata=ContentMetadata(
|
|
author=AuthorInfo(
|
|
name="Test Article Author",
|
|
url_token="test-article-author",
|
|
headline="Writer and Developer"
|
|
),
|
|
voteup_count=5678,
|
|
comment_count=89,
|
|
created_time=1705226400, # 2024-01-14 10:00:00 UTC
|
|
updated_time=None
|
|
)
|
|
),
|
|
comments=[
|
|
CommentData(
|
|
id=333,
|
|
content="Excellent article!",
|
|
author=CommentAuthor(name="Reader1", url_token="reader-1"),
|
|
created_time=1705230000,
|
|
like_count=28,
|
|
child_count=1,
|
|
is_author=False
|
|
)
|
|
],
|
|
error=None
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_crawl_no_comments_result():
|
|
"""Mock CrawlResult without comments."""
|
|
from kb_workflow.crawler.models import (
|
|
CrawlResult, AnswerData, AuthorInfo, ContentMetadata
|
|
)
|
|
|
|
return CrawlResult(
|
|
success=True,
|
|
content_type="answer",
|
|
data=AnswerData(
|
|
id=789012,
|
|
question_id=123456,
|
|
question_title="Test Question No Comments",
|
|
content="<p>Answer without comments.</p>",
|
|
excerpt="Answer without comments.",
|
|
metadata=ContentMetadata(
|
|
author=AuthorInfo(name="Test Author", url_token="test-author"),
|
|
voteup_count=100,
|
|
comment_count=0,
|
|
created_time=1705312800,
|
|
updated_time=None
|
|
)
|
|
),
|
|
comments=[],
|
|
error=None
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_comments_list():
|
|
"""Mock list of CommentData for testing comment formatting.
|
|
|
|
Provides a realistic set of comments with various attributes.
|
|
"""
|
|
from kb_workflow.crawler.models import CommentData, CommentAuthor
|
|
|
|
return [
|
|
CommentData(
|
|
id=1001,
|
|
content="This is the first comment with some content.",
|
|
author=CommentAuthor(name="User1", url_token="user-1"),
|
|
created_time=1705316400,
|
|
like_count=100,
|
|
child_count=5,
|
|
is_author=False
|
|
),
|
|
CommentData(
|
|
id=1002,
|
|
content="This is the second comment, from the author.",
|
|
author=CommentAuthor(name="AuthorName", url_token="author-token"),
|
|
created_time=1705320000,
|
|
like_count=50,
|
|
child_count=2,
|
|
is_author=True
|
|
),
|
|
CommentData(
|
|
id=1003,
|
|
content="Third comment here.",
|
|
author=CommentAuthor(name="User3", url_token="user-3"),
|
|
created_time=1705323600,
|
|
like_count=25,
|
|
child_count=0,
|
|
is_author=False
|
|
)
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_zhihu_login_result():
|
|
"""Mock ZhihuLoginResult from ZhihuLogin.login_by_qrcode().
|
|
|
|
Simulates successful QR code login result.
|
|
Used for testing cookie refresh flow.
|
|
"""
|
|
from kb_workflow.auth.zhihu_login import ZhihuLoginResult
|
|
from pathlib import Path
|
|
|
|
return ZhihuLoginResult(
|
|
success=True,
|
|
cookie_str="z_c0=test_cookie_value; _xsrf=test_xsrf; d_c0=test_d_c0",
|
|
qrcode_path=Path("/tmp/test_qrcode.png"),
|
|
error_message=None
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_crawl_failed_result():
|
|
"""Mock failed CrawlResult for error handling tests."""
|
|
from kb_workflow.crawler.models import CrawlResult
|
|
|
|
return CrawlResult(
|
|
success=False,
|
|
content_type="answer",
|
|
data=None,
|
|
comments=None,
|
|
error="Cookie expired or invalid"
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_idempotency_checker():
|
|
"""Mock IdempotencyChecker for testing idempotent behavior."""
|
|
mock = MagicMock()
|
|
mock.is_processed.return_value = False
|
|
return mock
|
|
|
|
|
|
# =============================================================================
|
|
# Basic Adapter Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestZhihuAdapterBasic:
|
|
"""Tests for basic ZhihuAdapter functionality"""
|
|
|
|
def test_get_content_type_answer(self):
|
|
"""Test content type detection for answer URLs"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "test" # Avoid config loading
|
|
|
|
assert adapter.get_content_type("https://www.zhihu.com/question/123456/answer/789012") == "answer"
|
|
assert adapter.get_content_type("https://zhihu.com/question/123/answer/456") == "answer"
|
|
|
|
def test_get_content_type_zhuanlan(self):
|
|
"""Test content type detection for zhuanlan URLs"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "test" # Avoid config loading
|
|
|
|
assert adapter.get_content_type("https://zhuanlan.zhihu.com/p/123456") == "zhuanlan"
|
|
assert adapter.get_content_type("https://zhuanlan.zhihu.com/p/abc123") == "zhuanlan"
|
|
|
|
def test_get_content_type_unknown(self):
|
|
"""Test content type detection for unknown URLs"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "test" # Avoid config loading
|
|
|
|
assert adapter.get_content_type("https://www.zhihu.com/question/123456") == "unknown"
|
|
assert adapter.get_content_type("https://www.zhihu.com/") == "unknown"
|
|
assert adapter.get_content_type("https://example.com") == "unknown"
|
|
|
|
def test_get_output_filename(self):
|
|
"""Test output filename generation"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "test" # Avoid config loading
|
|
|
|
assert adapter.get_output_filename("789012", "answer", "metadata.json") == "zhihu_789012_metadata.json"
|
|
assert adapter.get_output_filename("123456", "zhuanlan", "content.md") == "zhihu_123456_content.md"
|
|
|
|
def test_platform_constant(self):
|
|
"""Test PLATFORM constant"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "test" # Avoid config loading
|
|
assert adapter.PLATFORM == "zhihu"
|
|
|
|
|
|
# =============================================================================
|
|
# ZhihuCrawler Integration Tests (Phase 09)
|
|
# =============================================================================
|
|
|
|
|
|
class TestZhihuAdapterCrawlerIntegration:
|
|
"""Tests for ZhihuAdapter integration with ZhihuCrawler.
|
|
|
|
These tests verify the adapter correctly uses the new ZhihuCrawler
|
|
instead of ZhihuPlaywrightExtractor.
|
|
"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_metadata_answer(self, mock_crawl_answer_result):
|
|
"""Test fetching answer metadata via ZhihuCrawler.
|
|
|
|
Verifies:
|
|
- ZhihuCrawler.crawl_answer() is called with include_comments=True
|
|
- Metadata is correctly extracted from CrawlResult
|
|
- Output format matches expected adapter response
|
|
"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "test_cookie"
|
|
|
|
with patch.object(adapter, '_crawl_with_crawler', return_value=mock_crawl_answer_result):
|
|
metadata = await adapter.fetch_metadata("789012", "answer", source_url="https://www.zhihu.com/question/123456/answer/789012")
|
|
|
|
assert metadata["title"] == "Test Question Title for Pipeline Integration"
|
|
assert metadata["author"] == "Test Author"
|
|
assert metadata["type"] == "answer"
|
|
assert metadata["voteup_count"] == 1234
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_metadata_article(self, mock_crawl_article_result):
|
|
"""Test fetching zhuanlan article metadata via ZhihuCrawler.
|
|
|
|
Verifies:
|
|
- ZhihuCrawler.crawl_article() is called with correct URL
|
|
- Metadata is correctly extracted from CrawlResult
|
|
- Output format matches expected adapter response
|
|
"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "test_cookie"
|
|
|
|
with patch.object(adapter, '_crawl_with_crawler', return_value=mock_crawl_article_result):
|
|
metadata = await adapter.fetch_metadata("123456", "zhuanlan")
|
|
|
|
assert metadata["title"] == "Test Article Title for Pipeline Integration"
|
|
assert metadata["author"] == "Test Article Author"
|
|
assert metadata["type"] == "zhuanlan"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_content_with_comments(self, mock_crawl_answer_result):
|
|
"""Test fetching content with comments appended.
|
|
|
|
Verifies:
|
|
- Content is fetched with include_comments=True
|
|
- Comments are appended to content in correct Markdown format
|
|
- Comment format: numbered list with author mention
|
|
"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "test_cookie"
|
|
|
|
with patch.object(adapter, '_crawl_with_crawler', return_value=mock_crawl_answer_result):
|
|
content = await adapter.fetch_content("789012", "answer", source_url="https://www.zhihu.com/question/123456/answer/789012")
|
|
|
|
# Verify YAML frontmatter
|
|
assert "---" in content
|
|
assert "title:" in content
|
|
|
|
# Verify main content
|
|
assert "Test Question Title for Pipeline Integration" in content
|
|
|
|
# Verify comments section is appended
|
|
assert "## 热门评论" in content
|
|
assert "@Commenter1" in content
|
|
assert "Great answer!" in content
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_metadata_no_cookie_raises_auth_error(self):
|
|
"""Test that missing cookie raises AuthError"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "" # Empty cookie
|
|
|
|
with pytest.raises(AuthError) as exc_info:
|
|
await adapter.fetch_metadata("789012", "answer")
|
|
|
|
assert exc_info.value.auth_type == "zhihu_cookie"
|
|
|
|
|
|
# =============================================================================
|
|
# Cookie Expiration and Auto-Login Tests (Task 2)
|
|
# =============================================================================
|
|
|
|
|
|
class TestZhihuAdapterCookieHandling:
|
|
"""Tests for cookie handling and auto-login"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cookie_valid_returns_data(self, mock_crawl_answer_result):
|
|
"""Test that valid cookie allows normal data retrieval"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "valid_cookie"
|
|
|
|
with patch.object(adapter, '_crawl_with_crawler', return_value=mock_crawl_answer_result):
|
|
metadata = await adapter.fetch_metadata("789012", "answer")
|
|
|
|
assert metadata["title"] == "Test Question Title for Pipeline Integration"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cookie_expired_triggers_login(self, mock_crawl_failed_result, mock_zhihu_login_result, mock_crawl_answer_result):
|
|
"""Test that expired cookie triggers QR code login flow.
|
|
|
|
Verifies:
|
|
- CrawlResult with success=False triggers login
|
|
- ZhihuLogin.login_by_qrcode() is called
|
|
- New cookie is used to retry the fetch
|
|
"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "expired_cookie"
|
|
|
|
# First call fails (cookie expired), second succeeds after login
|
|
crawl_results = [mock_crawl_failed_result, mock_crawl_answer_result]
|
|
|
|
with patch.object(adapter, '_crawl_with_crawler', side_effect=crawl_results):
|
|
with patch.object(adapter, '_trigger_qrcode_login', return_value=mock_zhihu_login_result):
|
|
metadata = await adapter.fetch_metadata("789012", "answer")
|
|
|
|
# Verify login was triggered and cookie was updated
|
|
assert adapter._cookie == mock_zhihu_login_result.cookie_str
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_login_retry_success(self, mock_crawl_failed_result, mock_zhihu_login_result, mock_crawl_answer_result):
|
|
"""Test that after successful login, retry succeeds"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "expired_cookie"
|
|
|
|
crawl_results = [mock_crawl_failed_result, mock_crawl_answer_result]
|
|
|
|
with patch.object(adapter, '_crawl_with_crawler', side_effect=crawl_results):
|
|
with patch.object(adapter, '_trigger_qrcode_login', return_value=mock_zhihu_login_result):
|
|
content = await adapter.fetch_content("789012", "answer")
|
|
|
|
# Verify login was triggered and cookie was updated
|
|
assert adapter._cookie == mock_zhihu_login_result.cookie_str
|
|
assert "Test Question Title for Pipeline Integration" in content
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_login_failure_raises_error(self, mock_crawl_failed_result):
|
|
"""Test that failed login raises appropriate error"""
|
|
from kb_workflow.auth.zhihu_login import ZhihuLoginResult
|
|
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "expired_cookie"
|
|
|
|
failed_login = ZhihuLoginResult(
|
|
success=False,
|
|
cookie_str="",
|
|
error_message="Login timeout"
|
|
)
|
|
|
|
with patch.object(adapter, '_crawl_with_crawler', return_value=mock_crawl_failed_result):
|
|
with patch.object(adapter, '_trigger_qrcode_login', return_value=failed_login):
|
|
with pytest.raises(AuthError) as exc_info:
|
|
await adapter.fetch_metadata("789012", "answer")
|
|
|
|
assert exc_info.value.auth_type == "zhihu_cookie"
|
|
|
|
|
|
# =============================================================================
|
|
# Comment Formatting Tests (Task 3)
|
|
# =============================================================================
|
|
|
|
|
|
class TestZhihuAdapterCommentFormatting:
|
|
"""Tests for comment formatting functionality"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_comment_formatting_with_comments(self, mock_crawl_answer_result):
|
|
"""Test that comments are formatted with proper Markdown structure.
|
|
|
|
Verifies:
|
|
- Comments section starts with "---" separator
|
|
- "## 热门评论" heading is present
|
|
- Each comment is numbered with bold author name
|
|
"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "test_cookie"
|
|
|
|
with patch.object(adapter, '_crawl_with_crawler', return_value=mock_crawl_answer_result):
|
|
content = await adapter.fetch_content("789012", "answer")
|
|
|
|
# Check comment section structure
|
|
assert "---" in content
|
|
assert "## 热门评论" in content
|
|
|
|
# Check comment formatting: numbered list with author
|
|
assert "1. **@Commenter1**" in content or "1. **@Commenter1**" in content
|
|
assert "Great answer!" in content
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_comment_formatting_no_comments(self, mock_crawl_no_comments_result):
|
|
"""Test that no comments section is added when there are no comments.
|
|
|
|
Verifies:
|
|
- No "---" separator for empty comments
|
|
- No "## 热门评论" heading when comments list is empty
|
|
"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "test_cookie"
|
|
|
|
with patch.object(adapter, '_crawl_with_crawler', return_value=mock_crawl_no_comments_result):
|
|
content = await adapter.fetch_content("789012", "answer")
|
|
|
|
# No comments section should be present
|
|
assert "## 热门评论" not in content
|
|
|
|
def test_format_comments_section(self, mock_comments_list):
|
|
"""Test internal _format_comments_section method.
|
|
|
|
Verifies:
|
|
- Comments are formatted as numbered list
|
|
- Author name is bold/mentioned
|
|
- Like count is included
|
|
- Format matches expected style:
|
|
1. **@AuthorName** (100 likes): Comment content...
|
|
"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "test"
|
|
|
|
result = adapter._format_comments_section(mock_comments_list)
|
|
|
|
# Check format structure
|
|
assert "1. **@User1**" in result
|
|
assert "(100 likes)" in result or "100 likes" in result
|
|
assert "This is the first comment" in result
|
|
|
|
# Check author comment indicator
|
|
assert "@AuthorName" in result
|
|
|
|
|
|
# =============================================================================
|
|
# Idempotency Tests (Task 3)
|
|
# =============================================================================
|
|
|
|
|
|
class TestZhihuAdapterIdempotency:
|
|
"""Tests for idempotent execution"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_idempotent_execution_skips_processed(self, mock_crawl_answer_result, tmp_path):
|
|
"""Test that idempotent check prevents reprocessing when output_dir is set.
|
|
|
|
Verifies:
|
|
- If output_dir is set and content is already processed, cached result is returned
|
|
- No duplicate fetches are made
|
|
"""
|
|
import json
|
|
|
|
adapter = ZhihuAdapter(output_dir=tmp_path)
|
|
adapter._cookie = "test_cookie"
|
|
|
|
# Create existing files to simulate already processed content
|
|
metadata_file = tmp_path / "zhihu_789012_metadata.json"
|
|
content_file = tmp_path / "zhihu_789012_content.md"
|
|
cached_metadata = {
|
|
"title": "Cached Title",
|
|
"author": "Cached Author",
|
|
"type": "answer"
|
|
}
|
|
metadata_file.write_text(json.dumps(cached_metadata))
|
|
content_file.write_text("Cached content")
|
|
|
|
# When content is already processed, should return cached result
|
|
with patch.object(adapter, '_crawl_with_crawler') as mock_crawl:
|
|
metadata = await adapter.fetch_metadata("789012", "answer")
|
|
|
|
# Should NOT have called the crawler since content is cached
|
|
mock_crawl.assert_not_called()
|
|
|
|
# Should return cached metadata
|
|
assert metadata["title"] == "Cached Title"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_different_content_processes_independently(self, mock_crawl_answer_result, mock_crawl_article_result):
|
|
"""Test that different content IDs are processed independently.
|
|
|
|
Verifies:
|
|
- Processing one content doesn't affect processing of another
|
|
- Each content_id is tracked separately
|
|
"""
|
|
adapter = ZhihuAdapter()
|
|
adapter._cookie = "test_cookie"
|
|
|
|
# Process answer
|
|
with patch.object(adapter, '_crawl_with_crawler', return_value=mock_crawl_answer_result):
|
|
metadata_answer = await adapter.fetch_metadata("789012", "answer")
|
|
|
|
# Process article
|
|
with patch.object(adapter, '_crawl_with_crawler', return_value=mock_crawl_article_result):
|
|
metadata_article = await adapter.fetch_metadata("123456", "zhuanlan")
|
|
|
|
# Both should return their respective data
|
|
assert metadata_answer["title"] == "Test Question Title for Pipeline Integration"
|
|
assert metadata_article["title"] == "Test Article Title for Pipeline Integration"
|
|
|
|
|
|
# =============================================================================
|
|
# Cookie Settings Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestZhihuAdapterCookieSettings:
|
|
"""Tests for cookie loading from settings"""
|
|
|
|
def test_cookie_from_settings(self):
|
|
"""Test cookie is loaded from settings"""
|
|
with patch('kb_workflow.pipeline.platforms.zhihu.get_settings') as mock_settings:
|
|
mock_secret = MagicMock()
|
|
mock_secret.get_secret_value.return_value = "test_cookie_value"
|
|
mock_settings.return_value.zhihu_cookie = mock_secret
|
|
|
|
adapter = ZhihuAdapter()
|
|
assert adapter.cookie == "test_cookie_value"
|