procurement-ingest/tests/test_bundle_consistency.py
Adam Moussa f5c09757f3
feat(lambda): report unhandled Lambda errors to Sentry (PLAT-138) (#203)
* feat(lambda): report unhandled Lambda errors to Sentry

* fix(lambda): strip Sentry stack-frame locals

* style(tests): wrap long lines in sentry_init tests
2026-08-29 20:02:54 +00:00

623 lines
26 KiB
Python

"""Bundle-consistency tests for Terraform Lambda packaging.
Verifies that terraform/build_packages.sh actually ships every first-party
sibling module each handler imports. Guards against a regression where the
copy step silently drops a module the handler depends on -- history: PR #105
shipped without template_parser.py, and PR #2 nearly shipped without
derived_fields.py, both allowlist-maintenance misses that would ImportError
at runtime.
Pure file reads + ast on handlers -- no AWS/boto3, no Terraform plan, no moto.
Fast and has no dependency on the moto-before-handler import-order invariant
that the rest of the suite relies on.
"""
import ast
import re
from dataclasses import dataclass, field
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
BUILD_PACKAGES = REPO_ROOT / "terraform" / "build_packages.sh"
PO_HANDLER = REPO_ROOT / "lambdas" / "po" / "email_processor" / "handler.py"
WO_HANDLER = REPO_ROOT / "lambdas" / "wo" / "email_processor" / "handler.py"
API_HANDLER = REPO_ROOT / "lambdas" / "api" / "handler.py"
SHOC_EMITTER_HANDLER = REPO_ROOT / "lambdas" / "wo" / "shoc_emitter" / "handler.py"
SHOC_ROTATOR_HANDLER = REPO_ROOT / "lambdas" / "wo" / "shoc_hmac_rotator" / "handler.py"
# Phase 3: ses_auth/web_ui_auth/email_parsing/emf are single-sourced here and
# shipped into the email-processor bundles via copy_shared_all.
SHARED_DIR = REPO_ROOT / "lambdas" / "shared"
# The names Phase 3 single-sourced under lambdas/shared/. After the move NONE
# of these may reappear as a top-level .py in either email-processor pipeline
# dir: every handler loader resolves a pipeline-local copy FIRST
# (tests/conftest.py load_handler checks path.parent before _SHARED_DIR;
# lambdas/wo/email_processor/tests/_wo_parser_support.py inserts the pipeline
# dir ahead of shared/ on sys.path), so a stray reappearance would silently
# SHADOW the single shared source in every handler-loaded test while
# test_ses_auth / test_parse_raw_email keep exercising shared/ -- divergence
# undetected. This is exactly the future-drift invariant the retired
# byte-identity ses_auth fixture used to guard; it is now enforced by policing
# the pipeline dirs and shared/ SEPARATELY (never as a union, which would let
# the same name already expected in shared/ mask a pipeline-dir stray) --
# see test_no_shared_module_shadow_in_pipeline_dirs and the per-dir exact-set
# pins below.
SHARED_MODULES = frozenset(
{"ses_auth", "email_parsing", "emf", "web_ui_auth", "sentry_init"}
)
# Exact top-level .py stems each dir must hold. Pinned as exact sets (both
# bounds): copy_py_dir ships every top-level .py in these dirs, so a stray
# scratch/secrets .py -- or a shadow copy of a shared module -- would silently
# ship into the production zip. Any new top-level module must be added here
# deliberately, the moment to decide whether it SHOULD ship (a real module) or
# must be excluded.
PO_PIPELINE_MODULES = frozenset(
{
"handler",
"template_parser",
"derived_fields",
"extraction",
"enrichment",
"telemetry",
"persistence",
"prompts",
}
)
WO_PIPELINE_MODULES = frozenset(
{
"__init__",
"handler",
"template_parser",
"extraction",
"telemetry",
"persistence",
"prompts",
}
)
# Full shipped set of each email-processor bundle (pipeline dir + shared).
# Derived from the per-dir pins above so it stays consistent with them; policed
# per-dir (NOT via this union) so a shared-name shadow in a pipeline dir cannot
# be masked. web_ui_auth is a deliberate, harmless ride-along of copy_shared_all
# (constraint #8) -- never imported by the email handlers, but it ships, so it
# is part of the shipped set.
PO_EXPECTED_TOP_LEVEL_MODULES = PO_PIPELINE_MODULES | SHARED_MODULES
WO_EXPECTED_TOP_LEVEL_MODULES = WO_PIPELINE_MODULES | SHARED_MODULES
# procurement-api (lambdas/api/): same exact-set discipline.
API_PIPELINE_MODULES = frozenset(
{
"handler",
"router",
"pagination",
"serialization",
"wo_repo",
"po_repo",
}
)
# Non-.py files the api package must also copy: the handler serves the spec,
# docs page, and the vendored Redoc bundle from its own package dir, so dropping
# any copy 500s /docs at runtime with green CI.
API_DATA_FILES = (
"api/openapi.json",
"api/docs.html",
"api/redoc.standalone.js",
"api/fonts.css",
)
# SHOC webhook emitter + HMAC rotator. Same exact-set discipline.
SHOC_EMITTER_MODULES = frozenset({"__init__", "handler", "envelope", "delivery"})
SHOC_ROTATOR_MODULES = frozenset({"__init__", "handler"})
@dataclass
class PackageRecipe:
"""What build_packages.sh copies into one terraform/build/<name> dir."""
py_dirs: list[str] = field(default_factory=list)
shared_all: bool = False
src_files: list[str] = field(default_factory=list)
def _first_party_sibling_imports(handler_path: Path) -> set[str]:
"""Top-level module names handler.py imports that are first-party siblings.
Parses only top-level (module-body) `import X` / `from X import ...`
statements -- not imports nested in functions -- and keeps a name only
if `<sibling_dir>/X.py` exists, which filters out stdlib/third-party
imports (json, os, boto3, ...) and keeps exactly the modules the
packaging step is obligated to ship.
"""
tree = ast.parse(handler_path.read_text())
names: set[str] = set()
for node in tree.body:
if isinstance(node, ast.Import):
for alias in node.names:
names.add(alias.name.split(".")[0])
elif isinstance(node, ast.ImportFrom):
if node.module:
names.add(node.module.split(".")[0])
sibling_dir = handler_path.parent
# A first-party sibling resolves either next to the handler (per-pipeline:
# template_parser/derived_fields) or under lambdas/shared/ (single-sourced:
# ses_auth/email_parsing/emf, Phase 3). Both must count, or the ships-all
# pin would silently drop the shared siblings.
return {
name
for name in names
if (sibling_dir / f"{name}.py").exists() or (SHARED_DIR / f"{name}.py").exists()
}
_FOR_LOOP_RE = re.compile(
r"for\s+(\w+)\s+in\s+([^;]+);\s*do\s*"
r'copy_src_file\s+"([^"]*)\$\{\1\}([^"]*)"\s+"(\$\{BUILD\}/[^"]*)\$\{\1\}([^"]*)"\s*'
r"done",
re.MULTILINE,
)
def _parse_build_packages(script_text: str | None = None) -> dict[str, PackageRecipe]:
"""Parse copy_* calls from build_packages.sh into per-package recipes.
Strips `#` comments so a commented-out copy cannot false-pass. Expands
simple `for f in a b c; do copy_src_file "api/${f}" ...; done` loops used
for the procurement-api static assets.
"""
text = BUILD_PACKAGES.read_text() if script_text is None else script_text
# Drop full-line and trailing comments before parsing.
cleaned_lines = []
for raw_line in text.splitlines():
cleaned_lines.append(raw_line.split("#", 1)[0])
text = "\n".join(cleaned_lines)
recipes: dict[str, PackageRecipe] = {}
def _pkg(dest: str) -> PackageRecipe:
# dest is "${BUILD}/po_email_processor" or "${BUILD}/po_web_ui/web_ui_auth.py"
m = re.match(r"\$\{BUILD\}/([^/\s\"']+)", dest)
if not m:
raise AssertionError(f"unrecognized build dest: {dest!r}")
name = m.group(1)
return recipes.setdefault(name, PackageRecipe())
# Expand for-loops first so ${f} never lands as a literal src path.
for match in _FOR_LOOP_RE.finditer(text):
items = match.group(2).split()
src_prefix, src_suffix = match.group(3), match.group(4)
dest_prefix, dest_suffix = match.group(5), match.group(6)
for item in items:
dest = f"{dest_prefix}{item}{dest_suffix}"
_pkg(dest).src_files.append(f"{src_prefix}{item}{src_suffix}")
text_without_loops = _FOR_LOOP_RE.sub("", text)
for raw_line in text_without_loops.splitlines():
line = raw_line.strip()
if not line:
continue
m = re.match(
r'copy_py_dir\s+"([^"]+)"\s+"(\$\{BUILD\}/[^"]+)"',
line,
)
if m:
_pkg(m.group(2)).py_dirs.append(m.group(1))
continue
m = re.match(r'copy_shared_all\s+"(\$\{BUILD\}/[^"]+)"', line)
if m:
_pkg(m.group(1)).shared_all = True
continue
m = re.match(
r'copy_src_file\s+"([^"]+)"\s+"(\$\{BUILD\}/[^"]+)"',
line,
)
if m:
_pkg(m.group(2)).src_files.append(m.group(1))
continue
return recipes
def _shipped_modules(recipe: PackageRecipe) -> set[str]:
"""Module stems a PackageRecipe would place at the zip root."""
shipped: set[str] = set()
for rel_dir in recipe.py_dirs:
glob_dir = REPO_ROOT / "lambdas" / rel_dir
shipped.update(p.stem for p in glob_dir.glob("*.py"))
if recipe.shared_all:
shipped.update(p.stem for p in SHARED_DIR.glob("*.py"))
for rel in recipe.src_files:
if rel.endswith(".py"):
shipped.add(Path(rel).stem)
return shipped
def _packaging_ships_all(recipe: PackageRecipe, sibling_names: set[str]) -> bool:
"""True if recipe is guaranteed to ship every name in sibling_names."""
if not recipe.py_dirs and not recipe.shared_all and not recipe.src_files:
return False
shipped = _shipped_modules(recipe)
return all(name in shipped for name in sibling_names)
def test_po_bundling_ships_all_first_party_siblings():
siblings = _first_party_sibling_imports(PO_HANDLER)
# Sanity: PO handler.py is known to import ses_auth, template_parser,
# and derived_fields as bare-name siblings. If this ever collapses to
# an empty set, the test below would vacuously pass -- guard against that.
assert siblings, "expected first-party sibling imports in PO handler.py"
recipes = _parse_build_packages()
recipe = recipes["po_email_processor"]
assert "po/email_processor" in recipe.py_dirs, (
"terraform/build_packages.sh must copy_py_dir po/email_processor into "
"po_email_processor so every first-party sibling handler.py imports "
f"ships automatically. Recipe: {recipe}"
)
assert recipe.shared_all, (
"terraform/build_packages.sh must copy_shared_all into "
"po_email_processor so the single-sourced shared modules ship flat "
f"beside handler.py. Recipe: {recipe}"
)
assert _packaging_ships_all(recipe, siblings), (
f"po_email_processor packaging does not ship all of {sorted(siblings)}: "
f"{recipe}"
)
def test_wo_bundling_ships_all_first_party_siblings():
siblings = _first_party_sibling_imports(WO_HANDLER)
assert siblings, "expected first-party sibling imports in WO handler.py"
recipes = _parse_build_packages()
recipe = recipes["wo_email_processor"]
assert "wo/email_processor" in recipe.py_dirs, (
"terraform/build_packages.sh must copy_py_dir wo/email_processor into "
"wo_email_processor so every first-party sibling ships while tests/ "
f"and requirements.txt are excluded. Recipe: {recipe}"
)
assert recipe.shared_all, (
"terraform/build_packages.sh must copy_shared_all into "
"wo_email_processor so the single-sourced shared modules ship flat "
f"beside handler.py. Recipe: {recipe}"
)
assert _packaging_ships_all(recipe, siblings), (
f"wo_email_processor packaging does not ship all of {sorted(siblings)}: "
f"{recipe}"
)
def test_po_email_processor_dir_ships_no_unexpected_top_level_modules():
"""Upper bound on the PO pipeline dir alone (NOT unioned with shared/).
The sibling-import tests above prove packaging ships every module the
handler needs (shipped >= required). This proves the other direction for
the pipeline dir (shipped <= expected): because copy_py_dir copies every
top-level .py in that dir, a stray scratch or secrets .py, OR a shadow
copy of a moved shared module, would silently ship into the zip. Policing
this dir SEPARATELY from shared/ (rather than as a union with it) is what
makes a strayed-back ses_auth.py / email_parsing.py / emf.py FAIL here
instead of being masked by the same name already being expected in shared/.
"""
top_level = {p.stem for p in PO_HANDLER.parent.glob("*.py")}
assert top_level == set(PO_PIPELINE_MODULES), (
"unexpected top-level .py set in lambdas/po/email_processor -- "
"copy_py_dir would ship exactly these into the Lambda zip. Found "
f"{sorted(top_level)}, expected {sorted(PO_PIPELINE_MODULES)}. A moved "
f"shared module ({sorted(SHARED_MODULES)}) reappearing here would "
"SHADOW the single shared source in every handler-loaded test -- "
"remove it. If a new per-pipeline module is intended, add it to "
"PO_PIPELINE_MODULES."
)
def test_wo_email_processor_dir_ships_no_unexpected_top_level_modules():
"""Upper bound on the WO pipeline dir alone (NOT unioned with shared/)."""
top_level = {p.stem for p in WO_HANDLER.parent.glob("*.py")}
assert top_level == set(WO_PIPELINE_MODULES), (
"unexpected top-level .py set in lambdas/wo/email_processor -- "
"copy_py_dir would ship exactly these into the Lambda zip. Found "
f"{sorted(top_level)}, expected {sorted(WO_PIPELINE_MODULES)}. A moved "
f"shared module ({sorted(SHARED_MODULES)}) reappearing here would "
"SHADOW the single shared source in every handler-loaded test -- "
"remove it. If a new per-pipeline module is intended, add it to "
"WO_PIPELINE_MODULES."
)
def test_shared_dir_ships_no_unexpected_top_level_modules():
"""Upper bound on lambdas/shared/ alone (NOT unioned with a pipeline dir).
copy_shared_all ships every top-level .py under lambdas/shared/ into BOTH
email-processor bundles, so a stray scratch/secrets .py here would leak into
both production zips. Pinned to exactly the single-sourced shared modules.
"""
top_level = {p.stem for p in SHARED_DIR.glob("*.py")}
assert top_level == set(SHARED_MODULES), (
"unexpected top-level .py set in lambdas/shared -- copy_shared_all "
"would ship exactly these into BOTH email-processor Lambda zips. "
f"Found {sorted(top_level)}, expected {sorted(SHARED_MODULES)}. If a "
"new shared module is intended, add it to SHARED_MODULES; if it is a "
"scratch or secrets file, remove it before deploy."
)
def test_no_shared_module_shadow_in_pipeline_dirs():
"""The moved shared names must live ONLY under lambdas/shared/."""
for name in sorted(SHARED_MODULES):
assert (SHARED_DIR / f"{name}.py").exists(), (
f"{name}.py must exist under lambdas/shared/ (single source of truth)"
)
assert not (PO_HANDLER.parent / f"{name}.py").exists(), (
f"{name}.py reappeared in lambdas/po/email_processor -- it would "
"SHADOW lambdas/shared/{name}.py in every PO handler-loaded test "
"(the loader resolves the pipeline-local copy first). Delete it; "
"the single source lives under lambdas/shared/."
)
assert not (WO_HANDLER.parent / f"{name}.py").exists(), (
f"{name}.py reappeared in lambdas/wo/email_processor -- it would "
"SHADOW lambdas/shared/{name}.py in every WO handler-loaded test "
"(_wo_parser_support inserts the pipeline dir ahead of shared/ on "
"sys.path). Delete it; the single source lives under lambdas/shared/."
)
def test_detection_logic_catches_allowlist_missing_a_sibling():
"""Unit-level check on `_packaging_ships_all` itself.
Simulates the historical regression shape: packaging copies only an
explicit filename set that omits a required sibling. Phase 5 note: the
PR #2 near-miss module (derived_fields) is now a TRANSITIVE import via
enrichment.py; the representative near-miss here is enrichment (PO-only,
a direct handler import).
"""
siblings = _first_party_sibling_imports(PO_HANDLER)
assert "enrichment" in siblings # sanity: a PO-only direct flat-sibling import
incomplete = PackageRecipe(
src_files=[
"po/email_processor/handler.py",
"shared/ses_auth.py",
"po/email_processor/template_parser.py",
]
)
assert not _packaging_ships_all(incomplete, siblings)
# Control: explicit files covering all required direct siblings is complete.
complete = PackageRecipe(
src_files=[
"po/email_processor/handler.py",
"shared/ses_auth.py",
"po/email_processor/template_parser.py",
"shared/email_parsing.py",
"po/email_processor/prompts.py",
"po/email_processor/telemetry.py",
"po/email_processor/extraction.py",
"po/email_processor/enrichment.py",
"po/email_processor/persistence.py",
"shared/sentry_init.py",
]
)
assert _packaging_ships_all(complete, siblings)
def test_detection_logic_rejects_narrowed_py_dir():
"""A recipe that only copies handler.py must not satisfy ships-all."""
siblings = _first_party_sibling_imports(PO_HANDLER)
narrowed = PackageRecipe(src_files=["po/email_processor/handler.py"])
assert not _packaging_ships_all(narrowed, siblings)
def test_detection_logic_rejects_commented_out_shared_copy():
"""A copy_shared_all surviving only in a `#` comment must not count as run."""
script = (
'copy_py_dir "po/email_processor" "${BUILD}/po_email_processor"\n'
'# copy_shared_all "${BUILD}/po_email_processor"\n'
)
recipes = _parse_build_packages(script)
recipe = recipes["po_email_processor"]
assert not recipe.shared_all
po_siblings = _first_party_sibling_imports(PO_HANDLER)
assert not _packaging_ships_all(recipe, po_siblings)
def test_detection_logic_rejects_wrong_pipeline_dir():
"""A copy_py_dir pointing at the WRONG pipeline must not pass ships-all.
A slip that leaves po_email_processor copying wo/email_processor would
stage a bundle missing enrichment.py (PO-only) -- a runtime ImportError.
"""
po_siblings = _first_party_sibling_imports(PO_HANDLER)
assert "enrichment" in po_siblings # lives only under po/email_processor
wrong = PackageRecipe(py_dirs=["wo/email_processor"])
assert not _packaging_ships_all(wrong, po_siblings)
arbitrary = PackageRecipe(py_dirs=["venv/lib"])
assert not _packaging_ships_all(arbitrary, po_siblings)
def test_po_web_ui_bundle_stages_shared_auth_module():
"""The PO web_ui package must copy shared/web_ui_auth.py.
Phase 3 removed the inline auth block from lambdas/po/web_ui/handler.py,
which now does a module-top-level `from web_ui_auth import is_authenticated`;
web_ui_auth.py lives ONLY under lambdas/shared/. Dropping this copy would
ImportError the auth-gated Lambda at cold start with green CI.
"""
recipes = _parse_build_packages()
recipe = recipes["po_web_ui"]
assert "po/web_ui" in recipe.py_dirs, (
f"po_web_ui must copy_py_dir po/web_ui. Recipe: {recipe}"
)
assert "shared/web_ui_auth.py" in recipe.src_files, (
"terraform/build_packages.sh must copy_src_file shared/web_ui_auth.py "
"into po_web_ui so `from web_ui_auth import is_authenticated` resolves "
f"at cold start. Recipe: {recipe}"
)
def test_wo_web_ui_bundle_stages_shared_auth_module():
"""The WO web_ui package must copy shared/web_ui_auth.py."""
recipes = _parse_build_packages()
recipe = recipes["wo_web_ui"]
assert "wo/web_ui" in recipe.py_dirs, (
f"wo_web_ui must copy_py_dir wo/web_ui. Recipe: {recipe}"
)
assert "shared/web_ui_auth.py" in recipe.src_files, (
"terraform/build_packages.sh must copy_src_file shared/web_ui_auth.py "
"into wo_web_ui so `from web_ui_auth import is_authenticated` resolves "
f"at cold start. Recipe: {recipe}"
)
def test_detection_logic_rejects_commented_out_web_ui_auth_cp():
"""A web_ui_auth copy surviving only in a `#` comment must not pass."""
script = (
'copy_py_dir "po/web_ui" "${BUILD}/po_web_ui"\n'
'# copy_src_file "shared/web_ui_auth.py" "${BUILD}/po_web_ui/web_ui_auth.py"\n'
)
recipes = _parse_build_packages(script)
recipe = recipes["po_web_ui"]
assert "shared/web_ui_auth.py" not in recipe.src_files
def test_api_bundling_ships_all_first_party_siblings():
"""The procurement-api package must ship every sibling handler.py imports."""
required = _first_party_sibling_imports(API_HANDLER)
assert required, "expected api/handler.py to import first-party siblings"
recipes = _parse_build_packages()
recipe = recipes["procurement_api"]
assert _packaging_ships_all(recipe, required), (
f"procurement_api packaging does not ship all first-party siblings "
f"{sorted(required)}. Recipe: {recipe}"
)
def test_api_dir_ships_no_unexpected_top_level_modules():
"""Exact-set pin on lambdas/api/*.py -- both bounds."""
api_dir = REPO_ROOT / "lambdas" / "api"
top_level = {p.stem for p in api_dir.glob("*.py")}
assert top_level == set(API_PIPELINE_MODULES), (
f"lambdas/api/ top-level modules {sorted(top_level)} != pinned "
f"{sorted(API_PIPELINE_MODULES)}. If a new module is intended, add it "
"to API_PIPELINE_MODULES."
)
def test_api_bundle_stages_shared_auth_module():
"""The api package must copy shared/web_ui_auth.py (docs-token gate)."""
recipes = _parse_build_packages()
recipe = recipes["procurement_api"]
assert "shared/web_ui_auth.py" in recipe.src_files, (
"terraform/build_packages.sh must copy_src_file shared/web_ui_auth.py "
"into procurement_api so the docs-token gate resolves at cold start. "
f"Recipe: {recipe}"
)
def test_api_bundle_stages_spec_and_docs_page():
"""The api package must copy openapi.json, docs.html, and Redoc assets."""
recipes = _parse_build_packages()
recipe = recipes["procurement_api"]
for rel in API_DATA_FILES:
assert rel in recipe.src_files, (
f"terraform/build_packages.sh must copy_src_file {rel!r} into "
f"procurement_api. Recipe: {recipe}"
)
def test_shoc_emitter_bundling_ships_all_first_party_siblings():
"""The SHOC emitter package must ship every sibling handler.py imports."""
required = _first_party_sibling_imports(SHOC_EMITTER_HANDLER)
assert required == {"envelope", "delivery", "sentry_init"}, (
f"expected the emitter handler's first-party siblings to be envelope + "
f"delivery + sentry_init, found {sorted(required)} — update this pin "
"deliberately"
)
recipes = _parse_build_packages()
recipe = recipes["wo_shoc_emitter"]
assert "wo/shoc_emitter" in recipe.py_dirs, (
f"wo_shoc_emitter must copy_py_dir wo/shoc_emitter. Recipe: {recipe}"
)
assert _packaging_ships_all(recipe, required), (
f"wo_shoc_emitter packaging does not ship all first-party siblings "
f"{sorted(required)}. Recipe: {recipe}"
)
def test_shoc_rotator_bundling_ships_handler():
"""The rotator package must ship handler.py and shared sentry_init."""
required = _first_party_sibling_imports(SHOC_ROTATOR_HANDLER)
assert required == {"sentry_init"}, (
f"the rotator handler first-party siblings {sorted(required)} — "
"expected only sentry_init; extend this ships-all test if more appear"
)
recipes = _parse_build_packages()
recipe = recipes["wo_shoc_hmac_rotator"]
assert "wo/shoc_hmac_rotator" in recipe.py_dirs, (
f"wo_shoc_hmac_rotator must copy_py_dir wo/shoc_hmac_rotator. Recipe: {recipe}"
)
assert _packaging_ships_all(recipe, {"handler", "sentry_init"}), (
f"wo_shoc_hmac_rotator packaging does not ship handler.py + sentry_init. "
f"Recipe: {recipe}"
)
def test_shoc_emitter_dir_ships_no_unexpected_top_level_modules():
"""Exact-set pin on lambdas/wo/shoc_emitter/*.py -- both bounds."""
top_level = {p.stem for p in SHOC_EMITTER_HANDLER.parent.glob("*.py")}
assert top_level == set(SHOC_EMITTER_MODULES), (
f"lambdas/wo/shoc_emitter/ top-level modules {sorted(top_level)} != "
f"pinned {sorted(SHOC_EMITTER_MODULES)}. If a new module is intended, "
"add it to SHOC_EMITTER_MODULES."
)
def test_shoc_rotator_dir_ships_no_unexpected_top_level_modules():
"""Exact-set pin on lambdas/wo/shoc_hmac_rotator/*.py -- both bounds."""
top_level = {p.stem for p in SHOC_ROTATOR_HANDLER.parent.glob("*.py")}
assert top_level == set(SHOC_ROTATOR_MODULES), (
f"lambdas/wo/shoc_hmac_rotator/ top-level modules {sorted(top_level)} "
f"!= pinned {sorted(SHOC_ROTATOR_MODULES)}. If a new module is "
"intended, add it to SHOC_ROTATOR_MODULES."
)
def test_email_processor_packages_do_not_collide_with_web_ui_dirs():
"""Email-processor recipes must not copy web_ui dirs (and vice versa)."""
recipes = _parse_build_packages()
for name in ("po_email_processor", "wo_email_processor"):
recipe = recipes[name]
assert not any("web_ui" in d for d in recipe.py_dirs), (
f"{name} packaging unexpectedly copies a web_ui dir: {recipe}"
)
for name in ("po_web_ui", "wo_web_ui"):
recipe = recipes[name]
assert not any("email_processor" in d for d in recipe.py_dirs), (
f"{name} packaging unexpectedly copies an email_processor dir: {recipe}"
)
def test_non_email_processor_packages_copy_sentry_init():
"""Functions that do not copy_shared_all must copy sentry_init by name."""
recipes = _parse_build_packages()
for name in (
"po_web_ui",
"wo_web_ui",
"procurement_api",
"po_site_extractor",
"wo_shoc_emitter",
"wo_shoc_hmac_rotator",
):
recipe = recipes[name]
assert "shared/sentry_init.py" in recipe.src_files, (
f"terraform/build_packages.sh must copy_src_file shared/sentry_init.py "
f"into {name} so `import sentry_init` resolves at cold start. "
f"Recipe: {recipe}"
)