""" 智联招聘 HTTP 层 mock 测试(ARCH-05 / QUAL-03) 使用 unittest.mock.MagicMock 替代真实 HTTP 客户端,无网络依赖。 """ from __future__ import annotations from unittest.mock import MagicMock from crawler_core.zhilian.sign import ZhilianSign from spiderJobs.platforms.zhilian.api import ( GetCompanyDetail, GetCompanyExtDetail, GetPositionDetail, SearchCompanyPositions, SearchPositions, ) from spiderJobs.platforms.zhilian.client import ZhilianClient from crawler_core.base import Result # ───────────────────────────────────────────────────────── # 1. SearchPositions(POST cgate) # ───────────────────────────────────────────────────────── class TestSearchPositions: def _make_client(self, status_code=200, data=None): mock_client = MagicMock() mock_client.post.return_value = (status_code, data or {}) return mock_client def test_search_success_returns_list(self): data = { "data": { "list": [{"number": "CC123", "name": "Python 工程师"}], "numFound": 1, }, } searcher = SearchPositions( keyword="Python", city_code=538, client=self._make_client(200, data), ) result = searcher.search(page_index=1) assert result.success is True def test_search_http_403(self): searcher = SearchPositions(client=self._make_client(403, {})) result = searcher.search(page_index=1) assert result.success is False assert result.status_code == 403 def test_search_http_500(self): searcher = SearchPositions(client=self._make_client(500, {})) result = searcher.search(page_index=1) assert result.success is False def test_search_builds_keyword_param(self): mock_client = MagicMock() mock_client.post.return_value = (200, {"data": {"list": []}}) searcher = SearchPositions(keyword="Java", city_code=530, client=mock_client) searcher.search(page_index=1) assert mock_client.post.called call_kwargs = mock_client.post.call_args body = call_kwargs[0][1] if len(call_kwargs[0]) > 1 else None if body: assert "Java" in str(body) # ───────────────────────────────────────────────────────── # 2. GetPositionDetail(GET cgate) # ───────────────────────────────────────────────────────── class TestGetPositionDetail: def test_fetch_success(self): mock_client = MagicMock() mock_client.get.return_value = (200, { "data": {"number": "CC123", "jobName": "高级工程师"}, }) fetcher = GetPositionDetail(number="CC123", client=mock_client) result = fetcher.fetch() assert result.success is True def test_fetch_404(self): mock_client = MagicMock() mock_client.get.return_value = (404, {}) fetcher = GetPositionDetail(number="notexist", client=mock_client) result = fetcher.fetch() assert result.success is False assert result.status_code == 404 # ───────────────────────────────────────────────────────── # 3. GetCompanyExtDetail(GET cgate) # ───────────────────────────────────────────────────────── class TestGetCompanyExtDetail: def test_fetch_success(self): mock_client = MagicMock() mock_client.get.return_value = (200, { "data": {"companyName": "智联测试公司"}, }) fetcher = GetCompanyExtDetail( company_name="智联测试公司", company_number="CZ123", client=mock_client, ) result = fetcher.fetch() assert result.success is True # ───────────────────────────────────────────────────────── # 4. GetCompanyDetail(GET cgate) # ───────────────────────────────────────────────────────── class TestGetCompanyDetail: def test_fetch_success(self): mock_client = MagicMock() mock_client.get.return_value = (200, { "data": {"companyNumber": "CZ123", "name": "智联公司"}, }) fetcher = GetCompanyDetail(number="CZ123", client=mock_client) result = fetcher.fetch() assert result.success is True def test_fetch_http_error(self): mock_client = MagicMock() mock_client.get.return_value = (500, {}) fetcher = GetCompanyDetail(number="CZ123", client=mock_client) result = fetcher.fetch() assert result.success is False # ───────────────────────────────────────────────────────── # 5. SearchCompanyPositions(GET capi)— 验证 sign_params 被调用 # ───────────────────────────────────────────────────────── class TestSearchCompanyPositions: def test_search_success_calls_sign_params(self): mock_signer = MagicMock(spec=ZhilianSign) mock_signer.sign_params.return_value = {"at": "", "rt": ""} mock_client = MagicMock() mock_client.signer = mock_signer mock_client.get.return_value = (200, { "data": {"list": [{"jobName": "测试岗位"}]}, "pageInfo": {}, }) searcher = SearchCompanyPositions(company_id="CZ123", client=mock_client) result = searcher.search(page_index=1) assert result.success is True assert mock_signer.sign_params.called # 确认 sign_params 被调用 def test_search_http_error(self): mock_signer = MagicMock(spec=ZhilianSign) mock_signer.sign_params.return_value = {} mock_client = MagicMock() mock_client.signer = mock_signer mock_client.get.return_value = (403, {}) searcher = SearchCompanyPositions(company_id="CZ123", client=mock_client) result = searcher.search(page_index=1) assert result.success is False # ───────────────────────────────────────────────────────── # 6. ZhilianClient — 签名头注入 # ───────────────────────────────────────────────────────── class TestZhilianClientHeaders: def test_sign_headers_injects_at_rt(self): signer = ZhilianSign(at="test_at", rt="test_rt") client = ZhilianClient(signer=signer) headers = client.signer.sign_headers() assert headers["x-zp-at"] == "test_at" assert headers["x-zp-rt"] == "test_rt" def test_sign_headers_has_required_keys(self): client = ZhilianClient() headers = client.signer.sign_headers() for key in ["x-zp-at", "x-zp-rt", "x-zp-action-id", "x-zp-device-id"]: assert key in headers, f"缺少头信息: {key}" def test_default_signer_empty_tokens(self): client = ZhilianClient() headers = client.signer.sign_headers() assert headers["x-zp-at"] == "" assert headers["x-zp-rt"] == "" def test_sign_params_has_required_keys(self): client = ZhilianClient() params = client.signer.sign_params() for key in ["at", "rt", "channel", "platform", "version", "d"]: assert key in params, f"缺少签名参数: {key}"