JobData/tests/job51/test_job51_client.py
win 8c2c2d29d7 feat(03): migrate job51+zhilian to crawler_core (ARCH-04/05)
job51 (spiderJobs/platforms/job51/):
- client.py: HTTPClient+Job51Sign from crawler_core
- api.py: ApiResult→Result, self._http→self.http_client, _request() POST overrides
- main.py: BaseFetcher/BaseSearcher from crawler_core
- sign.py: backward-compatible stub re-exporting crawler_core.qcwy.sign.Job51Sign

zhilian (spiderJobs/platforms/zhilian/):
- client.py: HTTPClient+ZhilianSign from crawler_core
- api.py: add _parse_zhilian_response (HTTP 200=success), add _parse()/_request()
  to all classes (GET fetchers + POST searcher overrides)
- main.py: BaseFetcher/BaseSearcher from crawler_core
- sign.py: backward-compatible stub re-exporting crawler_core.zhilian.sign.ZhilianSign

tests: 34 new mock tests (17 job51 + 17 zhilian)
Full regression: 98 passed (job51:17 + zhilian:17 + boss:22 + crawler_core:41 + 1)
2026-03-21 19:18:22 +08:00

217 lines
8.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
前程无忧 (51Job) HTTP 层 mock 测试ARCH-04 / QUAL-03
使用 unittest.mock.MagicMock 替代真实 HTTP 客户端,无网络依赖。
"""
from __future__ import annotations
from unittest.mock import MagicMock
from spiderJobs.platforms.job51.api import (
GetCompanyInfo,
GetJobDetail,
SearchCompanyJobs,
SearchRecommendJobs,
_parse_job51_response,
)
from spiderJobs.platforms.job51.client import Job51Client
from crawler_core.base import Result
# ─────────────────────────────────────────────────────────
# 1. _parse_job51_response 纯函数测试
# ─────────────────────────────────────────────────────────
class TestParseJob51Response:
def test_http_error_returns_failure(self):
result = _parse_job51_response(500, {})
assert result.success is False
assert result.status_code == 500
def test_status_zero_returns_failure(self):
result = _parse_job51_response(200, {"status": 0, "message": "系统繁忙"})
assert result.success is False
assert "系统繁忙" in result.error
def test_status_str_zero_returns_failure(self):
result = _parse_job51_response(200, {"status": "0", "message": "错误"})
assert result.success is False
def test_status_one_with_resultbody_joblist(self):
raw = {
"status": 1,
"resultbody": {
"jobList": {"items": [{"jobId": "123", "jobName": "Python 工程师"}], "totalCount": 1}
}
}
result = _parse_job51_response(200, raw)
assert result.success is True
assert len(result.list) == 1
assert result.list[0]["jobName"] == "Python 工程师"
def test_status_one_no_items_is_end_page(self):
raw = {"status": 1, "resultbody": {"jobList": {"items": []}}}
result = _parse_job51_response(200, raw)
assert result.success is True
assert result.is_end_page is True
def test_non_dict_raw_returns_failure(self):
result = _parse_job51_response(200, "not a dict")
assert result.success is False
def test_detail_payload(self):
raw = {"status": 1, "resultbody": {"companyName": "测试公司"}}
result = _parse_job51_response(200, raw)
assert result.success is True
assert result.data["companyName"] == "测试公司"
# ─────────────────────────────────────────────────────────
# 2. SearchRecommendJobs
# ─────────────────────────────────────────────────────────
class TestSearchRecommendJobs:
def _make_client(self, return_value):
mock_client = MagicMock()
mock_client.post.return_value = return_value
return mock_client
def test_search_success(self):
raw = {
"status": 1,
"resultbody": {
"jobList": {"items": [{"jobId": "1", "jobName": "测试职位"}]}
}
}
searcher = SearchRecommendJobs(job_area="020000",
client=self._make_client((200, raw)))
result = searcher.search(page_index=1)
assert result.success is True
assert len(result.list) == 1
def test_search_http_error(self):
searcher = SearchRecommendJobs(client=self._make_client((403, {})))
result = searcher.search(page_index=1)
assert result.success is False
assert result.status_code == 403
def test_search_biz_error(self):
raw = {"status": 0, "message": "接口限流"}
searcher = SearchRecommendJobs(client=self._make_client((200, raw)))
result = searcher.search(page_index=1)
assert result.success is False
# ─────────────────────────────────────────────────────────
# 3. GetJobDetail路径拼接版
# ─────────────────────────────────────────────────────────
class TestGetJobDetail:
def test_fetch_success(self):
mock_client = MagicMock()
mock_client.get.return_value = (200, {
"status": 1,
"resultbody": {"jobName": "数据工程师", "salary": "20k-30k"},
})
fetcher = GetJobDetail(job_id="170651439", client=mock_client)
result = fetcher.fetch()
assert result.success is True
assert result.data["jobName"] == "数据工程师"
def test_fetch_exception_handled(self):
mock_client = MagicMock()
mock_client.get.side_effect = ConnectionError("网络超时")
fetcher = GetJobDetail(job_id="123", client=mock_client)
result = fetcher.fetch()
assert result.success is False
assert "网络超时" in result.error
def test_fetch_http_error(self):
mock_client = MagicMock()
mock_client.get.return_value = (404, {})
fetcher = GetJobDetail(job_id="nonexist", client=mock_client)
result = fetcher.fetch()
assert result.success is False
assert result.status_code == 404
# ─────────────────────────────────────────────────────────
# 4. GetCompanyInfo
# ─────────────────────────────────────────────────────────
class TestGetCompanyInfo:
def test_fetch_success(self):
mock_client = MagicMock()
mock_client.get.return_value = (200, {
"status": 1,
"resultbody": {"companyName": "测试科技有限公司", "coId": "9825088"},
})
fetcher = GetCompanyInfo(company_id="9825088", client=mock_client)
result = fetcher.fetch()
assert result.success is True
assert result.data["companyName"] == "测试科技有限公司"
def test_fetch_exception(self):
mock_client = MagicMock()
mock_client.get.side_effect = TimeoutError("请求超时")
fetcher = GetCompanyInfo(company_id="123", client=mock_client)
result = fetcher.fetch()
assert result.success is False
# ─────────────────────────────────────────────────────────
# 5. SearchCompanyJobs
# ─────────────────────────────────────────────────────────
class TestSearchCompanyJobs:
def test_search_success(self):
mock_client = MagicMock()
mock_client.post.return_value = (200, {
"status": 1,
"resultbody": {"items": [{"jobId": "1"}], "totalCount": 1},
})
searcher = SearchCompanyJobs(company_id="9825088", client=mock_client)
result = searcher.search(page_index=1)
assert result.success is True
assert len(result.list) == 1
def test_search_empty(self):
mock_client = MagicMock()
mock_client.post.return_value = (200, {
"status": 1,
"resultbody": {"items": [], "totalCount": 0},
})
searcher = SearchCompanyJobs(company_id="9825088", client=mock_client)
result = searcher.search(page_index=1)
assert result.success is True
assert result.is_end_page is True
# ─────────────────────────────────────────────────────────
# 6. Job51Client — sign 注入
# ─────────────────────────────────────────────────────────
class TestJob51ClientHeaders:
def test_headers_contain_sign(self):
client = Job51Client()
headers = client._job51_headers(sign="test_sign_value")
assert headers["sign"] == "test_sign_value"
def test_headers_uuid_format(self):
client = Job51Client()
headers = client._job51_headers(sign="abc")
assert len(headers["uuid"]) >= 20
def test_headers_empty_account(self):
client = Job51Client()
headers = client._job51_headers(sign="xyz")
assert headers["user-token"] == ""
assert headers["account-id"] == ""