# Apply Progress: Notification Center ## TDD Cycle Evidence (PR-1) | Cycle | Task | Test File | RED | GREEN | Evidence | |-------|------|-----------|-----|-------|----------| | 1 | NC-PR1-004 (basic CRUD) | `tests/unit/test_notification_service.py` | 13 tests written against missing service | All 13 pass | `pytest tests/unit/test_notification_service.py` → 13 passed | | 2 | NC-PR1-005 (edge cases) | `tests/unit/test_notification_service.py` | Already included in cycle 1 | Added wrong-owner, mute-categories, cross-user isolation | Same 13 tests pass | | 3 | NC-PR1-007 (basic endpoints) | `tests/integration/test_notifications_api.py` | 10 tests written against missing router | All 10 pass | `pytest tests/integration/test_notifications_api.py` → 10 passed | | 4 | NC-PR1-008 (API edge cases) | `tests/integration/test_notifications_api.py` | Already included in cycle 3 | Pagination, 404 ownership, mute categories at API layer | Same 10 tests pass | | 5 | NC-PR1-010 (REFACTOR) | All files | — | ruff clean, no regressions | `ruff check` passes on all new files; existing unit tests 223 passed (4 pre-existing failures unrelated) | ## TDD Cycle Evidence (PR-2) | Cycle | Task | Test File | RED | GREEN | Evidence | |-------|------|-----------|-----|-------|----------| | 1 | NC-PR2-004 (producer flow) | `tests/integration/test_notifications_lifecycle.py` | 4 tests written against unwired producers | Wired `lifecycle_hooks.py` and `health_monitor.py` | 4 passed | | 2 | NC-PR2-004 (severity mapping) | `tests/integration/test_notifications_lifecycle.py` | Added error + warning severity tests | Already green from implementation | 6 passed | | 3 | NC-PR2-003 (UserConfig schema) | `src/api/user_config.py` | Schema extended with new optional fields | PATCH/GET endpoints validate correctly | Verified manually | | 4 | NC-PR2-005 (REFACTOR) | All files | — | ruff clean, no regressions across 41 related tests | All pass | ## Completed Tasks ### PR-1: Backend Core - [x] NC-PR1-001: Alembic migration for `notifications` table - [x] NC-PR1-002: SQLAlchemy `Notification` model (`apps/api/src/models/notification.py`) - [x] NC-PR1-003: Export `Notification` in `models/__init__.py` - [x] NC-PR1-004: NotificationService unit tests — basic CRUD (RED) - [x] NC-PR1-005: Implement `NotificationService` singleton (GREEN) - [x] NC-PR1-006: Service edge-case and isolation tests (TRIANGULATE) - [x] NC-PR1-007: API integration tests — basic endpoints (RED) - [x] NC-PR1-008: Implement FastAPI router + Pydantic schemas (GREEN) - [x] NC-PR1-009: API edge-case and ownership tests (TRIANGULATE) - [x] NC-PR1-010: Register router in `main.py` + import `Notification` for Alembic - [x] NC-PR1-011: Code quality pass — ruff, test regressions, smoke tests (REFACTOR) ### PR-2: Backend Integration - [x] NC-PR2-001: Wire `lifecycle_hooks.py` to `NotificationService` - [x] NC-PR2-002: Wire `health_monitor.py` to `NotificationService` - [x] NC-PR2-003: Extend `UserConfig` schema for notification preferences - [x] NC-PR2-004: Event producer integration tests (RED) - [x] NC-PR2-005: Verify producer tests pass and clean up (GREEN / REFACTOR) ## Files Changed ### PR-1 Files 1. `apps/api/alembic/versions/2026_05_29_add_notifications_table.py` *(new)* — Alembic migration 2. `apps/api/src/models/notification.py` *(new)* — SQLAlchemy model 3. `apps/api/src/models/__init__.py` — Export `Notification` 4. `apps/api/src/services/notification_service.py` *(new)* — `NotificationService` singleton 5. `apps/api/src/api/notifications.py` *(new)* — FastAPI router + Pydantic schemas 6. `apps/api/src/api/__init__.py` — Export `notifications_router` 7. `apps/api/src/main.py` — Register router, import `Notification` for Alembic 8. `apps/api/tests/unit/test_notification_service.py` *(new)* — 13 unit tests 9. `apps/api/tests/integration/test_notifications_api.py` *(new)* — 10 integration tests 10. `apps/api/tests/integration/test_models.py` — Updated expected tables list ### PR-2 Files 11. `apps/api/src/services/lifecycle_hooks.py` — Wired `NotificationService` after event bus publish 12. `apps/api/src/services/health_monitor.py` — Wired `NotificationService` after state change event publish 13. `apps/api/src/api/user_config.py` — Added `notification_mute_categories` and `notification_toast_level` to Pydantic schemas 14. `apps/api/tests/integration/test_notifications_lifecycle.py` *(new)* — 6 integration tests for event-to-notification flow ## Test Commands & Exit Codes ### PR-1 ```bash # Unit tests for NotificationService (13 tests) cd apps/api && python -m pytest tests/unit/test_notification_service.py -v # Exit: 0 — 13 passed # Integration tests for notifications API (10 tests) cd apps/api && python -m pytest tests/integration/test_notifications_api.py -v # Exit: 0 — 10 passed # Existing unit tests (no regressions in our code) cd apps/api && python -m pytest tests/unit/ -v # Exit: 1 — 223 passed, 4 failed (pre-existing failures in test_config.py and test_git_repository_clone_preflight.py) ``` ### PR-2 ```bash # New integration tests for event producers (6 tests) cd apps/api && python -m pytest tests/integration/test_notifications_lifecycle.py -v # Exit: 0 — 6 passed # NotificationService unit tests (no regressions) cd apps/api && python -m pytest tests/unit/test_notification_service.py -v # Exit: 0 — 13 passed # Notifications API integration tests (no regressions) cd apps/api && python -m pytest tests/integration/test_notifications_api.py -v # Exit: 0 — 10 passed # Health monitor unit tests (no regressions) cd apps/api && python -m pytest tests/unit/test_health_monitor.py -v # Exit: 0 — 6 passed # Event integration tests (no regressions) cd apps/api && python -m pytest tests/integration/test_events.py -v # Exit: 0 — 6 passed # Combined relevant test suite (41 tests) cd apps/api && python -m pytest \ tests/unit/test_notification_service.py \ tests/integration/test_notifications_api.py \ tests/integration/test_notifications_lifecycle.py \ tests/unit/test_health_monitor.py \ tests/integration/test_events.py \ -v # Exit: 0 — 41 passed # Ruff linting on all PR-2 modified files cd apps/api && python -m ruff check \ src/services/lifecycle_hooks.py \ src/services/health_monitor.py \ src/api/user_config.py \ tests/integration/test_notifications_lifecycle.py # Exit: 0 — All checks passed ``` ## Deviations from Design ### PR-1 - **SQLAlchemy `metadata` column name conflict:** `Base.metadata` is reserved by SQLAlchemy DeclarativeBase. Used `notification_metadata` as the Python attribute name with DB column name `"metadata"`. In the Pydantic response model, used `Field(serialization_alias="metadata")` so the JSON API still exposes `metadata` as specified. - **`created_at` type in Pydantic:** Used `datetime` instead of `str` to leverage FastAPI's automatic ISO serialization. ### PR-2 - None. All mappings and behaviors match the design spec (section 1.3) and task requirements exactly. ## Surprises / Decisions ### PR-1 1. **SQLite `func.now()` resolution:** `test_list_notifications_orders_by_created_at_desc` failed because multiple rapid INSERTs got identical timestamps. Fixed by explicitly setting `created_at` offsets in the test after creation. 2. **Pre-existing integration test failures:** ~40 integration tests fail due to missing `asyncpg` module and direct PostgreSQL connection attempts in their custom setup code. These are unrelated to our changes. 3. **Pre-existing `test_models.py` outdated:** The `test_expected_tables_are_registered` assertion had a hardcoded set missing many newer tables. Updated it to include all current tables (including `notifications`). ### PR-2 1. **Health monitor `test_health_monitor_unhealthy_creates_warning_notification` required `public_url`:** The health monitor only checks tunnel health when `instance.public_url` is truthy. Without setting it on the test fixture instance, `_derive_status` returned `"running"` instead of `"unhealthy"`, which created an `"info"` notification. Fixed by setting `test_instance.public_url` in the test before calling `_check_instance`. 2. **Patch target for failure test:** The `test_notification_failure_does_not_block_event_pipeline` patches `src.services.lifecycle_hooks.notification_service.create_notification`. This only works because `lifecycle_hooks.py` imports `notification_service` at module level, making the attribute resolvable by `unittest.mock.patch`. 3. **No schema migration needed for UserConfig:** Preferences are stored in the existing JSON `config` blob, consistent with the existing pattern (theme, editor, git identity). No Alembic migration required. ## Remaining Tasks - [ ] PR-3: Frontend Core (NC-PR3-001 through NC-PR3-012) - [ ] PR-4: Toast Coordination (NC-PR4-001 through NC-PR4-006) ## PR Boundary This progress covers PR-1 and PR-2. PR-3 (frontend core — NotificationProvider, useNotifications, NotificationCenter, NotificationItem, styles, AppShell integration) and PR-4 (toast coordination — EventToastBridge preferences, settings UI) are out of scope.