meal-order-manager/tests/test_pdf.py
Adam Moussa 10d135e32e
Some checks are pending
Deploy / deploy (push) Waiting to run
Add weekly summary PDF and admin order-list download (#16) (#17)
Generate a per-person weekly summary PDF at Thursday close and store it
alongside the CSV reports, plus a client-side admin download that rolls
orders up into item -> total quantity for bulk ordering.

- shared/pdf.py: build_weekly_summary_pdf() via fpdf2 (pure-Python,
  ARM64-safe; first non-boto3 layer dep). Per-person employee -> item ->
  quantity, no pricing.
- aggregate_orders: write reports/{week}/weekly-summary-{week}.pdf
  (application/pdf) and stamp weekly_summary_pdf_s3_key on the SUMMARY.
  No new IAM (existing S3CrudPolicy). No email/Slack delivery.
- generate_form.py: "Download order list" admin button aggregates the
  loaded week's orders into an item->qty CSV (no per-employee breakdown,
  no prices) via a Blob download. Works for open weeks too.
- Tests: tests/test_pdf.py; aggregate happy-path now asserts 3 S3
  uploads + the pdf key.
- README updated.
2026-06-01 18:49:58 -04:00

105 lines
3.4 KiB
Python

"""Unit tests for src/shared/shared/pdf.py (weekly summary PDF generator)."""
import pytest
from fpdf import FPDF as _RealFPDF
from shared.pdf import build_weekly_summary_pdf
class _NoCompressFPDF(_RealFPDF):
"""FPDF subclass with stream compression off, so the content stream holds
readable text we can assert against without a PDF parser."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_compression(False)
@pytest.fixture()
def readable_pdf(monkeypatch):
"""Patch the generator to emit uncompressed (searchable) PDFs."""
monkeypatch.setattr("shared.pdf.FPDF", _NoCompressFPDF)
def _orders():
return [
{
"employee_name": "Bob Jones",
"employee_email": "bob@seahavenind.com",
"items": [
{"name": "Chicken Parm", "quantity": 2},
{"name": "Caesar Salad", "quantity": 1},
],
},
{
"employee_name": "Alice Smith",
"employee_email": "alice@seahavenind.com",
"items": [{"name": "Veggie Bowl", "quantity": 3}],
},
]
def _build(orders, **overrides):
kwargs = {
"week": "2026-W19",
"generated_at": "2026-05-22T16:35:40-04:00",
"total_employees": len(orders),
"total_meals": sum(i["quantity"] for o in orders for i in o["items"]),
}
kwargs.update(overrides)
return build_weekly_summary_pdf(orders, **kwargs)
class TestPdfStructure:
def test_returns_pdf_bytes(self):
pdf = _build(_orders())
assert isinstance(pdf, bytes)
assert pdf[:5] == b"%PDF-", "Output should be a valid PDF"
assert len(pdf) > 0
def test_empty_orders_still_valid_pdf(self):
pdf = _build([], total_employees=0, total_meals=0)
assert pdf[:5] == b"%PDF-"
class TestPdfContent:
def test_header_shows_week_and_counts(self, readable_pdf):
pdf = _build(_orders())
text = pdf.decode("latin-1")
assert "2026-W19" in text
assert "2 employees" in text
assert "6 meals" in text
def test_each_employee_and_item_present(self, readable_pdf):
orders = _orders()
pdf = _build(orders)
text = pdf.decode("latin-1")
for order in orders:
assert order["employee_name"] in text, f"missing {order['employee_name']}"
assert order["employee_email"] in text
for item in order["items"]:
assert item["name"] in text, f"missing item {item['name']}"
def test_no_prices_anywhere(self, readable_pdf):
"""Per issue #16: strictly employee, item, qty — never a price."""
orders = _orders()
# Even if price fields are present on the items, they must not be rendered.
for o in orders:
for i in o["items"]:
i["price"] = 12.50
i["subtotal"] = 99.99
pdf = _build(orders)
text = pdf.decode("latin-1")
assert "$" not in text, "PDF must contain no currency symbol"
assert "12.50" not in text and "99.99" not in text, "PDF must not render prices"
def test_non_latin1_chars_do_not_crash(self):
orders = [
{
"employee_name": "José 🍜 Ramírez",
"employee_email": "jose@seahavenind.com",
"items": [{"name": "Phở Bowl", "quantity": 1}],
}
]
pdf = _build(orders)
assert pdf[:5] == b"%PDF-"