Add shared core library and architecture plans (Phase 1)
Some checks failed
Validate / Hassfest (push) Has been cancelled
Some checks failed
Validate / Hassfest (push) Has been cancelled
Extract HA-independent logic from the integration into packages/core/ as a standalone Python library (immich-watcher-core). This is the first phase of restructuring the project to support a standalone web app alongside the existing HAOS integration. Core library modules: - models: SharedLinkInfo, AssetInfo, AlbumData, AlbumChange dataclasses - immich_client: Async Immich API client (aiohttp, session-injected) - change_detector: Pure function for album change detection - asset_utils: Filtering, sorting, URL building utilities - telegram/client: Full Telegram Bot API (text, photo, video, media groups) - telegram/cache: File ID cache with pluggable storage backend - telegram/media: Media size checks, URL extraction, group splitting - notifications/queue: Persistent notification queue - storage: StorageBackend protocol + JSON file implementation All modules have zero Home Assistant imports. 50 unit tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
22
plans/README.md
Normal file
22
plans/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Plans
|
||||
|
||||
This folder contains the primary architecture plan and phase-specific subplans for the Immich Watcher project restructuring.
|
||||
|
||||
## Structure
|
||||
|
||||
- `primary-plan.md` -- Master plan with architecture decisions and phase overview
|
||||
- `phase-1-core-library.md` -- Extract shared core library
|
||||
- `phase-2-haos-refactor.md` -- Wire core into HAOS integration
|
||||
- `phase-3-server-backend.md` -- Build standalone FastAPI server
|
||||
- `phase-4-frontend.md` -- Build SvelteKit web UI
|
||||
- `phase-5-haos-server-sync.md` -- Optional HAOS-Server integration
|
||||
|
||||
## Tracking
|
||||
|
||||
Each plan uses checkbox tracking:
|
||||
- `[ ]` -- Not started
|
||||
- `[x]` -- Completed
|
||||
- `[~]` -- In progress
|
||||
- `[-]` -- Skipped/deferred
|
||||
|
||||
Phase subplans are created when work on that phase begins (not all upfront).
|
||||
211
plans/phase-1-core-library.md
Normal file
211
plans/phase-1-core-library.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# Phase 1: Extract Core Library
|
||||
|
||||
**Status**: Not started
|
||||
**Parent**: [primary-plan.md](primary-plan.md)
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Extract all HA-independent logic from the integration into `packages/core/` as a standalone Python library (`immich-watcher-core`). This library will be consumed by both the HAOS integration and the standalone server.
|
||||
|
||||
---
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
packages/core/
|
||||
pyproject.toml
|
||||
src/immich_watcher_core/
|
||||
__init__.py
|
||||
constants.py # Event types, attribute names, asset types, defaults
|
||||
models.py # SharedLinkInfo, AssetInfo, AlbumData, AlbumChange
|
||||
immich_client.py # Async Immich API client (aiohttp)
|
||||
change_detector.py # detect_album_changes() pure function
|
||||
asset_utils.py # Asset filtering, sorting, URL building
|
||||
telegram/
|
||||
__init__.py
|
||||
client.py # TelegramClient - full Bot API operations
|
||||
cache.py # TelegramFileCache with CacheBackend protocol
|
||||
media.py # _split_media_by_upload_size, photo limit checks, URL helpers
|
||||
notifications/
|
||||
__init__.py
|
||||
queue.py # NotificationQueue with QueueBackend protocol
|
||||
storage.py # CacheBackend/QueueBackend protocols + JSON file implementations
|
||||
tests/
|
||||
__init__.py
|
||||
test_models.py
|
||||
test_immich_client.py
|
||||
test_change_detector.py
|
||||
test_asset_utils.py
|
||||
test_telegram_client.py
|
||||
test_telegram_cache.py
|
||||
test_notification_queue.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tasks
|
||||
|
||||
### 1. Package setup `[ ]`
|
||||
|
||||
- [x] Create `packages/core/pyproject.toml` with:
|
||||
- Name: `immich-watcher-core`
|
||||
- Dependencies: `aiohttp`, `jinja2`
|
||||
- Optional dev deps: `pytest`, `pytest-asyncio`, `aioresponses`
|
||||
- [x] Create `packages/core/src/immich_watcher_core/__init__.py`
|
||||
|
||||
### 2. Extract constants `[ ]`
|
||||
|
||||
**Source**: `custom_components/immich_album_watcher/const.py`
|
||||
|
||||
Extract to `constants.py`:
|
||||
- Event names: `EVENT_ALBUM_CHANGED`, `EVENT_ASSETS_ADDED`, etc. (L29-34)
|
||||
- Attribute names: all `ATTR_*` constants (L37-77)
|
||||
- Asset types: `ASSET_TYPE_IMAGE`, `ASSET_TYPE_VIDEO` (L80-81)
|
||||
- Defaults: `DEFAULT_SCAN_INTERVAL`, `DEFAULT_TELEGRAM_CACHE_TTL`, `NEW_ASSETS_RESET_DELAY`, `DEFAULT_SHARE_PASSWORD` (L23-27)
|
||||
|
||||
**Keep in HA const.py**: `DOMAIN`, `CONF_*`, `SUBENTRY_TYPE_ALBUM`, `PLATFORMS`, `SERVICE_*` (HA-specific)
|
||||
|
||||
### 3. Extract data models `[ ]`
|
||||
|
||||
**Source**: `custom_components/immich_album_watcher/coordinator.py` L66-326
|
||||
|
||||
Extract to `models.py`:
|
||||
- `SharedLinkInfo` (L67-111) -- dataclass, zero HA deps
|
||||
- `AssetInfo` (L114-249) -- dataclass, uses only `ASSET_TYPE_IMAGE` from constants
|
||||
- `AlbumData` (L252-308) -- dataclass, uses `AssetInfo` + `ASSET_TYPE_*`
|
||||
- `AlbumChange` (L311-325) -- dataclass, pure data
|
||||
|
||||
All use only stdlib + our constants. No changes needed except import paths.
|
||||
|
||||
### 4. Extract Immich API client `[ ]`
|
||||
|
||||
**Source**: `custom_components/immich_album_watcher/coordinator.py`
|
||||
|
||||
Extract to `immich_client.py` as `ImmichClient` class:
|
||||
- Constructor takes: `session: aiohttp.ClientSession`, `url: str`, `api_key: str`
|
||||
- `async get_server_config() -> str | None` (L640-668) -- returns external_domain
|
||||
- `async get_users() -> dict[str, str]` (L617-638) -- user_id -> name
|
||||
- `async get_people() -> dict[str, str]` (L593-615) -- person_id -> name
|
||||
- `async get_shared_links(album_id: str) -> list[SharedLinkInfo]` (L670-699)
|
||||
- `async get_album(album_id: str) -> AlbumData | None` (L876-898 fetch part)
|
||||
- `async create_shared_link(album_id, password?) -> bool` (L1199-1243)
|
||||
- `async delete_shared_link(link_id) -> bool` (L1245-1271)
|
||||
- `async set_shared_link_password(link_id, password?) -> bool` (L1144-1176)
|
||||
- `async ping() -> bool` -- validate connection (used by config_flow)
|
||||
- Properties: `url`, `external_url`, `api_key`
|
||||
- Helper: `get_internal_download_url(url)` (L384-400)
|
||||
|
||||
**Key design**: Accept `aiohttp.ClientSession` via constructor. HA provides `async_get_clientsession(hass)`, standalone creates its own.
|
||||
|
||||
### 5. Extract asset utilities `[ ]`
|
||||
|
||||
**Source**: `custom_components/immich_album_watcher/coordinator.py` L458-591, L761-856
|
||||
|
||||
Extract to `asset_utils.py`:
|
||||
- `filter_assets(assets, ...)` -- favorite_only, min_rating, asset_type, date range, memory_date, geolocation (L498-557)
|
||||
- `sort_assets(assets, order_by, order)` (L559-581)
|
||||
- `build_asset_detail(asset, external_url, shared_links, include_thumbnail)` (L801-856)
|
||||
- URL builders: `get_asset_public_url`, `get_asset_download_url`, `get_asset_video_url`, `get_asset_photo_url` (L761-799)
|
||||
- Album URL helpers: `get_public_url`, `get_any_url`, `get_protected_url`, etc. (L709-759)
|
||||
|
||||
### 6. Extract change detection `[ ]`
|
||||
|
||||
**Source**: `custom_components/immich_album_watcher/coordinator.py` L979-1066
|
||||
|
||||
Extract to `change_detector.py`:
|
||||
- `detect_album_changes(old_state, new_state, pending_asset_ids) -> tuple[AlbumChange | None, set[str]]`
|
||||
- Pure function: takes two `AlbumData` + pending set, returns change + updated pending set
|
||||
- No HA dependencies
|
||||
|
||||
### 7. Extract storage protocols `[ ]`
|
||||
|
||||
**Source**: `custom_components/immich_album_watcher/storage.py`
|
||||
|
||||
Extract to `storage.py`:
|
||||
|
||||
```python
|
||||
class CacheBackend(Protocol):
|
||||
"""Abstract storage backend for caches."""
|
||||
async def load(self) -> dict[str, Any]: ...
|
||||
async def save(self, data: dict[str, Any]) -> None: ...
|
||||
async def remove(self) -> None: ...
|
||||
|
||||
class QueueBackend(Protocol):
|
||||
"""Abstract storage backend for queues."""
|
||||
async def load(self) -> dict[str, Any]: ...
|
||||
async def save(self, data: dict[str, Any]) -> None: ...
|
||||
async def remove(self) -> None: ...
|
||||
|
||||
class JsonFileBackend:
|
||||
"""Simple JSON file storage backend (for standalone server)."""
|
||||
def __init__(self, path: Path): ...
|
||||
```
|
||||
|
||||
### 8. Extract TelegramFileCache `[ ]`
|
||||
|
||||
**Source**: `custom_components/immich_album_watcher/storage.py` L71-262
|
||||
|
||||
Extract to `telegram/cache.py`:
|
||||
- `TelegramFileCache` -- takes `CacheBackend` instead of `hass + Store`
|
||||
- All logic unchanged: TTL mode, thumbhash mode, cleanup, get/set/set_many
|
||||
- Remove `hass`/`Store` imports
|
||||
|
||||
### 9. Extract NotificationQueue `[ ]`
|
||||
|
||||
**Source**: `custom_components/immich_album_watcher/storage.py` L265-328
|
||||
|
||||
Extract to `notifications/queue.py`:
|
||||
- `NotificationQueue` -- takes `QueueBackend` instead of `hass + Store`
|
||||
- All logic unchanged: enqueue, get_all, has_pending, remove_indices, clear
|
||||
|
||||
### 10. Extract Telegram client `[ ]`
|
||||
|
||||
**Source**: `custom_components/immich_album_watcher/sensor.py` L55-60, L61-111, L114-170, L455-550, L551-1700+
|
||||
|
||||
Extract to `telegram/client.py` as `TelegramClient`:
|
||||
- Constructor: `session: aiohttp.ClientSession`, `bot_token: str`, `cache: TelegramFileCache | None`, `asset_cache: TelegramFileCache | None`, `url_resolver: Callable[[str], str] | None` (for internal URL conversion)
|
||||
- `async send_notification(chat_id, assets?, caption?, ...)` -- main entry (L455-549)
|
||||
- `async send_message(...)` (L551-597)
|
||||
- `async send_chat_action(...)` (L599-665)
|
||||
- `_log_error(...)` (L667-803)
|
||||
- `async send_photo(...)` (L805-958)
|
||||
- `async send_video(...)` (L960-1105)
|
||||
- `async send_document(...)` (L1107-1215)
|
||||
- `async send_media_group(...)` (L1217-end)
|
||||
|
||||
Extract to `telegram/media.py`:
|
||||
- Constants: `TELEGRAM_API_BASE_URL`, `TELEGRAM_MAX_PHOTO_SIZE`, `TELEGRAM_MAX_VIDEO_SIZE`, `TELEGRAM_MAX_DIMENSION_SUM` (L56-59)
|
||||
- `_is_asset_id(value)` (L76-85)
|
||||
- `_extract_asset_id_from_url(url)` (L88-111)
|
||||
- `_split_media_by_upload_size(media_items, max_upload_size)` (L114-170)
|
||||
|
||||
### 11. Write tests `[ ]`
|
||||
|
||||
- `test_models.py` -- `SharedLinkInfo.from_api_response`, `AssetInfo.from_api_response`, `AlbumData.from_api_response`, processing status checks
|
||||
- `test_immich_client.py` -- Mock aiohttp responses for each API call (use `aioresponses`)
|
||||
- `test_change_detector.py` -- Various change scenarios: add only, remove only, rename, sharing changed, pending assets becoming processed, no change
|
||||
- `test_asset_utils.py` -- Filter/sort combinations, URL building
|
||||
- `test_telegram_cache.py` -- TTL expiry, thumbhash validation, batch set, cleanup
|
||||
- `test_notification_queue.py` -- Enqueue, get_all, remove_indices, clear
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] All extracted modules have zero Home Assistant imports
|
||||
- [ ] `pyproject.toml` is valid and installable (`pip install -e packages/core`)
|
||||
- [ ] All tests pass
|
||||
- [ ] The HAOS integration is NOT modified yet (that's Phase 2)
|
||||
- [ ] No functionality is lost in extraction -- behavior matches original exactly
|
||||
|
||||
---
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
1. **Session injection**: `ImmichClient` and `TelegramClient` accept `aiohttp.ClientSession` -- no global session creation
|
||||
2. **Storage protocols**: `CacheBackend`/`QueueBackend` protocols allow HA's `Store` and standalone's SQLite/JSON to satisfy the same interface
|
||||
3. **URL resolver callback**: Telegram client accepts optional `url_resolver: Callable[[str], str]` for converting external URLs to internal ones (coordinator owns this mapping)
|
||||
4. **Logging**: Use stdlib `logging` throughout. Consumers configure their own handlers.
|
||||
5. **No async_get_clientsession**: All HA-specific session management stays in the integration
|
||||
214
plans/primary-plan.md
Normal file
214
plans/primary-plan.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# Immich Watcher: Standalone Web App + Shared Core Architecture
|
||||
|
||||
**Status**: Planning
|
||||
**Created**: 2026-03-19
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The current `immich_album_watcher` HA integration contains ~3,600 lines of tightly coupled code: Immich API client, change detection, Telegram notifications, and HA entities/services. A separate HA Blueprint (~2,000 lines) adds message templating, filtering, scheduled notifications, and memory mode.
|
||||
|
||||
**Goal**: Enable Immich album change notifications **without Home Assistant** via a standalone web application, while keeping the HAOS integration functional and sharing as much logic as possible.
|
||||
|
||||
---
|
||||
|
||||
## Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|---|---|---|
|
||||
| Architecture | Hybrid (Option C) | HAOS standalone with shared core lib, optional server sync. No breaking changes for HA-only users. |
|
||||
| Frontend | SvelteKit + Shadcn-svelte | Small bundle, fast, calm UI aesthetic. Good fit for self-hosted. |
|
||||
| Notifications | Telegram + Generic webhook | Telegram from existing code. Webhook enables Discord/Slack/ntfy/custom. |
|
||||
| Auth | Multi-user (admin/user roles) | Supports shared Immich servers. Admin manages servers/users, users manage own trackers. |
|
||||
| Backend | FastAPI + SQLite + APScheduler | Async-native Python, zero external DB deps, proven scheduler. |
|
||||
|
||||
---
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
immich-watcher/
|
||||
packages/
|
||||
core/ # Shared Python library
|
||||
pyproject.toml
|
||||
src/immich_watcher_core/
|
||||
immich_client.py # Immich API (from coordinator.py)
|
||||
models.py # AssetInfo, AlbumData, AlbumChange, SharedLinkInfo
|
||||
change_detector.py # Change detection (from coordinator.py)
|
||||
telegram/
|
||||
client.py # Telegram Bot API (from sensor.py)
|
||||
cache.py # File cache with pluggable backend
|
||||
media.py # Media download, size checks, group splitting
|
||||
webhook/
|
||||
client.py # Generic webhook notification provider
|
||||
templates.py # Jinja2 template engine
|
||||
storage.py # Abstract storage protocol + SQLite impl
|
||||
constants.py # Shared constants (from const.py)
|
||||
tests/
|
||||
|
||||
server/ # Standalone FastAPI app
|
||||
pyproject.toml
|
||||
src/immich_watcher_server/
|
||||
main.py # FastAPI entry point
|
||||
database/models.py # SQLModel ORM
|
||||
database/migrations/ # Alembic
|
||||
api/ # REST endpoints
|
||||
services/
|
||||
scheduler.py # APScheduler background polling
|
||||
watcher.py # Album polling orchestrator
|
||||
notifier.py # Notification dispatch
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
|
||||
haos/ # Home Assistant integration (moved)
|
||||
custom_components/immich_album_watcher/
|
||||
... (refactored to use core library)
|
||||
hacs.json
|
||||
|
||||
frontend/ # SvelteKit web UI source
|
||||
package.json
|
||||
src/
|
||||
dist/ # Built static files served by FastAPI
|
||||
|
||||
plans/ # This folder
|
||||
README.md
|
||||
LICENSE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Shared Core Library Extractions
|
||||
|
||||
| Core Module | Source File | What Gets Extracted |
|
||||
|---|---|---|
|
||||
| `immich_client.py` | coordinator.py | All `/api/` calls, session injection via constructor |
|
||||
| `models.py` | coordinator.py L66-326 | Dataclasses (already HA-independent) |
|
||||
| `change_detector.py` | coordinator.py L979-1066 | `detect_album_changes()` pure function |
|
||||
| `telegram/client.py` | sensor.py (~1200 lines) | Full Telegram Bot API: send_message/photo/video/media_group |
|
||||
| `telegram/cache.py` | storage.py | TelegramFileCache with `CacheBackend` protocol |
|
||||
| `templates.py` | NEW (from blueprint logic) | Jinja2 renderer with ~40 variables matching blueprint |
|
||||
| `storage.py` | storage.py | Abstract protocol + SQLite implementation |
|
||||
| `webhook/client.py` | NEW | Generic webhook POST JSON with event data |
|
||||
|
||||
The `ImmichClient` accepts an `aiohttp.ClientSession` in constructor -- HA provides its managed session, standalone creates its own.
|
||||
|
||||
---
|
||||
|
||||
## Standalone Server Design
|
||||
|
||||
### Backend: FastAPI + SQLite + APScheduler
|
||||
|
||||
**Database tables**: `users`, `immich_servers`, `notification_targets`, `message_templates`, `album_trackers`, `album_states`, `telegram_cache`, `notification_queue`
|
||||
|
||||
**Key API endpoints**:
|
||||
- `POST /api/auth/setup` / `POST /api/auth/login` -- JWT auth
|
||||
- `CRUD /api/servers` -- Immich server connections
|
||||
- `GET /api/servers/{id}/albums` -- Fetch album list from Immich
|
||||
- `CRUD /api/trackers` -- Album trackers (album selection, event types, template overrides, targets)
|
||||
- `CRUD /api/templates` -- Message templates with preview
|
||||
- `CRUD /api/targets` -- Notification targets (Telegram chats, webhooks)
|
||||
- `CRUD /api/users` -- User management (admin only)
|
||||
- `GET /api/status` -- Dashboard data
|
||||
|
||||
**Background**: APScheduler runs one job per tracker at its scan interval. Each job: fetch album -> detect changes -> render template -> dispatch notification.
|
||||
|
||||
### Frontend: SvelteKit + Shadcn-svelte
|
||||
|
||||
**Pages**:
|
||||
1. **Setup wizard** -- First-run: create admin account, connect Immich server
|
||||
2. **Login** -- Username/password
|
||||
3. **Dashboard** -- Active trackers overview, recent events timeline, server status
|
||||
4. **Servers** -- Add/edit Immich server connections (URL + API key validation)
|
||||
5. **Trackers** -- Create/edit album trackers:
|
||||
- Album picker (multi-select, fetched from Immich)
|
||||
- Event type toggles (assets added/removed, renamed, sharing changed, deleted)
|
||||
- Notification target selection
|
||||
- Template selection or per-tracker override
|
||||
- Scan interval, quiet hours
|
||||
6. **Templates** -- Jinja2 template editor:
|
||||
- CodeMirror with Jinja2 syntax highlighting
|
||||
- Live preview with sample album data
|
||||
- Variable reference sidebar
|
||||
- Default templates for common use cases
|
||||
7. **Targets** -- Manage notification destinations (Telegram chats, webhooks)
|
||||
8. **Users** -- User management (admin only)
|
||||
9. **Settings** -- Global defaults
|
||||
|
||||
### Auth: Multi-user, bcrypt + JWT
|
||||
|
||||
- Multiple user accounts with admin/user roles
|
||||
- Admin: full access (user management, server configuration)
|
||||
- User: manage own trackers, templates, and targets
|
||||
- First-run setup creates initial admin account
|
||||
|
||||
### Deployment: Single Docker container, SQLite in mounted volume
|
||||
|
||||
---
|
||||
|
||||
## HAOS Integration Changes
|
||||
|
||||
The integration gets refactored to delegate to core:
|
||||
|
||||
```python
|
||||
# coordinator.py becomes thin wrapper
|
||||
class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator):
|
||||
def __init__(self, ...):
|
||||
self._client = ImmichClient(session, url, api_key) # from core
|
||||
|
||||
async def _async_update_data(self):
|
||||
album = await self._client.get_album(self._album_id)
|
||||
change = detect_album_changes(old, album, pending) # from core
|
||||
if change: self._fire_events(change, album) # HA-specific
|
||||
return album
|
||||
|
||||
# sensor.py Telegram methods delegate to core
|
||||
async def _execute_telegram_notification(self, ...):
|
||||
telegram = TelegramClient(session, token, cache) # from core
|
||||
return await telegram.send_notification(...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phases
|
||||
|
||||
> **Rule**: Before starting work on any phase, create a detailed trackable subplan at `plans/phase-N-<name>.md` with granular tasks, specific files to create/modify, and acceptance criteria. Do not begin implementation until the subplan is reviewed.
|
||||
|
||||
### Phase 1: Extract Core Library `[x]`
|
||||
- Extract models, Immich client, change detection, Telegram client, cache into `packages/core/`
|
||||
- Write unit tests for all extracted modules
|
||||
- **Subplan**: `plans/phase-1-core-library.md`
|
||||
|
||||
### Phase 2: Wire Core into HAOS Integration `[ ]`
|
||||
- Move integration to `packages/haos/`
|
||||
- Refactor coordinator.py, sensor.py, storage.py to use core library
|
||||
- Update manifest.json, hacs.json for new structure
|
||||
- Verify identical behavior with real Immich server
|
||||
- **Subplan**: `plans/phase-2-haos-refactor.md`
|
||||
|
||||
### Phase 3: Build Server Backend `[ ]`
|
||||
- FastAPI app with database, scheduler, API endpoints
|
||||
- Telegram + webhook notification providers
|
||||
- Jinja2 template engine with variable system matching blueprint
|
||||
- **Subplan**: `plans/phase-3-server-backend.md`
|
||||
|
||||
### Phase 4: Build Frontend `[ ]`
|
||||
- SvelteKit app with all pages (setup, dashboard, trackers, templates, targets, users)
|
||||
- Template editor with live preview
|
||||
- Album picker connected to Immich API
|
||||
- **Subplan**: `plans/phase-4-frontend.md`
|
||||
|
||||
### Phase 5: HAOS-Server Sync (Optional) `[ ]`
|
||||
- Add optional server URL to HA config flow
|
||||
- Implement tracker/template config sync
|
||||
- **Subplan**: `plans/phase-5-haos-server-sync.md`
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
1. **Core library**: Unit tests for Immich client (mocked HTTP), change detection (pure function), Telegram client (mocked API), template rendering
|
||||
2. **HAOS integration**: After refactoring, verify all existing entities, services, and events work identically with a real Immich server
|
||||
3. **Server backend**: API integration tests with SQLite in-memory DB, scheduler tests
|
||||
4. **Frontend**: Manual testing of all pages, template editor preview, album picker
|
||||
5. **End-to-end**: Docker Compose up -> create account -> connect Immich -> create tracker -> trigger album change -> verify notification received
|
||||
Reference in New Issue
Block a user