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
|
|
|
"""Tests for slack-bot _handle_register and _handle_rate."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestRegister:
|
|
|
|
|
def test_register_links_account(
|
|
|
|
|
self, slackbot_app, schedule, seed, respond, text_of
|
|
|
|
|
):
|
|
|
|
|
seed.roster("114", "Alice")
|
|
|
|
|
slackbot_app._handle_register(respond, schedule, "U_ALICE", "register 114")
|
|
|
|
|
assert "Linked" in text_of(respond)
|
|
|
|
|
assert schedule.get_employee_by_extension("114")["slack_user_id"] == "U_ALICE"
|
|
|
|
|
|
|
|
|
|
def test_register_unknown_extension(self, slackbot_app, schedule, respond, text_of):
|
|
|
|
|
slackbot_app._handle_register(respond, schedule, "U_ALICE", "register 999")
|
|
|
|
|
assert "not found" in text_of(respond).lower()
|
|
|
|
|
|
|
|
|
|
def test_register_usage(self, slackbot_app, schedule, respond, text_of):
|
|
|
|
|
slackbot_app._handle_register(respond, schedule, "U_ALICE", "register")
|
|
|
|
|
assert "Usage" in text_of(respond)
|
|
|
|
|
|
2026-06-18 12:05:25 -04:00
|
|
|
def test_register_reregister_self_is_idempotent(
|
|
|
|
|
self, slackbot_app, schedule, seed, respond, text_of
|
|
|
|
|
):
|
|
|
|
|
seed.roster("114", "Alice")
|
|
|
|
|
slackbot_app._handle_register(respond, schedule, "U_ALICE", "register 114")
|
|
|
|
|
slackbot_app._handle_register(respond, schedule, "U_ALICE", "register 114")
|
|
|
|
|
assert "Linked" in text_of(respond)
|
|
|
|
|
assert schedule.get_employee_by_extension("114")["slack_user_id"] == "U_ALICE"
|
|
|
|
|
|
|
|
|
|
def test_register_cannot_hijack_others_extension(
|
|
|
|
|
self, slackbot_app, schedule, seed, respond, text_of
|
|
|
|
|
):
|
|
|
|
|
seed.roster("114", "Alice")
|
|
|
|
|
# Alice claims her extension first.
|
|
|
|
|
slackbot_app._handle_register(respond, schedule, "U_ALICE", "register 114")
|
|
|
|
|
# Mallory tries to claim Alice's extension — must be rejected.
|
|
|
|
|
slackbot_app._handle_register(respond, schedule, "U_MALLORY", "register 114")
|
|
|
|
|
assert "already registered" in text_of(respond).lower()
|
|
|
|
|
# The binding must still point at Alice, not Mallory.
|
|
|
|
|
assert schedule.get_employee_by_extension("114")["slack_user_id"] == "U_ALICE"
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
class TestRate:
|
2026-06-18 12:05:25 -04:00
|
|
|
def test_non_admin_rejected(self, slackbot_app, schedule, seed, respond, text_of):
|
|
|
|
|
seed.roster("114", "Alice")
|
|
|
|
|
slackbot_app._handle_rate(respond, schedule, "rate 114 90", False)
|
|
|
|
|
assert "restricted" in text_of(respond).lower()
|
|
|
|
|
# The rate must not have been changed by a non-admin.
|
|
|
|
|
assert schedule.get_shift_rate("114") == 0.0
|
|
|
|
|
|
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
|
|
|
def test_show_rates(self, slackbot_app, schedule, seed, respond, text_of):
|
|
|
|
|
seed.config(shift_rate="50")
|
|
|
|
|
seed.roster("114", "Alice", shift_rate="75")
|
2026-06-18 12:05:25 -04:00
|
|
|
slackbot_app._handle_rate(respond, schedule, "rate", True)
|
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
|
|
|
text = text_of(respond)
|
|
|
|
|
assert "$50.00" in text and "Alice" in text and "$75.00" in text
|
|
|
|
|
|
|
|
|
|
def test_show_rates_no_custom(self, slackbot_app, schedule, seed, respond, text_of):
|
|
|
|
|
seed.config(shift_rate="50")
|
2026-06-18 12:05:25 -04:00
|
|
|
slackbot_app._handle_rate(respond, schedule, "rate", True)
|
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
|
|
|
assert "No per-person rates" in text_of(respond)
|
|
|
|
|
|
|
|
|
|
def test_set_default(self, slackbot_app, schedule, seed, respond, text_of):
|
|
|
|
|
seed.config(shift_rate="50")
|
2026-06-18 12:05:25 -04:00
|
|
|
slackbot_app._handle_rate(respond, schedule, "rate default 60", True)
|
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
|
|
|
assert schedule.get_shift_rate() == 60.0
|
|
|
|
|
assert "$60.00" in text_of(respond)
|
|
|
|
|
|
|
|
|
|
def test_set_default_invalid_amount(self, slackbot_app, schedule, respond, text_of):
|
2026-06-18 12:05:25 -04:00
|
|
|
slackbot_app._handle_rate(respond, schedule, "rate default abc", True)
|
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
|
|
|
assert "Invalid amount" in text_of(respond)
|
|
|
|
|
|
|
|
|
|
def test_set_default_usage(self, slackbot_app, schedule, respond, text_of):
|
2026-06-18 12:05:25 -04:00
|
|
|
slackbot_app._handle_rate(respond, schedule, "rate default", True)
|
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
|
|
|
assert "Usage" in text_of(respond)
|
|
|
|
|
|
|
|
|
|
def test_set_per_employee(self, slackbot_app, schedule, seed, respond, text_of):
|
|
|
|
|
seed.roster("114", "Alice")
|
2026-06-18 12:05:25 -04:00
|
|
|
slackbot_app._handle_rate(respond, schedule, "rate 114 90", True)
|
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
|
|
|
assert schedule.get_shift_rate("114") == 90.0
|
|
|
|
|
assert "Alice" in text_of(respond) and "$90.00" in text_of(respond)
|
|
|
|
|
|
|
|
|
|
def test_set_per_employee_unknown(self, slackbot_app, schedule, respond, text_of):
|
2026-06-18 12:05:25 -04:00
|
|
|
slackbot_app._handle_rate(respond, schedule, "rate 999 90", True)
|
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
|
|
|
assert "not found" in text_of(respond).lower()
|
|
|
|
|
|
|
|
|
|
def test_set_per_employee_strips_dollar_sign(
|
|
|
|
|
self, slackbot_app, schedule, seed, respond
|
|
|
|
|
):
|
|
|
|
|
seed.roster("114", "Alice")
|
2026-06-18 12:05:25 -04:00
|
|
|
slackbot_app._handle_rate(respond, schedule, "rate 114 $90", True)
|
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
|
|
|
assert schedule.get_shift_rate("114") == 90.0
|