style: apply ruff auto-fix
- Auto-formatted code with ruff format - Applied ruff linting fixes with --fix Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
+14
-48
@@ -49,9 +49,7 @@ async def exchange_dropbox_token(
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Use shared OAuth helper (handles secure logging and error handling)
|
# Use shared OAuth helper (handles secure logging and error handling)
|
||||||
token_data = exchange_oauth_token(
|
token_data = exchange_oauth_token(provider_name="Dropbox", token_url=token_url, payload=payload)
|
||||||
provider_name="Dropbox", token_url=token_url, payload=payload
|
|
||||||
)
|
|
||||||
|
|
||||||
# Return just what's needed by the frontend
|
# Return just what's needed by the frontend
|
||||||
return {
|
return {
|
||||||
@@ -79,19 +77,13 @@ async def update_dropbox_settings(
|
|||||||
|
|
||||||
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
||||||
changed_by = (
|
changed_by = (
|
||||||
user.get("preferred_username")
|
user.get("preferred_username") or user.get("username") or user.get("email") or user.get("id") or "wizard"
|
||||||
or user.get("username")
|
|
||||||
or user.get("email")
|
|
||||||
or user.get("id")
|
|
||||||
or "wizard"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update settings in memory and persist to database
|
# Update settings in memory and persist to database
|
||||||
if refresh_token:
|
if refresh_token:
|
||||||
settings.dropbox_refresh_token = refresh_token
|
settings.dropbox_refresh_token = refresh_token
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "dropbox_refresh_token", refresh_token, changed_by=changed_by)
|
||||||
db, "dropbox_refresh_token", refresh_token, changed_by=changed_by
|
|
||||||
)
|
|
||||||
logger.info("Updated DROPBOX_REFRESH_TOKEN in memory and database")
|
logger.info("Updated DROPBOX_REFRESH_TOKEN in memory and database")
|
||||||
|
|
||||||
if app_key:
|
if app_key:
|
||||||
@@ -101,9 +93,7 @@ async def update_dropbox_settings(
|
|||||||
|
|
||||||
if app_secret:
|
if app_secret:
|
||||||
settings.dropbox_app_secret = app_secret
|
settings.dropbox_app_secret = app_secret
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "dropbox_app_secret", app_secret, changed_by=changed_by)
|
||||||
db, "dropbox_app_secret", app_secret, changed_by=changed_by
|
|
||||||
)
|
|
||||||
logger.info("Updated DROPBOX_APP_SECRET in memory and database")
|
logger.info("Updated DROPBOX_APP_SECRET in memory and database")
|
||||||
|
|
||||||
if folder_path:
|
if folder_path:
|
||||||
@@ -135,11 +125,7 @@ async def test_dropbox_token(request: Request):
|
|||||||
try:
|
try:
|
||||||
logger.info("Testing Dropbox token validity")
|
logger.info("Testing Dropbox token validity")
|
||||||
|
|
||||||
if (
|
if not settings.dropbox_refresh_token or not settings.dropbox_app_key or not settings.dropbox_app_secret:
|
||||||
not settings.dropbox_refresh_token
|
|
||||||
or not settings.dropbox_app_key
|
|
||||||
or not settings.dropbox_app_secret
|
|
||||||
):
|
|
||||||
logger.warning("Dropbox credentials not fully configured")
|
logger.warning("Dropbox credentials not fully configured")
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
@@ -167,14 +153,10 @@ async def test_dropbox_token(request: Request):
|
|||||||
"client_secret": settings.dropbox_app_secret,
|
"client_secret": settings.dropbox_app_secret,
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh_response = requests.post(
|
refresh_response = requests.post(refresh_url, data=refresh_data, timeout=settings.http_request_timeout)
|
||||||
refresh_url, data=refresh_data, timeout=settings.http_request_timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
if refresh_response.status_code != 200:
|
if refresh_response.status_code != 200:
|
||||||
logger.error(
|
logger.error(f"Failed to refresh Dropbox token: {refresh_response.text}")
|
||||||
f"Failed to refresh Dropbox token: {refresh_response.text}"
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": "Refresh token has expired or is invalid",
|
"message": "Refresh token has expired or is invalid",
|
||||||
@@ -193,9 +175,7 @@ async def test_dropbox_token(request: Request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
logger.error(
|
logger.error(f"Dropbox token test failed: {response.status_code} {response.text}")
|
||||||
f"Dropbox token test failed: {response.status_code} {response.text}"
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": f"Token validation failed with status {response.status_code}: {response.text}",
|
"message": f"Token validation failed with status {response.status_code}: {response.text}",
|
||||||
@@ -243,11 +223,7 @@ async def save_dropbox_settings(
|
|||||||
try:
|
try:
|
||||||
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
||||||
changed_by = (
|
changed_by = (
|
||||||
user.get("preferred_username")
|
user.get("preferred_username") or user.get("username") or user.get("email") or user.get("id") or "wizard"
|
||||||
or user.get("username")
|
|
||||||
or user.get("email")
|
|
||||||
or user.get("id")
|
|
||||||
or "wizard"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update settings in memory
|
# Update settings in memory
|
||||||
@@ -262,27 +238,19 @@ async def save_dropbox_settings(
|
|||||||
|
|
||||||
# Persist to database (primary storage)
|
# Persist to database (primary storage)
|
||||||
if refresh_token:
|
if refresh_token:
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "dropbox_refresh_token", refresh_token, changed_by=changed_by)
|
||||||
db, "dropbox_refresh_token", refresh_token, changed_by=changed_by
|
|
||||||
)
|
|
||||||
if app_key:
|
if app_key:
|
||||||
save_setting_to_db(db, "dropbox_app_key", app_key, changed_by=changed_by)
|
save_setting_to_db(db, "dropbox_app_key", app_key, changed_by=changed_by)
|
||||||
if app_secret:
|
if app_secret:
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "dropbox_app_secret", app_secret, changed_by=changed_by)
|
||||||
db, "dropbox_app_secret", app_secret, changed_by=changed_by
|
|
||||||
)
|
|
||||||
if folder_path:
|
if folder_path:
|
||||||
save_setting_to_db(db, "dropbox_folder", folder_path, changed_by=changed_by)
|
save_setting_to_db(db, "dropbox_folder", folder_path, changed_by=changed_by)
|
||||||
|
|
||||||
# Best-effort .env file write
|
# Best-effort .env file write
|
||||||
try:
|
try:
|
||||||
env_path = os.path.join(
|
env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ".env")
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ".env"
|
|
||||||
)
|
|
||||||
if not os.path.exists(env_path):
|
if not os.path.exists(env_path):
|
||||||
logger.warning(
|
logger.warning(f".env file not found at {env_path}, skipping file write")
|
||||||
f".env file not found at {env_path}, skipping file write"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logger.info(f"Updating Dropbox settings in {env_path}")
|
logger.info(f"Updating Dropbox settings in {env_path}")
|
||||||
|
|
||||||
@@ -303,9 +271,7 @@ async def save_dropbox_settings(
|
|||||||
stripped_line = line.rstrip()
|
stripped_line = line.rstrip()
|
||||||
is_updated = False
|
is_updated = False
|
||||||
for key, value in dropbox_settings.items():
|
for key, value in dropbox_settings.items():
|
||||||
if stripped_line.startswith(
|
if stripped_line.startswith(f"{key}=") or stripped_line.startswith(f"# {key}="):
|
||||||
f"{key}="
|
|
||||||
) or stripped_line.startswith(f"# {key}="):
|
|
||||||
new_env_lines.append(f"{key}={value}")
|
new_env_lines.append(f"{key}={value}")
|
||||||
updated.add(key)
|
updated.add(key)
|
||||||
is_updated = True
|
is_updated = True
|
||||||
|
|||||||
+18
-57
@@ -49,9 +49,7 @@ async def exchange_google_drive_token(
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Use shared OAuth helper (handles secure logging and error handling)
|
# Use shared OAuth helper (handles secure logging and error handling)
|
||||||
token_data = exchange_oauth_token(
|
token_data = exchange_oauth_token(provider_name="Google Drive", token_url=token_url, payload=payload)
|
||||||
provider_name="Google Drive", token_url=token_url, payload=payload
|
|
||||||
)
|
|
||||||
|
|
||||||
# Return just what's needed by the frontend
|
# Return just what's needed by the frontend
|
||||||
return {
|
return {
|
||||||
@@ -80,11 +78,7 @@ async def update_google_drive_settings(
|
|||||||
|
|
||||||
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
||||||
changed_by = (
|
changed_by = (
|
||||||
user.get("preferred_username")
|
user.get("preferred_username") or user.get("username") or user.get("email") or user.get("id") or "wizard"
|
||||||
or user.get("username")
|
|
||||||
or user.get("email")
|
|
||||||
or user.get("id")
|
|
||||||
or "wizard"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert use_oauth string to boolean
|
# Convert use_oauth string to boolean
|
||||||
@@ -93,30 +87,22 @@ async def update_google_drive_settings(
|
|||||||
# Update settings in memory and persist to database
|
# Update settings in memory and persist to database
|
||||||
if refresh_token:
|
if refresh_token:
|
||||||
settings.google_drive_refresh_token = refresh_token
|
settings.google_drive_refresh_token = refresh_token
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "google_drive_refresh_token", refresh_token, changed_by=changed_by)
|
||||||
db, "google_drive_refresh_token", refresh_token, changed_by=changed_by
|
|
||||||
)
|
|
||||||
logger.info("Updated GOOGLE_DRIVE_REFRESH_TOKEN in memory and database")
|
logger.info("Updated GOOGLE_DRIVE_REFRESH_TOKEN in memory and database")
|
||||||
|
|
||||||
if client_id:
|
if client_id:
|
||||||
settings.google_drive_client_id = client_id
|
settings.google_drive_client_id = client_id
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "google_drive_client_id", client_id, changed_by=changed_by)
|
||||||
db, "google_drive_client_id", client_id, changed_by=changed_by
|
|
||||||
)
|
|
||||||
logger.info("Updated GOOGLE_DRIVE_CLIENT_ID in memory and database")
|
logger.info("Updated GOOGLE_DRIVE_CLIENT_ID in memory and database")
|
||||||
|
|
||||||
if client_secret:
|
if client_secret:
|
||||||
settings.google_drive_client_secret = client_secret
|
settings.google_drive_client_secret = client_secret
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "google_drive_client_secret", client_secret, changed_by=changed_by)
|
||||||
db, "google_drive_client_secret", client_secret, changed_by=changed_by
|
|
||||||
)
|
|
||||||
logger.info("Updated GOOGLE_DRIVE_CLIENT_SECRET in memory and database")
|
logger.info("Updated GOOGLE_DRIVE_CLIENT_SECRET in memory and database")
|
||||||
|
|
||||||
if folder_id:
|
if folder_id:
|
||||||
settings.google_drive_folder_id = folder_id
|
settings.google_drive_folder_id = folder_id
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "google_drive_folder_id", folder_id, changed_by=changed_by)
|
||||||
db, "google_drive_folder_id", folder_id, changed_by=changed_by
|
|
||||||
)
|
|
||||||
logger.info("Updated GOOGLE_DRIVE_FOLDER_ID in memory and database")
|
logger.info("Updated GOOGLE_DRIVE_FOLDER_ID in memory and database")
|
||||||
|
|
||||||
# Set the OAuth flag
|
# Set the OAuth flag
|
||||||
@@ -127,9 +113,7 @@ async def update_google_drive_settings(
|
|||||||
str(use_oauth_bool).lower(),
|
str(use_oauth_bool).lower(),
|
||||||
changed_by=changed_by,
|
changed_by=changed_by,
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(f"Updated GOOGLE_DRIVE_USE_OAUTH in memory and database to {use_oauth_bool}")
|
||||||
f"Updated GOOGLE_DRIVE_USE_OAUTH in memory and database to {use_oauth_bool}"
|
|
||||||
)
|
|
||||||
|
|
||||||
notify_settings_updated()
|
notify_settings_updated()
|
||||||
|
|
||||||
@@ -154,8 +138,7 @@ async def test_google_drive_token(request: Request):
|
|||||||
Tests both OAuth and service account approaches based on configuration.
|
Tests both OAuth and service account approaches based on configuration.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from app.tasks.upload_to_google_drive import (get_drive_service_oauth,
|
from app.tasks.upload_to_google_drive import get_drive_service_oauth, get_google_drive_service
|
||||||
get_google_drive_service)
|
|
||||||
|
|
||||||
logger.info("Testing Google Drive token validity")
|
logger.info("Testing Google Drive token validity")
|
||||||
|
|
||||||
@@ -232,9 +215,7 @@ async def test_google_drive_token(request: Request):
|
|||||||
else:
|
else:
|
||||||
# Test service account connection
|
# Test service account connection
|
||||||
if not settings.google_drive_credentials_json:
|
if not settings.google_drive_credentials_json:
|
||||||
logger.warning(
|
logger.warning("Google Drive service account credentials not configured")
|
||||||
"Google Drive service account credentials not configured"
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": "Google Drive service account credentials are not configured",
|
"message": "Google Drive service account credentials are not configured",
|
||||||
@@ -253,9 +234,7 @@ async def test_google_drive_token(request: Request):
|
|||||||
else:
|
else:
|
||||||
user_display = user_email
|
user_display = user_email
|
||||||
|
|
||||||
logger.info(
|
logger.info(f"Successfully connected to Google Drive using service account as {user_display}")
|
||||||
f"Successfully connected to Google Drive using service account as {user_display}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
@@ -398,20 +377,14 @@ async def save_dropbox_settings(
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Get the path to the .env file
|
# Get the path to the .env file
|
||||||
env_path = os.path.join(
|
env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ".env")
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ".env"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Convert use_oauth string to boolean
|
# Convert use_oauth string to boolean
|
||||||
use_oauth_bool = use_oauth.lower() in ("true", "1", "yes", "y", "t")
|
use_oauth_bool = use_oauth.lower() in ("true", "1", "yes", "y", "t")
|
||||||
|
|
||||||
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
||||||
changed_by = (
|
changed_by = (
|
||||||
user.get("preferred_username")
|
user.get("preferred_username") or user.get("username") or user.get("email") or user.get("id") or "wizard"
|
||||||
or user.get("username")
|
|
||||||
or user.get("email")
|
|
||||||
or user.get("id")
|
|
||||||
or "wizard"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define settings to update
|
# Define settings to update
|
||||||
@@ -446,9 +419,7 @@ async def save_dropbox_settings(
|
|||||||
stripped_line = line.rstrip()
|
stripped_line = line.rstrip()
|
||||||
is_updated = False
|
is_updated = False
|
||||||
for key, value in drive_settings.items():
|
for key, value in drive_settings.items():
|
||||||
if stripped_line.startswith(
|
if stripped_line.startswith(f"{key}=") or stripped_line.startswith(f"# {key}="):
|
||||||
f"{key}="
|
|
||||||
) or stripped_line.startswith(f"# {key}="):
|
|
||||||
# Uncomment if commented out - check the original stripped line
|
# Uncomment if commented out - check the original stripped line
|
||||||
new_env_lines.append(f"{key}={value}")
|
new_env_lines.append(f"{key}={value}")
|
||||||
updated.add(key)
|
updated.add(key)
|
||||||
@@ -468,9 +439,7 @@ async def save_dropbox_settings(
|
|||||||
|
|
||||||
logger.info("Successfully updated Google Drive settings in .env file")
|
logger.info("Successfully updated Google Drive settings in .env file")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(
|
logger.warning(f"Failed to update .env file: {str(e)}, but will continue with in-memory update")
|
||||||
f"Failed to update .env file: {str(e)}, but will continue with in-memory update"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f".env file not found at {env_path}, skipping file update but continuing with in-memory update"
|
f".env file not found at {env_path}, skipping file update but continuing with in-memory update"
|
||||||
@@ -497,21 +466,13 @@ async def save_dropbox_settings(
|
|||||||
changed_by=changed_by,
|
changed_by=changed_by,
|
||||||
)
|
)
|
||||||
if refresh_token:
|
if refresh_token:
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "google_drive_refresh_token", refresh_token, changed_by=changed_by)
|
||||||
db, "google_drive_refresh_token", refresh_token, changed_by=changed_by
|
|
||||||
)
|
|
||||||
if client_id:
|
if client_id:
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "google_drive_client_id", client_id, changed_by=changed_by)
|
||||||
db, "google_drive_client_id", client_id, changed_by=changed_by
|
|
||||||
)
|
|
||||||
if client_secret:
|
if client_secret:
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "google_drive_client_secret", client_secret, changed_by=changed_by)
|
||||||
db, "google_drive_client_secret", client_secret, changed_by=changed_by
|
|
||||||
)
|
|
||||||
if folder_id:
|
if folder_id:
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "google_drive_folder_id", folder_id, changed_by=changed_by)
|
||||||
db, "google_drive_folder_id", folder_id, changed_by=changed_by
|
|
||||||
)
|
|
||||||
|
|
||||||
notify_settings_updated()
|
notify_settings_updated()
|
||||||
|
|
||||||
|
|||||||
+25
-79
@@ -51,9 +51,7 @@ async def exchange_onedrive_token(
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Use shared OAuth helper (handles secure logging and error handling)
|
# Use shared OAuth helper (handles secure logging and error handling)
|
||||||
token_data = exchange_oauth_token(
|
token_data = exchange_oauth_token(provider_name="OneDrive", token_url=token_url, payload=payload)
|
||||||
provider_name="OneDrive", token_url=token_url, payload=payload
|
|
||||||
)
|
|
||||||
|
|
||||||
# Return just what's needed by the frontend
|
# Return just what's needed by the frontend
|
||||||
return {
|
return {
|
||||||
@@ -94,9 +92,7 @@ async def test_onedrive_token(request: Request):
|
|||||||
"scope": "offline_access Files.ReadWrite",
|
"scope": "offline_access Files.ReadWrite",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(token_url, data=refresh_data, timeout=settings.http_request_timeout)
|
||||||
token_url, data=refresh_data, timeout=settings.http_request_timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
logger.error(f"Failed to refresh OneDrive token: {response.text}")
|
logger.error(f"Failed to refresh OneDrive token: {response.text}")
|
||||||
@@ -108,25 +104,19 @@ async def test_onedrive_token(request: Request):
|
|||||||
|
|
||||||
token_data = response.json()
|
token_data = response.json()
|
||||||
access_token = token_data.get("access_token")
|
access_token = token_data.get("access_token")
|
||||||
expires_in = token_data.get(
|
expires_in = token_data.get("expires_in", 3600) # Default to 1 hour if not specified
|
||||||
"expires_in", 3600
|
|
||||||
) # Default to 1 hour if not specified
|
|
||||||
|
|
||||||
# Check if we got a new refresh token (Microsoft sometimes issues a new one)
|
# Check if we got a new refresh token (Microsoft sometimes issues a new one)
|
||||||
new_refresh_token = token_data.get("refresh_token")
|
new_refresh_token = token_data.get("refresh_token")
|
||||||
if new_refresh_token and new_refresh_token != settings.onedrive_refresh_token:
|
if new_refresh_token and new_refresh_token != settings.onedrive_refresh_token:
|
||||||
logger.info(
|
logger.info("Received new refresh token from Microsoft - will update configuration")
|
||||||
"Received new refresh token from Microsoft - will update configuration"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update refresh token in memory
|
# Update refresh token in memory
|
||||||
settings.onedrive_refresh_token = new_refresh_token
|
settings.onedrive_refresh_token = new_refresh_token
|
||||||
|
|
||||||
# Also try to update .env file if it exists
|
# Also try to update .env file if it exists
|
||||||
try:
|
try:
|
||||||
env_path = os.path.join(
|
env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ".env")
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ".env"
|
|
||||||
)
|
|
||||||
if os.path.exists(env_path):
|
if os.path.exists(env_path):
|
||||||
with open(env_path, "r") as f:
|
with open(env_path, "r") as f:
|
||||||
env_lines = f.readlines()
|
env_lines = f.readlines()
|
||||||
@@ -136,17 +126,13 @@ async def test_onedrive_token(request: Request):
|
|||||||
|
|
||||||
for line in env_lines:
|
for line in env_lines:
|
||||||
if line.startswith("ONEDRIVE_REFRESH_TOKEN="):
|
if line.startswith("ONEDRIVE_REFRESH_TOKEN="):
|
||||||
updated_lines.append(
|
updated_lines.append(f"ONEDRIVE_REFRESH_TOKEN={new_refresh_token}\n")
|
||||||
f"ONEDRIVE_REFRESH_TOKEN={new_refresh_token}\n"
|
|
||||||
)
|
|
||||||
updated = True
|
updated = True
|
||||||
else:
|
else:
|
||||||
updated_lines.append(line)
|
updated_lines.append(line)
|
||||||
|
|
||||||
if not updated:
|
if not updated:
|
||||||
updated_lines.append(
|
updated_lines.append(f"ONEDRIVE_REFRESH_TOKEN={new_refresh_token}\n")
|
||||||
f"ONEDRIVE_REFRESH_TOKEN={new_refresh_token}\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
with open(env_path, "w") as f:
|
with open(env_path, "w") as f:
|
||||||
f.writelines(updated_lines)
|
f.writelines(updated_lines)
|
||||||
@@ -172,22 +158,16 @@ async def test_onedrive_token(request: Request):
|
|||||||
finally:
|
finally:
|
||||||
_db.close()
|
_db.close()
|
||||||
except Exception as _e:
|
except Exception as _e:
|
||||||
logger.warning(
|
logger.warning(f"Failed to persist rotated OneDrive refresh token to database: {_e}")
|
||||||
f"Failed to persist rotated OneDrive refresh token to database: {_e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test the access token by getting user information
|
# Test the access token by getting user information
|
||||||
user_info_url = "https://graph.microsoft.com/v1.0/me"
|
user_info_url = "https://graph.microsoft.com/v1.0/me"
|
||||||
headers = {"Authorization": f"Bearer {access_token}"}
|
headers = {"Authorization": f"Bearer {access_token}"}
|
||||||
|
|
||||||
user_response = requests.get(
|
user_response = requests.get(user_info_url, headers=headers, timeout=settings.http_request_timeout)
|
||||||
user_info_url, headers=headers, timeout=settings.http_request_timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
if user_response.status_code != 200:
|
if user_response.status_code != 200:
|
||||||
logger.error(
|
logger.error(f"OneDrive token test failed: {user_response.status_code} {user_response.text}")
|
||||||
f"OneDrive token test failed: {user_response.status_code} {user_response.text}"
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": f"Token validation failed with status {user_response.status_code}: {user_response.text}",
|
"message": f"Token validation failed with status {user_response.status_code}: {user_response.text}",
|
||||||
@@ -263,22 +243,14 @@ async def save_onedrive_settings(
|
|||||||
try:
|
try:
|
||||||
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
||||||
changed_by = (
|
changed_by = (
|
||||||
user.get("preferred_username")
|
user.get("preferred_username") or user.get("username") or user.get("email") or user.get("id") or "wizard"
|
||||||
or user.get("username")
|
|
||||||
or user.get("email")
|
|
||||||
or user.get("id")
|
|
||||||
or "wizard"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Best-effort .env file write
|
# Best-effort .env file write
|
||||||
try:
|
try:
|
||||||
env_path = os.path.join(
|
env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ".env")
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ".env"
|
|
||||||
)
|
|
||||||
if not os.path.exists(env_path):
|
if not os.path.exists(env_path):
|
||||||
logger.warning(
|
logger.warning(f".env file not found at {env_path}, skipping file write")
|
||||||
f".env file not found at {env_path}, skipping file write"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logger.info(f"Updating OneDrive settings in {env_path}")
|
logger.info(f"Updating OneDrive settings in {env_path}")
|
||||||
|
|
||||||
@@ -301,9 +273,7 @@ async def save_onedrive_settings(
|
|||||||
stripped_line = line.rstrip()
|
stripped_line = line.rstrip()
|
||||||
is_updated = False
|
is_updated = False
|
||||||
for key, value in onedrive_settings.items():
|
for key, value in onedrive_settings.items():
|
||||||
if stripped_line.startswith(
|
if stripped_line.startswith(f"{key}=") or stripped_line.startswith(f"# {key}="):
|
||||||
f"{key}="
|
|
||||||
) or stripped_line.startswith(f"# {key}="):
|
|
||||||
new_env_lines.append(f"{key}={value}")
|
new_env_lines.append(f"{key}={value}")
|
||||||
updated.add(key)
|
updated.add(key)
|
||||||
is_updated = True
|
is_updated = True
|
||||||
@@ -336,25 +306,15 @@ async def save_onedrive_settings(
|
|||||||
|
|
||||||
# Persist to database (primary)
|
# Persist to database (primary)
|
||||||
if refresh_token:
|
if refresh_token:
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "onedrive_refresh_token", refresh_token, changed_by=changed_by)
|
||||||
db, "onedrive_refresh_token", refresh_token, changed_by=changed_by
|
|
||||||
)
|
|
||||||
if client_id:
|
if client_id:
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "onedrive_client_id", client_id, changed_by=changed_by)
|
||||||
db, "onedrive_client_id", client_id, changed_by=changed_by
|
|
||||||
)
|
|
||||||
if client_secret:
|
if client_secret:
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "onedrive_client_secret", client_secret, changed_by=changed_by)
|
||||||
db, "onedrive_client_secret", client_secret, changed_by=changed_by
|
|
||||||
)
|
|
||||||
if tenant_id:
|
if tenant_id:
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "onedrive_tenant_id", tenant_id, changed_by=changed_by)
|
||||||
db, "onedrive_tenant_id", tenant_id, changed_by=changed_by
|
|
||||||
)
|
|
||||||
if folder_path:
|
if folder_path:
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "onedrive_folder_path", folder_path, changed_by=changed_by)
|
||||||
db, "onedrive_folder_path", folder_path, changed_by=changed_by
|
|
||||||
)
|
|
||||||
|
|
||||||
notify_settings_updated()
|
notify_settings_updated()
|
||||||
|
|
||||||
@@ -388,47 +348,33 @@ async def update_onedrive_settings(
|
|||||||
|
|
||||||
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
||||||
changed_by = (
|
changed_by = (
|
||||||
user.get("preferred_username")
|
user.get("preferred_username") or user.get("username") or user.get("email") or user.get("id") or "wizard"
|
||||||
or user.get("username")
|
|
||||||
or user.get("email")
|
|
||||||
or user.get("id")
|
|
||||||
or "wizard"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update settings in memory and persist to database
|
# Update settings in memory and persist to database
|
||||||
if refresh_token:
|
if refresh_token:
|
||||||
settings.onedrive_refresh_token = refresh_token
|
settings.onedrive_refresh_token = refresh_token
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "onedrive_refresh_token", refresh_token, changed_by=changed_by)
|
||||||
db, "onedrive_refresh_token", refresh_token, changed_by=changed_by
|
|
||||||
)
|
|
||||||
logger.info("Updated ONEDRIVE_REFRESH_TOKEN in memory and database")
|
logger.info("Updated ONEDRIVE_REFRESH_TOKEN in memory and database")
|
||||||
|
|
||||||
if client_id:
|
if client_id:
|
||||||
settings.onedrive_client_id = client_id
|
settings.onedrive_client_id = client_id
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "onedrive_client_id", client_id, changed_by=changed_by)
|
||||||
db, "onedrive_client_id", client_id, changed_by=changed_by
|
|
||||||
)
|
|
||||||
logger.info("Updated ONEDRIVE_CLIENT_ID in memory and database")
|
logger.info("Updated ONEDRIVE_CLIENT_ID in memory and database")
|
||||||
|
|
||||||
if client_secret:
|
if client_secret:
|
||||||
settings.onedrive_client_secret = client_secret
|
settings.onedrive_client_secret = client_secret
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "onedrive_client_secret", client_secret, changed_by=changed_by)
|
||||||
db, "onedrive_client_secret", client_secret, changed_by=changed_by
|
|
||||||
)
|
|
||||||
logger.info("Updated ONEDRIVE_CLIENT_SECRET in memory and database")
|
logger.info("Updated ONEDRIVE_CLIENT_SECRET in memory and database")
|
||||||
|
|
||||||
if tenant_id:
|
if tenant_id:
|
||||||
settings.onedrive_tenant_id = tenant_id
|
settings.onedrive_tenant_id = tenant_id
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "onedrive_tenant_id", tenant_id, changed_by=changed_by)
|
||||||
db, "onedrive_tenant_id", tenant_id, changed_by=changed_by
|
|
||||||
)
|
|
||||||
logger.info("Updated ONEDRIVE_TENANT_ID in memory and database")
|
logger.info("Updated ONEDRIVE_TENANT_ID in memory and database")
|
||||||
|
|
||||||
if folder_path:
|
if folder_path:
|
||||||
settings.onedrive_folder_path = folder_path
|
settings.onedrive_folder_path = folder_path
|
||||||
save_setting_to_db(
|
save_setting_to_db(db, "onedrive_folder_path", folder_path, changed_by=changed_by)
|
||||||
db, "onedrive_folder_path", folder_path, changed_by=changed_by
|
|
||||||
)
|
|
||||||
logger.info("Updated ONEDRIVE_FOLDER_PATH in memory and database")
|
logger.info("Updated ONEDRIVE_FOLDER_PATH in memory and database")
|
||||||
|
|
||||||
notify_settings_updated()
|
notify_settings_updated()
|
||||||
|
|||||||
+24
-60
@@ -11,16 +11,19 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
from app.utils.input_validation import (validate_setting_key,
|
from app.utils.input_validation import validate_setting_key, validate_setting_key_format
|
||||||
validate_setting_key_format)
|
from app.utils.settings_service import (
|
||||||
from app.utils.settings_service import (SETTING_METADATA,
|
SETTING_METADATA,
|
||||||
delete_setting_from_db,
|
delete_setting_from_db,
|
||||||
get_all_settings_from_db,
|
get_all_settings_from_db,
|
||||||
get_audit_log, get_setting_history,
|
get_audit_log,
|
||||||
|
get_setting_history,
|
||||||
get_setting_metadata,
|
get_setting_metadata,
|
||||||
get_settings_by_category,
|
get_settings_by_category,
|
||||||
rollback_setting, save_setting_to_db,
|
rollback_setting,
|
||||||
validate_setting_value)
|
save_setting_to_db,
|
||||||
|
validate_setting_value,
|
||||||
|
)
|
||||||
from app.utils.settings_sync import notify_settings_updated
|
from app.utils.settings_sync import notify_settings_updated
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -37,9 +40,7 @@ def require_admin(request: Request) -> dict:
|
|||||||
"""
|
"""
|
||||||
user = request.session.get("user")
|
user = request.session.get("user")
|
||||||
if not user or not user.get("is_admin"):
|
if not user or not user.get("is_admin"):
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required"
|
|
||||||
)
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
@@ -93,9 +94,7 @@ async def get_settings(request: Request, db: DbSession, admin: AdminUser):
|
|||||||
# Get settings organized by category
|
# Get settings organized by category
|
||||||
categories = get_settings_by_category()
|
categories = get_settings_by_category()
|
||||||
|
|
||||||
return SettingsListResponse(
|
return SettingsListResponse(settings=current_settings, categories=categories, db_settings=db_settings)
|
||||||
settings=current_settings, categories=categories, db_settings=db_settings
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error retrieving settings: {e}")
|
logger.error(f"Error retrieving settings: {e}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -118,9 +117,7 @@ async def get_setting(key: str, request: Request, db: DbSession, admin: AdminUse
|
|||||||
# Get metadata
|
# Get metadata
|
||||||
metadata = get_setting_metadata(key)
|
metadata = get_setting_metadata(key)
|
||||||
|
|
||||||
return SettingResponse(
|
return SettingResponse(key=key, value=str(value) if value is not None else None, metadata=metadata)
|
||||||
key=key, value=str(value) if value is not None else None, metadata=metadata
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error retrieving setting {key}: {e}")
|
logger.error(f"Error retrieving setting {key}: {e}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -147,18 +144,12 @@ async def update_setting(
|
|||||||
if setting.value is not None:
|
if setting.value is not None:
|
||||||
is_valid, error_message = validate_setting_value(key, setting.value)
|
is_valid, error_message = validate_setting_value(key, setting.value)
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error_message)
|
||||||
status_code=status.HTTP_400_BAD_REQUEST, detail=error_message
|
|
||||||
)
|
|
||||||
|
|
||||||
# Determine the username for the audit log
|
# Determine the username for the audit log
|
||||||
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
||||||
changed_by = (
|
changed_by = (
|
||||||
user.get("preferred_username")
|
user.get("preferred_username") or user.get("username") or user.get("email") or user.get("id") or "admin"
|
||||||
or user.get("username")
|
|
||||||
or user.get("email")
|
|
||||||
or user.get("id")
|
|
||||||
or "admin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Save to database
|
# Save to database
|
||||||
@@ -203,11 +194,7 @@ async def delete_setting(key: str, request: Request, db: DbSession, admin: Admin
|
|||||||
try:
|
try:
|
||||||
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
||||||
changed_by = (
|
changed_by = (
|
||||||
user.get("preferred_username")
|
user.get("preferred_username") or user.get("username") or user.get("email") or user.get("id") or "admin"
|
||||||
or user.get("username")
|
|
||||||
or user.get("email")
|
|
||||||
or user.get("id")
|
|
||||||
or "admin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
success = delete_setting_from_db(db, key, changed_by=changed_by)
|
success = delete_setting_from_db(db, key, changed_by=changed_by)
|
||||||
@@ -291,9 +278,7 @@ async def list_credentials(request: Request, db: DbSession, admin: AdminUser):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/bulk-update")
|
@router.post("/bulk-update")
|
||||||
async def bulk_update_settings(
|
async def bulk_update_settings(updates: list[SettingUpdate], request: Request, db: DbSession, admin: AdminUser):
|
||||||
updates: list[SettingUpdate], request: Request, db: DbSession, admin: AdminUser
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Update multiple settings at once.
|
Update multiple settings at once.
|
||||||
Admin only.
|
Admin only.
|
||||||
@@ -303,36 +288,24 @@ async def bulk_update_settings(
|
|||||||
|
|
||||||
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
||||||
changed_by = (
|
changed_by = (
|
||||||
user.get("preferred_username")
|
user.get("preferred_username") or user.get("username") or user.get("email") or user.get("id") or "admin"
|
||||||
or user.get("username")
|
|
||||||
or user.get("email")
|
|
||||||
or user.get("id")
|
|
||||||
or "admin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for update in updates:
|
for update in updates:
|
||||||
try:
|
try:
|
||||||
# Validate the setting value
|
# Validate the setting value
|
||||||
if update.value is not None:
|
if update.value is not None:
|
||||||
is_valid, error_message = validate_setting_value(
|
is_valid, error_message = validate_setting_value(update.key, update.value)
|
||||||
update.key, update.value
|
|
||||||
)
|
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
errors.append({"key": update.key, "error": error_message})
|
errors.append({"key": update.key, "error": error_message})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Save to database
|
# Save to database
|
||||||
success = save_setting_to_db(
|
success = save_setting_to_db(db, update.key, update.value, changed_by=changed_by)
|
||||||
db, update.key, update.value, changed_by=changed_by
|
|
||||||
)
|
|
||||||
if success:
|
if success:
|
||||||
results.append(
|
results.append({"key": update.key, "value": update.value, "status": "success"})
|
||||||
{"key": update.key, "value": update.value, "status": "success"}
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
errors.append(
|
errors.append({"key": update.key, "error": "Failed to save to database"})
|
||||||
{"key": update.key, "error": "Failed to save to database"}
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error updating setting {update.key}: {e}")
|
logger.error(f"Error updating setting {update.key}: {e}")
|
||||||
errors.append({"key": update.key, "error": str(e)})
|
errors.append({"key": update.key, "error": str(e)})
|
||||||
@@ -340,10 +313,7 @@ async def bulk_update_settings(
|
|||||||
if results:
|
if results:
|
||||||
notify_settings_updated()
|
notify_settings_updated()
|
||||||
|
|
||||||
restart_required = any(
|
restart_required = any(get_setting_metadata(result["key"]).get("restart_required", False) for result in results)
|
||||||
get_setting_metadata(result["key"]).get("restart_required", False)
|
|
||||||
for result in results
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": len(errors) == 0,
|
"success": len(errors) == 0,
|
||||||
@@ -422,11 +392,7 @@ async def rollback_setting_to_history(
|
|||||||
try:
|
try:
|
||||||
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
user = request.session.get("user", {}) if hasattr(request, "session") else {}
|
||||||
changed_by = (
|
changed_by = (
|
||||||
user.get("preferred_username")
|
user.get("preferred_username") or user.get("username") or user.get("email") or user.get("id") or "admin"
|
||||||
or user.get("username")
|
|
||||||
or user.get("email")
|
|
||||||
or user.get("id")
|
|
||||||
or "admin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
success = rollback_setting(db, key, history_id, changed_by=changed_by)
|
success = rollback_setting(db, key, history_id, changed_by=changed_by)
|
||||||
@@ -498,9 +464,7 @@ async def export_env_settings(
|
|||||||
return FastAPIResponse(
|
return FastAPIResponse(
|
||||||
content=content,
|
content=content,
|
||||||
media_type="text/plain",
|
media_type="text/plain",
|
||||||
headers={
|
headers={"Content-Disposition": f'attachment; filename="docuelevate-{source}.env"'},
|
||||||
"Content-Disposition": f'attachment; filename="docuelevate-{source}.env"'
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error exporting settings: {e}")
|
logger.error(f"Error exporting settings: {e}")
|
||||||
|
|||||||
+10
-13
@@ -3,32 +3,30 @@
|
|||||||
from celery.schedules import crontab
|
from celery.schedules import crontab
|
||||||
|
|
||||||
# Ensure tasks are loaded
|
# Ensure tasks are loaded
|
||||||
from app import \
|
from app import tasks # noqa: F401 - Imports app/tasks.py so Celery can register tasks
|
||||||
tasks # noqa: F401 - Imports app/tasks.py so Celery can register tasks
|
|
||||||
# Import the shared Celery instance
|
# Import the shared Celery instance
|
||||||
from app.celery_app import celery
|
from app.celery_app import celery
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.tasks.check_credentials import check_credentials
|
from app.tasks.check_credentials import check_credentials
|
||||||
from app.tasks.convert_to_pdf import convert_to_pdf # noqa: F401
|
from app.tasks.convert_to_pdf import convert_to_pdf # noqa: F401
|
||||||
from app.tasks.embed_metadata_into_pdf import \
|
from app.tasks.embed_metadata_into_pdf import embed_metadata_into_pdf # noqa: F401
|
||||||
embed_metadata_into_pdf # noqa: F401
|
from app.tasks.extract_metadata_with_gpt import extract_metadata_with_gpt # noqa: F401
|
||||||
from app.tasks.extract_metadata_with_gpt import \
|
|
||||||
extract_metadata_with_gpt # noqa: F401
|
|
||||||
from app.tasks.imap_tasks import pull_all_inboxes # noqa: F401
|
from app.tasks.imap_tasks import pull_all_inboxes # noqa: F401
|
||||||
from app.tasks.monitor_stalled_steps import monitor_stalled_steps # noqa: F401
|
from app.tasks.monitor_stalled_steps import monitor_stalled_steps # noqa: F401
|
||||||
|
|
||||||
# **Ensure all tasks are imported before Celery starts**
|
# **Ensure all tasks are imported before Celery starts**
|
||||||
from app.tasks.process_document import process_document # noqa: F401
|
from app.tasks.process_document import process_document # noqa: F401
|
||||||
from app.tasks.process_with_azure_document_intelligence import \
|
from app.tasks.process_with_azure_document_intelligence import process_with_azure_document_intelligence # noqa: F401
|
||||||
process_with_azure_document_intelligence # noqa: F401
|
|
||||||
from app.tasks.refine_text_with_gpt import refine_text_with_gpt # noqa: F401
|
from app.tasks.refine_text_with_gpt import refine_text_with_gpt # noqa: F401
|
||||||
from app.tasks.rotate_pdf_pages import rotate_pdf_pages # noqa: F401
|
from app.tasks.rotate_pdf_pages import rotate_pdf_pages # noqa: F401
|
||||||
from app.tasks.send_to_all import send_to_all_destinations # noqa: F401
|
from app.tasks.send_to_all import send_to_all_destinations # noqa: F401
|
||||||
|
|
||||||
# Import new send tasks
|
# Import new send tasks
|
||||||
from app.tasks.upload_to_dropbox import upload_to_dropbox # noqa: F401
|
from app.tasks.upload_to_dropbox import upload_to_dropbox # noqa: F401
|
||||||
from app.tasks.upload_to_email import upload_to_email # noqa: F401
|
from app.tasks.upload_to_email import upload_to_email # noqa: F401
|
||||||
from app.tasks.upload_to_ftp import upload_to_ftp # noqa: F401
|
from app.tasks.upload_to_ftp import upload_to_ftp # noqa: F401
|
||||||
from app.tasks.upload_to_google_drive import \
|
from app.tasks.upload_to_google_drive import upload_to_google_drive # noqa: F401
|
||||||
upload_to_google_drive # noqa: F401
|
|
||||||
from app.tasks.upload_to_nextcloud import upload_to_nextcloud # noqa: F401
|
from app.tasks.upload_to_nextcloud import upload_to_nextcloud # noqa: F401
|
||||||
from app.tasks.upload_to_onedrive import upload_to_onedrive # noqa: F401
|
from app.tasks.upload_to_onedrive import upload_to_onedrive # noqa: F401
|
||||||
from app.tasks.upload_to_paperless import upload_to_paperless # noqa: F401
|
from app.tasks.upload_to_paperless import upload_to_paperless # noqa: F401
|
||||||
@@ -36,6 +34,7 @@ from app.tasks.upload_to_s3 import upload_to_s3 # noqa: F401
|
|||||||
from app.tasks.upload_to_sftp import upload_to_sftp # noqa: F401
|
from app.tasks.upload_to_sftp import upload_to_sftp # noqa: F401
|
||||||
from app.tasks.upload_to_webdav import upload_to_webdav # noqa: F401
|
from app.tasks.upload_to_webdav import upload_to_webdav # noqa: F401
|
||||||
from app.tasks.uptime_kuma_tasks import ping_uptime_kuma # noqa: F401
|
from app.tasks.uptime_kuma_tasks import ping_uptime_kuma # noqa: F401
|
||||||
|
|
||||||
# Register the settings reload signal handler so workers pick up config changes
|
# Register the settings reload signal handler so workers pick up config changes
|
||||||
from app.utils.settings_sync import register_settings_reload_signal
|
from app.utils.settings_sync import register_settings_reload_signal
|
||||||
|
|
||||||
@@ -95,6 +94,4 @@ celery.conf.beat_schedule = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Remove None entries from beat_schedule
|
# Remove None entries from beat_schedule
|
||||||
celery.conf.beat_schedule = {
|
celery.conf.beat_schedule = {k: v for k, v in celery.conf.beat_schedule.items() if v is not None}
|
||||||
k: v for k, v in celery.conf.beat_schedule.items() if v is not None
|
|
||||||
}
|
|
||||||
|
|||||||
+11
-32
@@ -1,7 +1,6 @@
|
|||||||
# app/models.py
|
# app/models.py
|
||||||
|
|
||||||
from sqlalchemy import (Boolean, Column, DateTime, ForeignKey, Integer, String,
|
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, func
|
||||||
Text, UniqueConstraint, func)
|
|
||||||
|
|
||||||
from app.database import Base
|
from app.database import Base
|
||||||
|
|
||||||
@@ -70,33 +69,21 @@ class FileProcessingStep(Base):
|
|||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
file_id = Column(Integer, ForeignKey(_FILES_ID_FK), nullable=False, index=True)
|
file_id = Column(Integer, ForeignKey(_FILES_ID_FK), nullable=False, index=True)
|
||||||
step_name = Column(
|
step_name = Column(String, nullable=False, index=True) # e.g., "hash_file", "upload_to_dropbox"
|
||||||
String, nullable=False, index=True
|
status = Column(String, nullable=False) # "pending", "in_progress", "success", "failure", "skipped"
|
||||||
) # e.g., "hash_file", "upload_to_dropbox"
|
|
||||||
status = Column(
|
|
||||||
String, nullable=False
|
|
||||||
) # "pending", "in_progress", "success", "failure", "skipped"
|
|
||||||
started_at = Column(DateTime(timezone=True), nullable=True) # When step started
|
started_at = Column(DateTime(timezone=True), nullable=True) # When step started
|
||||||
completed_at = Column(
|
completed_at = Column(DateTime(timezone=True), nullable=True) # When step finished (success/failure)
|
||||||
DateTime(timezone=True), nullable=True
|
|
||||||
) # When step finished (success/failure)
|
|
||||||
error_message = Column(Text, nullable=True) # Error message if status is "failure"
|
error_message = Column(Text, nullable=True) # Error message if status is "failure"
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
updated_at = Column(
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
|
||||||
)
|
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (UniqueConstraint("file_id", "step_name", name="unique_file_step"),)
|
||||||
UniqueConstraint("file_id", "step_name", name="unique_file_step"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessingLog(Base):
|
class ProcessingLog(Base):
|
||||||
__tablename__ = "processing_logs"
|
__tablename__ = "processing_logs"
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
file_id = Column(
|
file_id = Column(Integer, ForeignKey(_FILES_ID_FK), nullable=True) # Optional file association
|
||||||
Integer, ForeignKey(_FILES_ID_FK), nullable=True
|
|
||||||
) # Optional file association
|
|
||||||
task_id = Column(String, index=True) # Celery task ID
|
task_id = Column(String, index=True) # Celery task ID
|
||||||
step_name = Column(String) # e.g., "OCR", "convert_to_pdf", "upload_s3"
|
step_name = Column(String) # e.g., "OCR", "convert_to_pdf", "upload_s3"
|
||||||
status = Column(String) # "pending", "in_progress", "success", "failure"
|
status = Column(String) # "pending", "in_progress", "success", "failure"
|
||||||
@@ -111,16 +98,10 @@ class ApplicationSettings(Base):
|
|||||||
__tablename__ = "application_settings"
|
__tablename__ = "application_settings"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
key = Column(
|
key = Column(String, unique=True, index=True, nullable=False) # Setting key (e.g., 'database_url')
|
||||||
String, unique=True, index=True, nullable=False
|
value = Column(String, nullable=True) # Setting value (stored as string, converted as needed)
|
||||||
) # Setting key (e.g., 'database_url')
|
|
||||||
value = Column(
|
|
||||||
String, nullable=True
|
|
||||||
) # Setting value (stored as string, converted as needed)
|
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
updated_at = Column(
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SettingsAuditLog(Base):
|
class SettingsAuditLog(Base):
|
||||||
@@ -132,8 +113,6 @@ class SettingsAuditLog(Base):
|
|||||||
key = Column(String, nullable=False, index=True) # Setting key that was changed
|
key = Column(String, nullable=False, index=True) # Setting key that was changed
|
||||||
old_value = Column(String, nullable=True) # Previous value (None if first-time set)
|
old_value = Column(String, nullable=True) # Previous value (None if first-time set)
|
||||||
new_value = Column(String, nullable=True) # New value (None if deleted)
|
new_value = Column(String, nullable=True) # New value (None if deleted)
|
||||||
changed_by = Column(
|
changed_by = Column(String, nullable=False) # Username of the admin who made the change
|
||||||
String, nullable=False
|
|
||||||
) # Username of the admin who made the change
|
|
||||||
changed_at = Column(DateTime(timezone=True), server_default=func.now(), index=True)
|
changed_at = Column(DateTime(timezone=True), server_default=func.now(), index=True)
|
||||||
action = Column(String, nullable=False) # "update" or "delete"
|
action = Column(String, nullable=False) # "update" or "delete"
|
||||||
|
|||||||
@@ -871,9 +871,7 @@ def get_setting_from_db(db: Session, key: str) -> Optional[str]:
|
|||||||
Setting value as string (decrypted if necessary), or None if not found
|
Setting value as string (decrypted if necessary), or None if not found
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
setting = (
|
setting = db.query(ApplicationSettings).filter(ApplicationSettings.key == key).first()
|
||||||
db.query(ApplicationSettings).filter(ApplicationSettings.key == key).first()
|
|
||||||
)
|
|
||||||
if not setting:
|
if not setting:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -890,9 +888,7 @@ def get_setting_from_db(db: Session, key: str) -> Optional[str]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def save_setting_to_db(
|
def save_setting_to_db(db: Session, key: str, value: Optional[str], changed_by: str = "system") -> bool:
|
||||||
db: Session, key: str, value: Optional[str], changed_by: str = "system"
|
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Save or update a setting in the database.
|
Save or update a setting in the database.
|
||||||
|
|
||||||
@@ -914,20 +910,15 @@ def save_setting_to_db(
|
|||||||
storage_value = value
|
storage_value = value
|
||||||
|
|
||||||
if metadata.get("sensitive", False) and value:
|
if metadata.get("sensitive", False) and value:
|
||||||
from app.utils.encryption import (encrypt_value,
|
from app.utils.encryption import encrypt_value, is_encryption_available
|
||||||
is_encryption_available)
|
|
||||||
|
|
||||||
if is_encryption_available():
|
if is_encryption_available():
|
||||||
storage_value = encrypt_value(value)
|
storage_value = encrypt_value(value)
|
||||||
logger.debug(f"Encrypted sensitive setting: {key}")
|
logger.debug(f"Encrypted sensitive setting: {key}")
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(f"Storing sensitive setting {key} in plaintext (encryption unavailable)")
|
||||||
f"Storing sensitive setting {key} in plaintext (encryption unavailable)"
|
|
||||||
)
|
|
||||||
|
|
||||||
setting = (
|
setting = db.query(ApplicationSettings).filter(ApplicationSettings.key == key).first()
|
||||||
db.query(ApplicationSettings).filter(ApplicationSettings.key == key).first()
|
|
||||||
)
|
|
||||||
old_storage_value = setting.value if setting else None
|
old_storage_value = setting.value if setting else None
|
||||||
|
|
||||||
if setting:
|
if setting:
|
||||||
@@ -1015,9 +1006,7 @@ def delete_setting_from_db(db: Session, key: str, changed_by: str = "system") ->
|
|||||||
True if successful, False otherwise
|
True if successful, False otherwise
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
setting = (
|
setting = db.query(ApplicationSettings).filter(ApplicationSettings.key == key).first()
|
||||||
db.query(ApplicationSettings).filter(ApplicationSettings.key == key).first()
|
|
||||||
)
|
|
||||||
if setting:
|
if setting:
|
||||||
# Capture old value for audit log (decrypt if sensitive)
|
# Capture old value for audit log (decrypt if sensitive)
|
||||||
metadata = get_setting_metadata(key)
|
metadata = get_setting_metadata(key)
|
||||||
@@ -1045,9 +1034,7 @@ def delete_setting_from_db(db: Session, key: str, changed_by: str = "system") ->
|
|||||||
db.add(audit_entry)
|
db.add(audit_entry)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
logger.info(
|
logger.info(f"Deleted setting {key} from database (changed_by={changed_by})")
|
||||||
f"Deleted setting {key} from database (changed_by={changed_by})"
|
|
||||||
)
|
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
except SQLAlchemyError as e:
|
except SQLAlchemyError as e:
|
||||||
@@ -1131,9 +1118,7 @@ def validate_setting_value(key: str, value: str) -> Tuple[bool, Optional[str]]:
|
|||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
def get_audit_log(
|
def get_audit_log(db: Session, limit: int = 100, offset: int = 0) -> List[Dict[str, Any]]:
|
||||||
db: Session, limit: int = 100, offset: int = 0
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
"""
|
||||||
Retrieve the settings audit log, most recent first.
|
Retrieve the settings audit log, most recent first.
|
||||||
|
|
||||||
@@ -1150,11 +1135,7 @@ def get_audit_log(
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
entries = (
|
entries = (
|
||||||
db.query(SettingsAuditLog)
|
db.query(SettingsAuditLog).order_by(SettingsAuditLog.changed_at.desc()).limit(limit).offset(offset).all()
|
||||||
.order_by(SettingsAuditLog.changed_at.desc())
|
|
||||||
.limit(limit)
|
|
||||||
.offset(offset)
|
|
||||||
.all()
|
|
||||||
)
|
)
|
||||||
result = []
|
result = []
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
@@ -1164,20 +1145,10 @@ def get_audit_log(
|
|||||||
{
|
{
|
||||||
"id": entry.id,
|
"id": entry.id,
|
||||||
"key": entry.key,
|
"key": entry.key,
|
||||||
"old_value": (
|
"old_value": ("[REDACTED]" if is_sensitive and entry.old_value else entry.old_value),
|
||||||
"[REDACTED]"
|
"new_value": ("[REDACTED]" if is_sensitive and entry.new_value else entry.new_value),
|
||||||
if is_sensitive and entry.old_value
|
|
||||||
else entry.old_value
|
|
||||||
),
|
|
||||||
"new_value": (
|
|
||||||
"[REDACTED]"
|
|
||||||
if is_sensitive and entry.new_value
|
|
||||||
else entry.new_value
|
|
||||||
),
|
|
||||||
"changed_by": entry.changed_by,
|
"changed_by": entry.changed_by,
|
||||||
"changed_at": (
|
"changed_at": (entry.changed_at.isoformat() if entry.changed_at else None),
|
||||||
entry.changed_at.isoformat() if entry.changed_at else None
|
|
||||||
),
|
|
||||||
"action": entry.action,
|
"action": entry.action,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1215,20 +1186,10 @@ def get_setting_history(db: Session, key: str) -> List[Dict[str, Any]]:
|
|||||||
{
|
{
|
||||||
"id": entry.id,
|
"id": entry.id,
|
||||||
"key": entry.key,
|
"key": entry.key,
|
||||||
"old_value": (
|
"old_value": ("[REDACTED]" if is_sensitive and entry.old_value else entry.old_value),
|
||||||
"[REDACTED]"
|
"new_value": ("[REDACTED]" if is_sensitive and entry.new_value else entry.new_value),
|
||||||
if is_sensitive and entry.old_value
|
|
||||||
else entry.old_value
|
|
||||||
),
|
|
||||||
"new_value": (
|
|
||||||
"[REDACTED]"
|
|
||||||
if is_sensitive and entry.new_value
|
|
||||||
else entry.new_value
|
|
||||||
),
|
|
||||||
"changed_by": entry.changed_by,
|
"changed_by": entry.changed_by,
|
||||||
"changed_at": (
|
"changed_at": (entry.changed_at.isoformat() if entry.changed_at else None),
|
||||||
entry.changed_at.isoformat() if entry.changed_at else None
|
|
||||||
),
|
|
||||||
"action": entry.action,
|
"action": entry.action,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1238,9 +1199,7 @@ def get_setting_history(db: Session, key: str) -> List[Dict[str, Any]]:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def rollback_setting(
|
def rollback_setting(db: Session, key: str, history_id: int, changed_by: str = "system") -> bool:
|
||||||
db: Session, key: str, history_id: int, changed_by: str = "system"
|
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Revert a setting to the value recorded in a specific audit log entry.
|
Revert a setting to the value recorded in a specific audit log entry.
|
||||||
|
|
||||||
@@ -1264,14 +1223,10 @@ def rollback_setting(
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
history_entry = (
|
history_entry = (
|
||||||
db.query(SettingsAuditLog)
|
db.query(SettingsAuditLog).filter(SettingsAuditLog.id == history_id, SettingsAuditLog.key == key).first()
|
||||||
.filter(SettingsAuditLog.id == history_id, SettingsAuditLog.key == key)
|
|
||||||
.first()
|
|
||||||
)
|
)
|
||||||
if not history_entry:
|
if not history_entry:
|
||||||
logger.warning(
|
logger.warning(f"Rollback failed: audit log entry {history_id} not found for key '{key}'")
|
||||||
f"Rollback failed: audit log entry {history_id} not found for key '{key}'"
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
target_value = history_entry.new_value
|
target_value = history_entry.new_value
|
||||||
@@ -1282,9 +1237,7 @@ def rollback_setting(
|
|||||||
else:
|
else:
|
||||||
return save_setting_to_db(db, key, target_value, changed_by=changed_by)
|
return save_setting_to_db(db, key, target_value, changed_by=changed_by)
|
||||||
except SQLAlchemyError as e:
|
except SQLAlchemyError as e:
|
||||||
logger.error(
|
logger.error(f"Error rolling back setting {key} to history entry {history_id}: {e}")
|
||||||
f"Error rolling back setting {key} to history entry {history_id}: {e}"
|
|
||||||
)
|
|
||||||
db.rollback()
|
db.rollback()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
+7
-10
@@ -12,12 +12,13 @@ from fastapi.responses import RedirectResponse
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.utils.config_validator.masking import mask_sensitive_value
|
from app.utils.config_validator.masking import mask_sensitive_value
|
||||||
from app.utils.settings_service import (SETTING_METADATA,
|
from app.utils.settings_service import (
|
||||||
|
SETTING_METADATA,
|
||||||
get_all_settings_from_db,
|
get_all_settings_from_db,
|
||||||
get_setting_metadata,
|
get_setting_metadata,
|
||||||
get_settings_by_category)
|
get_settings_by_category,
|
||||||
from app.views.base import (APIRouter, get_db, require_login, settings,
|
)
|
||||||
templates)
|
from app.views.base import APIRouter, get_db, require_login, settings, templates
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@@ -102,9 +103,7 @@ async def settings_page(request: Request, db: Session = Depends(get_db)):
|
|||||||
settings_data[category].append(
|
settings_data[category].append(
|
||||||
{
|
{
|
||||||
"key": key,
|
"key": key,
|
||||||
"display_value": (
|
"display_value": (display_value if display_value is not None else ""),
|
||||||
display_value if display_value is not None else ""
|
|
||||||
),
|
|
||||||
"metadata": metadata,
|
"metadata": metadata,
|
||||||
"source": source,
|
"source": source,
|
||||||
"source_label": source_label,
|
"source_label": source_label,
|
||||||
@@ -175,9 +174,7 @@ async def credentials_page(request: Request, db: Session = Depends(get_db)):
|
|||||||
)
|
)
|
||||||
|
|
||||||
total = sum(len(v) for v in categories.values())
|
total = sum(len(v) for v in categories.values())
|
||||||
configured_count = sum(
|
configured_count = sum(1 for creds in categories.values() for c in creds if c["configured"])
|
||||||
1 for creds in categories.values() for c in creds if c["configured"]
|
|
||||||
)
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"credentials.html",
|
"credentials.html",
|
||||||
|
|||||||
+5
-17
@@ -40,11 +40,7 @@ async def setup_wizard(request: Request, step: int = 1, db: Session = Depends(ge
|
|||||||
current_settings = wizard_steps.get(step, [])
|
current_settings = wizard_steps.get(step, [])
|
||||||
|
|
||||||
# Get step category (all settings in a step should have same category)
|
# Get step category (all settings in a step should have same category)
|
||||||
step_category = (
|
step_category = current_settings[0].get("wizard_category", "Configuration") if current_settings else "Configuration"
|
||||||
current_settings[0].get("wizard_category", "Configuration")
|
|
||||||
if current_settings
|
|
||||||
else "Configuration"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Enrich settings with current live values
|
# Enrich settings with current live values
|
||||||
from app.config import settings as app_settings
|
from app.config import settings as app_settings
|
||||||
@@ -68,9 +64,7 @@ async def setup_wizard(request: Request, step: int = 1, db: Session = Depends(ge
|
|||||||
else:
|
else:
|
||||||
current_value = ""
|
current_value = ""
|
||||||
value_source = "none"
|
value_source = "none"
|
||||||
enriched_settings.append(
|
enriched_settings.append({**s, "current_value": current_value, "value_source": value_source})
|
||||||
{**s, "current_value": current_value, "value_source": value_source}
|
|
||||||
)
|
|
||||||
current_settings = enriched_settings
|
current_settings = enriched_settings
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
@@ -88,9 +82,7 @@ async def setup_wizard(request: Request, step: int = 1, db: Session = Depends(ge
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/setup")
|
@router.post("/setup")
|
||||||
async def setup_wizard_save(
|
async def setup_wizard_save(request: Request, step: int = Form(...), db: Session = Depends(get_db)):
|
||||||
request: Request, step: int = Form(...), db: Session = Depends(get_db)
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Save settings from the current wizard step.
|
Save settings from the current wizard step.
|
||||||
"""
|
"""
|
||||||
@@ -138,9 +130,7 @@ async def setup_wizard_save(
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error saving wizard settings: {e}")
|
logger.error(f"Error saving wizard settings: {e}")
|
||||||
return RedirectResponse(
|
return RedirectResponse(url=f"/setup?step={step}&error=save_failed", status_code=303)
|
||||||
url=f"/setup?step={step}&error=save_failed", status_code=303
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/setup/skip")
|
@router.get("/setup/skip")
|
||||||
@@ -176,9 +166,7 @@ async def setup_wizard_undo_skip(request: Request, db: Session = Depends(get_db)
|
|||||||
try:
|
try:
|
||||||
from app.utils.settings_service import delete_setting_from_db
|
from app.utils.settings_service import delete_setting_from_db
|
||||||
|
|
||||||
delete_setting_from_db(
|
delete_setting_from_db(db, "_setup_wizard_skipped", changed_by="wizard_undo_skip")
|
||||||
db, "_setup_wizard_skipped", changed_by="wizard_undo_skip"
|
|
||||||
)
|
|
||||||
logger.info("Setup wizard skip marker removed; redirecting to wizard")
|
logger.info("Setup wizard skip marker removed; redirecting to wizard")
|
||||||
return RedirectResponse(url="/setup?step=1", status_code=303)
|
return RedirectResponse(url="/setup?step=1", status_code=303)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -249,9 +249,7 @@ class TestTestDropboxToken:
|
|||||||
mock_settings.dropbox_app_secret = "app-secret"
|
mock_settings.dropbox_app_secret = "app-secret"
|
||||||
mock_settings.http_request_timeout = 30
|
mock_settings.http_request_timeout = 30
|
||||||
|
|
||||||
mock_post.side_effect = requests.exceptions.ConnectionError(
|
mock_post.side_effect = requests.exceptions.ConnectionError("Connection refused")
|
||||||
"Connection refused"
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get("/api/dropbox/test-token")
|
response = client.get("/api/dropbox/test-token")
|
||||||
|
|
||||||
@@ -312,9 +310,7 @@ class TestSaveDropboxSettings:
|
|||||||
assert "new-token" in content
|
assert "new-token" in content
|
||||||
|
|
||||||
@patch("app.api.dropbox.settings")
|
@patch("app.api.dropbox.settings")
|
||||||
def test_save_settings_with_all_optional_fields(
|
def test_save_settings_with_all_optional_fields(self, mock_settings, client, tmp_path):
|
||||||
self, mock_settings, client, tmp_path
|
|
||||||
):
|
|
||||||
"""Test saving all Dropbox settings including optional fields."""
|
"""Test saving all Dropbox settings including optional fields."""
|
||||||
mock_settings.dropbox_refresh_token = ""
|
mock_settings.dropbox_refresh_token = ""
|
||||||
mock_settings.dropbox_app_key = ""
|
mock_settings.dropbox_app_key = ""
|
||||||
|
|||||||
@@ -72,9 +72,7 @@ class TestExchangeOneDriveToken:
|
|||||||
@patch("app.api.onedrive.exchange_oauth_token")
|
@patch("app.api.onedrive.exchange_oauth_token")
|
||||||
def test_exchange_token_error(self, mock_exchange, client: TestClient):
|
def test_exchange_token_error(self, mock_exchange, client: TestClient):
|
||||||
"""Test token exchange with error from OAuth provider."""
|
"""Test token exchange with error from OAuth provider."""
|
||||||
mock_exchange.side_effect = HTTPException(
|
mock_exchange.side_effect = HTTPException(status_code=400, detail="Invalid authorization code")
|
||||||
status_code=400, detail="Invalid authorization code"
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
"/api/onedrive/exchange-token",
|
"/api/onedrive/exchange-token",
|
||||||
@@ -109,9 +107,7 @@ class TestTestOneDriveToken:
|
|||||||
@patch("requests.post")
|
@patch("requests.post")
|
||||||
@patch("requests.get")
|
@patch("requests.get")
|
||||||
@patch("app.config.settings")
|
@patch("app.config.settings")
|
||||||
def test_test_token_success(
|
def test_test_token_success(self, mock_settings, mock_get, mock_post, client: TestClient):
|
||||||
self, mock_settings, mock_get, mock_post, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test successful token validation with properly mocked responses."""
|
"""Test successful token validation with properly mocked responses."""
|
||||||
# Configure settings with property mocking
|
# Configure settings with property mocking
|
||||||
type(mock_settings).onedrive_refresh_token = "test_refresh_token"
|
type(mock_settings).onedrive_refresh_token = "test_refresh_token"
|
||||||
@@ -162,9 +158,7 @@ class TestTestOneDriveToken:
|
|||||||
|
|
||||||
@patch("requests.post")
|
@patch("requests.post")
|
||||||
@patch("app.config.settings")
|
@patch("app.config.settings")
|
||||||
def test_test_token_refresh_failed(
|
def test_test_token_refresh_failed(self, mock_settings, mock_post, client: TestClient):
|
||||||
self, mock_settings, mock_post, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test when token refresh fails."""
|
"""Test when token refresh fails."""
|
||||||
type(mock_settings).onedrive_refresh_token = "invalid_token"
|
type(mock_settings).onedrive_refresh_token = "invalid_token"
|
||||||
type(mock_settings).onedrive_client_id = "test_client_id"
|
type(mock_settings).onedrive_client_id = "test_client_id"
|
||||||
@@ -189,9 +183,7 @@ class TestTestOneDriveToken:
|
|||||||
@patch("requests.post")
|
@patch("requests.post")
|
||||||
@patch("requests.get")
|
@patch("requests.get")
|
||||||
@patch("app.config.settings")
|
@patch("app.config.settings")
|
||||||
def test_test_token_new_refresh_token_issued(
|
def test_test_token_new_refresh_token_issued(self, mock_settings, mock_get, mock_post, client: TestClient):
|
||||||
self, mock_settings, mock_get, mock_post, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test when Microsoft issues a new refresh token."""
|
"""Test when Microsoft issues a new refresh token."""
|
||||||
type(mock_settings).onedrive_refresh_token = "old_refresh_token"
|
type(mock_settings).onedrive_refresh_token = "old_refresh_token"
|
||||||
type(mock_settings).onedrive_client_id = "test_client_id"
|
type(mock_settings).onedrive_client_id = "test_client_id"
|
||||||
@@ -278,9 +270,7 @@ class TestTestOneDriveToken:
|
|||||||
@patch("requests.post")
|
@patch("requests.post")
|
||||||
@patch("requests.get")
|
@patch("requests.get")
|
||||||
@patch("app.config.settings")
|
@patch("app.config.settings")
|
||||||
def test_test_token_user_info_failed(
|
def test_test_token_user_info_failed(self, mock_settings, mock_get, mock_post, client: TestClient):
|
||||||
self, mock_settings, mock_get, mock_post, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test when user info request fails."""
|
"""Test when user info request fails."""
|
||||||
mock_settings.onedrive_refresh_token = "test_token"
|
mock_settings.onedrive_refresh_token = "test_token"
|
||||||
mock_settings.onedrive_client_id = "test_client_id"
|
mock_settings.onedrive_client_id = "test_client_id"
|
||||||
@@ -356,9 +346,7 @@ class TestSaveOneDriveSettings:
|
|||||||
@patch("os.path.exists")
|
@patch("os.path.exists")
|
||||||
@patch("os.path.dirname")
|
@patch("os.path.dirname")
|
||||||
@patch("app.config.settings")
|
@patch("app.config.settings")
|
||||||
def test_save_settings_success(
|
def test_save_settings_success(self, mock_settings, mock_dirname, mock_exists, mock_file, client: TestClient):
|
||||||
self, mock_settings, mock_dirname, mock_exists, mock_file, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test successful save to .env file."""
|
"""Test successful save to .env file."""
|
||||||
mock_exists.return_value = True
|
mock_exists.return_value = True
|
||||||
mock_dirname.return_value = "/app"
|
mock_dirname.return_value = "/app"
|
||||||
@@ -380,9 +368,7 @@ class TestSaveOneDriveSettings:
|
|||||||
|
|
||||||
@patch("os.path.exists")
|
@patch("os.path.exists")
|
||||||
@patch("os.path.dirname")
|
@patch("os.path.dirname")
|
||||||
def test_save_settings_env_file_not_found(
|
def test_save_settings_env_file_not_found(self, mock_dirname, mock_exists, client: TestClient):
|
||||||
self, mock_dirname, mock_exists, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test that missing .env file is non-fatal — DB write still succeeds."""
|
"""Test that missing .env file is non-fatal — DB write still succeeds."""
|
||||||
mock_exists.return_value = False
|
mock_exists.return_value = False
|
||||||
mock_dirname.return_value = "/app"
|
mock_dirname.return_value = "/app"
|
||||||
@@ -468,17 +454,13 @@ class TestSaveOneDriveSettings:
|
|||||||
|
|
||||||
def test_save_settings_missing_required_field(self, client: TestClient):
|
def test_save_settings_missing_required_field(self, client: TestClient):
|
||||||
"""Test save without required refresh_token."""
|
"""Test save without required refresh_token."""
|
||||||
response = client.post(
|
response = client.post("/api/onedrive/save-settings", data={"tenant_id": "common"})
|
||||||
"/api/onedrive/save-settings", data={"tenant_id": "common"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 422 # Validation error
|
assert response.status_code == 422 # Validation error
|
||||||
|
|
||||||
@patch("os.path.exists")
|
@patch("os.path.exists")
|
||||||
@patch("os.path.dirname")
|
@patch("os.path.dirname")
|
||||||
def test_save_settings_exception_handling(
|
def test_save_settings_exception_handling(self, mock_dirname, mock_exists, client: TestClient):
|
||||||
self, mock_dirname, mock_exists, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test that exceptions in .env write are non-fatal — DB write still succeeds."""
|
"""Test that exceptions in .env write are non-fatal — DB write still succeeds."""
|
||||||
mock_exists.side_effect = Exception("Unexpected error")
|
mock_exists.side_effect = Exception("Unexpected error")
|
||||||
|
|
||||||
@@ -498,9 +480,7 @@ class TestUpdateOneDriveSettings:
|
|||||||
|
|
||||||
@patch("app.tasks.upload_to_onedrive.get_onedrive_token")
|
@patch("app.tasks.upload_to_onedrive.get_onedrive_token")
|
||||||
@patch("app.config.settings")
|
@patch("app.config.settings")
|
||||||
def test_update_settings_success(
|
def test_update_settings_success(self, mock_settings, mock_get_token, client: TestClient):
|
||||||
self, mock_settings, mock_get_token, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test successful settings update in memory."""
|
"""Test successful settings update in memory."""
|
||||||
mock_get_token.return_value = "test_token"
|
mock_get_token.return_value = "test_token"
|
||||||
|
|
||||||
@@ -521,9 +501,7 @@ class TestUpdateOneDriveSettings:
|
|||||||
|
|
||||||
@patch("app.tasks.upload_to_onedrive.get_onedrive_token")
|
@patch("app.tasks.upload_to_onedrive.get_onedrive_token")
|
||||||
@patch("app.config.settings")
|
@patch("app.config.settings")
|
||||||
def test_update_settings_minimal(
|
def test_update_settings_minimal(self, mock_settings, mock_get_token, client: TestClient):
|
||||||
self, mock_settings, mock_get_token, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test update with only required fields."""
|
"""Test update with only required fields."""
|
||||||
mock_get_token.return_value = "test_token"
|
mock_get_token.return_value = "test_token"
|
||||||
|
|
||||||
@@ -536,9 +514,7 @@ class TestUpdateOneDriveSettings:
|
|||||||
|
|
||||||
@patch("app.tasks.upload_to_onedrive.get_onedrive_token")
|
@patch("app.tasks.upload_to_onedrive.get_onedrive_token")
|
||||||
@patch("app.config.settings")
|
@patch("app.config.settings")
|
||||||
def test_update_settings_token_test_fails(
|
def test_update_settings_token_test_fails(self, mock_settings, mock_get_token, client: TestClient):
|
||||||
self, mock_settings, mock_get_token, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test update when token test fails."""
|
"""Test update when token test fails."""
|
||||||
mock_get_token.side_effect = Exception("Token invalid")
|
mock_get_token.side_effect = Exception("Token invalid")
|
||||||
|
|
||||||
@@ -554,16 +530,12 @@ class TestUpdateOneDriveSettings:
|
|||||||
|
|
||||||
def test_update_settings_missing_required_field(self, client: TestClient):
|
def test_update_settings_missing_required_field(self, client: TestClient):
|
||||||
"""Test update without required refresh_token."""
|
"""Test update without required refresh_token."""
|
||||||
response = client.post(
|
response = client.post("/api/onedrive/update-settings", data={"tenant_id": "common"})
|
||||||
"/api/onedrive/update-settings", data={"tenant_id": "common"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 422
|
assert response.status_code == 422
|
||||||
|
|
||||||
@patch("app.config.settings")
|
@patch("app.config.settings")
|
||||||
def test_update_settings_exception_handling(
|
def test_update_settings_exception_handling(self, mock_settings, client: TestClient):
|
||||||
self, mock_settings, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test exception handling in update settings."""
|
"""Test exception handling in update settings."""
|
||||||
mock_settings.onedrive_refresh_token = None
|
mock_settings.onedrive_refresh_token = None
|
||||||
|
|
||||||
@@ -619,9 +591,7 @@ class TestGetOneDriveFullConfig:
|
|||||||
assert "status" in data
|
assert "status" in data
|
||||||
|
|
||||||
@patch("app.config.settings")
|
@patch("app.config.settings")
|
||||||
def test_get_full_config_exception_handling(
|
def test_get_full_config_exception_handling(self, mock_settings, client: TestClient):
|
||||||
self, mock_settings, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test exception handling in get full config."""
|
"""Test exception handling in get full config."""
|
||||||
# Even with exception, endpoint catches it
|
# Even with exception, endpoint catches it
|
||||||
response = client.get("/api/onedrive/get-full-config")
|
response = client.get("/api/onedrive/get-full-config")
|
||||||
@@ -676,9 +646,7 @@ class TestOneDriveIntegration:
|
|||||||
@patch("requests.post")
|
@patch("requests.post")
|
||||||
@patch("requests.get")
|
@patch("requests.get")
|
||||||
@patch("app.config.settings")
|
@patch("app.config.settings")
|
||||||
def test_token_refresh_rotation(
|
def test_token_refresh_rotation(self, mock_settings, mock_get, mock_post, client: TestClient):
|
||||||
self, mock_settings, mock_get, mock_post, client: TestClient
|
|
||||||
):
|
|
||||||
"""Test token refresh with automatic rotation."""
|
"""Test token refresh with automatic rotation."""
|
||||||
type(mock_settings).onedrive_refresh_token = "old_token"
|
type(mock_settings).onedrive_refresh_token = "old_token"
|
||||||
type(mock_settings).onedrive_client_id = "test_client_id"
|
type(mock_settings).onedrive_client_id = "test_client_id"
|
||||||
|
|||||||
@@ -45,9 +45,7 @@ class TestAuditLogOnSave:
|
|||||||
def test_save_creates_audit_entry(self, db_session):
|
def test_save_creates_audit_entry(self, db_session):
|
||||||
from app.utils.settings_service import save_setting_to_db
|
from app.utils.settings_service import save_setting_to_db
|
||||||
|
|
||||||
result = save_setting_to_db(
|
result = save_setting_to_db(db_session, "workdir", "/new/path", changed_by="alice")
|
||||||
db_session, "workdir", "/new/path", changed_by="alice"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result is True
|
assert result is True
|
||||||
entry = db_session.query(SettingsAuditLog).filter_by(key="workdir").first()
|
entry = db_session.query(SettingsAuditLog).filter_by(key="workdir").first()
|
||||||
@@ -73,18 +71,13 @@ class TestAuditLogOnSave:
|
|||||||
assert update_entry.new_value == "/new/path"
|
assert update_entry.new_value == "/new/path"
|
||||||
|
|
||||||
def test_delete_creates_audit_entry(self, db_session):
|
def test_delete_creates_audit_entry(self, db_session):
|
||||||
from app.utils.settings_service import (delete_setting_from_db,
|
from app.utils.settings_service import delete_setting_from_db, save_setting_to_db
|
||||||
save_setting_to_db)
|
|
||||||
|
|
||||||
save_setting_to_db(db_session, "workdir", "/some/path", changed_by="admin")
|
save_setting_to_db(db_session, "workdir", "/some/path", changed_by="admin")
|
||||||
result = delete_setting_from_db(db_session, "workdir", changed_by="carol")
|
result = delete_setting_from_db(db_session, "workdir", changed_by="carol")
|
||||||
|
|
||||||
assert result is True
|
assert result is True
|
||||||
delete_entry = (
|
delete_entry = db_session.query(SettingsAuditLog).filter_by(key="workdir", action="delete").first()
|
||||||
db_session.query(SettingsAuditLog)
|
|
||||||
.filter_by(key="workdir", action="delete")
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
assert delete_entry is not None
|
assert delete_entry is not None
|
||||||
assert delete_entry.old_value == "/some/path"
|
assert delete_entry.old_value == "/some/path"
|
||||||
assert delete_entry.new_value is None
|
assert delete_entry.new_value is None
|
||||||
@@ -93,9 +86,7 @@ class TestAuditLogOnSave:
|
|||||||
def test_delete_nonexistent_returns_false_no_entry(self, db_session):
|
def test_delete_nonexistent_returns_false_no_entry(self, db_session):
|
||||||
from app.utils.settings_service import delete_setting_from_db
|
from app.utils.settings_service import delete_setting_from_db
|
||||||
|
|
||||||
result = delete_setting_from_db(
|
result = delete_setting_from_db(db_session, "nonexistent_key", changed_by="admin")
|
||||||
db_session, "nonexistent_key", changed_by="admin"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result is False
|
assert result is False
|
||||||
assert db_session.query(SettingsAuditLog).count() == 0
|
assert db_session.query(SettingsAuditLog).count() == 0
|
||||||
@@ -119,8 +110,7 @@ class TestGetAuditLog:
|
|||||||
"""get_audit_log returns entries, masks sensitive values."""
|
"""get_audit_log returns entries, masks sensitive values."""
|
||||||
|
|
||||||
def test_returns_all_entries_most_recent_first(self, db_session):
|
def test_returns_all_entries_most_recent_first(self, db_session):
|
||||||
from app.utils.settings_service import (get_audit_log,
|
from app.utils.settings_service import get_audit_log, save_setting_to_db
|
||||||
save_setting_to_db)
|
|
||||||
|
|
||||||
save_setting_to_db(db_session, "workdir", "/first", changed_by="u1")
|
save_setting_to_db(db_session, "workdir", "/first", changed_by="u1")
|
||||||
save_setting_to_db(db_session, "workdir", "/second", changed_by="u2")
|
save_setting_to_db(db_session, "workdir", "/second", changed_by="u2")
|
||||||
@@ -133,12 +123,9 @@ class TestGetAuditLog:
|
|||||||
assert log[1]["new_value"] == "/first"
|
assert log[1]["new_value"] == "/first"
|
||||||
|
|
||||||
def test_sensitive_values_are_masked(self, db_session):
|
def test_sensitive_values_are_masked(self, db_session):
|
||||||
from app.utils.settings_service import (get_audit_log,
|
from app.utils.settings_service import get_audit_log, save_setting_to_db
|
||||||
save_setting_to_db)
|
|
||||||
|
|
||||||
save_setting_to_db(
|
save_setting_to_db(db_session, "openai_api_key", "sk-secret123", changed_by="admin")
|
||||||
db_session, "openai_api_key", "sk-secret123", changed_by="admin"
|
|
||||||
)
|
|
||||||
|
|
||||||
log = get_audit_log(db_session)
|
log = get_audit_log(db_session)
|
||||||
|
|
||||||
@@ -146,8 +133,7 @@ class TestGetAuditLog:
|
|||||||
assert entry["new_value"] == "[REDACTED]"
|
assert entry["new_value"] == "[REDACTED]"
|
||||||
|
|
||||||
def test_required_fields_present(self, db_session):
|
def test_required_fields_present(self, db_session):
|
||||||
from app.utils.settings_service import (get_audit_log,
|
from app.utils.settings_service import get_audit_log, save_setting_to_db
|
||||||
save_setting_to_db)
|
|
||||||
|
|
||||||
save_setting_to_db(db_session, "workdir", "/path", changed_by="alice")
|
save_setting_to_db(db_session, "workdir", "/path", changed_by="alice")
|
||||||
|
|
||||||
@@ -167,8 +153,7 @@ class TestGetAuditLog:
|
|||||||
assert field in entry
|
assert field in entry
|
||||||
|
|
||||||
def test_limit_and_offset(self, db_session):
|
def test_limit_and_offset(self, db_session):
|
||||||
from app.utils.settings_service import (get_audit_log,
|
from app.utils.settings_service import get_audit_log, save_setting_to_db
|
||||||
save_setting_to_db)
|
|
||||||
|
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
save_setting_to_db(db_session, "workdir", f"/path{i}", changed_by="admin")
|
save_setting_to_db(db_session, "workdir", f"/path{i}", changed_by="admin")
|
||||||
@@ -190,8 +175,7 @@ class TestGetSettingHistory:
|
|||||||
"""get_setting_history returns only entries for the requested key."""
|
"""get_setting_history returns only entries for the requested key."""
|
||||||
|
|
||||||
def test_returns_only_matching_key(self, db_session):
|
def test_returns_only_matching_key(self, db_session):
|
||||||
from app.utils.settings_service import (get_setting_history,
|
from app.utils.settings_service import get_setting_history, save_setting_to_db
|
||||||
save_setting_to_db)
|
|
||||||
|
|
||||||
save_setting_to_db(db_session, "workdir", "/wdir", changed_by="admin")
|
save_setting_to_db(db_session, "workdir", "/wdir", changed_by="admin")
|
||||||
save_setting_to_db(db_session, "debug", "true", changed_by="admin")
|
save_setting_to_db(db_session, "debug", "true", changed_by="admin")
|
||||||
@@ -219,32 +203,21 @@ class TestRollbackSetting:
|
|||||||
"""rollback_setting reinstates the value from a given audit log entry."""
|
"""rollback_setting reinstates the value from a given audit log entry."""
|
||||||
|
|
||||||
def test_rollback_to_previous_value(self, db_session):
|
def test_rollback_to_previous_value(self, db_session):
|
||||||
from app.utils.settings_service import (get_setting_from_db,
|
from app.utils.settings_service import get_setting_from_db, rollback_setting, save_setting_to_db
|
||||||
rollback_setting,
|
|
||||||
save_setting_to_db)
|
|
||||||
|
|
||||||
save_setting_to_db(
|
save_setting_to_db(db_session, "workdir", "/v1", changed_by="admin") # entry id 1
|
||||||
db_session, "workdir", "/v1", changed_by="admin"
|
save_setting_to_db(db_session, "workdir", "/v2", changed_by="admin") # entry id 2
|
||||||
) # entry id 1
|
|
||||||
save_setting_to_db(
|
|
||||||
db_session, "workdir", "/v2", changed_by="admin"
|
|
||||||
) # entry id 2
|
|
||||||
|
|
||||||
first_entry = (
|
first_entry = db_session.query(SettingsAuditLog).filter_by(key="workdir").first()
|
||||||
db_session.query(SettingsAuditLog).filter_by(key="workdir").first()
|
|
||||||
)
|
|
||||||
# first entry has new_value="/v1"
|
# first entry has new_value="/v1"
|
||||||
success = rollback_setting(
|
success = rollback_setting(db_session, "workdir", first_entry.id, changed_by="rollbacker")
|
||||||
db_session, "workdir", first_entry.id, changed_by="rollbacker"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert success is True
|
assert success is True
|
||||||
current = get_setting_from_db(db_session, "workdir")
|
current = get_setting_from_db(db_session, "workdir")
|
||||||
assert current == "/v1"
|
assert current == "/v1"
|
||||||
|
|
||||||
def test_rollback_creates_new_audit_entry(self, db_session):
|
def test_rollback_creates_new_audit_entry(self, db_session):
|
||||||
from app.utils.settings_service import (rollback_setting,
|
from app.utils.settings_service import rollback_setting, save_setting_to_db
|
||||||
save_setting_to_db)
|
|
||||||
|
|
||||||
save_setting_to_db(db_session, "workdir", "/v1", changed_by="admin")
|
save_setting_to_db(db_session, "workdir", "/v1", changed_by="admin")
|
||||||
entry = db_session.query(SettingsAuditLog).filter_by(key="workdir").first()
|
entry = db_session.query(SettingsAuditLog).filter_by(key="workdir").first()
|
||||||
@@ -255,8 +228,7 @@ class TestRollbackSetting:
|
|||||||
assert db_session.query(SettingsAuditLog).count() == initial_count + 1
|
assert db_session.query(SettingsAuditLog).count() == initial_count + 1
|
||||||
|
|
||||||
def test_rollback_wrong_history_id_returns_false(self, db_session):
|
def test_rollback_wrong_history_id_returns_false(self, db_session):
|
||||||
from app.utils.settings_service import (rollback_setting,
|
from app.utils.settings_service import rollback_setting, save_setting_to_db
|
||||||
save_setting_to_db)
|
|
||||||
|
|
||||||
save_setting_to_db(db_session, "workdir", "/v1", changed_by="admin")
|
save_setting_to_db(db_session, "workdir", "/v1", changed_by="admin")
|
||||||
|
|
||||||
@@ -265,8 +237,7 @@ class TestRollbackSetting:
|
|||||||
assert result is False
|
assert result is False
|
||||||
|
|
||||||
def test_rollback_wrong_key_returns_false(self, db_session):
|
def test_rollback_wrong_key_returns_false(self, db_session):
|
||||||
from app.utils.settings_service import (rollback_setting,
|
from app.utils.settings_service import rollback_setting, save_setting_to_db
|
||||||
save_setting_to_db)
|
|
||||||
|
|
||||||
save_setting_to_db(db_session, "workdir", "/v1", changed_by="admin")
|
save_setting_to_db(db_session, "workdir", "/v1", changed_by="admin")
|
||||||
entry = db_session.query(SettingsAuditLog).filter_by(key="workdir").first()
|
entry = db_session.query(SettingsAuditLog).filter_by(key="workdir").first()
|
||||||
@@ -287,8 +258,7 @@ class TestNotifySettingsUpdated:
|
|||||||
"""notify_settings_updated publishes the settings version key to Redis."""
|
"""notify_settings_updated publishes the settings version key to Redis."""
|
||||||
|
|
||||||
def test_sets_redis_key(self):
|
def test_sets_redis_key(self):
|
||||||
from app.utils.settings_sync import (SETTINGS_VERSION_KEY,
|
from app.utils.settings_sync import SETTINGS_VERSION_KEY, notify_settings_updated
|
||||||
notify_settings_updated)
|
|
||||||
|
|
||||||
mock_redis = MagicMock()
|
mock_redis = MagicMock()
|
||||||
mock_redis_instance = MagicMock()
|
mock_redis_instance = MagicMock()
|
||||||
@@ -394,9 +364,7 @@ class TestHistoryEndpoint:
|
|||||||
mock_db = MagicMock()
|
mock_db = MagicMock()
|
||||||
mock_admin = {"is_admin": True}
|
mock_admin = {"is_admin": True}
|
||||||
|
|
||||||
result = asyncio.run(
|
result = asyncio.run(get_key_history("workdir", mock_request, mock_db, mock_admin))
|
||||||
get_key_history("workdir", mock_request, mock_db, mock_admin)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result["key"] == "workdir"
|
assert result["key"] == "workdir"
|
||||||
assert len(result["history"]) == 1
|
assert len(result["history"]) == 1
|
||||||
@@ -421,9 +389,7 @@ class TestRollbackEndpoint:
|
|||||||
mock_db = MagicMock()
|
mock_db = MagicMock()
|
||||||
mock_admin = {"is_admin": True}
|
mock_admin = {"is_admin": True}
|
||||||
|
|
||||||
result = asyncio.run(
|
result = asyncio.run(rollback_setting_to_history("workdir", 1, mock_request, mock_db, mock_admin))
|
||||||
rollback_setting_to_history("workdir", 1, mock_request, mock_db, mock_admin)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result["success"] is True
|
assert result["success"] is True
|
||||||
mock_notify.assert_called_once()
|
mock_notify.assert_called_once()
|
||||||
@@ -443,10 +409,6 @@ class TestRollbackEndpoint:
|
|||||||
mock_admin = {"is_admin": True}
|
mock_admin = {"is_admin": True}
|
||||||
|
|
||||||
with pytest.raises(HTTPException) as exc_info:
|
with pytest.raises(HTTPException) as exc_info:
|
||||||
asyncio.run(
|
asyncio.run(rollback_setting_to_history("workdir", 9999, mock_request, mock_db, mock_admin))
|
||||||
rollback_setting_to_history(
|
|
||||||
"workdir", 9999, mock_request, mock_db, mock_admin
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert exc_info.value.status_code == 404
|
assert exc_info.value.status_code == 404
|
||||||
|
|||||||
@@ -48,9 +48,7 @@ class TestSetupWizardDbPersist:
|
|||||||
|
|
||||||
@patch("app.views.wizard.notify_settings_updated")
|
@patch("app.views.wizard.notify_settings_updated")
|
||||||
@patch("app.views.wizard.save_setting_to_db")
|
@patch("app.views.wizard.save_setting_to_db")
|
||||||
def test_notify_not_called_when_no_settings_saved(
|
def test_notify_not_called_when_no_settings_saved(self, mock_save, mock_notify, client):
|
||||||
self, mock_save, mock_notify, client
|
|
||||||
):
|
|
||||||
"""Test that notify_settings_updated is NOT called when saved_count == 0."""
|
"""Test that notify_settings_updated is NOT called when saved_count == 0."""
|
||||||
mock_save.return_value = False
|
mock_save.return_value = False
|
||||||
|
|
||||||
@@ -65,9 +63,7 @@ class TestSetupWizardDbPersist:
|
|||||||
@patch("app.views.wizard.notify_settings_updated")
|
@patch("app.views.wizard.notify_settings_updated")
|
||||||
@patch("app.views.wizard.secrets.token_hex")
|
@patch("app.views.wizard.secrets.token_hex")
|
||||||
@patch("app.views.wizard.save_setting_to_db")
|
@patch("app.views.wizard.save_setting_to_db")
|
||||||
def test_auto_generate_session_secret(
|
def test_auto_generate_session_secret(self, mock_save, mock_token, mock_notify, client):
|
||||||
self, mock_save, mock_token, mock_notify, client
|
|
||||||
):
|
|
||||||
"""Test that session_secret auto-generate path produces a real token."""
|
"""Test that session_secret auto-generate path produces a real token."""
|
||||||
mock_save.return_value = True
|
mock_save.return_value = True
|
||||||
mock_token.return_value = "deadbeef" * 8
|
mock_token.return_value = "deadbeef" * 8
|
||||||
@@ -136,9 +132,7 @@ class TestDropboxSaveSettingsDbPersist:
|
|||||||
@patch("app.api.dropbox.settings")
|
@patch("app.api.dropbox.settings")
|
||||||
@patch("app.api.dropbox.notify_settings_updated")
|
@patch("app.api.dropbox.notify_settings_updated")
|
||||||
@patch("app.api.dropbox.save_setting_to_db")
|
@patch("app.api.dropbox.save_setting_to_db")
|
||||||
def test_db_written_even_when_env_missing(
|
def test_db_written_even_when_env_missing(self, mock_save, mock_notify, mock_settings, client):
|
||||||
self, mock_save, mock_notify, mock_settings, client
|
|
||||||
):
|
|
||||||
"""Test that DB is written even when .env doesn't exist (no exception)."""
|
"""Test that DB is written even when .env doesn't exist (no exception)."""
|
||||||
mock_save.return_value = True
|
mock_save.return_value = True
|
||||||
|
|
||||||
@@ -157,9 +151,7 @@ class TestDropboxSaveSettingsDbPersist:
|
|||||||
@patch("app.api.dropbox.settings")
|
@patch("app.api.dropbox.settings")
|
||||||
@patch("app.api.dropbox.notify_settings_updated")
|
@patch("app.api.dropbox.notify_settings_updated")
|
||||||
@patch("app.api.dropbox.save_setting_to_db")
|
@patch("app.api.dropbox.save_setting_to_db")
|
||||||
def test_notify_settings_updated_called(
|
def test_notify_settings_updated_called(self, mock_save, mock_notify, mock_settings, client):
|
||||||
self, mock_save, mock_notify, mock_settings, client
|
|
||||||
):
|
|
||||||
"""Test that notify_settings_updated is called."""
|
"""Test that notify_settings_updated is called."""
|
||||||
mock_save.return_value = True
|
mock_save.return_value = True
|
||||||
|
|
||||||
@@ -175,9 +167,7 @@ class TestDropboxSaveSettingsDbPersist:
|
|||||||
@patch("app.api.dropbox.settings")
|
@patch("app.api.dropbox.settings")
|
||||||
@patch("app.api.dropbox.notify_settings_updated")
|
@patch("app.api.dropbox.notify_settings_updated")
|
||||||
@patch("app.api.dropbox.save_setting_to_db")
|
@patch("app.api.dropbox.save_setting_to_db")
|
||||||
def test_all_provided_values_persisted(
|
def test_all_provided_values_persisted(self, mock_save, mock_notify, mock_settings, client):
|
||||||
self, mock_save, mock_notify, mock_settings, client
|
|
||||||
):
|
|
||||||
"""Test that all provided values are persisted to DB."""
|
"""Test that all provided values are persisted to DB."""
|
||||||
mock_save.return_value = True
|
mock_save.return_value = True
|
||||||
|
|
||||||
@@ -212,9 +202,7 @@ class TestGoogleDriveUpdateSettingsDbPersist:
|
|||||||
@patch("app.api.google_drive.settings")
|
@patch("app.api.google_drive.settings")
|
||||||
@patch("app.api.google_drive.notify_settings_updated")
|
@patch("app.api.google_drive.notify_settings_updated")
|
||||||
@patch("app.api.google_drive.save_setting_to_db")
|
@patch("app.api.google_drive.save_setting_to_db")
|
||||||
def test_db_written_for_each_provided_field(
|
def test_db_written_for_each_provided_field(self, mock_save, mock_notify, mock_settings, client):
|
||||||
self, mock_save, mock_notify, mock_settings, client
|
|
||||||
):
|
|
||||||
"""Test that DB is written for each provided field."""
|
"""Test that DB is written for each provided field."""
|
||||||
mock_save.return_value = True
|
mock_save.return_value = True
|
||||||
|
|
||||||
@@ -241,9 +229,7 @@ class TestGoogleDriveUpdateSettingsDbPersist:
|
|||||||
@patch("app.api.google_drive.settings")
|
@patch("app.api.google_drive.settings")
|
||||||
@patch("app.api.google_drive.notify_settings_updated")
|
@patch("app.api.google_drive.notify_settings_updated")
|
||||||
@patch("app.api.google_drive.save_setting_to_db")
|
@patch("app.api.google_drive.save_setting_to_db")
|
||||||
def test_use_oauth_saved_as_lowercase_string(
|
def test_use_oauth_saved_as_lowercase_string(self, mock_save, mock_notify, mock_settings, client):
|
||||||
self, mock_save, mock_notify, mock_settings, client
|
|
||||||
):
|
|
||||||
"""Test that use_oauth is saved as 'true' or 'false' string."""
|
"""Test that use_oauth is saved as 'true' or 'false' string."""
|
||||||
mock_save.return_value = True
|
mock_save.return_value = True
|
||||||
|
|
||||||
@@ -253,11 +239,7 @@ class TestGoogleDriveUpdateSettingsDbPersist:
|
|||||||
follow_redirects=False,
|
follow_redirects=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
use_oauth_calls = [
|
use_oauth_calls = [call for call in mock_save.call_args_list if call[0][1] == "google_drive_use_oauth"]
|
||||||
call
|
|
||||||
for call in mock_save.call_args_list
|
|
||||||
if call[0][1] == "google_drive_use_oauth"
|
|
||||||
]
|
|
||||||
assert len(use_oauth_calls) == 1
|
assert len(use_oauth_calls) == 1
|
||||||
assert use_oauth_calls[0][0][2] in ("true", "false")
|
assert use_oauth_calls[0][0][2] in ("true", "false")
|
||||||
|
|
||||||
@@ -289,9 +271,7 @@ class TestOneDriveSaveSettingsDbPersist:
|
|||||||
@patch("app.api.onedrive.settings")
|
@patch("app.api.onedrive.settings")
|
||||||
@patch("app.api.onedrive.notify_settings_updated")
|
@patch("app.api.onedrive.notify_settings_updated")
|
||||||
@patch("app.api.onedrive.save_setting_to_db")
|
@patch("app.api.onedrive.save_setting_to_db")
|
||||||
def test_db_written_even_without_env_file(
|
def test_db_written_even_without_env_file(self, mock_save, mock_notify, mock_settings, client):
|
||||||
self, mock_save, mock_notify, mock_settings, client
|
|
||||||
):
|
|
||||||
"""Test that DB is written even when .env file does not exist."""
|
"""Test that DB is written even when .env file does not exist."""
|
||||||
mock_save.return_value = True
|
mock_save.return_value = True
|
||||||
|
|
||||||
@@ -435,9 +415,7 @@ class TestExportEnvEndpoint:
|
|||||||
mock_request = MagicMock()
|
mock_request = MagicMock()
|
||||||
mock_admin = {"id": "admin", "is_admin": True}
|
mock_admin = {"id": "admin", "is_admin": True}
|
||||||
|
|
||||||
result = asyncio.run(
|
result = asyncio.run(export_env_settings(mock_request, db_session, mock_admin, source="db"))
|
||||||
export_env_settings(mock_request, db_session, mock_admin, source="db")
|
|
||||||
)
|
|
||||||
assert result.media_type == "text/plain"
|
assert result.media_type == "text/plain"
|
||||||
|
|
||||||
def test_content_disposition_header(self, db_session):
|
def test_content_disposition_header(self, db_session):
|
||||||
@@ -449,9 +427,7 @@ class TestExportEnvEndpoint:
|
|||||||
mock_request = MagicMock()
|
mock_request = MagicMock()
|
||||||
mock_admin = {"id": "admin", "is_admin": True}
|
mock_admin = {"id": "admin", "is_admin": True}
|
||||||
|
|
||||||
result = asyncio.run(
|
result = asyncio.run(export_env_settings(mock_request, db_session, mock_admin, source="db"))
|
||||||
export_env_settings(mock_request, db_session, mock_admin, source="db")
|
|
||||||
)
|
|
||||||
cd = result.headers.get("content-disposition", "")
|
cd = result.headers.get("content-disposition", "")
|
||||||
assert "attachment" in cd
|
assert "attachment" in cd
|
||||||
assert ".env" in cd
|
assert ".env" in cd
|
||||||
@@ -468,11 +444,7 @@ class TestExportEnvEndpoint:
|
|||||||
mock_admin = {"id": "admin", "is_admin": True}
|
mock_admin = {"id": "admin", "is_admin": True}
|
||||||
|
|
||||||
with pytest.raises(HTTPException) as exc_info:
|
with pytest.raises(HTTPException) as exc_info:
|
||||||
asyncio.run(
|
asyncio.run(export_env_settings(mock_request, db_session, mock_admin, source="invalid"))
|
||||||
export_env_settings(
|
|
||||||
mock_request, db_session, mock_admin, source="invalid"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assert exc_info.value.status_code == 400
|
assert exc_info.value.status_code == 400
|
||||||
|
|
||||||
def test_default_source_is_db(self, db_session):
|
def test_default_source_is_db(self, db_session):
|
||||||
@@ -497,11 +469,7 @@ class TestExportEnvEndpoint:
|
|||||||
mock_request = MagicMock()
|
mock_request = MagicMock()
|
||||||
mock_admin = {"id": "admin", "is_admin": True}
|
mock_admin = {"id": "admin", "is_admin": True}
|
||||||
|
|
||||||
result = asyncio.run(
|
result = asyncio.run(export_env_settings(mock_request, db_session, mock_admin, source="effective"))
|
||||||
export_env_settings(
|
|
||||||
mock_request, db_session, mock_admin, source="effective"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assert result.media_type == "text/plain"
|
assert result.media_type == "text/plain"
|
||||||
|
|
||||||
def test_output_contains_docuelevate_header(self, db_session):
|
def test_output_contains_docuelevate_header(self, db_session):
|
||||||
@@ -513,7 +481,5 @@ class TestExportEnvEndpoint:
|
|||||||
mock_request = MagicMock()
|
mock_request = MagicMock()
|
||||||
mock_admin = {"id": "admin", "is_admin": True}
|
mock_admin = {"id": "admin", "is_admin": True}
|
||||||
|
|
||||||
result = asyncio.run(
|
result = asyncio.run(export_env_settings(mock_request, db_session, mock_admin, source="db"))
|
||||||
export_env_settings(mock_request, db_session, mock_admin, source="db")
|
|
||||||
)
|
|
||||||
assert b"DocuElevate" in result.body
|
assert b"DocuElevate" in result.body
|
||||||
|
|||||||
Reference in New Issue
Block a user