from __future__ import annotations import json import pytest from conftest import FakeFetcher, fixture_bytes, fixture_path, make_result from companyatlas.fetch import file_result from companyatlas.sdk import connector as C from companyatlas.sdk.connector import ConnectorContext CASES = [ ("greenhouse-v1", ("greenhouse", "stripe_jobs.json"), "https://boards-api.greenhouse.io/v1/boards/stripe/jobs?content=false", 20, "Abuse Investigator", "8172487"), ("lever-v1", ("lever", "palantir_postings.json"), "https://api.lever.co/v0/postings/palantir?mode=json", 20, "Administrative Business Partner", None), ("ashby-v1", ("ashby", "ashby_board.json"), "https://api.ashbyhq.com/posting-api/job-board/ashby", 20, "Engineering Manager - EU", None), ("smartrecruiters-v1", ("smartrecruiters", "smartrecruiters_postings.json"), "https://api.smartrecruiters.com/v1/companies/smartrecruiters/postings?limit=100", 1, "Data Operations Consultant", "744000148454651"), ("workable-v1", ("workable", "epignosis_widget.json"), "https://apply.workable.com/api/v1/widget/accounts/epignosis", 7, "Data Engineer", "E38DB16625"), ("workday-v1", ("workday", "nvidia_jobs_page1.json"), "https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite/jobs", 20, "Senior Embedded Software Engineer, DPU - Networking", "JR2017846"), ("recruitee-v1", ("recruitee", "vandebron_offers.json"), "https://vandebron.recruitee.com/api/offers/", 11, "Manager Market Operations", None), ("personio-v1", ("personio", "personio_jobs.xml"), "https://personio.jobs.personio.de/xml", 1, "Staff Software Engineer, Data Platform", "1834171"), ("teamtailor-v1", ("teamtailor", "teamtailor_jobs_feed.json"), "https://career.teamtailor.com/jobs.json", 10, "Account Executive - UK Enterprise", "8021334"), ] @pytest.mark.parametrize("cid,fixture,url,count,first_title,first_ext", CASES) def test_ats_connector_extracts_jobs(cid: str, fixture: tuple[str, str], url: str, count: int, first_title: str, first_ext: str | None) -> None: con = C.get(cid) assert C.for_surface("jobs_board", url).connector_id == cid ct = "application/xml" if fixture[1].endswith(".xml") else "application/json" ex = con.extract({"url": url, "surface": "jobs_board", "config": {}}, file_result(fixture_path(*fixture), url=url, content_type=ct)) assert len(ex.jobs) == count assert ex.jobs[0].title == first_title if first_ext: assert ex.jobs[0].external_id == first_ext assert all(j.url for j in ex.jobs) assert len(ex.blocks) == count and all(b.kind == "job_listing" for b in ex.blocks) assert ex.meta["structured"] is True and ex.meta["vendor"] == con.vendor # type: ignore[attr-defined] for j in ex.jobs: assert j.country is None or (len(j.country) == 2 and j.country.isupper()) def test_greenhouse_fields() -> None: ex = C.get("greenhouse-v1").extract({"url": "https://boards-api.greenhouse.io/v1/boards/stripe/jobs?content=false", "config": {}}, file_result(fixture_path("greenhouse", "stripe_jobs.json"), url="x", content_type="application/json")) j = ex.jobs[0] assert j.url.startswith("https://stripe.com/jobs/") and j.location_text == "Dublin" and j.posted_at is not None def test_workday_paginates_via_post(fake_fetcher: FakeFetcher) -> None: url = "https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite/jobs" page = json.loads(fixture_bytes("workday", "nvidia_jobs_page1.json")) page["total"] = 25 fake_fetcher.add(url, json.dumps(page), content_type="application/json") con = C.get("workday-v1") async def go(): # type: ignore[no-untyped-def] return await con.fetch(ConnectorContext(company={}), {"url": url, "config": {}}, fake_fetcher) # type: ignore[arg-type] import asyncio res = asyncio.run(go()) assert res.headers["x-companyatlas-pages"] == "2" # 25 total, 20 per page → 2 POSTs assert fake_fetcher.calls.count(f"POST {url}") == 2 ex = con.extract({"url": url, "config": {}}, res) assert ex.jobs and ex.jobs[0].external_id == "JR2017846" assert ex.jobs[0].url.endswith("_JR2017846") def test_smartrecruiters_pagination(fake_fetcher: FakeFetcher) -> None: base = "https://api.smartrecruiters.com/v1/companies/acme/postings?limit=100" p1 = {"offset": 0, "limit": 100, "totalFound": 2, "content": [{"id": "1", "name": "A", "location": {"city": "Berlin", "country": "de"}}]} p2 = {"offset": 1, "limit": 100, "totalFound": 2, "content": [{"id": "2", "name": "B", "location": {"city": "Paris", "country": "fr"}}]} fake_fetcher.add(base, json.dumps(p1), content_type="application/json") fake_fetcher.add(base + "&offset=1", json.dumps(p2), content_type="application/json") import asyncio con = C.get("smartrecruiters-v1") res = asyncio.run(con.fetch(ConnectorContext(company={}), {"url": base, "config": {"token": "acme"}}, fake_fetcher)) # type: ignore[arg-type] ex = con.extract({"url": base, "config": {"token": "acme"}}, res) assert [j.title for j in ex.jobs] == ["A", "B"] and ex.jobs[1].country == "FR" assert ex.jobs[0].url == "https://jobs.smartrecruiters.com/acme/1" def test_statuspage_and_feed_and_sitemap() -> None: sp = C.get("statuspage-v1").extract({"url": "https://www.githubstatus.com/api/v2/summary.json", "config": {}}, file_result(fixture_path("statuspage", "github_summary.json"), url="x", content_type="application/json")) assert sp.meta["indicator"] == "none" and sp.meta["component_count"] >= 5 and sp.blocks[0].kind == "hero" fd = C.get("feed-v1").extract({"url": "https://www.acme-cloud.example/blog/atom.xml", "surface": "feed", "config": {}}, file_result(fixture_path("feed", "blog_atom.xml"), url="x", content_type="application/atom+xml")) assert fd.news[0].title == "Introducing Atlas AI" and fd.news[0].published_at.year == 2026 and fd.news[0].category == "blog" assert len(fd.blocks) == 3 sm = C.get("sitemap-v1").extract({"url": "https://www.acme-cloud.example/sitemap-pages.xml", "surface": "sitemap", "config": {"canonical_domain": "acme-cloud.example"}}, file_result(fixture_path("sitemap", "sitemap_pages.xml"), url="x", content_type="application/xml")) assert sm.meta["url_count"] == 10 # the .png is dropped surfaces = {d.surface.value for d in sm.discovered} assert {"pricing", "careers", "leadership", "locations", "newsroom", "changelog"} <= surfaces idx = C.get("sitemap-v1") from companyatlas.connectors.sitemap import parse_sitemap pages, children = parse_sitemap(fixture_bytes("sitemap", "sitemap_index.xml").decode()) assert not pages and len(children) == 2 and children[0][1].startswith("2026-09-10") assert idx.meta.supports_discovery def test_feed_json_feed_variant() -> None: body = json.dumps({"version": "https://jsonfeed.org/version/1.1", "title": "T", "items": [{"id": "1", "title": "Hello world post", "url": "https://x.example/p/1", "date_published": "2026-01-02T00:00:00Z", "summary": "s"}]}) ex = C.get("feed-v1").extract({"url": "https://x.example/feed.json", "surface": "blog", "config": {}}, make_result("https://x.example/feed.json", body, content_type="application/feed+json")) assert ex.news[0].title == "Hello world post" and ex.news[0].category == "blog"