diff options
| author | Ben Sima <ben@bsima.me> | 2025-11-12 20:30:39 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bsima.me> | 2025-11-12 20:30:39 -0500 |
| commit | 3a0ad310b951a81033e8530a8e39613903538e81 (patch) | |
| tree | 896e0b369078b87d0e49c1422f2bddbea250b7bd | |
| parent | 61632862367416dc5154af536a7a4a4e7751d9a6 (diff) | |
Document database migrations and external API testing patterns
Add to AGENTS.md: - SQLite migration limitations (no UNIQUE
constraints via ALTER TABLE) - Migration logging best practices -
Testing external API integrations (Stripe pattern) - Mock webhook
testing approach - Local webhook testing with CLI tools - Handling API
version differences with fallback field names - Database inspection
for debugging integrations
These patterns emerged from implementing and debugging the Stripe
billing integration.
| -rw-r--r-- | AGENTS.md | 80 |
1 files changed, 80 insertions, 0 deletions
@@ -549,6 +549,86 @@ lint Omni/YourNamespace.hs **Fix all reported errors** related to your changes before marking the task as complete. This ensures code quality and prevents breaking the build for other contributors. +## Database Migrations + +### SQLite Limitations + +When writing migrations for SQLite databases: + +1. **Cannot add UNIQUE constraints via ALTER TABLE** - SQLite's ALTER TABLE doesn't support adding constraints. Add columns without UNIQUE and rely on application logic to enforce uniqueness. + + ```python + # Wrong - will fail silently + cursor.execute("ALTER TABLE users ADD COLUMN email TEXT UNIQUE") + + # Correct - add without UNIQUE + cursor.execute("ALTER TABLE users ADD COLUMN email TEXT") + ``` + +2. **Always log migration failures** - Don't silently swallow exceptions. Use `logger.debug()` to log when columns already exist. + +3. **Migrations run on startup** - `Database.init_db()` should be called in the `main()` function of web services to ensure migrations run every time the service starts. + +## Testing External API Integrations + +When integrating with external services like Stripe, payment processors, or other APIs: + +### Mock Webhooks in Tests + +Instead of calling real APIs, mock the webhook payloads: + +```python +class TestWebhookHandling(Test.TestCase): + def test_subscription_created(self): + # Mock the webhook payload structure + subscription = { + "id": "sub_test123", + "customer": "cus_test123", + "status": "active", + # ... other fields + } + _handle_webhook(subscription) + # Assert expected database changes +``` + +### Local Webhook Testing + +For services like Stripe that send webhooks: + +1. **Use CLI tools** - Most services provide CLI tools for local testing: + ```bash + stripe listen --forward-to localhost:8000/webhook + stripe trigger checkout.session.completed + ``` + +2. **Bypass signature verification in test mode** - Check if secret is configured before verifying signatures: + ```python + if WEBHOOK_SECRET: + event = verify_signature(payload, signature, WEBHOOK_SECRET) + else: + logger.warning("Signature verification skipped (test mode)") + event = json.loads(payload) + ``` + +3. **Handle API version differences** - External APIs change over time. Use fallbacks for field names: + ```python + # Try multiple field names for compatibility + period_start = ( + subscription.get("current_period_start") + or subscription.get("billing_cycle_anchor") + or subscription.get("start_date") + ) + ``` + +### Verify with Database Inspection + +When debugging integrations, check the database directly: +```bash +sqlite3 path/to/db.db "SELECT * FROM table WHERE condition;" +``` + +This confirms whether webhooks/integrations actually updated the data. + ## Future Enhancements Planned features (not yet implemented): |
