from __future__ import annotations from decimal import Decimal from apps.parsers.clients.common.schemas import GenericParserItem from apps.parsers.clients.trudvsem import TrudvsemClient from apps.parsers.clients.vacancies import ( HHVacanciesClient, SuperJobVacanciesClient, VacanciesClient, VacanciesClientError, ) from apps.parsers.models import ParserLoadLog from django.test import SimpleTestCase from tests.utils import TestHTTPServer class TrudvsemClientTest(SimpleTestCase): def test_fetch_vacancies_by_company_inn_uses_company_endpoint(self): with TestHTTPServer() as server: server.add_json( "/vacancies/company/inn/7701000001", { "results": { "vacancies": [ { "vacancy": { "id": "trudvsem-1", "company": { "inn": "7701000001", "ogrn": "1027700000001", "name": "АО Реестр", }, "job-name": "Инженер", "salary": {"from": 100000}, } } ] } }, ) client = TrudvsemClient( base_url=server.base_url, http_adapter=server.adapter, ) records = client.fetch_vacancies( company_inn="7701000001", limit=100, offset=0, ) self.assertEqual(len(records), 1) self.assertEqual(records[0].external_id, "trudvsem-1") self.assertEqual(records[0].inn, "7701000001") self.assertEqual(records[0].ogrn, "1027700000001") class HHVacanciesClientTest(SimpleTestCase): def test_fetch_vacancies_maps_hh_items_to_generic_records(self): with TestHTTPServer() as server: server.add_json( "/vacancies", { "items": [ { "id": "123", "name": "Инженер", "published_at": "2026-05-06T10:00:00+0300", "salary": {"from": 120000, "to": 150000}, "employer": {"id": "77", "name": "АО Пример"}, "alternate_url": "https://hh.ru/vacancy/123", "type": {"name": "Открытая"}, "archived": False, } ] }, ) client = HHVacanciesClient( base_url=server.base_url, http_adapter=server.adapter, ) records = client.fetch_vacancies(limit=10, offset=0, text="Инженер") self.assertEqual(len(records), 1) record = records[0] self.assertEqual(record.source, ParserLoadLog.Source.TRUDVSEM) self.assertEqual(record.external_id, "hh:123") self.assertEqual(record.title, "Инженер") self.assertEqual(record.organisation_name, "АО Пример") self.assertEqual(record.amount, Decimal("120000")) self.assertEqual(record.url, "https://hh.ru/vacancy/123") self.assertEqual(record.payload["vacancy_source"], "hh") def test_fetch_vacancies_tolerates_unexpected_nested_shapes(self): with TestHTTPServer() as server: server.add_json( "/vacancies", { "items": [ { "id": "124", "name": "Инженер", "salary": "по договоренности", "employer": "АО Пример", "type": "open", } ] }, ) client = HHVacanciesClient( base_url=server.base_url, http_adapter=server.adapter, ) records = client.fetch_vacancies(limit=10) self.assertEqual(len(records), 1) self.assertEqual(records[0].external_id, "hh:124") self.assertEqual(records[0].organisation_name, "") self.assertIsNone(records[0].amount) class SuperJobVacanciesClientTest(SimpleTestCase): def test_fetch_vacancies_maps_superjob_objects_to_generic_records(self): with TestHTTPServer() as server: server.add_json( "/2.0/vacancies/", { "objects": [ { "id": 456, "id_client": 99, "profession": "Технолог", "firm_name": "ООО Пример", "payment_from": 90000, "payment_to": 130000, "date_published": 1778064000, "link": "https://www.superjob.ru/vakansii/456.html", "is_archive": False, } ], "total": 1, "more": False, }, ) client = SuperJobVacanciesClient( app_id="test-app-id", base_url=server.base_url, http_adapter=server.adapter, ) records = client.fetch_vacancies(limit=10, offset=0, text="Технолог") self.assertEqual(len(records), 1) record = records[0] self.assertEqual(record.source, ParserLoadLog.Source.TRUDVSEM) self.assertEqual(record.external_id, "superjob:456") self.assertEqual(record.title, "Технолог") self.assertEqual(record.organisation_name, "ООО Пример") self.assertEqual(record.amount, Decimal("90000")) self.assertEqual(record.url, "https://www.superjob.ru/vakansii/456.html") self.assertEqual(record.payload["vacancy_source"], "superjob") class VacanciesClientTest(SimpleTestCase): def test_fetch_vacancies_combines_enabled_sources(self): class _Provider: def __init__(self, source_name: str): self.source_name = source_name def fetch_vacancies(self, **kwargs): return [ GenericParserItem( source=ParserLoadLog.Source.TRUDVSEM, external_id=f"{self.source_name}:1", payload={"vacancy_source": self.source_name, "kwargs": kwargs}, ) ] client = VacanciesClient( source_clients={ "trudvsem": _Provider("trudvsem"), "hh": _Provider("hh"), "superjob": _Provider("superjob"), } ) records = client.fetch_vacancies(limit=10, offset=5, text="инженер") self.assertEqual( [record.payload["vacancy_source"] for record in records], ["trudvsem", "hh", "superjob"], ) def test_fetch_vacancies_skips_sources_without_company_inn_support(self): class _Provider: supports_company_inn = False def fetch_vacancies(self, **kwargs): return [ GenericParserItem( source=ParserLoadLog.Source.TRUDVSEM, external_id="unsupported:1", ) ] class _InnProvider: supports_company_inn = True def fetch_vacancies(self, **kwargs): return [ GenericParserItem( source=ParserLoadLog.Source.TRUDVSEM, external_id="trudvsem:1", ) ] client = VacanciesClient( source_clients={ "trudvsem": _InnProvider(), "hh": _Provider(), "superjob": _Provider(), } ) records = client.fetch_vacancies(company_inn="7700000000") self.assertEqual([record.external_id for record in records], ["trudvsem:1"]) def test_fetch_vacancies_skips_unconfigured_optional_source_by_default(self): class _Provider: supports_company_inn = True def fetch_vacancies(self, **kwargs): return [ GenericParserItem( source=ParserLoadLog.Source.TRUDVSEM, external_id="trudvsem:1", ) ] client = VacanciesClient(source_clients={"trudvsem": _Provider()}) records = client.fetch_vacancies() self.assertEqual([record.external_id for record in records], ["trudvsem:1"]) def test_fetch_vacancies_requires_superjob_app_id_when_explicitly_selected(self): client = VacanciesClient(sources=["superjob"], superjob_app_id="") with self.assertRaises(VacanciesClientError): client.fetch_vacancies()