meal-order-manager/tests/test_publish_menu.py
Adam Moussa cc506f3c1f
Some checks are pending
Deploy API / Deploy API to dev (push) Waiting to run
Deploy API / Deploy API to prod (push) Waiting to run
feat(menu): publish the weekly menu from the job worker (PLAT-229) (#219)
* feat(menu): publish the weekly menu from the job worker

Monday publish parses the catalog embedded in the Redefine menu page and runs on the Fargate worker, so the GitHub Actions scrape cron can go away.

* fix(menu): address review feedback

Use the form deadline in the Monday Slack post, and compare that message exactly so CodeQL does not treat the test as URL sanitization.
2026-09-25 22:10:24 +00:00

145 lines
5 KiB
Python

"""publish_menu writes the menu, then the form, then Slack."""
from unittest.mock import MagicMock, patch
import pytest
from server.jobs import publish_menu
from server.jobs import run_job
MENU = {
"scraped_at": "2026-09-21T07:30:00-04:00",
"menu_url": "https://www.redefinemeals.com/menu",
"meal_count": 1,
"meals": [{"name": "Korean Steak Bowl", "price": 12.49}],
}
ENV = {
"MENU_URL": "https://www.redefinemeals.com/menu",
"GOOGLE_CLIENT_ID": "client.apps.googleusercontent.com",
"FORM_BUCKET": "meal-order-manager-form-test",
"DISTRIBUTION_ID": "E123",
"FORM_URL": "https://orders.seahaven.com",
"TABLE_NAME": "meal-order-manager-orders",
}
def _clients(s3, cloudfront):
def client(name, **_kwargs):
if name == "s3":
return s3
if name == "cloudfront":
return cloudfront
raise AssertionError(name)
return client
@patch("shared.slack.post_channel_message", return_value={"ok": True})
@patch("server.jobs.publish_menu.put_menu")
@patch(
"server.jobs.publish_menu.get_settings",
return_value={"bulk_discount_percent": 10, "company_subsidy_percent": 50},
)
@patch("server.jobs.publish_menu.generate_form", return_value="<html>form</html>")
@patch("server.jobs.publish_menu.current_week", return_value="2026-W38")
@patch("server.jobs.publish_menu.fetch_menu", return_value=MENU)
@patch("server.jobs.publish_menu.boto3.client")
def test_publish_menu_uploads_after_the_menu_write(
mock_client,
mock_fetch,
mock_week,
mock_generate,
mock_settings,
mock_put,
mock_slack,
):
s3 = MagicMock()
cloudfront = MagicMock()
mock_client.side_effect = _clients(s3, cloudfront)
order = []
mock_put.side_effect = lambda *args, **kwargs: order.append("menu")
s3.put_object.side_effect = lambda **kwargs: order.append(kwargs["Key"])
cloudfront.create_invalidation.side_effect = lambda **kwargs: order.append(
"invalidate"
)
mock_slack.side_effect = lambda *args, **kwargs: (
order.append("slack") or {"ok": True}
)
with patch.dict("os.environ", ENV, clear=False):
result = publish_menu.lambda_handler({}, None)
assert result == {"status": "published", "week": "2026-W38", "meal_count": 1}
mock_fetch.assert_called_once_with("https://www.redefinemeals.com/menu")
mock_week.assert_called_once()
mock_settings.assert_called_once()
mock_generate.assert_called_once()
assert mock_generate.call_args.kwargs["week"] == "2026-W38"
assert mock_generate.call_args.kwargs["google_client_id"] == ENV["GOOGLE_CLIENT_ID"]
assert mock_generate.call_args.kwargs["bulk_discount"] == 10
assert mock_generate.call_args.kwargs["company_subsidy"] == 50
mock_put.assert_called_once_with("2026-W38", MENU)
assert s3.put_object.call_count == 2
index = s3.put_object.call_args_list[0].kwargs
archive = s3.put_object.call_args_list[1].kwargs
assert index["Bucket"] == ENV["FORM_BUCKET"]
assert index["Key"] == "index.html"
assert index["CacheControl"] == "no-cache"
assert archive["Key"] == "archive/2026-W38.html"
invalidation = cloudfront.create_invalidation.call_args.kwargs
assert invalidation["DistributionId"] == "E123"
assert invalidation["InvalidationBatch"]["Paths"]["Items"] == ["/index.html"]
mock_slack.assert_called_once()
assert mock_slack.call_args.args[0] == (
"This week's meal order is open! Deadline: Thursday at 11:59 PM."
)
assert mock_slack.call_args.args[1][1]["text"]["text"] == (
f"*<{ENV['FORM_URL']}|Place your order>*\n\n*Deadline:* Thursday at 11:59 PM\n"
)
assert order == [
"menu",
"index.html",
"archive/2026-W38.html",
"invalidate",
"slack",
]
@patch("shared.slack.post_channel_message", return_value={"ok": True})
@patch("server.jobs.publish_menu.put_menu")
@patch("server.jobs.publish_menu.get_settings", return_value={})
@patch("server.jobs.publish_menu.generate_form", return_value="<html>form</html>")
@patch("server.jobs.publish_menu.current_week", return_value="2026-W38")
@patch("server.jobs.publish_menu.fetch_menu", return_value=MENU)
@patch("server.jobs.publish_menu.boto3.client")
def test_publish_menu_does_not_notify_when_upload_fails(
mock_client,
_fetch,
_week,
_generate,
_settings,
mock_put,
mock_slack,
):
s3 = MagicMock()
s3.put_object.side_effect = RuntimeError("s3 down")
mock_client.side_effect = _clients(s3, MagicMock())
with patch.dict("os.environ", ENV, clear=False):
with pytest.raises(RuntimeError, match="s3 down"):
publish_menu.lambda_handler({}, None)
mock_put.assert_called_once()
mock_slack.assert_not_called()
def test_run_job_dispatches_publish_menu():
with patch(
"server.jobs.publish_menu.lambda_handler", return_value={"status": "published"}
) as handler:
result = run_job({"event": "publish_menu"})
assert result == {"status": "published"}
handler.assert_called_once()