Documentation Standardisation & Unit Test Stabilization
- Overhauled README.md to serve as a unified system index and WIP tracker. - Standardized documentation filenames (ARCH__, GUIDE__, PLAN__) for better discoverability. - Archived completed project plans and scopes. - Fixed regressions in unit tests (errors, models, email) caused by V3 architectural updates. - Ensured unit tests remain non-destructive and environment-independent via mocking.
This commit is contained in:
104
README.md
104
README.md
@@ -1,2 +1,102 @@
|
||||
# Aether API Python FastAPI
|
||||
The Aether API was created and is being developed by Scott Idem using the Python FastAPI framework.
|
||||
# Aether API (FastAPI)
|
||||
|
||||
The **Aether API** is a high-performance, multi-tenant backend infrastructure built using the Python **FastAPI** framework. It serves as the central data and logic hub for the Aether Platform, supporting both legacy applications and modern V3/V4 standards.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Architecture Overview
|
||||
|
||||
The API is currently in a transitional state between legacy (V1/V2) patterns and the modern **V3 CRUD Architecture**.
|
||||
|
||||
### **V3 CRUD (Modern)**
|
||||
- **Path:** `/v3/crud/`
|
||||
- **Core Principles:**
|
||||
- **`id_random` Primary:** All public communication uses URL-safe string IDs. Internal integer IDs are hidden.
|
||||
- **Nested URL Structure:** Enforces parent-child relationships (e.g., `/v3/crud/journal/{id}/entry/`).
|
||||
- **Granular Dependencies:** Uses specialized FastAPI dependencies for Account Context, Pagination, Filtering, and Serialization.
|
||||
- **Advanced Search:** POST-based search with recursive logic and standardized operators.
|
||||
- **Schema Discovery:** Dynamic introspection of database and Pydantic models via `/v3/crud/{obj_type}/schema`.
|
||||
|
||||
### **V3 Actions**
|
||||
- **Path:** `/v3/action/`
|
||||
- Handles complex binary operations (uploads, streaming downloads) separately from standard metadata CRUD.
|
||||
|
||||
### **Legacy API (V1/V2)**
|
||||
- **Path:** `/`, `/api/`, `/crud/`, `/v2/crud/`
|
||||
- Maintained for backward compatibility but currently being systematically audited and deprecated.
|
||||
- **Deprecation System:** Accessing legacy routes triggers a `!!! DEPRECATED ROUTE ACCESSED` warning in logs.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Core Technologies
|
||||
- **Framework:** FastAPI (v0.95.1)
|
||||
- **Database:** MariaDB (Remote Master) + SQLAlchemy (v1.4.52)
|
||||
- **Caching/ID Resolution:** Redis
|
||||
- **Security:** JWT (JSON Web Tokens) + API Key Machine Authorization
|
||||
- **Logging:** Structured logging with module-level isolation and rotation.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### **Local Development**
|
||||
1. **Environment:** Requires Python 3.9+.
|
||||
2. **Setup:**
|
||||
```bash
|
||||
virtualenv environment
|
||||
source environment/bin/activate
|
||||
pip install -r admin/requirements.txt
|
||||
```
|
||||
3. **Run:**
|
||||
```bash
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 5005 --reload
|
||||
```
|
||||
- **Documentation:** [GUIDE__LOCAL_DEVELOPMENT.md](documentation/GUIDE__LOCAL_DEVELOPMENT.md)
|
||||
|
||||
### **Deployment**
|
||||
- The API is typically deployed via **Docker Compose** within the `aether_container_env`.
|
||||
- **Manual Deployment:** [GUIDE__DEPLOYMENT_MANUAL.md](documentation/GUIDE__DEPLOYMENT_MANUAL.md)
|
||||
|
||||
---
|
||||
|
||||
## 📂 Documentation Index
|
||||
|
||||
### **Architecture & Standards**
|
||||
- [V3 Core Architecture](documentation/ARCH__V3_CORE.md): Modular structure and boot sequence.
|
||||
- [V3 Development Standards](documentation/ARCH__V3_DEVELOPMENT_STANDARDS.md): ID Vision, inheritance, and naming rules.
|
||||
- [Unified Agent Arch](documentation/ARCH__UNIFIED_AGENT.md): Vision for cross-stack AI agent awareness.
|
||||
|
||||
### **Integration Guides**
|
||||
- [V3 Frontend API Guide](documentation/GUIDE__V3_FRONTEND_API.md): How to use the V3 CRUD and Search endpoints.
|
||||
- [Frontend Code Samples](documentation/FRONTEND_API_SAMPLES.md): TypeScript snippets for common API calls.
|
||||
|
||||
### **Security**
|
||||
- [Project Security Hardening](documentation/PLAN__SECURITY_HARDENING.md): Path towards cryptographic JWT verification.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Suite
|
||||
The project maintains an exhaustive test suite under the `tests/` directory.
|
||||
- **Unit Tests:** `tests/unit/` (Mocked logic).
|
||||
- **Integration Tests:** `tests/integration/` (Local DB/Redis connectivity).
|
||||
- **E2E Tests:** `tests/e2e/` (Network-based API validation).
|
||||
- **Documentation:** [tests/README.md](tests/README.md)
|
||||
|
||||
---
|
||||
|
||||
## 🚧 Current Status & Work in Progress
|
||||
|
||||
### **Active Workstreams**
|
||||
- **[Backend] API Deprecation:** Systematic pruning of orphaned routers and methods (ID: 111523094).
|
||||
- **[ID Vision]:** Phase 2 complete. String-ID standardization extended to Page, Post, Person, Journal, Contact, and User models.
|
||||
- **[V3 Migration]:** Transitioning Launcher file caching to V3 CRUD (ID: 173518010).
|
||||
|
||||
### **Known Bugs / Issues**
|
||||
- **Badge Rendering:**Corrupted numeric `id` fields in `event_badge_template` table causing template load failures in Svelte 5 views.
|
||||
- **Websockets:** Legacy `websockets.py` and `websockets_redis.py` require unification and stability improvements.
|
||||
- **Intermittent Timeouts:** Some E2E tests occasionally reproduce 403s/Timeouts on nested GET calls (investigating).
|
||||
|
||||
---
|
||||
|
||||
## 📜 Release Snapshot
|
||||
Current Baseline: **`release/2026-01-28-v3_prod-snapshot`** (Stable v4.9.0).
|
||||
@@ -7,6 +7,13 @@ from unittest.mock import MagicMock, patch
|
||||
# Add current directory to path
|
||||
sys.path.append(os.getcwd())
|
||||
|
||||
# Mock app.config BEFORE imports
|
||||
mock_config = MagicMock()
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.SMTP = {}
|
||||
mock_config.settings = mock_settings
|
||||
sys.modules['app.config'] = mock_config
|
||||
|
||||
# Mock html2text before importing app.lib_email
|
||||
sys.modules['html2text'] = MagicMock()
|
||||
|
||||
@@ -19,12 +26,7 @@ from app.config import settings
|
||||
class TestEmail(unittest.TestCase):
|
||||
def test_send_email_missing_settings(self):
|
||||
print("\nTesting send_email with missing SMTP settings...")
|
||||
# Backup original settings
|
||||
original_smtp = getattr(settings, 'SMTP', {})
|
||||
|
||||
# Clear settings
|
||||
settings.SMTP = {}
|
||||
|
||||
# settings is already mocked to have an empty SMTP dict
|
||||
result = send_email(
|
||||
from_email="test@example.com",
|
||||
to_email="test@example.com",
|
||||
@@ -33,15 +35,11 @@ class TestEmail(unittest.TestCase):
|
||||
test=True
|
||||
)
|
||||
|
||||
# Restore settings
|
||||
settings.SMTP = original_smtp
|
||||
|
||||
print(f"Result (should be False): {result}")
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_send_email_invalid_port(self):
|
||||
print("\nTesting send_email with invalid port...")
|
||||
original_smtp = getattr(settings, 'SMTP', {})
|
||||
|
||||
settings.SMTP = {
|
||||
'server': 'smtp.example.com',
|
||||
@@ -58,10 +56,8 @@ class TestEmail(unittest.TestCase):
|
||||
test=True
|
||||
)
|
||||
|
||||
settings.SMTP = original_smtp
|
||||
|
||||
print(f"Result (should be False): {result}")
|
||||
self.assertFalse(result)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
@@ -27,14 +27,16 @@ def test_error_formatting():
|
||||
formatted = format_db_error(raw)
|
||||
print(f"Raw: {raw}")
|
||||
print(f"Formatted: {formatted}")
|
||||
if formatted == "Duplicate entry 'abc' for key 'id_random'":
|
||||
|
||||
if formatted.category == "database_duplicate" and formatted.code == 1062:
|
||||
print("✅ Error formatting works.")
|
||||
else:
|
||||
print("❌ Error formatting FAILED.")
|
||||
|
||||
def test_null_error_handling():
|
||||
print("\n--- Testing Null Error Handling ---")
|
||||
if format_db_error(None) == "":
|
||||
formatted = format_db_error(None)
|
||||
if formatted.category == "unknown":
|
||||
print("✅ Null error handled correctly.")
|
||||
else:
|
||||
print("❌ Null error check FAILED.")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import sys
|
||||
import os
|
||||
from typing import ClassVar
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
# --- Environment Setup ---
|
||||
@@ -23,7 +22,7 @@ mock_lib_general = MagicMock()
|
||||
mock_lib_general.log = MagicMock()
|
||||
mock_lib_general.logging = MagicMock()
|
||||
sys.modules['app.lib_general'] = mock_lib_general
|
||||
sys.modules['app.log'] = MagicMock() # Ensure app.log is also mocked if needed separately
|
||||
|
||||
mock_db_sql = MagicMock()
|
||||
mock_db_sql.redis_lookup_id_random.return_value = 1
|
||||
mock_db_sql.get_id_random.return_value = "mock_id"
|
||||
@@ -44,13 +43,7 @@ except Exception as e:
|
||||
def test_person_null_given_name():
|
||||
"""Test that given_name=None is converted to empty string."""
|
||||
try:
|
||||
# construct() bypasses validation, so we use the constructor
|
||||
# We provide dummy values for other likely required fields
|
||||
p = Person_Base.construct(given_name=None)
|
||||
# Note: In Pydantic V1 validators run on __init__.
|
||||
# Since we mocked the environment, we'll test the validator function directly if init fails.
|
||||
|
||||
from app.models.person_models import Person_Base
|
||||
# In Pydantic V1, we test the validator classmethod directly if instantiation is too complex with mocks
|
||||
val = Person_Base.given_name_validator(None)
|
||||
if val == "":
|
||||
print("✅ given_name validator: None -> '' (Success)")
|
||||
@@ -62,7 +55,6 @@ def test_person_null_given_name():
|
||||
def test_person_null_allow_auth_key():
|
||||
"""Test that allow_auth_key=None is converted to True."""
|
||||
try:
|
||||
from app.models.person_models import Person_Base
|
||||
val = Person_Base.allow_auth_key_validator(None)
|
||||
if val is True:
|
||||
print("✅ allow_auth_key validator: None -> True (Success)")
|
||||
@@ -73,4 +65,4 @@ def test_person_null_allow_auth_key():
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_person_null_given_name()
|
||||
test_person_null_allow_auth_key()
|
||||
test_person_null_allow_auth_key()
|
||||
Reference in New Issue
Block a user