From abe34de3d52c9b7386d85297fd9525c9659d4ef8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 22 Sep 2026 00:13:16 +0000 Subject: [PATCH] fix(api): split week params and allow live menu nulls (DEV-289) Menu and form-status take current or YYYY-WNN. Orders take YYYY-WNN or a calendar date and reject current. Menu payloads may emit null menu_url, calories, protein, and image_url. Co-authored-by: Adam Moussa --- openapi.yaml | 26 +++++++++++++++++--------- tests/test_openapi_contract.py | 12 ++++++++++++ tests/test_submit_order.py | 18 ++++++++++++++++++ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index 36af0b5..b7ce5e3 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -56,7 +56,7 @@ paths: summary: Published menu for a week security: [] parameters: - - $ref: "#/components/parameters/WeekPath" + - $ref: "#/components/parameters/MenuWeekPath" responses: "200": description: Menu payload @@ -76,7 +76,7 @@ paths: summary: Open or closed for a week security: [] parameters: - - $ref: "#/components/parameters/WeekPath" + - $ref: "#/components/parameters/MenuWeekPath" responses: "200": description: Form status @@ -128,7 +128,7 @@ paths: security: - portalCognito: [] parameters: - - $ref: "#/components/parameters/WeekPath" + - $ref: "#/components/parameters/OrderWeekPath" responses: "200": description: Empty list when the caller has no order @@ -322,11 +322,19 @@ components: name: X-Meals-Publish-Key parameters: - WeekPath: + MenuWeekPath: name: week in: path required: true - description: "`current`, `YYYY-WNN`, or for orders a calendar date that maps to a week" + description: "`current` or `YYYY-WNN`. Other values are 400." + schema: + type: string + minLength: 1 + OrderWeekPath: + name: week + in: path + required: true + description: "`YYYY-WNN` or `YYYY-MM-DD`. `current` is 400." schema: type: string minLength: 1 @@ -373,9 +381,9 @@ components: price: type: number calories: - type: [string, number] + type: [string, number, "null"] protein: - type: [string, number] + type: [string, number, "null"] description: type: [string, "null"] dietary_tags: @@ -383,7 +391,7 @@ components: items: type: string image_url: - type: string + type: [string, "null"] is_new: type: boolean @@ -405,7 +413,7 @@ components: scraped_at: type: [string, "null"] menu_url: - type: string + type: [string, "null"] meal_count: type: integer meals: diff --git a/tests/test_openapi_contract.py b/tests/test_openapi_contract.py index d64fb41..7676ca0 100644 --- a/tests/test_openapi_contract.py +++ b/tests/test_openapi_contract.py @@ -24,6 +24,18 @@ def test_openapi_uses_redocly_recommended(): assert '"400":' not in roster form_status = spec.split("/api/form-status/{week}:", 1)[1].split("\n /", 1)[0] assert '"400":' in form_status + menu = spec.split("/api/menu/{week}:", 1)[1].split("\n /", 1)[0] + orders = spec.split("/api/orders/{week}:", 1)[1].split("\n /", 1)[0] + assert "MenuWeekPath" in menu + assert "MenuWeekPath" in form_status + assert "OrderWeekPath" in orders + assert "WeekPath" not in spec.split("components:", 1)[1].split("MenuWeekPath", 1)[0] + assert "`current` or `YYYY-WNN`" in spec + assert "`YYYY-WNN` or `YYYY-MM-DD`" in spec + meal = spec.split(" Meal:", 1)[1].split("\n MenuPayload:", 1)[0] + assert 'type: [string, number, "null"]' in meal + assert 'type: [string, "null"]' in meal + assert 'menu_url:\n type: [string, "null"]' in spec assert "root: openapi.yaml" in redocly assert '"openapi:lint"' in package assert '"@redocly/cli": "2.52.1"' in package diff --git a/tests/test_submit_order.py b/tests/test_submit_order.py index d435798..c583893 100644 --- a/tests/test_submit_order.py +++ b/tests/test_submit_order.py @@ -2164,6 +2164,24 @@ def test_my_orders_empty_when_none(mock_looks_like, mock_portal, mock_get): assert body == {"week": "2026-W36", "orders": []} +@patch("submit_order_handler.get_order") +@patch( + "submit_order_handler._verify_portal_token", + return_value={ + "name": "Portal Employee", + "email": "portal.employee@seahavenind.com", + }, +) +@patch("submit_order_handler.looks_like_cognito_token", return_value=True) +def test_my_orders_rejects_current_week(mock_looks_like, mock_portal, mock_get): + status, body = _parse_response( + submit_order_handler.lambda_handler(_orders_event(week="current"), None) + ) + assert status == 400 + assert "YYYY-WNN" in body["error"] + mock_get.assert_not_called() + + def test_my_orders_requires_bearer(): status, body = _parse_response( submit_order_handler.lambda_handler(_orders_event(token=""), None)