Spaces:
Sleeping
Sleeping
| """Structural + unit: API handlers call shipped eligibility/strategy/tax functions.""" | |
| from __future__ import annotations | |
| import inspect | |
| import pytest | |
| from fastapi import HTTPException | |
| from endpoints import company as company_ep | |
| from core.eligibility.spine import ( | |
| build_eligibility_report, | |
| eligibility_override_is_complete, | |
| ) | |
| from core.strategy.recommend import PATH_TAX, recommend_strategy_paths | |
| from core.strategy.tax_paths import ( | |
| PARENT_STRATEGY_PATH_ID, | |
| TAX_PATH_IP_BOX, | |
| TAX_PATH_PSI, | |
| TAX_PATH_ULGA_BR, | |
| ) | |
| def test_company_router_has_eligibility_and_strategy_routes(): | |
| paths = {getattr(r, "path", "") for r in company_ep.router.routes} | |
| # FastAPI may store path without prefix on route | |
| joined = " ".join(sorted(paths)) | |
| assert "eligibility" in joined | |
| assert "strategy" in joined or "recommend" in joined | |
| assert "tax" in joined | |
| def test_tax_router_has_paths_route(): | |
| """Plan §8 GET /api/tax/paths + company /tax/paths both exposed.""" | |
| company_paths = " ".join( | |
| sorted(getattr(r, "path", "") for r in company_ep.router.routes) | |
| ) | |
| assert "tax" in company_paths and "paths" in company_paths | |
| tax_paths = {getattr(r, "path", "") for r in company_ep.tax_router.routes} | |
| joined = " ".join(sorted(tax_paths)) | |
| assert "paths" in joined | |
| assert any( | |
| getattr(r, "path", "").endswith("/paths") or getattr(r, "path", "") == "/paths" | |
| for r in company_ep.tax_router.routes | |
| ) | |
| def test_get_eligibility_handler_uses_build_report(): | |
| src = inspect.getsource(company_ep.get_company_eligibility) | |
| assert "build_eligibility_report" in src | |
| def test_strategy_handler_uses_recommend(): | |
| src = inspect.getsource(company_ep.post_strategy_recommend) | |
| assert "recommend_strategy_paths" in src | |
| # POST must load profile spine when eligibility omitted (parity with GET) | |
| assert "company_data_from_profile" in src | |
| assert "get_db" in src or "db" in src | |
| # Incomplete body.eligibility must rebuild (not fail-open) | |
| assert "eligibility_override_is_complete" in src | |
| src2 = inspect.getsource(company_ep.get_strategy_recommend) | |
| assert "recommend_strategy_paths" in src2 | |
| def test_handlers_are_callable_with_pure_backend(): | |
| # Direct pure path the handlers wrap | |
| rep = build_eligibility_report( | |
| nip="8721990161", | |
| company_data={"msp_status": "mikro", "de_minimis_manual_eur": 100}, | |
| ) | |
| strat = recommend_strategy_paths(eligibility=rep, goal="B+R", company_data={}) | |
| assert rep["nip"] | |
| assert len(strat["paths"]) >= 4 | |
| tax = next(p for p in strat["paths"] if p["id"] == PATH_TAX) | |
| assert tax.get("tax_paths") and len(tax["tax_paths"]) == 3 | |
| assert tax.get("never_say_submit_to_parp") is True | |
| def test_get_tax_paths_api_surface(): | |
| """GET tax-paths returns PSI / ulga_br / ip_box with PSI PARP guardrail.""" | |
| out = company_ep.get_tax_paths(path_id=None, token_data={"sub": "user_tax_api"}) | |
| assert out["status"] == "ok" | |
| assert out["parent_strategy_path_id"] == PARENT_STRATEGY_PATH_ID | |
| assert out["checklist_ids"] == [TAX_PATH_PSI, TAX_PATH_ULGA_BR, TAX_PATH_IP_BOX] | |
| assert len(out["paths"]) == 3 | |
| ids = [p["id"] for p in out["paths"]] | |
| assert ids == [TAX_PATH_PSI, TAX_PATH_ULGA_BR, TAX_PATH_IP_BOX] | |
| psi = next(p for p in out["paths"] if p["id"] == TAX_PATH_PSI) | |
| assert psi["never_say_submit_to_parp"] is True | |
| assert isinstance(psi["checklist"], list) and len(psi["checklist"]) >= 3 | |
| blob = " ".join(psi["checklist"] + psi.get("advisor_notes", [])).lower() | |
| assert "parp" in blob | |
| def test_get_tax_paths_single_id_and_404(): | |
| one = company_ep.get_tax_paths(path_id="psi", token_data={"sub": "user_tax_api"}) | |
| assert one["status"] == "ok" | |
| assert one["path"]["id"] == TAX_PATH_PSI | |
| assert one["checklist_ids"] == [TAX_PATH_PSI] | |
| with pytest.raises(HTTPException) as ei: | |
| company_ep.get_tax_paths(path_id="not_a_path", token_data={"sub": "user_tax_api"}) | |
| assert ei.value.status_code == 404 | |
| def test_get_tax_paths_plan_alias_delegates(): | |
| """Plan-canonical /api/tax/paths handler shares response shape.""" | |
| a = company_ep.get_tax_paths(path_id=None, token_data={"sub": "u"}) | |
| b = company_ep.get_tax_paths_plan_alias(path_id=None, token_data={"sub": "u"}) | |
| assert a["checklist_ids"] == b["checklist_ids"] | |
| assert len(a["paths"]) == len(b["paths"]) == 3 | |
| def test_get_tax_paths_compact_alias(): | |
| """GET /api/company/tax-paths compact surface (enabled flag + catalog).""" | |
| out = company_ep.get_tax_paths_compact(token_data={"sub": "user_tax_compact"}) | |
| assert out["status"] == "ok" | |
| assert out["enabled"] is True | |
| assert len(out["tax_paths"]) == 3 | |
| ids = [p["id"] for p in out["tax_paths"]] | |
| assert ids == [TAX_PATH_PSI, TAX_PATH_ULGA_BR, TAX_PATH_IP_BOX] | |
| psi = next(p for p in out["tax_paths"] if p["id"] == TAX_PATH_PSI) | |
| assert psi["never_say_submit_to_parp"] is True | |
| def test_post_strategy_incomplete_eligibility_rebuilds_and_includes_tax(): | |
| """Thin body.eligibility rebuilds spine; strategy still embeds tax checklists.""" | |
| class _EmptyDB: | |
| def query(self, *a, **k): | |
| class _Q: | |
| def filter(self, *a, **k): | |
| return self | |
| def first(self): | |
| return None | |
| return _Q() | |
| body = company_ep.StrategyRecommendBody( | |
| nip="8721990161", | |
| goal="B+R", | |
| eligibility={"msp": {"status": "mikro"}}, # incomplete — no de_minimis | |
| company_data={"msp_status": "mikro", "de_minimis_manual_eur": 100}, | |
| ) | |
| assert eligibility_override_is_complete(body.eligibility) is False | |
| out = company_ep.post_strategy_recommend( | |
| body=body, token_data={"sub": "user_tax_strat"}, db=_EmptyDB() | |
| ) | |
| elig = out["eligibility"] | |
| assert elig.get("de_minimis", {}).get("used_eur") == 100.0 | |
| assert elig.get("msp", {}).get("status") | |
| tax = next(p for p in out["strategy"]["paths"] if p["id"] == PATH_TAX) | |
| assert tax.get("tax_paths") and len(tax["tax_paths"]) == 3 | |
| assert tax.get("never_say_submit_to_parp") is True | |
| def test_post_strategy_incomplete_eligibility_rebuilds_in_handler_source(): | |
| """Guard residual: thin eligibility must not skip spine rebuild.""" | |
| assert eligibility_override_is_complete({"msp": {"status": "mikro"}}) is False | |
| src = inspect.getsource(company_ep.post_strategy_recommend) | |
| assert "if not eligibility_override_is_complete" in src | |
| assert "build_eligibility_report" in src | |