mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 08:36:25 +00:00
237 lines
8.0 KiB
Python
237 lines
8.0 KiB
Python
"""Tests for salary_lookup.py — format_entry, match_score, and search_company."""
|
|
|
|
import unittest
|
|
|
|
from salary_lookup import format_entry, match_score, search_company
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# format_entry tests (from #75 / #98)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class FormatEntryTests(unittest.TestCase):
|
|
def test_zero_count_is_displayed_as_zero(self):
|
|
entry = {
|
|
"company": "Example Corp",
|
|
"city": "",
|
|
"categories": {
|
|
"public_data": {
|
|
"count": 0,
|
|
"index": 100.0,
|
|
},
|
|
},
|
|
}
|
|
|
|
rendered = format_entry(entry, {"index_baseline": 100, "index_label": "Index"})
|
|
|
|
self.assertRegex(rendered, r"Public Data\s+0\s+100\.0")
|
|
|
|
def test_text_index_does_not_crash(self):
|
|
entry = {
|
|
"company": "Example Corp",
|
|
"city": "",
|
|
"categories": {
|
|
"sample": {
|
|
"count": 3,
|
|
"index": "private",
|
|
},
|
|
},
|
|
}
|
|
|
|
rendered = format_entry(entry, {"index_baseline": 100, "index_label": "Index"})
|
|
|
|
self.assertIn("private", rendered)
|
|
|
|
def test_format_entry_with_zero_baseline(self):
|
|
entry = {
|
|
"company": "Example Corp",
|
|
"city": "",
|
|
"categories": {
|
|
"it": {
|
|
"count": None,
|
|
"index": 45000.0,
|
|
},
|
|
},
|
|
}
|
|
rendered = format_entry(entry, {"index_baseline": 0, "index_label": "Salary"})
|
|
self.assertIn("45000.0", rendered)
|
|
self.assertNotIn("%", rendered)
|
|
|
|
def test_format_entry_with_custom_baseline(self):
|
|
entry = {
|
|
"company": "Example Corp",
|
|
"city": "",
|
|
"categories": {
|
|
"it": {
|
|
"count": None,
|
|
"index": 45000.0,
|
|
},
|
|
},
|
|
}
|
|
rendered = format_entry(entry, {"index_baseline": 40000, "index_label": "Salary"})
|
|
self.assertIn("45000.0", rendered)
|
|
self.assertIn("+12.5%", rendered)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# match_score tests (from #106)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestMatchScoreExactMatch(unittest.TestCase):
|
|
def test_exact_match_returns_100(self):
|
|
self.assertEqual(match_score("Novo Nordisk", "Novo Nordisk"), 100)
|
|
|
|
def test_exact_match_case_insensitive(self):
|
|
self.assertEqual(match_score("NOVO NORDISK", "Novo Nordisk"), 100)
|
|
|
|
def test_exact_match_after_suffix_stripping(self):
|
|
self.assertEqual(match_score("Mærsk", "Mærsk A/S"), 100)
|
|
|
|
|
|
class TestMatchScoreSubstring(unittest.TestCase):
|
|
def test_query_contained_in_entry_gives_high_score(self):
|
|
score = match_score("Carlsberg", "Carlsberg Danmark A/S")
|
|
self.assertGreaterEqual(score, 80)
|
|
|
|
def test_entry_contained_in_query_gives_high_score(self):
|
|
score = match_score("Carlsberg Danmark", "Carlsberg")
|
|
self.assertGreaterEqual(score, 80)
|
|
|
|
|
|
class TestMatchScoreShortQuery(unittest.TestCase):
|
|
def test_short_query_no_word_overlap_returns_zero(self):
|
|
score = match_score("ab", "Something Unrelated Company")
|
|
self.assertEqual(score, 0)
|
|
|
|
def test_short_query_with_word_overlap_scores(self):
|
|
score = match_score("IBM", "IBM Corporation")
|
|
self.assertGreater(score, 0)
|
|
|
|
|
|
class TestMatchScoreAnglicize(unittest.TestCase):
|
|
def test_oe_variant_matches_o_with_slash(self):
|
|
score = match_score("Maersk", "Mærsk A/S")
|
|
self.assertGreater(score, 0)
|
|
|
|
def test_aa_variant_matches_aa(self):
|
|
self.assertEqual(match_score("Aarsleff", "Aarsleff"), 100)
|
|
|
|
def test_danish_characters_roundtrip(self):
|
|
score = match_score("Maersk", "Mærsk A/S")
|
|
self.assertGreater(score, 0)
|
|
|
|
|
|
class TestMatchScoreNoOverlap(unittest.TestCase):
|
|
def test_completely_unrelated_names_return_zero(self):
|
|
self.assertEqual(match_score("Apple", "Vestas Wind Systems"), 0)
|
|
|
|
def test_empty_query_returns_zero(self):
|
|
self.assertEqual(match_score("", "Novo Nordisk"), 0)
|
|
|
|
def test_empty_entry_returns_zero(self):
|
|
self.assertEqual(match_score("Novo Nordisk", ""), 0)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# search_company tests (from #75 / #98 and #106)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _make_data(*entries):
|
|
return {"companies": list(entries)}
|
|
|
|
|
|
def _entry(company, city=""):
|
|
return {"company": company, "city": city}
|
|
|
|
|
|
class SearchCompanyTests(unittest.TestCase):
|
|
def test_search_company_with_none_city(self):
|
|
data = {
|
|
"companies": [
|
|
{
|
|
"company": "Acme",
|
|
"city": None,
|
|
}
|
|
]
|
|
}
|
|
results = search_company(data, "Acme", city="Aarhus")
|
|
self.assertEqual(results, [])
|
|
|
|
|
|
class TestSearchCompanyBasicMatch(unittest.TestCase):
|
|
def test_exact_name_returns_match(self):
|
|
data = _make_data(_entry("Novo Nordisk", "Bagsværd"))
|
|
results = search_company(data, "Novo Nordisk")
|
|
self.assertEqual(len(results), 1)
|
|
self.assertEqual(results[0]["company"], "Novo Nordisk")
|
|
|
|
def test_no_match_returns_empty_list(self):
|
|
data = _make_data(_entry("Vestas Wind Systems", "Aarhus"))
|
|
results = search_company(data, "Apple")
|
|
self.assertEqual(results, [])
|
|
|
|
def test_multiple_candidates_all_returned(self):
|
|
data = _make_data(
|
|
_entry("Carlsberg A/S", "Copenhagen"),
|
|
_entry("Carlsberg Danmark", "Fredericia"),
|
|
_entry("Unrelated Corp", "Odense"),
|
|
)
|
|
results = search_company(data, "Carlsberg")
|
|
companies = [r["company"] for r in results]
|
|
self.assertIn("Carlsberg A/S", companies)
|
|
self.assertIn("Carlsberg Danmark", companies)
|
|
self.assertNotIn("Unrelated Corp", companies)
|
|
|
|
|
|
class TestSearchCompanyCityFilter(unittest.TestCase):
|
|
def test_matching_city_is_included(self):
|
|
data = _make_data(
|
|
_entry("Novo Nordisk", "Bagsværd"),
|
|
_entry("Novo Nordisk", "Aarhus"),
|
|
)
|
|
results = search_company(data, "Novo Nordisk", city="Aarhus")
|
|
self.assertEqual(len(results), 1)
|
|
self.assertEqual(results[0]["city"], "Aarhus")
|
|
|
|
def test_non_matching_city_is_excluded(self):
|
|
data = _make_data(_entry("Novo Nordisk", "Bagsværd"))
|
|
results = search_company(data, "Novo Nordisk", city="Odense")
|
|
self.assertEqual(results, [])
|
|
|
|
def test_no_city_filter_returns_all_cities(self):
|
|
data = _make_data(
|
|
_entry("Novo Nordisk", "Bagsværd"),
|
|
_entry("Novo Nordisk", "Aarhus"),
|
|
)
|
|
results = search_company(data, "Novo Nordisk")
|
|
self.assertEqual(len(results), 2)
|
|
|
|
def test_city_filter_case_insensitive(self):
|
|
data = _make_data(_entry("Novo Nordisk", "København"))
|
|
results = search_company(data, "Novo Nordisk", city="københavn")
|
|
self.assertEqual(len(results), 1)
|
|
|
|
def test_anglicized_city_matches_danish_city(self):
|
|
data = _make_data(_entry("Novo Nordisk", "København"))
|
|
results = search_company(data, "Novo Nordisk", city="kobenhavn")
|
|
self.assertEqual(len(results), 1)
|
|
|
|
|
|
class TestSearchCompanyScoreThreshold(unittest.TestCase):
|
|
def test_low_score_matches_excluded(self):
|
|
data = _make_data(_entry("Novo Nordisk", "Bagsværd"))
|
|
results = search_company(data, "xyz")
|
|
self.assertEqual(results, [])
|
|
|
|
def test_results_sorted_by_relevance_descending(self):
|
|
data = _make_data(
|
|
_entry("Novo Nordisk International", "Bagsværd"),
|
|
_entry("Novo Nordisk", "Bagsværd"),
|
|
)
|
|
results = search_company(data, "Novo Nordisk")
|
|
self.assertEqual(results[0]["company"], "Novo Nordisk")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|