afterhours-shift-manager/tests/slack_bot/conftest.py

83 lines
2.2 KiB
Python
Raw Permalink Normal View History

Add pytest suite and wire it into CI (#85) (#86) * Add pytest suite and wire it into CI Stands up the first automated tests for the repo (151 tests) and turns on the CI test step. - Lift slack-bot handlers out of create_app() closures to module level so they're unit-testable; create_app is now a thin Bolt-wiring layer. No behavior change (handler entrypoints and create_app signature unchanged). - tests/ mirrors src/: shared layer (schedule, blocks, 3CX client, ring_scheduler, secrets) + all four Lambdas (pay math, drop/swap/pick/ admin/register/rate, pickup button, roster sync, queue scheduler). - All boundaries mocked: DynamoDB/SES/Secrets via moto, 3CX HTTP via responses, Slack via fakes, time via freezegun. No real network/AWS. - pyproject.toml pytest config (pythonpath=src/shared, importlib mode); per-package conftest loads each app.py under a unique name to avoid the four-app.py collision. tests/requirements.txt for test-only deps. - ci.yaml: run-tests: true (reusable workflow auto-installs deps) and lint the tests dir too. - README Testing section. Closes #85 * Add least-privilege permissions block to CI workflow Resolves the CodeQL actions/missing-workflow-permissions alert: the CI workflow now restricts GITHUB_TOKEN to contents: read (it only checks out, lints, and runs tests). * Stop logging extension numbers in 3CX queue updates Resolves 3 high CodeQL py/clear-text-logging-sensitive-data alerts: the queue/ring-group forwarding logs no longer include the routed extension values (closed/holiday/extension). Non-sensitive context (resource id, queue number) is retained.
2026-06-01 19:07:08 -04:00
"""Load src/slack-bot/app.py under a unique module name so it doesn't collide
with the other Lambdas' app.py modules in sys.modules, plus common test doubles."""
import importlib.util
import pathlib
import sys
from unittest.mock import MagicMock
import pytest
_ROOT = pathlib.Path(__file__).resolve().parents[2]
def _load(name, relpath):
spec = importlib.util.spec_from_file_location(name, _ROOT / relpath)
mod = importlib.util.module_from_spec(spec)
sys.modules[name] = mod
spec.loader.exec_module(mod)
return mod
@pytest.fixture
def slackbot_app():
return _load("slackbot_app", "src/slack-bot/app.py")
@pytest.fixture
def slackbot_handler(monkeypatch):
"""Load handler.py; skip Slack auth.test so request parsing is the unit."""
from slack_bolt import App
from slack_bolt.adapter.aws_lambda import SlackRequestHandler
slack_bot_dir = str(_ROOT / "src/slack-bot")
monkeypatch.syspath_prepend(slack_bot_dir)
saved = {name: sys.modules.get(name) for name in ("app", "handler")}
for name in saved:
sys.modules.pop(name, None)
mod = _load("slackbot_handler", "src/slack-bot/handler.py")
bolt_app = App(
token="xoxb-test",
signing_secret="test-secret",
process_before_response=True,
token_verification_enabled=False,
)
wrapped = SlackRequestHandler(app=bolt_app)
monkeypatch.setattr(mod, "_get_handler", lambda: wrapped)
yield mod
mod._slack_handler = None
for name, previous in saved.items():
if previous is None:
sys.modules.pop(name, None)
else:
sys.modules[name] = previous
Add pytest suite and wire it into CI (#85) (#86) * Add pytest suite and wire it into CI Stands up the first automated tests for the repo (151 tests) and turns on the CI test step. - Lift slack-bot handlers out of create_app() closures to module level so they're unit-testable; create_app is now a thin Bolt-wiring layer. No behavior change (handler entrypoints and create_app signature unchanged). - tests/ mirrors src/: shared layer (schedule, blocks, 3CX client, ring_scheduler, secrets) + all four Lambdas (pay math, drop/swap/pick/ admin/register/rate, pickup button, roster sync, queue scheduler). - All boundaries mocked: DynamoDB/SES/Secrets via moto, 3CX HTTP via responses, Slack via fakes, time via freezegun. No real network/AWS. - pyproject.toml pytest config (pythonpath=src/shared, importlib mode); per-package conftest loads each app.py under a unique name to avoid the four-app.py collision. tests/requirements.txt for test-only deps. - ci.yaml: run-tests: true (reusable workflow auto-installs deps) and lint the tests dir too. - README Testing section. Closes #85 * Add least-privilege permissions block to CI workflow Resolves the CodeQL actions/missing-workflow-permissions alert: the CI workflow now restricts GITHUB_TOKEN to contents: read (it only checks out, lints, and runs tests). * Stop logging extension numbers in 3CX queue updates Resolves 3 high CodeQL py/clear-text-logging-sensitive-data alerts: the queue/ring-group forwarding logs no longer include the routed extension values (closed/holiday/extension). Non-sensitive context (resource id, queue number) is retained.
2026-06-01 19:07:08 -04:00
@pytest.fixture
def respond():
return MagicMock(name="respond")
@pytest.fixture
def client():
return MagicMock(name="client")
@pytest.fixture
def routing_spy(slackbot_app, monkeypatch):
"""Replace the 3CX routing call with a spy so tests can assert on it
without touching the network."""
spy = MagicMock(name="_update_3cx_routing")
monkeypatch.setattr(slackbot_app, "_update_3cx_routing", spy)
return spy
@pytest.fixture
def text_of():
"""Helper: pull the `text=` kwarg from a mock's most recent call."""
def _get(mock):
return mock.call_args.kwargs.get("text", "")
return _get