meal-order-manager/tests/test_publish_menu.py
Adam Moussa 5f4ca3e7ef
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 17:47:39 -04: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()