crawlo 1.4.7__py3-none-any.whl → 1.4.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crawlo might be problematic. Click here for more details.
- crawlo/__init__.py +90 -90
- crawlo/__version__.py +1 -1
- crawlo/cli.py +75 -75
- crawlo/commands/__init__.py +14 -14
- crawlo/commands/check.py +594 -594
- crawlo/commands/genspider.py +186 -186
- crawlo/commands/help.py +140 -140
- crawlo/commands/list.py +155 -155
- crawlo/commands/run.py +379 -379
- crawlo/commands/startproject.py +460 -460
- crawlo/commands/stats.py +187 -187
- crawlo/commands/utils.py +196 -196
- crawlo/config.py +320 -320
- crawlo/config_validator.py +277 -277
- crawlo/core/__init__.py +52 -52
- crawlo/core/engine.py +451 -451
- crawlo/core/processor.py +47 -47
- crawlo/core/scheduler.py +290 -290
- crawlo/crawler.py +698 -698
- crawlo/data/__init__.py +5 -5
- crawlo/data/user_agents.py +194 -194
- crawlo/downloader/__init__.py +280 -280
- crawlo/downloader/aiohttp_downloader.py +233 -233
- crawlo/downloader/cffi_downloader.py +250 -250
- crawlo/downloader/httpx_downloader.py +265 -265
- crawlo/downloader/hybrid_downloader.py +212 -212
- crawlo/downloader/playwright_downloader.py +425 -425
- crawlo/downloader/selenium_downloader.py +486 -486
- crawlo/event.py +45 -45
- crawlo/exceptions.py +214 -214
- crawlo/extension/__init__.py +64 -64
- crawlo/extension/health_check.py +141 -141
- crawlo/extension/log_interval.py +94 -94
- crawlo/extension/log_stats.py +70 -70
- crawlo/extension/logging_extension.py +53 -53
- crawlo/extension/memory_monitor.py +104 -104
- crawlo/extension/performance_profiler.py +133 -133
- crawlo/extension/request_recorder.py +107 -107
- crawlo/factories/__init__.py +27 -27
- crawlo/factories/base.py +68 -68
- crawlo/factories/crawler.py +104 -104
- crawlo/factories/registry.py +84 -84
- crawlo/factories/utils.py +134 -134
- crawlo/filters/__init__.py +170 -170
- crawlo/filters/aioredis_filter.py +347 -347
- crawlo/filters/memory_filter.py +261 -261
- crawlo/framework.py +306 -306
- crawlo/initialization/__init__.py +44 -44
- crawlo/initialization/built_in.py +391 -391
- crawlo/initialization/context.py +141 -141
- crawlo/initialization/core.py +240 -240
- crawlo/initialization/phases.py +229 -229
- crawlo/initialization/registry.py +143 -143
- crawlo/initialization/utils.py +48 -48
- crawlo/interfaces.py +23 -23
- crawlo/items/__init__.py +23 -23
- crawlo/items/base.py +23 -23
- crawlo/items/fields.py +52 -52
- crawlo/items/items.py +104 -104
- crawlo/logging/__init__.py +42 -42
- crawlo/logging/config.py +280 -276
- crawlo/logging/factory.py +175 -175
- crawlo/logging/manager.py +104 -104
- crawlo/middleware/__init__.py +87 -87
- crawlo/middleware/default_header.py +132 -132
- crawlo/middleware/download_delay.py +104 -104
- crawlo/middleware/middleware_manager.py +142 -142
- crawlo/middleware/offsite.py +123 -123
- crawlo/middleware/proxy.py +209 -209
- crawlo/middleware/request_ignore.py +86 -86
- crawlo/middleware/response_code.py +150 -150
- crawlo/middleware/response_filter.py +136 -136
- crawlo/middleware/retry.py +124 -124
- crawlo/mode_manager.py +287 -287
- crawlo/network/__init__.py +21 -21
- crawlo/network/request.py +408 -376
- crawlo/network/response.py +598 -569
- crawlo/pipelines/__init__.py +52 -52
- crawlo/pipelines/base_pipeline.py +452 -452
- crawlo/pipelines/bloom_dedup_pipeline.py +145 -146
- crawlo/pipelines/console_pipeline.py +39 -39
- crawlo/pipelines/csv_pipeline.py +316 -316
- crawlo/pipelines/database_dedup_pipeline.py +196 -197
- crawlo/pipelines/json_pipeline.py +218 -218
- crawlo/pipelines/memory_dedup_pipeline.py +104 -105
- crawlo/pipelines/mongo_pipeline.py +140 -139
- crawlo/pipelines/mysql_pipeline.py +468 -469
- crawlo/pipelines/pipeline_manager.py +100 -100
- crawlo/pipelines/redis_dedup_pipeline.py +155 -155
- crawlo/project.py +347 -347
- crawlo/queue/__init__.py +9 -9
- crawlo/queue/pqueue.py +38 -38
- crawlo/queue/queue_manager.py +591 -591
- crawlo/queue/redis_priority_queue.py +518 -518
- crawlo/settings/__init__.py +7 -7
- crawlo/settings/default_settings.py +287 -284
- crawlo/settings/setting_manager.py +219 -219
- crawlo/spider/__init__.py +658 -657
- crawlo/stats_collector.py +81 -81
- crawlo/subscriber.py +129 -129
- crawlo/task_manager.py +138 -138
- crawlo/templates/crawlo.cfg.tmpl +10 -10
- crawlo/templates/project/__init__.py.tmpl +1 -1
- crawlo/templates/project/items.py.tmpl +13 -13
- crawlo/templates/project/middlewares.py.tmpl +38 -38
- crawlo/templates/project/pipelines.py.tmpl +35 -35
- crawlo/templates/project/settings.py.tmpl +113 -109
- crawlo/templates/project/settings_distributed.py.tmpl +160 -156
- crawlo/templates/project/settings_gentle.py.tmpl +174 -170
- crawlo/templates/project/settings_high_performance.py.tmpl +175 -171
- crawlo/templates/project/settings_minimal.py.tmpl +102 -98
- crawlo/templates/project/settings_simple.py.tmpl +172 -168
- crawlo/templates/project/spiders/__init__.py.tmpl +9 -9
- crawlo/templates/run.py.tmpl +23 -23
- crawlo/templates/spider/spider.py.tmpl +32 -32
- crawlo/templates/spiders_init.py.tmpl +4 -4
- crawlo/tools/__init__.py +86 -86
- crawlo/tools/date_tools.py +289 -289
- crawlo/tools/distributed_coordinator.py +384 -384
- crawlo/tools/scenario_adapter.py +262 -262
- crawlo/tools/text_cleaner.py +232 -232
- crawlo/utils/__init__.py +74 -50
- crawlo/utils/batch_processor.py +276 -276
- crawlo/utils/config_manager.py +442 -442
- crawlo/utils/controlled_spider_mixin.py +439 -439
- crawlo/utils/db_helper.py +250 -250
- crawlo/utils/encoding_helper.py +190 -0
- crawlo/utils/error_handler.py +410 -410
- crawlo/utils/fingerprint.py +121 -121
- crawlo/utils/func_tools.py +82 -82
- crawlo/utils/large_scale_helper.py +344 -344
- crawlo/utils/leak_detector.py +335 -335
- crawlo/utils/misc.py +81 -81
- crawlo/utils/mongo_connection_pool.py +157 -157
- crawlo/utils/mysql_connection_pool.py +197 -197
- crawlo/utils/performance_monitor.py +285 -285
- crawlo/utils/queue_helper.py +175 -175
- crawlo/utils/redis_checker.py +90 -90
- crawlo/utils/redis_connection_pool.py +578 -578
- crawlo/utils/redis_key_validator.py +198 -198
- crawlo/utils/request.py +278 -278
- crawlo/utils/request_serializer.py +225 -225
- crawlo/utils/resource_manager.py +337 -337
- crawlo/utils/response_helper.py +113 -0
- crawlo/utils/selector_helper.py +138 -137
- crawlo/utils/singleton.py +69 -69
- crawlo/utils/spider_loader.py +201 -201
- crawlo/utils/text_helper.py +94 -94
- {crawlo-1.4.7.dist-info → crawlo-1.4.8.dist-info}/METADATA +831 -689
- crawlo-1.4.8.dist-info/RECORD +347 -0
- examples/__init__.py +7 -7
- tests/__init__.py +7 -7
- tests/advanced_tools_example.py +217 -217
- tests/authenticated_proxy_example.py +110 -110
- tests/baidu_performance_test.py +108 -108
- tests/baidu_test.py +59 -59
- tests/bug_check_test.py +250 -250
- tests/cleaners_example.py +160 -160
- tests/comprehensive_framework_test.py +212 -212
- tests/comprehensive_test.py +81 -81
- tests/comprehensive_testing_summary.md +186 -186
- tests/config_validation_demo.py +142 -142
- tests/controlled_spider_example.py +205 -205
- tests/date_tools_example.py +180 -180
- tests/debug_configure.py +69 -69
- tests/debug_framework_logger.py +84 -84
- tests/debug_log_config.py +126 -126
- tests/debug_log_levels.py +63 -63
- tests/debug_pipelines.py +66 -66
- tests/detailed_log_test.py +233 -233
- tests/direct_selector_helper_test.py +96 -96
- tests/distributed_dedup_test.py +467 -467
- tests/distributed_test.py +66 -66
- tests/distributed_test_debug.py +76 -76
- tests/dynamic_loading_example.py +523 -523
- tests/dynamic_loading_test.py +104 -104
- tests/error_handling_example.py +171 -171
- tests/explain_mysql_update_behavior.py +76 -76
- tests/final_comprehensive_test.py +151 -151
- tests/final_log_test.py +260 -260
- tests/final_validation_test.py +182 -182
- tests/fix_log_test.py +142 -142
- tests/framework_performance_test.py +202 -202
- tests/log_buffering_test.py +111 -111
- tests/log_generation_timing_test.py +153 -153
- tests/monitor_redis_dedup.sh +72 -72
- tests/ofweek_scrapy/ofweek_scrapy/items.py +12 -12
- tests/ofweek_scrapy/ofweek_scrapy/middlewares.py +100 -100
- tests/ofweek_scrapy/ofweek_scrapy/pipelines.py +13 -13
- tests/ofweek_scrapy/ofweek_scrapy/settings.py +84 -84
- tests/ofweek_scrapy/ofweek_scrapy/spiders/__init__.py +4 -4
- tests/ofweek_scrapy/scrapy.cfg +11 -11
- tests/optimized_performance_test.py +211 -211
- tests/performance_comparison.py +244 -244
- tests/queue_blocking_test.py +113 -113
- tests/queue_test.py +89 -89
- tests/redis_key_validation_demo.py +130 -130
- tests/request_params_example.py +150 -150
- tests/response_improvements_example.py +144 -144
- tests/scrapy_comparison/ofweek_scrapy.py +138 -138
- tests/scrapy_comparison/scrapy_test.py +133 -133
- tests/simple_cli_test.py +54 -54
- tests/simple_command_test.py +119 -119
- tests/simple_crawlo_test.py +126 -126
- tests/simple_follow_test.py +38 -38
- tests/simple_log_test2.py +137 -137
- tests/simple_optimization_test.py +128 -128
- tests/simple_queue_type_test.py +41 -41
- tests/simple_response_selector_test.py +94 -94
- tests/simple_selector_helper_test.py +154 -154
- tests/simple_selector_test.py +207 -207
- tests/simple_spider_test.py +49 -49
- tests/simple_url_test.py +73 -73
- tests/simulate_mysql_update_test.py +139 -139
- tests/spider_log_timing_test.py +177 -177
- tests/test_advanced_tools.py +148 -148
- tests/test_all_commands.py +230 -230
- tests/test_all_pipeline_fingerprints.py +133 -133
- tests/test_all_redis_key_configs.py +145 -145
- tests/test_asyncmy_usage.py +56 -56
- tests/test_batch_processor.py +178 -178
- tests/test_cleaners.py +54 -54
- tests/test_cli_arguments.py +118 -118
- tests/test_component_factory.py +174 -174
- tests/test_config_consistency.py +80 -80
- tests/test_config_merge.py +152 -152
- tests/test_config_validator.py +182 -182
- tests/test_controlled_spider_mixin.py +79 -79
- tests/test_crawler_process_import.py +38 -38
- tests/test_crawler_process_spider_modules.py +47 -47
- tests/test_crawlo_proxy_integration.py +114 -114
- tests/test_date_tools.py +123 -123
- tests/test_dedup_fix.py +220 -220
- tests/test_dedup_pipeline_consistency.py +124 -124
- tests/test_default_header_middleware.py +313 -313
- tests/test_distributed.py +65 -65
- tests/test_double_crawlo_fix.py +204 -204
- tests/test_double_crawlo_fix_simple.py +124 -124
- tests/test_download_delay_middleware.py +221 -221
- tests/test_downloader_proxy_compatibility.py +272 -272
- tests/test_edge_cases.py +305 -305
- tests/test_encoding_core.py +56 -56
- tests/test_encoding_detection.py +126 -126
- tests/test_enhanced_error_handler.py +270 -270
- tests/test_enhanced_error_handler_comprehensive.py +245 -245
- tests/test_error_handler_compatibility.py +112 -112
- tests/test_factories.py +252 -252
- tests/test_factory_compatibility.py +196 -196
- tests/test_final_validation.py +153 -153
- tests/test_fingerprint_consistency.py +135 -135
- tests/test_fingerprint_simple.py +51 -51
- tests/test_get_component_logger.py +83 -83
- tests/test_hash_performance.py +99 -99
- tests/test_integration.py +169 -169
- tests/test_item_dedup_redis_key.py +122 -122
- tests/test_large_scale_helper.py +235 -235
- tests/test_logging_enhancements.py +374 -374
- tests/test_logging_final.py +184 -184
- tests/test_logging_integration.py +312 -312
- tests/test_logging_system.py +282 -282
- tests/test_middleware_debug.py +141 -141
- tests/test_mode_consistency.py +51 -51
- tests/test_multi_directory.py +67 -67
- tests/test_multiple_spider_modules.py +80 -80
- tests/test_mysql_pipeline_config.py +164 -164
- tests/test_mysql_pipeline_error.py +98 -98
- tests/test_mysql_pipeline_init_log.py +82 -82
- tests/test_mysql_pipeline_integration.py +132 -132
- tests/test_mysql_pipeline_refactor.py +143 -143
- tests/test_mysql_pipeline_refactor_simple.py +85 -85
- tests/test_mysql_pipeline_robustness.py +195 -195
- tests/test_mysql_pipeline_types.py +88 -88
- tests/test_mysql_update_columns.py +93 -93
- tests/test_offsite_middleware.py +244 -244
- tests/test_offsite_middleware_simple.py +203 -203
- tests/test_optimized_selector_naming.py +100 -100
- tests/test_parsel.py +29 -29
- tests/test_performance.py +327 -327
- tests/test_performance_monitor.py +115 -115
- tests/test_pipeline_fingerprint_consistency.py +86 -86
- tests/test_priority_behavior.py +211 -211
- tests/test_priority_consistency.py +151 -151
- tests/test_priority_consistency_fixed.py +249 -249
- tests/test_proxy_health_check.py +32 -32
- tests/test_proxy_middleware.py +217 -217
- tests/test_proxy_middleware_enhanced.py +212 -212
- tests/test_proxy_middleware_integration.py +142 -142
- tests/test_proxy_middleware_refactored.py +207 -207
- tests/test_proxy_only.py +83 -83
- tests/test_proxy_providers.py +56 -56
- tests/test_proxy_stats.py +19 -19
- tests/test_proxy_strategies.py +59 -59
- tests/test_proxy_with_downloader.py +152 -152
- tests/test_queue_empty_check.py +41 -41
- tests/test_queue_manager_double_crawlo.py +173 -173
- tests/test_queue_manager_redis_key.py +179 -179
- tests/test_queue_naming.py +154 -154
- tests/test_queue_type.py +106 -106
- tests/test_queue_type_redis_config_consistency.py +130 -130
- tests/test_random_headers_default.py +322 -322
- tests/test_random_headers_necessity.py +308 -308
- tests/test_random_user_agent.py +72 -72
- tests/test_redis_config.py +28 -28
- tests/test_redis_connection_pool.py +294 -294
- tests/test_redis_key_naming.py +181 -181
- tests/test_redis_key_validator.py +123 -123
- tests/test_redis_queue.py +224 -224
- tests/test_redis_queue_name_fix.py +175 -175
- tests/test_redis_queue_type_fallback.py +129 -129
- tests/test_request_ignore_middleware.py +182 -182
- tests/test_request_params.py +111 -111
- tests/test_request_serialization.py +70 -70
- tests/test_response_code_middleware.py +349 -349
- tests/test_response_filter_middleware.py +427 -427
- tests/test_response_follow.py +104 -104
- tests/test_response_improvements.py +152 -152
- tests/test_response_selector_methods.py +92 -92
- tests/test_response_url_methods.py +70 -70
- tests/test_response_urljoin.py +86 -86
- tests/test_retry_middleware.py +333 -333
- tests/test_retry_middleware_realistic.py +273 -273
- tests/test_scheduler.py +252 -252
- tests/test_scheduler_config_update.py +133 -133
- tests/test_scrapy_style_encoding.py +112 -112
- tests/test_selector_helper.py +100 -100
- tests/test_selector_optimizations.py +146 -146
- tests/test_simple_response.py +61 -61
- tests/test_spider_loader.py +49 -49
- tests/test_spider_loader_comprehensive.py +69 -69
- tests/test_spider_modules.py +84 -84
- tests/test_spiders/test_spider.py +9 -9
- tests/test_telecom_spider_redis_key.py +205 -205
- tests/test_template_content.py +87 -87
- tests/test_template_redis_key.py +134 -134
- tests/test_tools.py +159 -159
- tests/test_user_agent_randomness.py +176 -176
- tests/test_user_agents.py +96 -96
- tests/untested_features_report.md +138 -138
- tests/verify_debug.py +51 -51
- tests/verify_distributed.py +117 -117
- tests/verify_log_fix.py +111 -111
- tests/verify_mysql_warnings.py +109 -109
- crawlo/utils/log.py +0 -80
- crawlo/utils/url_utils.py +0 -40
- crawlo-1.4.7.dist-info/RECORD +0 -347
- {crawlo-1.4.7.dist-info → crawlo-1.4.8.dist-info}/WHEEL +0 -0
- {crawlo-1.4.7.dist-info → crawlo-1.4.8.dist-info}/entry_points.txt +0 -0
- {crawlo-1.4.7.dist-info → crawlo-1.4.8.dist-info}/top_level.txt +0 -0
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
#!/usr/bin/python
|
|
2
|
-
# -*- coding:UTF-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
RequestIgnoreMiddleware 测试文件
|
|
5
|
-
用于测试请求忽略中间件的功能
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
import unittest
|
|
10
|
-
from unittest.mock import Mock, patch
|
|
11
|
-
|
|
12
|
-
from crawlo.middleware.request_ignore import RequestIgnoreMiddleware
|
|
13
|
-
from crawlo.exceptions import IgnoreRequestError
|
|
14
|
-
from crawlo.settings.setting_manager import SettingManager
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class MockLogger:
|
|
18
|
-
"""Mock Logger 类,用于测试日志输出"""
|
|
19
|
-
def __init__(self, name, level=None):
|
|
20
|
-
self.name = name
|
|
21
|
-
self.level = level
|
|
22
|
-
self.logs = []
|
|
23
|
-
|
|
24
|
-
def debug(self, msg):
|
|
25
|
-
self.logs.append(('debug', msg))
|
|
26
|
-
|
|
27
|
-
def info(self, msg):
|
|
28
|
-
self.logs.append(('info', msg))
|
|
29
|
-
|
|
30
|
-
def warning(self, msg):
|
|
31
|
-
self.logs.append(('warning', msg))
|
|
32
|
-
|
|
33
|
-
def error(self, msg):
|
|
34
|
-
self.logs.append(('error', msg))
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class MockStats:
|
|
38
|
-
"""Mock Stats 类,用于测试统计信息"""
|
|
39
|
-
def __init__(self):
|
|
40
|
-
self.stats = {}
|
|
41
|
-
|
|
42
|
-
def inc_value(self, key, value=1):
|
|
43
|
-
if key in self.stats:
|
|
44
|
-
self.stats[key] += value
|
|
45
|
-
else:
|
|
46
|
-
self.stats[key] = value
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class TestRequestIgnoreMiddleware(unittest.TestCase):
|
|
50
|
-
"""RequestIgnoreMiddleware 测试类"""
|
|
51
|
-
|
|
52
|
-
def setUp(self):
|
|
53
|
-
"""测试前准备"""
|
|
54
|
-
# 创建设置管理器
|
|
55
|
-
self.settings = SettingManager()
|
|
56
|
-
|
|
57
|
-
# 创建爬虫模拟对象
|
|
58
|
-
self.crawler = Mock()
|
|
59
|
-
self.crawler.settings = self.settings
|
|
60
|
-
self.crawler.stats = MockStats()
|
|
61
|
-
|
|
62
|
-
@patch('crawlo.utils.log.get_logger')
|
|
63
|
-
def test_middleware_initialization(self, mock_get_logger):
|
|
64
|
-
"""测试中间件初始化"""
|
|
65
|
-
self.settings.set('LOG_LEVEL', 'INFO')
|
|
66
|
-
mock_get_logger.return_value = MockLogger('RequestIgnoreMiddleware')
|
|
67
|
-
|
|
68
|
-
# 应该正常创建实例
|
|
69
|
-
middleware = RequestIgnoreMiddleware.create_instance(self.crawler)
|
|
70
|
-
self.assertIsInstance(middleware, RequestIgnoreMiddleware)
|
|
71
|
-
|
|
72
|
-
@patch('crawlo.utils.log.get_logger')
|
|
73
|
-
def test_request_ignore_event_handling(self, mock_get_logger):
|
|
74
|
-
"""测试请求忽略事件处理"""
|
|
75
|
-
self.settings.set('LOG_LEVEL', 'DEBUG')
|
|
76
|
-
mock_logger = MockLogger('RequestIgnoreMiddleware')
|
|
77
|
-
mock_get_logger.return_value = mock_logger
|
|
78
|
-
|
|
79
|
-
# 创建中间件实例
|
|
80
|
-
mock_stats = MockStats()
|
|
81
|
-
middleware = RequestIgnoreMiddleware(
|
|
82
|
-
stats=mock_stats,
|
|
83
|
-
log_level='DEBUG'
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
# 创建异常和请求对象
|
|
87
|
-
exc = IgnoreRequestError("test reason")
|
|
88
|
-
request = Mock()
|
|
89
|
-
request.url = 'http://example.com/page'
|
|
90
|
-
|
|
91
|
-
# 处理忽略事件
|
|
92
|
-
asyncio.run(middleware.request_ignore(exc, request, Mock()))
|
|
93
|
-
|
|
94
|
-
# 验证统计信息
|
|
95
|
-
self.assertIn('request_ignore_count', mock_stats.stats)
|
|
96
|
-
self.assertEqual(mock_stats.stats['request_ignore_count'], 1)
|
|
97
|
-
self.assertIn('request_ignore_count/reason/test reason', mock_stats.stats)
|
|
98
|
-
|
|
99
|
-
@patch('crawlo.utils.log.get_logger')
|
|
100
|
-
def test_request_ignore_event_handling_with_domain(self, mock_get_logger):
|
|
101
|
-
"""测试带域名的请求忽略事件处理"""
|
|
102
|
-
self.settings.set('LOG_LEVEL', 'DEBUG')
|
|
103
|
-
mock_logger = MockLogger('RequestIgnoreMiddleware')
|
|
104
|
-
mock_get_logger.return_value = mock_logger
|
|
105
|
-
|
|
106
|
-
# 创建中间件实例
|
|
107
|
-
mock_stats = MockStats()
|
|
108
|
-
middleware = RequestIgnoreMiddleware(
|
|
109
|
-
stats=mock_stats,
|
|
110
|
-
log_level='DEBUG'
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
# 创建异常和请求对象
|
|
114
|
-
exc = IgnoreRequestError("test reason")
|
|
115
|
-
request = Mock()
|
|
116
|
-
request.url = 'http://example.com/page'
|
|
117
|
-
|
|
118
|
-
# 处理忽略事件
|
|
119
|
-
asyncio.run(middleware.request_ignore(exc, request, Mock()))
|
|
120
|
-
|
|
121
|
-
# 验证域名统计信息
|
|
122
|
-
self.assertIn('request_ignore_count/domain/example.com', mock_stats.stats)
|
|
123
|
-
|
|
124
|
-
@patch('crawlo.utils.log.get_logger')
|
|
125
|
-
def test_request_ignore_event_handling_with_invalid_url(self, mock_get_logger):
|
|
126
|
-
"""测试带无效URL的请求忽略事件处理"""
|
|
127
|
-
self.settings.set('LOG_LEVEL', 'DEBUG')
|
|
128
|
-
mock_logger = MockLogger('RequestIgnoreMiddleware')
|
|
129
|
-
mock_get_logger.return_value = mock_logger
|
|
130
|
-
|
|
131
|
-
# 创建中间件实例
|
|
132
|
-
mock_stats = MockStats()
|
|
133
|
-
middleware = RequestIgnoreMiddleware(
|
|
134
|
-
stats=mock_stats,
|
|
135
|
-
log_level='DEBUG'
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
# 创建异常和请求对象(没有url属性,会触发异常)
|
|
139
|
-
exc = IgnoreRequestError("test reason")
|
|
140
|
-
request = Mock()
|
|
141
|
-
# 不设置request.url属性,这样在访问时会抛出AttributeError
|
|
142
|
-
|
|
143
|
-
# 处理忽略事件
|
|
144
|
-
asyncio.run(middleware.request_ignore(exc, request, Mock()))
|
|
145
|
-
|
|
146
|
-
# 验证无效URL统计信息
|
|
147
|
-
self.assertIn('request_ignore_count/domain/invalid_url', mock_stats.stats)
|
|
148
|
-
|
|
149
|
-
def test_process_exception_with_ignore_request_error(self):
|
|
150
|
-
"""测试处理IgnoreRequestError异常"""
|
|
151
|
-
# 创建中间件实例
|
|
152
|
-
middleware = RequestIgnoreMiddleware(
|
|
153
|
-
stats=MockStats(),
|
|
154
|
-
log_level='INFO'
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
# 创建异常和请求对象
|
|
158
|
-
exc = IgnoreRequestError("test reason")
|
|
159
|
-
request = Mock()
|
|
160
|
-
|
|
161
|
-
# 应该返回True表示异常已被处理
|
|
162
|
-
result = middleware.process_exception(request, exc, Mock())
|
|
163
|
-
self.assertTrue(result)
|
|
164
|
-
|
|
165
|
-
def test_process_exception_with_other_exception(self):
|
|
166
|
-
"""测试处理其他异常"""
|
|
167
|
-
# 创建中间件实例
|
|
168
|
-
middleware = RequestIgnoreMiddleware(
|
|
169
|
-
stats=MockStats(),
|
|
170
|
-
log_level='INFO'
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
# 创建异常和请求对象
|
|
174
|
-
exc = ValueError("test error")
|
|
175
|
-
request = Mock()
|
|
176
|
-
|
|
177
|
-
# 应该返回None表示异常未被处理
|
|
178
|
-
result = middleware.process_exception(request, exc, Mock())
|
|
179
|
-
self.assertIsNone(result)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if __name__ == '__main__':
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding:UTF-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
RequestIgnoreMiddleware 测试文件
|
|
5
|
+
用于测试请求忽略中间件的功能
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import unittest
|
|
10
|
+
from unittest.mock import Mock, patch
|
|
11
|
+
|
|
12
|
+
from crawlo.middleware.request_ignore import RequestIgnoreMiddleware
|
|
13
|
+
from crawlo.exceptions import IgnoreRequestError
|
|
14
|
+
from crawlo.settings.setting_manager import SettingManager
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MockLogger:
|
|
18
|
+
"""Mock Logger 类,用于测试日志输出"""
|
|
19
|
+
def __init__(self, name, level=None):
|
|
20
|
+
self.name = name
|
|
21
|
+
self.level = level
|
|
22
|
+
self.logs = []
|
|
23
|
+
|
|
24
|
+
def debug(self, msg):
|
|
25
|
+
self.logs.append(('debug', msg))
|
|
26
|
+
|
|
27
|
+
def info(self, msg):
|
|
28
|
+
self.logs.append(('info', msg))
|
|
29
|
+
|
|
30
|
+
def warning(self, msg):
|
|
31
|
+
self.logs.append(('warning', msg))
|
|
32
|
+
|
|
33
|
+
def error(self, msg):
|
|
34
|
+
self.logs.append(('error', msg))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MockStats:
|
|
38
|
+
"""Mock Stats 类,用于测试统计信息"""
|
|
39
|
+
def __init__(self):
|
|
40
|
+
self.stats = {}
|
|
41
|
+
|
|
42
|
+
def inc_value(self, key, value=1):
|
|
43
|
+
if key in self.stats:
|
|
44
|
+
self.stats[key] += value
|
|
45
|
+
else:
|
|
46
|
+
self.stats[key] = value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestRequestIgnoreMiddleware(unittest.TestCase):
|
|
50
|
+
"""RequestIgnoreMiddleware 测试类"""
|
|
51
|
+
|
|
52
|
+
def setUp(self):
|
|
53
|
+
"""测试前准备"""
|
|
54
|
+
# 创建设置管理器
|
|
55
|
+
self.settings = SettingManager()
|
|
56
|
+
|
|
57
|
+
# 创建爬虫模拟对象
|
|
58
|
+
self.crawler = Mock()
|
|
59
|
+
self.crawler.settings = self.settings
|
|
60
|
+
self.crawler.stats = MockStats()
|
|
61
|
+
|
|
62
|
+
@patch('crawlo.utils.log.get_logger')
|
|
63
|
+
def test_middleware_initialization(self, mock_get_logger):
|
|
64
|
+
"""测试中间件初始化"""
|
|
65
|
+
self.settings.set('LOG_LEVEL', 'INFO')
|
|
66
|
+
mock_get_logger.return_value = MockLogger('RequestIgnoreMiddleware')
|
|
67
|
+
|
|
68
|
+
# 应该正常创建实例
|
|
69
|
+
middleware = RequestIgnoreMiddleware.create_instance(self.crawler)
|
|
70
|
+
self.assertIsInstance(middleware, RequestIgnoreMiddleware)
|
|
71
|
+
|
|
72
|
+
@patch('crawlo.utils.log.get_logger')
|
|
73
|
+
def test_request_ignore_event_handling(self, mock_get_logger):
|
|
74
|
+
"""测试请求忽略事件处理"""
|
|
75
|
+
self.settings.set('LOG_LEVEL', 'DEBUG')
|
|
76
|
+
mock_logger = MockLogger('RequestIgnoreMiddleware')
|
|
77
|
+
mock_get_logger.return_value = mock_logger
|
|
78
|
+
|
|
79
|
+
# 创建中间件实例
|
|
80
|
+
mock_stats = MockStats()
|
|
81
|
+
middleware = RequestIgnoreMiddleware(
|
|
82
|
+
stats=mock_stats,
|
|
83
|
+
log_level='DEBUG'
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# 创建异常和请求对象
|
|
87
|
+
exc = IgnoreRequestError("test reason")
|
|
88
|
+
request = Mock()
|
|
89
|
+
request.url = 'http://example.com/page'
|
|
90
|
+
|
|
91
|
+
# 处理忽略事件
|
|
92
|
+
asyncio.run(middleware.request_ignore(exc, request, Mock()))
|
|
93
|
+
|
|
94
|
+
# 验证统计信息
|
|
95
|
+
self.assertIn('request_ignore_count', mock_stats.stats)
|
|
96
|
+
self.assertEqual(mock_stats.stats['request_ignore_count'], 1)
|
|
97
|
+
self.assertIn('request_ignore_count/reason/test reason', mock_stats.stats)
|
|
98
|
+
|
|
99
|
+
@patch('crawlo.utils.log.get_logger')
|
|
100
|
+
def test_request_ignore_event_handling_with_domain(self, mock_get_logger):
|
|
101
|
+
"""测试带域名的请求忽略事件处理"""
|
|
102
|
+
self.settings.set('LOG_LEVEL', 'DEBUG')
|
|
103
|
+
mock_logger = MockLogger('RequestIgnoreMiddleware')
|
|
104
|
+
mock_get_logger.return_value = mock_logger
|
|
105
|
+
|
|
106
|
+
# 创建中间件实例
|
|
107
|
+
mock_stats = MockStats()
|
|
108
|
+
middleware = RequestIgnoreMiddleware(
|
|
109
|
+
stats=mock_stats,
|
|
110
|
+
log_level='DEBUG'
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# 创建异常和请求对象
|
|
114
|
+
exc = IgnoreRequestError("test reason")
|
|
115
|
+
request = Mock()
|
|
116
|
+
request.url = 'http://example.com/page'
|
|
117
|
+
|
|
118
|
+
# 处理忽略事件
|
|
119
|
+
asyncio.run(middleware.request_ignore(exc, request, Mock()))
|
|
120
|
+
|
|
121
|
+
# 验证域名统计信息
|
|
122
|
+
self.assertIn('request_ignore_count/domain/example.com', mock_stats.stats)
|
|
123
|
+
|
|
124
|
+
@patch('crawlo.utils.log.get_logger')
|
|
125
|
+
def test_request_ignore_event_handling_with_invalid_url(self, mock_get_logger):
|
|
126
|
+
"""测试带无效URL的请求忽略事件处理"""
|
|
127
|
+
self.settings.set('LOG_LEVEL', 'DEBUG')
|
|
128
|
+
mock_logger = MockLogger('RequestIgnoreMiddleware')
|
|
129
|
+
mock_get_logger.return_value = mock_logger
|
|
130
|
+
|
|
131
|
+
# 创建中间件实例
|
|
132
|
+
mock_stats = MockStats()
|
|
133
|
+
middleware = RequestIgnoreMiddleware(
|
|
134
|
+
stats=mock_stats,
|
|
135
|
+
log_level='DEBUG'
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# 创建异常和请求对象(没有url属性,会触发异常)
|
|
139
|
+
exc = IgnoreRequestError("test reason")
|
|
140
|
+
request = Mock()
|
|
141
|
+
# 不设置request.url属性,这样在访问时会抛出AttributeError
|
|
142
|
+
|
|
143
|
+
# 处理忽略事件
|
|
144
|
+
asyncio.run(middleware.request_ignore(exc, request, Mock()))
|
|
145
|
+
|
|
146
|
+
# 验证无效URL统计信息
|
|
147
|
+
self.assertIn('request_ignore_count/domain/invalid_url', mock_stats.stats)
|
|
148
|
+
|
|
149
|
+
def test_process_exception_with_ignore_request_error(self):
|
|
150
|
+
"""测试处理IgnoreRequestError异常"""
|
|
151
|
+
# 创建中间件实例
|
|
152
|
+
middleware = RequestIgnoreMiddleware(
|
|
153
|
+
stats=MockStats(),
|
|
154
|
+
log_level='INFO'
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# 创建异常和请求对象
|
|
158
|
+
exc = IgnoreRequestError("test reason")
|
|
159
|
+
request = Mock()
|
|
160
|
+
|
|
161
|
+
# 应该返回True表示异常已被处理
|
|
162
|
+
result = middleware.process_exception(request, exc, Mock())
|
|
163
|
+
self.assertTrue(result)
|
|
164
|
+
|
|
165
|
+
def test_process_exception_with_other_exception(self):
|
|
166
|
+
"""测试处理其他异常"""
|
|
167
|
+
# 创建中间件实例
|
|
168
|
+
middleware = RequestIgnoreMiddleware(
|
|
169
|
+
stats=MockStats(),
|
|
170
|
+
log_level='INFO'
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# 创建异常和请求对象
|
|
174
|
+
exc = ValueError("test error")
|
|
175
|
+
request = Mock()
|
|
176
|
+
|
|
177
|
+
# 应该返回None表示异常未被处理
|
|
178
|
+
result = middleware.process_exception(request, exc, Mock())
|
|
179
|
+
self.assertIsNone(result)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
if __name__ == '__main__':
|
|
183
183
|
unittest.main()
|
tests/test_request_params.py
CHANGED
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
#!/usr/bin/python
|
|
2
|
-
# -*- coding: UTF-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
Request参数处理测试
|
|
5
|
-
"""
|
|
6
|
-
import unittest
|
|
7
|
-
from crawlo.network.request import Request
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class TestRequestParams(unittest.TestCase):
|
|
11
|
-
"""Request参数处理测试类"""
|
|
12
|
-
|
|
13
|
-
def test_get_params_handling(self):
|
|
14
|
-
"""测试GET参数处理"""
|
|
15
|
-
# 测试GET请求带参数
|
|
16
|
-
params = {"page": 1, "limit": 10, "search": "test"}
|
|
17
|
-
request = Request(
|
|
18
|
-
url="https://api.example.com/users",
|
|
19
|
-
method="GET",
|
|
20
|
-
get_params=params
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
# 验证参数是否正确附加到URL上
|
|
24
|
-
self.assertIn("https://api.example.com/users?", request.url)
|
|
25
|
-
self.assertIn("page=1", request.url)
|
|
26
|
-
self.assertIn("limit=10", request.url)
|
|
27
|
-
self.assertIn("search=test", request.url)
|
|
28
|
-
self.assertEqual(request.method, "GET")
|
|
29
|
-
|
|
30
|
-
def test_form_data_with_get(self):
|
|
31
|
-
"""测试GET请求使用form_data"""
|
|
32
|
-
# 测试GET请求使用form_data参数
|
|
33
|
-
form_data = {"category": "books", "sort": "name"}
|
|
34
|
-
request = Request(
|
|
35
|
-
url="https://api.example.com/products",
|
|
36
|
-
method="GET",
|
|
37
|
-
form_data=form_data
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
# 验证form_data是否正确作为GET参数附加到URL上
|
|
41
|
-
self.assertIn("https://api.example.com/products?", request.url)
|
|
42
|
-
self.assertIn("category=books", request.url)
|
|
43
|
-
self.assertIn("sort=name", request.url)
|
|
44
|
-
self.assertEqual(request.method, "GET")
|
|
45
|
-
|
|
46
|
-
def test_form_data_with_post(self):
|
|
47
|
-
"""测试POST请求使用form_data"""
|
|
48
|
-
# 测试POST请求使用form_data参数
|
|
49
|
-
form_data = {"username": "testuser", "password": "testpass"}
|
|
50
|
-
request = Request(
|
|
51
|
-
url="https://api.example.com/login",
|
|
52
|
-
method="POST",
|
|
53
|
-
form_data=form_data
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
# 验证form_data是否作为请求体发送
|
|
57
|
-
self.assertEqual(request.method, "POST")
|
|
58
|
-
self.assertIn("username=testuser", request.body.decode('utf-8'))
|
|
59
|
-
self.assertIn("password=testpass", request.body.decode('utf-8'))
|
|
60
|
-
self.assertEqual(request.headers.get('Content-Type'), 'application/x-www-form-urlencoded')
|
|
61
|
-
|
|
62
|
-
def test_json_body_with_post(self):
|
|
63
|
-
"""测试POST请求使用json_body"""
|
|
64
|
-
# 测试POST请求使用json_body参数
|
|
65
|
-
json_data = {"name": "John", "age": 30}
|
|
66
|
-
request = Request(
|
|
67
|
-
url="https://api.example.com/users",
|
|
68
|
-
method="POST",
|
|
69
|
-
json_body=json_data
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
# 验证json_body是否正确序列化
|
|
73
|
-
self.assertEqual(request.method, "POST")
|
|
74
|
-
import json
|
|
75
|
-
self.assertEqual(json.loads(request.body.decode('utf-8')), json_data)
|
|
76
|
-
self.assertEqual(request.headers.get('Content-Type'), 'application/json')
|
|
77
|
-
|
|
78
|
-
def test_get_params_with_existing_query(self):
|
|
79
|
-
"""测试向已有查询参数的URL添加GET参数"""
|
|
80
|
-
# 测试向已有查询参数的URL添加更多参数
|
|
81
|
-
params = {"page": 2, "limit": 20}
|
|
82
|
-
request = Request(
|
|
83
|
-
url="https://api.example.com/users?category=active",
|
|
84
|
-
method="GET",
|
|
85
|
-
get_params=params
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
# 验证原有参数和新参数都存在
|
|
89
|
-
self.assertIn("https://api.example.com/users?", request.url)
|
|
90
|
-
self.assertIn("category=active", request.url)
|
|
91
|
-
self.assertIn("page=2", request.url)
|
|
92
|
-
self.assertIn("limit=20", request.url)
|
|
93
|
-
|
|
94
|
-
def test_form_data_conversion_to_get(self):
|
|
95
|
-
"""测试form_data在GET请求中自动转换为URL参数"""
|
|
96
|
-
# 测试form_data在GET请求中自动转换为URL参数的行为
|
|
97
|
-
form_data = {"filter": "active", "sort": "date"}
|
|
98
|
-
request = Request(
|
|
99
|
-
url="https://api.example.com/posts",
|
|
100
|
-
method="GET",
|
|
101
|
-
form_data=form_data
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
# 验证form_data被正确转换为GET参数
|
|
105
|
-
self.assertIn("https://api.example.com/posts?", request.url)
|
|
106
|
-
self.assertIn("filter=active", request.url)
|
|
107
|
-
self.assertIn("sort=date", request.url)
|
|
108
|
-
self.assertEqual(request.method, "GET")
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if __name__ == '__main__':
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Request参数处理测试
|
|
5
|
+
"""
|
|
6
|
+
import unittest
|
|
7
|
+
from crawlo.network.request import Request
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestRequestParams(unittest.TestCase):
|
|
11
|
+
"""Request参数处理测试类"""
|
|
12
|
+
|
|
13
|
+
def test_get_params_handling(self):
|
|
14
|
+
"""测试GET参数处理"""
|
|
15
|
+
# 测试GET请求带参数
|
|
16
|
+
params = {"page": 1, "limit": 10, "search": "test"}
|
|
17
|
+
request = Request(
|
|
18
|
+
url="https://api.example.com/users",
|
|
19
|
+
method="GET",
|
|
20
|
+
get_params=params
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# 验证参数是否正确附加到URL上
|
|
24
|
+
self.assertIn("https://api.example.com/users?", request.url)
|
|
25
|
+
self.assertIn("page=1", request.url)
|
|
26
|
+
self.assertIn("limit=10", request.url)
|
|
27
|
+
self.assertIn("search=test", request.url)
|
|
28
|
+
self.assertEqual(request.method, "GET")
|
|
29
|
+
|
|
30
|
+
def test_form_data_with_get(self):
|
|
31
|
+
"""测试GET请求使用form_data"""
|
|
32
|
+
# 测试GET请求使用form_data参数
|
|
33
|
+
form_data = {"category": "books", "sort": "name"}
|
|
34
|
+
request = Request(
|
|
35
|
+
url="https://api.example.com/products",
|
|
36
|
+
method="GET",
|
|
37
|
+
form_data=form_data
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# 验证form_data是否正确作为GET参数附加到URL上
|
|
41
|
+
self.assertIn("https://api.example.com/products?", request.url)
|
|
42
|
+
self.assertIn("category=books", request.url)
|
|
43
|
+
self.assertIn("sort=name", request.url)
|
|
44
|
+
self.assertEqual(request.method, "GET")
|
|
45
|
+
|
|
46
|
+
def test_form_data_with_post(self):
|
|
47
|
+
"""测试POST请求使用form_data"""
|
|
48
|
+
# 测试POST请求使用form_data参数
|
|
49
|
+
form_data = {"username": "testuser", "password": "testpass"}
|
|
50
|
+
request = Request(
|
|
51
|
+
url="https://api.example.com/login",
|
|
52
|
+
method="POST",
|
|
53
|
+
form_data=form_data
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# 验证form_data是否作为请求体发送
|
|
57
|
+
self.assertEqual(request.method, "POST")
|
|
58
|
+
self.assertIn("username=testuser", request.body.decode('utf-8'))
|
|
59
|
+
self.assertIn("password=testpass", request.body.decode('utf-8'))
|
|
60
|
+
self.assertEqual(request.headers.get('Content-Type'), 'application/x-www-form-urlencoded')
|
|
61
|
+
|
|
62
|
+
def test_json_body_with_post(self):
|
|
63
|
+
"""测试POST请求使用json_body"""
|
|
64
|
+
# 测试POST请求使用json_body参数
|
|
65
|
+
json_data = {"name": "John", "age": 30}
|
|
66
|
+
request = Request(
|
|
67
|
+
url="https://api.example.com/users",
|
|
68
|
+
method="POST",
|
|
69
|
+
json_body=json_data
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# 验证json_body是否正确序列化
|
|
73
|
+
self.assertEqual(request.method, "POST")
|
|
74
|
+
import json
|
|
75
|
+
self.assertEqual(json.loads(request.body.decode('utf-8')), json_data)
|
|
76
|
+
self.assertEqual(request.headers.get('Content-Type'), 'application/json')
|
|
77
|
+
|
|
78
|
+
def test_get_params_with_existing_query(self):
|
|
79
|
+
"""测试向已有查询参数的URL添加GET参数"""
|
|
80
|
+
# 测试向已有查询参数的URL添加更多参数
|
|
81
|
+
params = {"page": 2, "limit": 20}
|
|
82
|
+
request = Request(
|
|
83
|
+
url="https://api.example.com/users?category=active",
|
|
84
|
+
method="GET",
|
|
85
|
+
get_params=params
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# 验证原有参数和新参数都存在
|
|
89
|
+
self.assertIn("https://api.example.com/users?", request.url)
|
|
90
|
+
self.assertIn("category=active", request.url)
|
|
91
|
+
self.assertIn("page=2", request.url)
|
|
92
|
+
self.assertIn("limit=20", request.url)
|
|
93
|
+
|
|
94
|
+
def test_form_data_conversion_to_get(self):
|
|
95
|
+
"""测试form_data在GET请求中自动转换为URL参数"""
|
|
96
|
+
# 测试form_data在GET请求中自动转换为URL参数的行为
|
|
97
|
+
form_data = {"filter": "active", "sort": "date"}
|
|
98
|
+
request = Request(
|
|
99
|
+
url="https://api.example.com/posts",
|
|
100
|
+
method="GET",
|
|
101
|
+
form_data=form_data
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# 验证form_data被正确转换为GET参数
|
|
105
|
+
self.assertIn("https://api.example.com/posts?", request.url)
|
|
106
|
+
self.assertIn("filter=active", request.url)
|
|
107
|
+
self.assertIn("sort=date", request.url)
|
|
108
|
+
self.assertEqual(request.method, "GET")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == '__main__':
|
|
112
112
|
unittest.main()
|