Skip to content

Commit 1b0d494

Browse files
author
Irving Popovetsky
committed
phase 0
1 parent 6dc85fc commit 1b0d494

9 files changed

Lines changed: 429 additions & 25 deletions

docker/Dockerfile.test

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# docker/Dockerfile.test
2+
# For running tests in Python 3.7 (required since 3.7 won't install on modern macOS)
3+
4+
FROM python:3.7-slim
5+
6+
ENV PIP_DISABLE_PIP_VERSION_CHECK=on \
7+
PYTHONUNBUFFERED=1
8+
9+
WORKDIR /app
10+
11+
# Install system dependencies
12+
RUN apt-get update && apt-get install -y --no-install-recommends \
13+
build-essential \
14+
&& rm -rf /var/lib/apt/lists/*
15+
16+
# Install specific poetry version compatible with Python 3.7
17+
RUN pip install "poetry==1.1.15"
18+
19+
# Copy dependency files
20+
COPY pyproject.toml poetry.lock ./
21+
22+
# Install dependencies (including dev)
23+
RUN poetry config virtualenvs.create false && \
24+
poetry install --no-interaction
25+
26+
# Copy source code
27+
COPY . .
28+
29+
# Default command: run tests
30+
CMD ["poetry", "run", "pytest", "-v"]

docker/docker-compose.test.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
version: '3.8'
2+
3+
services:
4+
test-py37:
5+
build:
6+
context: ..
7+
dockerfile: docker/Dockerfile.test
8+
environment:
9+
- SLACK_TOKEN=test_token
10+
- SLACK_VERIFY=supersecuretoken
11+
- SLACK_BOT_USER_ID=bot_user_id
12+
- SLACK_BOT_ID=bot_id
13+
- AIRTABLE_API_KEY=test_key
14+
- AIRTABLE_BASE_KEY=test_base
15+
- YELP_TOKEN=test_yelp
16+
- BACKEND_AUTH_TOKEN=devBackendToken
17+
volumes:
18+
- ../tests:/app/tests:ro
19+
- ../pybot:/app/pybot:ro
20+
command: poetry run pytest -v --tb=short
21+
22+
test-py312:
23+
build:
24+
context: ..
25+
dockerfile: docker/Dockerfile.test.py312
26+
environment:
27+
- SLACK_TOKEN=test_token
28+
- SLACK_VERIFY=supersecuretoken
29+
- SLACK_BOT_USER_ID=bot_user_id
30+
- SLACK_BOT_ID=bot_id
31+
- AIRTABLE_API_KEY=test_key
32+
- AIRTABLE_BASE_KEY=test_base
33+
- YELP_TOKEN=test_yelp
34+
- BACKEND_AUTH_TOKEN=devBackendToken
35+
volumes:
36+
- ../tests:/app/tests:ro
37+
- ../pybot:/app/pybot:ro
38+
command: poetry run pytest -v --tb=short

docs/UPGRADE_PLAN.md

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ This document provides a step-by-step plan to upgrade operationcode-pybot from P
1010

1111
---
1212

13-
## Phase 0: Establish Test Baseline (Days 1-4)
13+
## Phase 0: Establish Test Baseline (Days 1-4) ✅ COMPLETE
1414

1515
> **Why Docker?** Python 3.7 cannot be installed on modern macOS (especially Apple Silicon).
1616
> We must run the existing tests in a Docker container to establish a baseline before upgrading.
17+
>
18+
> **Status**: Completed January 4, 2026
19+
> **Result**: 57 tests passing, 0 failures, 1 warning (deprecation warning is expected)
1720
1821
### 0.1 Create Test Dockerfile
1922

@@ -35,8 +38,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
3538
build-essential \
3639
&& rm -rf /var/lib/apt/lists/*
3740

38-
# Install poetry
39-
RUN pip install poetry
41+
# Install specific poetry version compatible with Python 3.7
42+
RUN pip install "poetry==1.1.15"
4043

4144
# Copy dependency files
4245
COPY pyproject.toml poetry.lock ./
@@ -457,35 +460,62 @@ class TestActionMessages:
457460
# Example: "15 passed, 0 failed"
458461
```
459462

460-
### 0.7 Document Baseline
463+
### 0.7 Document Baseline
461464

462-
Create `docs/TEST_BASELINE.md`:
463-
464-
```markdown
465-
# Test Baseline (Pre-Upgrade)
465+
**Baseline Test Results - Python 3.7.17**
466466

467-
**Date**: [DATE]
468-
**Python Version**: 3.7.x
467+
**Date**: January 4, 2026
468+
**Python Version**: 3.7.17
469469
**Test Command**: `./scripts/test-py37.sh`
470+
**Docker Image**: python:3.7-slim
471+
472+
## Results Summary
473+
474+
**Total: 57 tests passed, 0 failed, 1 warning**
475+
476+
| Test Category | Tests | Status |
477+
|--------------|-------|--------|
478+
| **Upgrade Safety Tests** | 23 | ✅ All Passed |
479+
| - Module imports | 18 | ✅ |
480+
| - Async handler verification | 3 | ✅ |
481+
| - Plugin loading | 2 | ✅ |
482+
| **Existing Integration Tests** | 11 | ✅ All Passed |
483+
| - API endpoint tests | 4 | ✅ |
484+
| - Slack action tests | 4 | ✅ |
485+
| - Slack event tests | 4 | ✅ |
486+
| **New Unit Tests** | 23 | ✅ All Passed |
487+
| - Action message tests | 3 | ✅ |
488+
| - Lunch command tests | 8 | ✅ |
489+
| - Roll command tests | 11 | ✅ |
490+
491+
## Test Coverage by Module
492+
493+
- **test_upgrade_safety.py**: 23 tests - Verifies all modules import correctly and handlers are async
494+
- **test_slack_api_endpoint.py**: 4 tests - API credential detection and verification
495+
- **test_slack_actions.py**: 4 tests - Mentor claim/unclaim actions
496+
- **test_slack_events.py**: 4 tests - Team join and message logging
497+
- **test_action_messages.py**: 3 tests - Message attachment builders
498+
- **test_lunch_command.py**: 8 tests - Lunch command parsing and Yelp integration
499+
- **test_roll_command.py**: 11 tests - Dice roll input validation
500+
501+
## Warnings
502+
503+
1 deprecation warning (expected, will be addressed in upgrade):
504+
```
505+
DeprecationWarning: Inheritance class SirBot from web.Application is discouraged
506+
```
470507

471-
## Results
508+
This warning is expected and relates to aiohttp's deprecation patterns. It will be resolved during the Python 3.12 upgrade when we modernize the vendored sirbot code.
472509

473-
| Test File | Passed | Failed | Skipped |
474-
|-----------|--------|--------|---------|
475-
| test_slack_actions.py | X | X | X |
476-
| test_slack_events.py | X | X | X |
477-
| test_slack_api_endpoint.py | X | X | X |
478-
| test_upgrade_safety.py | X | X | X |
479-
| test_lunch_command.py | X | X | X |
480-
| test_roll_command.py | X | X | X |
481-
| test_action_messages.py | X | X | X |
482-
| **Total** | **X** | **X** | **X** |
510+
## Key Findings
483511

484-
## Notes
512+
1. ✅ All async handlers are properly defined with `async def`
513+
2. ✅ All modules import without syntax errors
514+
3. ✅ Both custom plugins instantiate correctly
515+
4. ✅ All existing integration tests pass
516+
5. ✅ New safety net tests provide comprehensive coverage
485517

486-
- [Any tests that fail and why]
487-
- [Any skipped tests and why]
488-
```
518+
**Baseline is stable and ready for upgrade work.**
489519

490520
---
491521

scripts/test-py312.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/bash
2+
# Run tests in Python 3.12 Docker container
3+
set -e
4+
5+
cd "$(dirname "$0")/.."
6+
7+
echo "🐍 Running tests in Python 3.12 container..."
8+
docker build -t pybot-test:py312 -f docker/Dockerfile.test.py312 .
9+
docker run --rm \
10+
-e SLACK_TOKEN=test_token \
11+
-e SLACK_VERIFY=supersecuretoken \
12+
-e SLACK_BOT_USER_ID=bot_user_id \
13+
-e SLACK_BOT_ID=bot_id \
14+
-e AIRTABLE_API_KEY=test_key \
15+
-e AIRTABLE_BASE_KEY=test_base \
16+
-e YELP_TOKEN=test_yelp \
17+
-e BACKEND_AUTH_TOKEN=devBackendToken \
18+
pybot-test:py312 \
19+
poetry run pytest -v "$@"

scripts/test-py37.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/bash
2+
# Run tests in Python 3.7 Docker container
3+
set -e
4+
5+
cd "$(dirname "$0")/.."
6+
7+
echo "🐍 Running tests in Python 3.7 container..."
8+
docker build -t pybot-test:py37 -f docker/Dockerfile.test .
9+
docker run --rm \
10+
-e SLACK_TOKEN=test_token \
11+
-e SLACK_VERIFY=supersecuretoken \
12+
-e SLACK_BOT_USER_ID=bot_user_id \
13+
-e SLACK_BOT_ID=bot_id \
14+
-e AIRTABLE_API_KEY=test_key \
15+
-e AIRTABLE_BASE_KEY=test_base \
16+
-e YELP_TOKEN=test_yelp \
17+
-e BACKEND_AUTH_TOKEN=devBackendToken \
18+
pybot-test:py37 \
19+
poetry run pytest -v "$@"

tests/test_upgrade_safety.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""
2+
Upgrade Safety Tests
3+
4+
These tests catch common Python 3.7 → 3.12 upgrade issues:
5+
- Import errors (syntax changes)
6+
- Async function verification (asyncio.coroutine removal)
7+
- Handler registration verification
8+
"""
9+
10+
import asyncio
11+
import importlib
12+
13+
import pytest
14+
15+
16+
class TestModuleImports:
17+
"""Verify all modules import without syntax errors."""
18+
19+
MODULES = [
20+
"pybot",
21+
"pybot.__main__",
22+
"pybot.endpoints.slack.commands",
23+
"pybot.endpoints.slack.events",
24+
"pybot.endpoints.slack.messages",
25+
"pybot.endpoints.slack.actions",
26+
"pybot.endpoints.slack.actions.general_actions",
27+
"pybot.endpoints.slack.actions.mentor_request",
28+
"pybot.endpoints.slack.actions.mentor_volunteer",
29+
"pybot.endpoints.slack.actions.new_member",
30+
"pybot.endpoints.slack.actions.report_message",
31+
"pybot.endpoints.slack.utils.slash_lunch",
32+
"pybot.endpoints.slack.utils.action_messages",
33+
"pybot.endpoints.airtable.requests",
34+
"pybot.endpoints.api.slack_api",
35+
"pybot.plugins.airtable.plugin",
36+
"pybot.plugins.airtable.api",
37+
"pybot.plugins.api.plugin",
38+
]
39+
40+
@pytest.mark.parametrize("module_name", MODULES)
41+
def test_module_imports(self, module_name):
42+
"""Each module should import without errors."""
43+
importlib.import_module(module_name)
44+
45+
46+
class TestAsyncHandlers:
47+
"""Verify all handlers are proper async functions.
48+
49+
Critical for Python 3.11+ where asyncio.coroutine() is removed.
50+
"""
51+
52+
def test_slash_commands_are_async(self):
53+
from pybot.endpoints.slack.commands import (
54+
slash_lunch,
55+
slash_mentor,
56+
slash_mentor_volunteer,
57+
slash_repeat,
58+
slash_report,
59+
slash_roll,
60+
)
61+
62+
handlers = [
63+
slash_lunch,
64+
slash_mentor,
65+
slash_mentor_volunteer,
66+
slash_repeat,
67+
slash_report,
68+
slash_roll,
69+
]
70+
for handler in handlers:
71+
# Get the wrapped function if decorated
72+
func = getattr(handler, "__wrapped__", handler)
73+
assert asyncio.iscoroutinefunction(func), \
74+
f"{handler.__name__} must be async"
75+
76+
def test_event_handlers_are_async(self):
77+
from pybot.endpoints.slack.events import team_join
78+
79+
assert asyncio.iscoroutinefunction(team_join), \
80+
"team_join must be async"
81+
82+
def test_action_handlers_are_async(self):
83+
from pybot.endpoints.slack.actions.general_actions import (
84+
claimed,
85+
delete_message,
86+
reset_claim,
87+
)
88+
89+
handlers = [claimed, delete_message, reset_claim]
90+
for handler in handlers:
91+
assert asyncio.iscoroutinefunction(handler), \
92+
f"{handler.__name__} must be async"
93+
94+
95+
class TestPluginLoading:
96+
"""Verify plugins can be instantiated."""
97+
98+
def test_airtable_plugin_instantiates(self):
99+
from pybot.plugins.airtable.plugin import AirtablePlugin
100+
plugin = AirtablePlugin()
101+
assert plugin.__name__ == "airtable"
102+
103+
def test_api_plugin_instantiates(self):
104+
from pybot.plugins.api.plugin import APIPlugin
105+
plugin = APIPlugin()
106+
assert plugin.__name__ == "api"

tests/unit/test_action_messages.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Unit tests for action message builders."""
2+
3+
import pytest
4+
5+
from pybot.endpoints.slack.utils.action_messages import (
6+
base_response,
7+
claimed_attachment,
8+
not_claimed_attachment,
9+
)
10+
11+
12+
class TestActionMessages:
13+
"""Test message attachment builders."""
14+
15+
def test_not_claimed_attachment_structure(self):
16+
attachment = not_claimed_attachment()
17+
18+
assert "callback_id" in attachment
19+
assert "actions" in attachment
20+
assert isinstance(attachment["actions"], list)
21+
22+
def test_claimed_attachment_includes_user(self):
23+
attachment = claimed_attachment("U12345")
24+
25+
assert "callback_id" in attachment
26+
# Should mention the user who claimed
27+
assert "U12345" in str(attachment)
28+
29+
def test_base_response_extracts_required_fields(self):
30+
mock_action = {
31+
"channel": {"id": "C123"},
32+
"message_ts": "123456.789",
33+
"original_message": {
34+
"text": "Test message"
35+
}
36+
}
37+
38+
response = base_response(mock_action)
39+
40+
assert response["channel"] == "C123"
41+
assert response["ts"] == "123456.789"
42+
assert response["text"] == "Test message"

0 commit comments

Comments
 (0)