diff --git a/tests/integration/test_mcp_routes.py b/tests/integration/test_mcp_routes.py index 87d9f2d..0d6708e 100644 --- a/tests/integration/test_mcp_routes.py +++ b/tests/integration/test_mcp_routes.py @@ -579,3 +579,113 @@ def test_yarn_error_returns_502_with_detail(tmp_path, monkeypatch): detail = r.json()["detail"] assert "YARN connection failed" in detail assert "Connection refused" in detail + + +def test_external_job_status_returns_502_on_yarn_error(tmp_path): + """get_external_job_status uses get_application_status under the + hood — same handler, same 502, same detail.""" + from unittest.mock import patch + from spark_executor.core.yarn_client import YarnError + + connection_store.store.save( + Connection( + name="prod", + master="yarn", + yarn_rm_url="http://ccam1:8088", + ) + ) + c = TestClient(app) + with patch( + "spark_executor.tools.external_jobs.get_application_status", + side_effect=YarnError("YARN application 'application_xxx' not found"), + ): + r = c.post( + "/get_external_job_status", + json={"application_id": "application_1740000000001_0001", "connection_name": "prod"}, + ) + assert r.status_code == 502 + assert "not found" in r.json()["detail"] + + +def test_external_job_result_returns_502_on_yarn_error(tmp_path): + """get_external_job_result also uses get_application_status (same + underlying YARN endpoint), and exercises the same YarnError path.""" + from unittest.mock import patch + from spark_executor.core.yarn_client import YarnError + + connection_store.store.save( + Connection( + name="prod", + master="yarn", + yarn_rm_url="http://ccam1:8088", + ) + ) + c = TestClient(app) + with patch( + "spark_executor.tools.external_jobs.get_application_status", + side_effect=YarnError("YARN connection failed: ConnectError: Connection refused"), + ): + r = c.post( + "/get_external_job_result", + json={"application_id": "application_1740000000001_0001", "connection_name": "prod"}, + ) + assert r.status_code == 502 + assert "YARN connection failed" in r.json()["detail"] + + +def test_external_job_logs_returns_502_on_yarn_error(tmp_path): + """get_external_job_logs uses get_application_logs — different + function, but raises YarnError the same way. Handler must catch + it the same way.""" + from unittest.mock import patch + from spark_executor.core.yarn_client import YarnError + + connection_store.store.save( + Connection( + name="prod", + master="yarn", + yarn_rm_url="http://ccam1:8088", + ) + ) + c = TestClient(app) + with patch( + "spark_executor.tools.external_jobs.get_application_logs", + side_effect=YarnError("YARN GET logs returned HTTP 500: cluster overloaded"), + ): + r = c.post( + "/get_external_job_logs", + json={ + "application_id": "application_1740000000001_0001", + "connection_name": "prod", + "tail_chars": 5000, + }, + ) + assert r.status_code == 502 + assert "YARN GET logs returned HTTP 500" in r.json()["detail"] + assert "cluster overloaded" in r.json()["detail"] + + +def test_list_applications_returns_502_on_yarn_error(tmp_path): + """list_applications uses list_applications_yarn — yet another + YARN-touching tool. Same YarnError -> 502 contract.""" + from unittest.mock import patch + from spark_executor.core.yarn_client import YarnError + + connection_store.store.save( + Connection( + name="prod", + master="yarn", + yarn_rm_url="http://ccam1:8088", + ) + ) + c = TestClient(app) + with patch( + "spark_executor.tools.external_jobs.list_applications_yarn", + side_effect=YarnError("YARN list applications failed: 503 Service Unavailable"), + ): + r = c.post( + "/list_applications", + json={"connection_name": "prod"}, + ) + assert r.status_code == 502 + assert "YARN list applications failed" in r.json()["detail"]