92 lines
3.7 KiB
Python
92 lines
3.7 KiB
Python
"""Warm-pool gate — the reorder/floor decision that keeps the shelf stocked without over-fetching."""
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from app.engine import search
|
|
from app.engine.search import pool_decision
|
|
from app.db.repo import search_signature
|
|
|
|
FLOOR, REORDER = 90, 100 # the configured thresholds (POOL_SAFETY_FLOOR / POOL_REORDER_AT)
|
|
|
|
|
|
def test_pool_healthy_serves_no_fetch():
|
|
assert pool_decision(150, False, FLOOR, REORDER) == "pool"
|
|
assert pool_decision(100, False, FLOOR, REORDER) == "pool" # exactly at reorder → still no fetch
|
|
|
|
|
|
def test_pool_dipping_serves_plus_background_refill():
|
|
assert pool_decision(99, False, FLOOR, REORDER) == "pool+refill" # below reorder → top up in bg
|
|
assert pool_decision(90, False, FLOOR, REORDER) == "pool+refill" # at the floor → still serve, refill
|
|
|
|
|
|
def test_pool_below_floor_blocks_and_fetches():
|
|
assert pool_decision(89, False, FLOOR, REORDER) == "sweep" # under the hard floor → blocking fetch
|
|
assert pool_decision(0, False, FLOOR, REORDER) == "sweep" # empty pool (first search) → fetch
|
|
|
|
|
|
def test_forced_fresh_always_fetches():
|
|
assert pool_decision(999, True, FLOOR, REORDER) == "sweep" # dev _fresh toggle bypasses the pool
|
|
assert pool_decision(5, True, FLOOR, REORDER, exhausted=True) == "sweep" # force overrides exhaustion
|
|
|
|
|
|
def test_exhausted_query_serves_instead_of_block_fetching():
|
|
# tapped-out niche query below the floor: serve what's pooled, DON'T re-buy duplicates
|
|
assert pool_decision(30, False, FLOOR, REORDER, exhausted=True) == "pool" # below floor but exhausted
|
|
assert pool_decision(1, False, FLOOR, REORDER, exhausted=True) == "pool" # even 1 job → serve it
|
|
# but an EMPTY exhausted pool must still try a fetch (nothing to serve)
|
|
assert pool_decision(0, False, FLOOR, REORDER, exhausted=True) == "sweep"
|
|
# exhaustion does NOT suppress the healthy/refill paths
|
|
assert pool_decision(200, False, FLOOR, REORDER, exhausted=True) == "pool"
|
|
|
|
|
|
def test_floor_never_above_reorder_invariant():
|
|
# the gate only makes sense when floor ≤ reorder (reorder early, floor as the hard backstop)
|
|
from app.config import get_settings
|
|
s = get_settings()
|
|
assert s.POOL_SAFETY_FLOOR <= s.POOL_REORDER_AT
|
|
|
|
|
|
def test_target_title_changes_search_signature():
|
|
base = {
|
|
"title": "Product Manager",
|
|
"role": ["Product"],
|
|
"targetTitles": ["Backend Engineer"],
|
|
"location": ["Bangalore · India"],
|
|
"workMode": ["Hybrid"],
|
|
}
|
|
changed = {**base, "targetTitles": ["Data Analyst"]}
|
|
|
|
assert search_signature(base) != search_signature(changed)
|
|
|
|
|
|
def test_work_mode_changes_search_signature_using_dashboard_contract():
|
|
base = {"title": "Data Analyst", "location": ["Bangalore · India"], "workMode": ["Hybrid"]}
|
|
changed = {**base, "workMode": ["Remote"]}
|
|
|
|
assert search_signature(base) != search_signature(changed)
|
|
|
|
|
|
def test_legacy_and_camel_case_work_mode_have_same_signature():
|
|
common = {"title": "Data Analyst", "location": ["Bangalore · India"]}
|
|
|
|
assert search_signature({**common, "workMode": ["Hybrid"]}) == search_signature({**common, "work_mode": ["Hybrid"]})
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_sweep_marks_total_board_failure(monkeypatch):
|
|
async def fail_board(*_args, **_kwargs):
|
|
raise RuntimeError("provider blocked")
|
|
|
|
monkeypatch.setattr(search, "_fetch_board", fail_board)
|
|
monkeypatch.setattr(search, "get_settings", lambda: SimpleNamespace(
|
|
BOARDS_ENABLED="naukri,foundit",
|
|
BOARD_TIMEOUT_S=1,
|
|
))
|
|
|
|
result = await search.run_sweep({"title": "Data Analyst", "location": ["Bangalore · India"]})
|
|
|
|
assert result["all_boards_failed"] is True
|
|
assert result["failed_boards"] == 2
|
|
assert result["sources"] == {}
|