返回 last30days-skill
test_jobs_source.py
根目录 / tests / test_jobs_source.py
1 import unittest
2 from unittest.mock import patch
3
4 from lib import jobs
5
6
7 class JobsSourceTests(unittest.TestCase):
8 def test_parse_greenhouse_response_preserves_core_fields(self):
9 payload = {
10 "jobs": [
11 {
12 "id": 123,
13 "title": "Enterprise Security Engineer",
14 "updated_at": "2026-06-01T10:00:00-05:00",
15 "absolute_url": "https://boards.greenhouse.io/acme/jobs/123",
16 "content": "<p>Build SSO and SOC 2 workflows.</p>",
17 "location": {"name": "Remote"},
18 "departments": [{"name": "Engineering"}],
19 "offices": [{"name": "US"}],
20 }
21 ]
22 }
23 parsed = jobs.parse_greenhouse_response(payload, board_token="acme")
24 self.assertEqual(1, len(parsed))
25 item = parsed[0]
26 self.assertEqual("Enterprise Security Engineer", item["title"])
27 self.assertEqual("2026-06-01", item["date"])
28 self.assertEqual("Engineering", item["department"])
29 self.assertIn("SSO", item["description"])
30 self.assertEqual("greenhouse", item["provider"])
31
32 @patch("lib.jobs.http.get_text", return_value=None)
33 @patch("lib.jobs.http.get")
34 def test_all_ats_miss_degrades_to_empty(self, mock_get, _mock_text):
35 # No careers page (get_text None), no web backend, every ATS probe 404s.
36 mock_get.side_effect = jobs.http.HTTPError("missing", status_code=404)
37 items, artifact = jobs.search_jobs(
38 "MissingCo",
39 ("2026-05-16", "2026-06-16"),
40 {},
41 web_backend="none",
42 )
43 self.assertEqual([], items)
44 self.assertEqual(0, artifact["resultCount"])
45 self.assertEqual("web", artifact["tier"])
46
47 def test_detect_ats_reads_provider_and_slug_from_embed(self):
48 html = '<a href="https://jobs.ashbyhq.com/listenlabs/4b17">Open roles</a>'
49 provider, slug = jobs.detect_ats(html)
50 self.assertEqual("ashby", provider)
51 self.assertEqual("listenlabs", slug)
52
53 def test_detect_ats_handles_greenhouse_embed_query(self):
54 html = '<script src="https://boards.greenhouse.io/embed/job_board?for=acmeco"></script>'
55 provider, slug = jobs.detect_ats(html)
56 self.assertEqual("greenhouse", provider)
57 self.assertEqual("acmeco", slug)
58
59 def test_detect_ats_returns_none_when_no_ats(self):
60 self.assertEqual((None, None), jobs.detect_ats("<html><body>About us</body></html>"))
61
62 def test_parse_ashby_response_core_fields(self):
63 payload = {
64 "jobs": [
65 {
66 "id": "abc",
67 "title": "Founding Research Scientist, Human Simulation",
68 "departmentName": "Research",
69 "locationName": "San Francisco",
70 "publishedDate": "2026-06-10",
71 "jobUrl": "https://jobs.ashbyhq.com/listenlabs/abc",
72 "descriptionPlain": "Build human simulation models.",
73 }
74 ]
75 }
76 parsed = jobs.parse_ashby_response(payload, slug="listenlabs")
77 self.assertEqual(1, len(parsed))
78 item = parsed[0]
79 self.assertEqual("Founding Research Scientist, Human Simulation", item["title"])
80 self.assertEqual("Research", item["department"])
81 self.assertEqual("2026-06-10", item["date"])
82 self.assertEqual("ashby", item["provider"])
83
84 def test_parse_lever_response_handles_list_and_epoch(self):
85 payload = [
86 {
87 "id": "xyz",
88 "text": "Staff Engineer",
89 "hostedUrl": "https://jobs.lever.co/acme/xyz",
90 "categories": {"department": "Engineering", "location": "Remote"},
91 "createdAt": 1749513600000,
92 "descriptionPlain": "Own the platform.",
93 }
94 ]
95 parsed = jobs.parse_lever_response(payload, slug="acme")
96 self.assertEqual(1, len(parsed))
97 self.assertEqual("Staff Engineer", parsed[0]["title"])
98 self.assertEqual("Engineering", parsed[0]["department"])
99 self.assertRegex(parsed[0]["date"], r"\d{4}-\d{2}-\d{2}")
100 self.assertEqual("lever", parsed[0]["provider"])
101
102 def test_parse_smartrecruiters_response_core_fields(self):
103 payload = {
104 "content": [
105 {
106 "id": "777",
107 "name": "Account Executive",
108 "department": {"label": "Sales"},
109 "location": {"city": "New York", "country": "us"},
110 "releasedDate": "2026-06-02T00:00:00.000Z",
111 }
112 ]
113 }
114 parsed = jobs.parse_smartrecruiters_response(payload, slug="acme")
115 self.assertEqual(1, len(parsed))
116 self.assertEqual("Account Executive", parsed[0]["title"])
117 self.assertEqual("Sales", parsed[0]["department"])
118 self.assertEqual("2026-06-02", parsed[0]["date"])
119 self.assertEqual("smartrecruiters", parsed[0]["provider"])
120
121 def test_extract_jsonld_jobs_parses_jobposting(self):
122 html = (
123 '<script type="application/ld+json">'
124 '{"@context":"https://schema.org","@type":"JobPosting",'
125 '"title":"Product Designer","datePosted":"2026-06-05",'
126 '"description":"<p>Design things.</p>",'
127 '"url":"https://acme.com/jobs/1",'
128 '"jobLocation":{"@type":"Place","address":{"@type":"PostalAddress",'
129 '"addressLocality":"SF","addressCountry":"US"}}}'
130 '</script>'
131 )
132 parsed = jobs.extract_jsonld_jobs(html, "https://acme.com/careers")
133 self.assertEqual(1, len(parsed))
134 self.assertEqual("Product Designer", parsed[0]["title"])
135 self.assertEqual("2026-06-05", parsed[0]["date"])
136 self.assertEqual("careers-jsonld", parsed[0]["provider"])
137 self.assertIn("SF", parsed[0]["location"])
138
139 def test_extract_jsonld_jobs_handles_graph_arrays(self):
140 html = (
141 '<script type="application/ld+json">'
142 '{"@graph":[{"@type":"Organization","name":"Acme"},'
143 '{"@type":"JobPosting","title":"GTM Lead","datePosted":"2026-06-01"}]}'
144 '</script>'
145 )
146 parsed = jobs.extract_jsonld_jobs(html, "https://acme.com/careers")
147 self.assertEqual(1, len(parsed))
148 self.assertEqual("GTM Lead", parsed[0]["title"])
149
150 def test_jsonld_jobs_without_urls_survive_normalize_and_dedupe(self):
151 from lib import dedupe, normalize
152
153 html = (
154 '<script type="application/ld+json">'
155 '{"@graph":['
156 '{"@type":"JobPosting","title":"Founding Designer","datePosted":"2026-06-01"},'
157 '{"@type":"JobPosting","title":"GTM Lead","datePosted":"2026-06-02"},'
158 '{"@type":"JobPosting","title":"Staff Engineer","datePosted":"2026-06-03"}'
159 ']}'
160 '</script>'
161 )
162 parsed = jobs.extract_jsonld_jobs(html, "https://acme.com/careers")
163 self.assertEqual(3, len(parsed))
164 self.assertTrue(all(item["url"] == "" for item in parsed))
165 self.assertTrue(all(item["source_url"] == "https://acme.com/careers" for item in parsed))
166
167 normalized = normalize.normalize_source_items("jobs", parsed, "2026-05-17", "2026-06-16")
168 kept = dedupe.dedupe_items(normalized)
169 self.assertEqual(
170 ["Founding Designer", "GTM Lead", "Staff Engineer"],
171 sorted(item.title for item in kept),
172 )
173
174
175 if __name__ == "__main__":
176 unittest.main()
177
178
179 class JobsDateAndBannerTests(unittest.TestCase):
180 def test_jobs_keep_open_roles_outside_date_window(self):
181 from lib import normalize
182 raw = [
183 {"id": "1", "title": "Founding Research Scientist", "url": "https://x/1",
184 "date": "2026-01-10", "provider": "ashby"}, # posted months ago, still open
185 {"id": "2", "title": "Account Executive", "url": "https://x/2",
186 "date": "2026-06-10", "provider": "ashby"}, # within window
187 ]
188 items = normalize.normalize_source_items("jobs", raw, "2026-05-17", "2026-06-16")
189 titles = [i.title for i in items]
190 self.assertIn("Founding Research Scientist", titles) # not dropped by date window
191 self.assertEqual(2, len(items))
192
193
194 class JobsDedupeTests(unittest.TestCase):
195 def test_jobs_dedupe_by_url_not_fuzzy_boilerplate(self):
196 from lib import dedupe, schema
197 # Distinct roles sharing heavy boilerplate but with unique URLs.
198 boiler = "TL;DR: Listen Labs is a fast-growing early-stage startup. Roll up your sleeves."
199 items = [
200 schema.SourceItem(item_id=f"AB{i}", source="jobs", title=t, body=boiler,
201 url=f"https://jobs.ashbyhq.com/listenlabs/{i}")
202 for i, t in enumerate(["Founding Research Scientist", "Account Executive",
203 "Growth Associate", "Business Development Rep"])
204 ]
205 kept = dedupe.dedupe_items(items)
206 self.assertEqual(4, len(kept)) # all distinct URLs survive
207
208 def test_jobs_dedupe_collapses_same_url(self):
209 from lib import dedupe, schema
210 items = [
211 schema.SourceItem(item_id="AB1", source="jobs", title="Role", body="x",
212 url="https://jobs.ashbyhq.com/listenlabs/1"),
213 schema.SourceItem(item_id="AB1b", source="jobs", title="Role", body="x",
214 url="https://jobs.ashbyhq.com/listenlabs/1"),
215 ]
216 self.assertEqual(1, len(dedupe.dedupe_items(items)))
217
217 lines PYTHON