diff --git a/functions/submit_order/handler.py b/functions/submit_order/handler.py index c39efff..174dc00 100644 --- a/functions/submit_order/handler.py +++ b/functions/submit_order/handler.py @@ -65,7 +65,14 @@ def _get_discount_settings() -> tuple[Decimal, Decimal]: def _google_auth_configured() -> bool: - return bool(os.environ.get("GOOGLE_CLIENT_ID_PARAM", "")) + return bool(_get_google_client_id()) + + +def _is_parameter_not_found(exc: Exception) -> bool: + response_data = getattr(exc, "response", {}) + if not isinstance(response_data, dict): + return False + return response_data.get("Error", {}).get("Code") == "ParameterNotFound" def _official_menu_retail_by_name(week: str) -> dict[str, Decimal]: @@ -87,7 +94,12 @@ def _get_google_client_id() -> str: if _google_client_id is None or (now - _google_client_id_ts) > CACHE_TTL_SECONDS: param = os.environ.get("GOOGLE_CLIENT_ID_PARAM", "") if param: - _google_client_id = get_parameter(param, decrypt=False) or "" + try: + _google_client_id = get_parameter(param, decrypt=False) or "" + except Exception as exc: + if not _is_parameter_not_found(exc): + raise + _google_client_id = "" else: _google_client_id = "" _google_client_id_ts = now @@ -187,12 +199,20 @@ def handle_submit(event): return response(400, {"error": "Invalid JSON"}) # --- Authentication --- - # If Google auth is configured (SSM param name is set), require a valid - # google_id_token. Manual fallback is only allowed when auth is NOT configured. - # If SSM fetch fails, fail closed (503) rather than silently disabling auth. + # If Google auth is configured (SSM param contains a client ID), require a valid + # google_id_token. Manual fallback is only allowed when auth is NOT configured. + # If SSM fetch fails for any other reason, fail closed (503). google_token = body.get("google_id_token") - if _google_auth_configured(): + try: + google_auth_configured = _google_auth_configured() + except Exception as exc: + logger.error("SSM fetch failed for Google client ID: %s", exc) + return response( + 503, {"error": "Authentication service temporarily unavailable"} + ) + + if google_auth_configured: try: client_id = _get_google_client_id() except Exception as exc: diff --git a/tests/test_submit_order.py b/tests/test_submit_order.py index 1998842..d874d20 100644 --- a/tests/test_submit_order.py +++ b/tests/test_submit_order.py @@ -409,6 +409,51 @@ def test_total_summation_multiple_items( # =========================================================================== +@patch("submit_order_handler._lambda") +@patch("submit_order_handler.put_order") +@patch("submit_order_handler.get_form_status", return_value="open") +@patch("submit_order_handler.current_week", return_value="2026-W20") +@patch( + "submit_order_handler.get_settings", + return_value={"bulk_discount_percent": 0, "company_subsidy_percent": 0}, +) +@patch("submit_order_handler.get_secret", return_value=TEST_API_KEY) +@patch("submit_order_handler.get_parameter") +@patch("submit_order_handler.get_menu") +def test_missing_google_client_id_param_allows_manual_submission( + mock_get_menu, + mock_get_parameter, + mock_secret, + mock_settings, + mock_week, + mock_status, + mock_put, + mock_lam, +): + """Missing Google client ID SSM parameter means auth is not configured.""" + from submit_order_handler import lambda_handler + + class ParameterNotFoundError(Exception): + response = {"Error": {"Code": "ParameterNotFound"}} + + items = _make_items([(10.00, 1)]) + mock_get_menu.return_value = _menu_doc_from_retail_pairs([(10.00, 1)]) + mock_get_parameter.side_effect = ParameterNotFoundError("ParameterNotFound") + + with patch.dict( + os.environ, + {"GOOGLE_CLIENT_ID_PARAM": "/meal-order-manager/google-client-id"}, + ): + result = lambda_handler(_submit_event(items), None) + + status, body = _parse_response(result) + + assert status == 200, f"Expected 200, got {status}: {body}" + saved_order = mock_put.call_args[0][2] + assert saved_order["employee_name"] == "Test User" + assert saved_order["employee_email"] == "test.user@seahavenind.com" + + @patch("submit_order_handler._lambda") @patch("submit_order_handler.put_order") @patch("submit_order_handler.get_form_status", return_value="open")