Spaces:
Sleeping
Sleeping
| """Regression tests β each pins a specific bug we fixed, so it can't return. | |
| Reference the symptom in the test name/docstring so a future failure is | |
| self-explanatory. | |
| """ | |
| import pytest | |
| from fastapi.testclient import TestClient | |
| from app.main import app | |
| from app.model import calculate_stats | |
| client = TestClient(app) | |
| COL = {"valuePct": 134.5, "direction": "higher"} | |
| RENT = {"valuePct": 409.4, "direction": "higher"} | |
| async def fake_diffs(country1, city1, country2, city2): | |
| return {"city_from": city1, "city_to": city2, | |
| "col_excl_rent": COL, "rent": RENT} | |
| async def fake_fx(from_currency, to_currency): | |
| return {"pair": f"{from_currency}/{to_currency}", "source": "Yahoo Finance", | |
| "latest": 0.30, "ema_30": 0.30, "ema_90": 0.30, "ema_180": 0.30, | |
| "forecast": 0.30, "trend": "flat", "spot_only": False, "scale": 1, | |
| "band_low": 0.30, "band_high": 0.30, "days": 200} | |
| def patched(monkeypatch): | |
| monkeypatch.setattr("app.main.get_percentage_diff", fake_diffs) | |
| monkeypatch.setattr("app.main.fetch_fx_ema", fake_fx) | |
| def test_currency_scale_bug_no_nonsensical_negative_savings(): | |
| """Was: destination costs computed on home-currency scale while net_new was | |
| in destination currency β wildly negative savings. Now FX-scaled.""" | |
| m = calculate_stats(16000, 24000, COL, RENT, fx_rate=0.30, w_rent=0.2, w_non_rent=0.6) | |
| assert m["rent_new"] == pytest.approx(0.2 * (16000 * 0.30) * (1 + 4.094)) | |
| assert m["savings_new"] > 0 # not a huge negative number | |
| def test_bare_except_no_longer_swallows_errors(patched, monkeypatch): | |
| """Was: a single bare `except` hid all failures. Now ValueErrors surface | |
| as user-facing messages.""" | |
| r = client.post("/compare", data={ | |
| "country1": "Malaysia", "city1": "Kuala Lumpur", | |
| "country2": "Singapore", "city2": "Singapore", | |
| "net_home": "16000", # neither net_new nor increment_pct | |
| }) | |
| assert "Provide either a new net salary" in r.text | |
| def test_thousand_separated_salary_input_is_accepted(patched): | |
| """Was: switching salary inputs to formatted text broke float parsing. | |
| Now commas are stripped server-side.""" | |
| r = client.post("/compare", data={ | |
| "country1": "Malaysia", "city1": "Kuala Lumpur", | |
| "country2": "Malaysia", "city2": "Penang", | |
| "net_home": "16,000", "net_new": "18,500", | |
| }) | |
| assert r.status_code == 200 | |
| assert "Monthly savings estimate" in r.text | |
| def test_get_compare_redirects_not_405(): | |
| """Was: GET /compare returned 405. Now redirects home.""" | |
| r = client.get("/compare", follow_redirects=False) | |
| assert r.status_code == 303 | |
| assert r.headers["location"] == "/relo" | |
| def test_multiword_names_title_cased_not_lowercased(patched): | |
| """Was: .capitalize() turned 'Kuala Lumpur' into 'Kuala lumpur'. Now .title().""" | |
| r = client.post("/compare", data={ | |
| "country1": "Malaysia", "city1": "kuala lumpur", | |
| "country2": "Malaysia", "city2": "george town", | |
| "net_home": "10000", "net_new": "11000", | |
| }) | |
| assert r.status_code == 200 | |
| # city_from echoes the (title-cased) submitted city | |
| assert "Kuala Lumpur" in r.text | |
| assert "Kuala lumpur" not in r.text | |
| def test_savings_pct_delta_uses_home_equivalent(): | |
| """Was: % delta mixed currencies. Now compares home-currency equivalents.""" | |
| m = calculate_stats(10000, 10000, COL, RENT, fx_rate=1.0) | |
| # same salary, pricier destination β fewer savings β negative delta | |
| assert m["savings_pct_delta"] < 0 | |