Files
Aniworld/docs/instructions.md
2026-02-20 21:22:16 +01:00

432 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Aniworld Web Application Development Instructions
This document provides detailed tasks for AI agents to implement a modern web application for the Aniworld anime download manager. All tasks should follow the coding guidelines specified in the project's copilot instructions.
## Project Overview
The goal is to create a FastAPI-based web application that provides a modern interface for the existing Aniworld anime download functionality. The core anime logic should remain in `SeriesApp.py` while the web layer provides REST API endpoints and a responsive UI.
## Architecture Principles
- **Single Responsibility**: Each file/class has one clear purpose
- **Dependency Injection**: Use FastAPI's dependency system
- **Clean Separation**: Web layer calls core logic, never the reverse
- **File Size Limit**: Maximum 500 lines per file
- **Type Hints**: Use comprehensive type annotations
- **Error Handling**: Proper exception handling and logging
## Additional Implementation Guidelines
### Code Style and Standards
- **Type Hints**: Use comprehensive type annotations throughout all modules
- **Docstrings**: Follow PEP 257 for function and class documentation
- **Error Handling**: Implement custom exception classes with meaningful messages
- **Logging**: Use structured logging with appropriate log levels
- **Security**: Validate all inputs and sanitize outputs
- **Performance**: Use async/await patterns for I/O operations
## 📞 Escalation
If you encounter:
- Architecture issues requiring design decisions
- Tests that conflict with documented requirements
- Breaking changes needed
- Unclear requirements or expectations
**Document the issue and escalate rather than guessing.**
---
## <20> Credentials
**Admin Login:**
- Username: `admin`
- Password: `Hallo123!`
---
## <20>📚 Helpful Commands
```bash
# Run all tests
conda run -n AniWorld python -m pytest tests/ -v --tb=short
# Run specific test file
conda run -n AniWorld python -m pytest tests/unit/test_websocket_service.py -v
# Run specific test class
conda run -n AniWorld python -m pytest tests/unit/test_websocket_service.py::TestWebSocketService -v
# Run specific test
conda run -n AniWorld python -m pytest tests/unit/test_websocket_service.py::TestWebSocketService::test_broadcast_download_progress -v
# Run with extra verbosity
conda run -n AniWorld python -m pytest tests/ -vv
# Run with full traceback
conda run -n AniWorld python -m pytest tests/ -v --tb=long
# Run and stop at first failure
conda run -n AniWorld python -m pytest tests/ -v -x
# Run tests matching pattern
conda run -n AniWorld python -m pytest tests/ -v -k "auth"
# Show all print statements
conda run -n AniWorld python -m pytest tests/ -v -s
#Run app
conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000 --reload
```
---
## Implementation Notes
1. **Incremental Development**: Implement features incrementally, testing each component thoroughly before moving to the next
2. **Code Review**: Review all generated code for adherence to project standards
3. **Documentation**: Document all public APIs and complex logic
4. **Testing**: Maintain test coverage above 80% for all new code
5. **Performance**: Profile and optimize critical paths, especially download and streaming operations
6. **Security**: Regular security audits and dependency updates
7. **Monitoring**: Implement comprehensive monitoring and alerting
8. **Maintenance**: Plan for regular maintenance and updates
---
## Task Completion Checklist
For each task completed:
- [ ] Implementation follows coding standards
- [ ] Unit tests written and passing
- [ ] Integration tests passing
- [ ] Documentation updated
- [ ] Error handling implemented
- [ ] Logging added
- [ ] Security considerations addressed
- [ ] Performance validated
- [ ] Code reviewed
- [ ] Task marked as complete in instructions.md
- [ ] Infrastructure.md updated and other docs
- [ ] Changes committed to git; keep your messages in git short and clear
- [ ] Take the next task
---
## TODO List:
### Task 1: Add APScheduler Dependency
- [ ] **1.1** Add `APScheduler>=3.10.4` to `requirements.txt`
- [ ] **1.2** Verify installation: `conda run -n AniWorld pip install APScheduler>=3.10.4`
- [ ] **1.3** Verify import works: `conda run -n AniWorld python -c "from apscheduler.schedulers.asyncio import AsyncIOScheduler; from apscheduler.triggers.cron import CronTrigger; print('OK')"`
---
### Task 2: Extend SchedulerConfig Model
**File:** `src/server/models/config.py`
The existing `SchedulerConfig` has `enabled: bool` and `interval_minutes: int`. Extend it with cron-based fields.
- [ ] **2.1** Add field `schedule_time: str = "03:00"` — 24h HH:MM format for the daily run time
- [ ] **2.2** Add field `schedule_days: List[str]` with default `["mon","tue","wed","thu","fri","sat","sun"]` — lowercase 3-letter day abbreviations
- [ ] **2.3** Add field `auto_download_after_rescan: bool = False` — whether to auto-queue and start downloads of all missing episodes after a scheduled rescan completes
- [ ] **2.4** Add a `field_validator` for `schedule_time` that validates HH:MM format (00:0023:59). Raise `ValueError` with a clear message on invalid input
- [ ] **2.5** Add a `field_validator` for `schedule_days` that validates each entry is one of `["mon","tue","wed","thu","fri","sat","sun"]`. Raise `ValueError` on invalid entries. Empty list is allowed (means scheduler won't run)
- [ ] **2.6** Keep `interval_minutes` field for backward compatibility but it will no longer be used when `schedule_time` is set. Add a docstring noting this deprecation
- [ ] **2.7** Update `data/config.json` default to include the new fields with their defaults
**Verification:**
- [ ] **2.8** Run: `conda run -n AniWorld python -c "from src.server.models.config import SchedulerConfig; c = SchedulerConfig(); print(c.schedule_time, c.schedule_days, c.auto_download_after_rescan)"`
- [ ] **2.9** Verify invalid time raises error: `SchedulerConfig(schedule_time='25:00')` should raise `ValidationError`
- [ ] **2.10** Verify invalid day raises error: `SchedulerConfig(schedule_days=['monday'])` should raise `ValidationError`
---
### Task 3: Write Unit Tests for SchedulerConfig Model
**File:** `tests/unit/test_scheduler_config_model.py` (new file)
- [ ] **3.1** Test default values: `SchedulerConfig()` creates instance with `schedule_time="03:00"`, `schedule_days` = all 7 days, `auto_download_after_rescan=False`
- [ ] **3.2** Test valid `schedule_time` values: `"00:00"`, `"03:00"`, `"12:30"`, `"23:59"`
- [ ] **3.3** Test invalid `schedule_time` values: `"25:00"`, `"3pm"`, `""`, `"3:00pm"`, `"24:00"`, `"-1:00"` — all must raise `ValidationError`
- [ ] **3.4** Test valid `schedule_days` values: `["mon"]`, `["mon","fri"]`, all 7 days, empty list `[]`
- [ ] **3.5** Test invalid `schedule_days` values: `["monday"]`, `["xyz"]`, `["Mon"]` (case-sensitive), `[""]` — all must raise `ValidationError`
- [ ] **3.6** Test `auto_download_after_rescan` accepts `True` and `False`
- [ ] **3.7** Test backward compatibility: creating `SchedulerConfig(enabled=True, interval_minutes=30)` without new fields uses defaults for `schedule_time`, `schedule_days`, `auto_download_after_rescan`
- [ ] **3.8** Test serialization roundtrip: `config.model_dump()` then `SchedulerConfig(**dumped)` produces identical config
**Verification:**
- [ ] **3.9** Run: `conda run -n AniWorld python -m pytest tests/unit/test_scheduler_config_model.py -v` — all tests pass
---
### Task 4: Rewrite SchedulerService to Use APScheduler with CronTrigger
**File:** `src/server/services/scheduler_service.py`
The existing service uses a custom `asyncio.sleep()` loop in `_scheduler_loop()`. Replace it with APScheduler's `AsyncIOScheduler` and `CronTrigger`.
- [ ] **4.1** Add imports: `from apscheduler.schedulers.asyncio import AsyncIOScheduler` and `from apscheduler.triggers.cron import CronTrigger`
- [ ] **4.2** Add a private method `_build_cron_trigger(self) -> CronTrigger` that converts `self._config.schedule_time` (HH:MM) and `self._config.schedule_days` (list of day abbreviations) into a `CronTrigger(hour=H, minute=M, day_of_week='mon,wed,fri')`. If `schedule_days` is empty, return `None` (no trigger = don't schedule)
- [ ] **4.3** Refactor `start()`: instead of creating an `asyncio.Task` with `_scheduler_loop`, create an `AsyncIOScheduler` instance, add a job using the `CronTrigger` from `_build_cron_trigger()`, and call `scheduler.start()`. Store the scheduler as `self._scheduler`. If `_build_cron_trigger()` returns `None`, log a warning and don't add a job. Set `self._is_running = True`
- [ ] **4.4** Refactor `stop()`: call `self._scheduler.shutdown(wait=False)` instead of cancelling an asyncio task. Set `self._is_running = False`
- [ ] **4.5** Add method `reload_config(self, config: SchedulerConfig) -> None`: update `self._config`, then if scheduler is running, reschedule the job using `self._scheduler.reschedule_job()` with the new cron trigger. If trigger is `None`, remove the job. If scheduler is not running and config is enabled, call `start()`
- [ ] **4.6** Remove or deprecate `_scheduler_loop()` method entirely
- [ ] **4.7** Update `get_status()` to return `next_run_time` obtained from `self._scheduler.get_job('scheduled_rescan').next_run_time` (handle case where job doesn't exist). Also include `schedule_time`, `schedule_days`, and `auto_download_after_rescan` in status dict
- [ ] **4.8** The job function should be `_perform_rescan()` (existing method) — keep it as the scheduled task. It is already async
- [ ] **4.9** Add structured logging: log when scheduler starts, stops, reschedules, and when cron trigger is built (include the cron expression in the log)
- [ ] **4.10** Ensure the file stays under 500 lines. If it exceeds, extract helper functions
**Verification:**
- [ ] **4.11** Start the app: `conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000`. Check logs for scheduler startup messages
- [ ] **4.12** Verify no import errors or startup crashes
---
### Task 5: Implement Auto-Download After Rescan Logic
**File:** `src/server/services/scheduler_service.py`
Extend `_perform_rescan()` to auto-queue and start downloads after scanning.
- [ ] **5.1** At the end of `_perform_rescan()`, after the scan completes successfully, check `self._config.auto_download_after_rescan`
- [ ] **5.2** If enabled: call `self._anime_service.get_all_anime()` to get all series. For each series that has missing episodes (non-empty `missing_episodes` list), call `self._download_service.add_to_queue()` for each missing episode. Refer to the existing `add_to_queue` method signature in `src/server/services/download_service.py` to pass the correct parameters
- [ ] **5.3** After all episodes are queued, call `self._download_service.start_download()` (or equivalent method from the existing download service) to begin processing the queue
- [ ] **5.4** Broadcast a WebSocket event `auto_download_started` with `{"queued_count": N}` where N is the number of episodes queued. Use the existing `_broadcast()` helper method
- [ ] **5.5** Wrap the auto-download logic in try/except. On failure, broadcast `auto_download_error` with `{"error": str(e)}` and log the error. Do NOT let auto-download failures crash the scheduler
- [ ] **5.6** If `auto_download_after_rescan` is `False`, skip the auto-download logic entirely (just log that auto-download is disabled)
- [ ] **5.7** Add structured logging for: auto-download started, number of episodes queued, auto-download completed, auto-download failed
**Verification:**
- [ ] **5.8** Read `src/server/services/download_service.py` to confirm the exact method names and signatures used for queueing and starting downloads. Use those exact method names
---
### Task 6: Update Scheduler API Endpoints
**File:** `src/server/api/scheduler.py`
- [ ] **6.1** Update **GET `/api/scheduler/config`**: Return response that includes the new config fields (`schedule_time`, `schedule_days`, `auto_download_after_rescan`) plus runtime status (`is_running`, `next_run`, `last_run`). The response shape should be `{"success": true, "config": {...all config fields...}, "status": {...runtime fields...}}`
- [ ] **6.2** Update **POST `/api/scheduler/config`**: Accept the new fields in the request body. Validate using the updated `SchedulerConfig` model. After saving config via `config_service`, call `scheduler_service.reload_config(new_config)` to apply changes immediately without restart. Return the same enriched response format as GET
- [ ] **6.3** Ensure backward compatibility: if a POST request only sends `{"enabled": true}` without new fields, use existing/default values for the omitted fields
- [ ] **6.4** Add proper error responses: 422 for validation errors (invalid time/days), 500 for scheduler failures
- [ ] **6.5** The existing **POST `/api/scheduler/trigger-rescan`** endpoint should remain unchanged — it triggers an immediate rescan (which will also auto-download if configured)
**Verification:**
- [ ] **6.6** Start the app and test with curl:
- `curl -H "Authorization: Bearer <token>" http://127.0.0.1:8000/api/scheduler/config` — should return new fields
- `curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" -d '{"enabled":true,"schedule_time":"02:00","schedule_days":["mon","wed","fri"],"auto_download_after_rescan":true}' http://127.0.0.1:8000/api/scheduler/config` — should save and return updated config
---
### Task 7: Update Frontend HTML — Scheduler Settings Section
**File:** `src/server/web/templates/index.html`
Update the scheduler section inside the config/settings modal (lines ~245-310).
- [ ] **7.1** Keep the existing "Enable Scheduler" checkbox (`scheduler-enabled`)
- [ ] **7.2** Replace or supplement the `scheduler-interval` (minutes) input with a **time picker input** (`type="time"`, id `scheduler-time`). Set default value to `03:00`. Add a label: "Run at" or equivalent with `data-text` attribute for i18n
- [ ] **7.3** Add **7 day-of-week toggle checkboxes** (MonSun) with ids `scheduler-day-mon` through `scheduler-day-sun`. All checked by default. Display them in a horizontal row. Each checkbox label should have a `data-text` attribute for i18n. Use short labels: "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
- [ ] **7.4** Keep the existing "Auto-download after rescan" checkbox (it already exists in the HTML as `auto-download-checkbox` around line 292). Ensure it maps to `auto_download_after_rescan`
- [ ] **7.5** Hide or remove the old `scheduler-interval` minutes input. If keeping for backward compat, move it to a collapsible "Advanced" section
- [ ] **7.6** Add a help text below the day checkboxes: "Scheduler runs at the selected time on checked days. Uncheck all to disable scheduling." — with `data-text` for i18n
- [ ] **7.7** Add a display area showing "Next scheduled run: <datetime>" that gets populated by JS (id `scheduler-next-run`)
- [ ] **7.8** Ensure all new elements work in both dark and light themes (use existing CSS classes)
**Verification:**
- [ ] **7.9** Start the app, open the settings modal, verify all new UI elements render correctly in both dark and light mode
- [ ] **7.10** Verify the day-of-week checkboxes are displayed horizontally and are all checked by default
---
### Task 8: Update Frontend CSS — Day-of-Week Toggle Styling
**File:** `src/server/web/static/css/styles.css`
- [ ] **8.1** Add styles for `.scheduler-days-container`: horizontal flexbox layout, gap between items, wrapping on small screens
- [ ] **8.2** Add styles for `.scheduler-day-toggle` checkboxes: styled as pill/button toggles. Unchecked = outline/muted, Checked = accent/filled. Use existing theme CSS variables for colors
- [ ] **8.3** Add styles for `.scheduler-day-label`: centered text, pointer cursor, appropriate padding
- [ ] **8.4** Ensure dark mode support using existing dark mode CSS variables/selectors (check existing dark mode patterns in the file)
- [ ] **8.5** Ensure responsive layout: on mobile, days should wrap to 2 rows if needed
- [ ] **8.6** Style the `#scheduler-next-run` display: muted text, smaller font size, italic
**Verification:**
- [ ] **8.7** Test in browser: verify day toggles look like toggle buttons, dark/light mode both work, responsive on narrow viewport
---
### Task 9: Update Frontend JavaScript — Scheduler Config Load/Save
**File:** `src/server/web/static/js/app.js`
- [ ] **9.1** Update `loadSchedulerConfig()` (around line 1550):
- Read `config.schedule_time` and set the time picker input value
- Read `config.schedule_days` array and check/uncheck the corresponding day checkboxes
- Read `config.auto_download_after_rescan` and set the auto-download checkbox (existing code at line 1562 already tries this — verify it matches the new API response shape)
- Read `status.next_run` and display in the `#scheduler-next-run` element
- Keep `config.enabled` handling as-is
- [ ] **9.2** Update `saveSchedulerConfig()` (around line 1583):
- Read the time picker value and send as `schedule_time`
- Collect checked day checkboxes into an array and send as `schedule_days`
- Read the auto-download checkbox and send as `auto_download_after_rescan`
- Send `enabled` from the enable checkbox
- POST to `/api/scheduler/config` with the new payload
- On success, update the `#scheduler-next-run` display from the response
- [ ] **9.3** Update `toggleSchedulerTimeInput()` (around line 1637): also toggle visibility of the day checkboxes and next-run display when scheduler is enabled/disabled
- [ ] **9.4** Handle WebSocket events `auto_download_started` and `auto_download_error` — the existing code at lines 263-272 in app.js already has handlers for these. Verify they display appropriate toast notifications. Update if the payload shape changed
**Verification:**
- [ ] **9.5** Start the app, open settings, change time to `02:00`, uncheck "Sat" and "Sun", enable auto-download, save. Reload the page and verify values persist
- [ ] **9.6** Check browser console for any JS errors during load/save
- [ ] **9.7** Verify the "Next scheduled run" display updates after saving
---
### Task 10: Update Config Service for Backward Compatibility
**File:** `src/server/services/config_service.py`
- [ ] **10.1** Ensure `load_config()` handles old `config.json` files that don't have `schedule_time`, `schedule_days`, or `auto_download_after_rescan` — Pydantic defaults should handle this, but verify by reading the load logic
- [ ] **10.2** Ensure `save_config()` writes the new fields to disk
- [ ] **10.3** If there's migration logic, add a migration step that adds default values for new fields when loading an old config
**Verification:**
- [ ] **10.4** Delete `schedule_time`, `schedule_days`, and `auto_download_after_rescan` from `data/config.json`, restart the app, verify defaults are used and the app doesn't crash
- [ ] **10.5** Verify the config is saved with the new fields after any settings change
---
### Task 11: Update FastAPI Lifespan for APScheduler
**File:** `src/server/fastapi_app.py`
- [ ] **11.1** Verify that the `lifespan` function (around line 475) calls `scheduler_service.start()` on startup — this should now initialize the APScheduler. Confirm it works
- [ ] **11.2** Verify that `scheduler_service.stop()` is called on shutdown — this should now call `scheduler.shutdown()`. Confirm it works
- [ ] **11.3** Ensure no duplicate scheduler instances are created (the singleton pattern in `get_scheduler_service()` should prevent this — verify)
**Verification:**
- [ ] **11.4** Start and stop the app, verify no errors in logs about scheduler startup/shutdown
- [ ] **11.5** Verify logs show "Scheduler started with cron trigger: ..." or similar
---
### Task 12: Write Unit Tests for SchedulerService (APScheduler + Auto-Download)
**File:** `tests/unit/test_scheduler_service.py`
Update the existing test file (currently 664 lines). Add new test cases; modify existing ones that test the old `asyncio.sleep` loop.
- [ ] **12.1** Test `_build_cron_trigger()`:
- Input `schedule_time="03:00"`, `schedule_days=["mon","wed","fri"]` → CronTrigger with `hour=3, minute=0, day_of_week='mon,wed,fri'`
- Input `schedule_time="23:59"`, all 7 days → `day_of_week='mon,tue,wed,thu,fri,sat,sun'`
- Input `schedule_days=[]` (empty) → returns `None`
- [ ] **12.2** Test `start()`:
- Mock `AsyncIOScheduler`. Verify `add_job()` is called with the `_perform_rescan` function and the correct CronTrigger
- Verify `scheduler.start()` is called
- Verify `self._is_running` is set to `True`
- [ ] **12.3** Test `start()` with empty `schedule_days`:
- Verify no job is added, warning is logged, `_is_running` is still set to `True` (scheduler is "running" but has no jobs)
- [ ] **12.4** Test `stop()`:
- Mock `AsyncIOScheduler`. Verify `scheduler.shutdown(wait=False)` is called
- Verify `self._is_running` is set to `False`
- [ ] **12.5** Test `reload_config()`:
- Change `schedule_time` from `"03:00"` to `"05:00"`. Verify `reschedule_job()` is called with new CronTrigger
- Change `schedule_days` from all days to `["mon"]`. Verify `reschedule_job()` is called with updated days
- [ ] **12.6** Test `reload_config()` with `schedule_days` changed to empty list:
- Verify the existing job is removed (`scheduler.remove_job()`)
- [ ] **12.7** Test `_perform_rescan()` with `auto_download_after_rescan=True`:
- Mock `anime_service.get_all_anime()` to return 2 series, one with 3 missing episodes and one with 0
- Verify `download_service.add_to_queue()` is called 3 times (once per missing episode)
- Verify `download_service.start_download()` (or equivalent) is called once
- Verify WebSocket broadcast of `auto_download_started` with `{"queued_count": 3}`
- [ ] **12.8** Test `_perform_rescan()` with `auto_download_after_rescan=False`:
- Mock same services. Verify `download_service.add_to_queue()` is NOT called
- Verify no `auto_download_started` WebSocket broadcast
- [ ] **12.9** Test `_perform_rescan()` auto-download error handling:
- Mock `download_service.add_to_queue()` to raise an exception
- Verify `auto_download_error` WebSocket broadcast is sent with the error message
- Verify the scheduler does NOT crash (exception is caught)
- Verify the rescan itself is still marked as successful
- [ ] **12.10** Test `get_status()` returns correct fields: `is_running`, `next_run_time`, `last_run`, `schedule_time`, `schedule_days`, `auto_download_after_rescan`
- [ ] **12.11** Update or remove existing tests that directly test the old `_scheduler_loop()` method since it no longer exists
- [ ] **12.12** Ensure total file stays under 500 lines. If it exceeds, split into `test_scheduler_service.py` and `test_scheduler_service_auto_download.py`
**Verification:**
- [ ] **12.13** Run: `conda run -n AniWorld python -m pytest tests/unit/test_scheduler_service.py -v --tb=short` — all tests pass
- [ ] **12.14** Run: `conda run -n AniWorld python -m pytest tests/unit/test_scheduler_config_model.py -v --tb=short` — all tests pass
- [ ] **12.15** Run coverage: `conda run -n AniWorld python -m pytest tests/unit/test_scheduler_service.py tests/unit/test_scheduler_config_model.py --cov=src/server/services/scheduler_service --cov=src/server/models/config --cov-report=term-missing` — target ≥80% on both modules
---
### Task 13: Write/Update API Endpoint Tests
**File:** `tests/api/test_scheduler_endpoints.py`
Update the existing test file (currently 429 lines).
- [ ] **13.1** Test GET `/api/scheduler/config` returns response with new fields: `schedule_time`, `schedule_days`, `auto_download_after_rescan`, and status fields `is_running`, `next_run`
- [ ] **13.2** Test POST `/api/scheduler/config` with valid new fields saves correctly:
- Send `{"enabled": true, "schedule_time": "02:00", "schedule_days": ["mon","wed","fri"], "auto_download_after_rescan": true}`
- Verify response contains the saved values
- Verify `scheduler_service.reload_config()` was called
- [ ] **13.3** Test POST with invalid `schedule_time` (`"25:00"`) returns 422 validation error
- [ ] **13.4** Test POST with invalid `schedule_days` (`["monday"]`) returns 422 validation error
- [ ] **13.5** Test POST with only `{"enabled": true}` (backward compat) — defaults are used for omitted fields, no error
- [ ] **13.6** Test POST with `schedule_days: []` — valid, scheduler has no scheduled jobs
- [ ] **13.7** Test trigger-rescan endpoint still works correctly and now includes auto-download behavior when configured
**Verification:**
- [ ] **13.8** Run: `conda run -n AniWorld python -m pytest tests/api/test_scheduler_endpoints.py -v --tb=short` — all tests pass
---
### Task 14: Write/Update Integration Tests
**File:** `tests/integration/test_scheduler_workflow.py`
Update the existing test file (currently 514 lines).
- [ ] **14.1** Test full workflow: Enable scheduler with `schedule_time="02:00"` and `schedule_days=["mon","fri"]` → verify APScheduler job is created with correct cron trigger parameters
- [ ] **14.2** Test config change workflow: Change `schedule_time` from `"02:00"` to `"04:00"` via API → verify job is rescheduled (mock scheduler or check `get_status()` response)
- [ ] **14.3** Test disable/re-enable workflow: Disable scheduler → verify job removed. Re-enable → verify job re-created
- [ ] **14.4** Test auto-download integration: Configure scheduler with `auto_download_after_rescan=True`, mock `anime_service` to return series with missing episodes → trigger rescan → verify episodes are queued in download service
- [ ] **14.5** Test auto-download disabled: Configure with `auto_download_after_rescan=False` → trigger rescan → verify no episodes are queued
**Verification:**
- [ ] **14.6** Run: `conda run -n AniWorld python -m pytest tests/integration/test_scheduler_workflow.py -v --tb=short` — all tests pass
---
### Task 15: Run Full Test Suite and Verify Coverage
- [ ] **15.1** Run all tests: `conda run -n AniWorld python -m pytest tests/ -v --tb=short` — all tests pass, no regressions
- [ ] **15.2** Run coverage for scheduler-related code: `conda run -n AniWorld python -m pytest tests/ --cov=src/server/services/scheduler_service --cov=src/server/models/config --cov=src/server/api/scheduler --cov-report=term-missing` — all three modules ≥80%
- [ ] **15.3** Fix any failing tests from other test files that may have been affected by the config model changes
- [ ] **15.4** Run: `conda run -n AniWorld python -m pytest tests/ -v -x` — stop at first failure, fix, repeat until all green
---
### Task 16: Update Documentation
- [ ] **16.1** Update `docs/features.md`: Replace "check interval (in minutes)" scheduler description with cron-based scheduling description. Add auto-download after rescan feature. Document the user-friendly time + day-of-week configuration
- [ ] **16.2** Update `docs/CONFIGURATION.md` (if exists): Document `scheduler.schedule_time`, `scheduler.schedule_days`, `scheduler.auto_download_after_rescan` config fields with examples
- [ ] **16.3** Update `docs/API.md` (if exists): Document updated GET/POST `/api/scheduler/config` request/response schemas with the new fields
- [ ] **16.4** Update `docs/instructions.md`: Add this feature to the TODO list as completed
**Verification:**
- [ ] **16.5** Review all updated docs for accuracy and completeness
---
### Task 17: Final Manual Verification
- [ ] **17.1** Start the app: `conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000 --reload`
- [ ] **17.2** Log in with admin credentials (`admin` / `Hallo123!`)
- [ ] **17.3** Open settings modal → Scheduler section. Verify: time picker shows `03:00`, all 7 days are checked, auto-download is unchecked
- [ ] **17.4** Change time to `02:00`, uncheck Sat and Sun, enable auto-download, save. Verify toast notification confirms save
- [ ] **17.5** Reload the page, reopen settings. Verify saved values persist
- [ ] **17.6** Check terminal logs for: "Scheduler rescheduled with cron trigger: ..." message
- [ ] **17.7** Trigger manual rescan. If auto-download is enabled and there are missing episodes, verify episodes appear in download queue
- [ ] **17.8** Disable scheduler entirely, save. Verify logs show scheduler stopped
- [ ] **17.9** Re-enable scheduler, save. Verify logs show scheduler started with correct cron expression
- [ ] **17.10** Test dark mode and light mode — all scheduler UI elements render correctly
- [ ] **17.11** Test on narrow viewport (mobile) — day checkboxes wrap properly
---
### Task 18: Commit and Clean Up
- [ ] **18.1** Review all changed files for adherence to architecture principles (single responsibility, <500 lines, type hints, docstrings)
- [ ] **18.2** Run linter: `conda run -n AniWorld python -m flake8 src/server/services/scheduler_service.py src/server/models/config.py src/server/api/scheduler.py` — no critical issues
- [ ] **18.3** Commit with message: `feat: cron-based scheduler with auto-download after rescan`
- [ ] **18.4** Mark this feature as complete in `docs/instructions.md` TODO list