Merge pull request #832 from christianlouis/copilot/fix-save-settings-env-not-found
This commit is contained in:
+12
-10
@@ -414,9 +414,10 @@ async def save_google_drive_settings(
|
||||
if folder_id:
|
||||
drive_settings["GOOGLE_DRIVE_FOLDER_ID"] = folder_id
|
||||
|
||||
# Try to update the .env file, but don't fail if it doesn't exist (for Docker containers)
|
||||
if os.path.exists(env_path):
|
||||
try:
|
||||
# Best-effort .env file write — failures here are non-fatal
|
||||
env_file_written = False
|
||||
try:
|
||||
if os.path.exists(env_path):
|
||||
logger.info(f"Updating Google Drive settings in {env_path}")
|
||||
|
||||
# Read the current .env file
|
||||
@@ -449,12 +450,13 @@ async def save_google_drive_settings(
|
||||
f.write("\n".join(new_env_lines) + "\n")
|
||||
|
||||
logger.info("Successfully updated Google Drive settings in .env file")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to update .env file: {str(e)}, but will continue with in-memory update")
|
||||
else:
|
||||
logger.warning(
|
||||
f".env file not found at {env_path}, skipping file update but continuing with in-memory update"
|
||||
)
|
||||
env_file_written = True
|
||||
else:
|
||||
logger.warning(
|
||||
f".env file not found at {env_path}, skipping file update but continuing with in-memory update"
|
||||
)
|
||||
except Exception as env_err:
|
||||
logger.warning(f"Failed to write .env file (non-fatal): {env_err}")
|
||||
|
||||
# Update the settings in memory (this always happens)
|
||||
if refresh_token:
|
||||
@@ -492,7 +494,7 @@ async def save_google_drive_settings(
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Google Drive settings have been saved",
|
||||
"in_memory_only": not os.path.exists(env_path),
|
||||
"in_memory_only": not env_file_written,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -267,6 +267,15 @@ class TestTestDropboxToken:
|
||||
class TestSaveDropboxSettings:
|
||||
"""Tests for save_dropbox_settings endpoint."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _admin_override(self):
|
||||
from app.api.dropbox import _require_admin
|
||||
from app.main import app as fastapi_app
|
||||
|
||||
fastapi_app.dependency_overrides[_require_admin] = lambda: {"is_admin": True}
|
||||
yield
|
||||
fastapi_app.dependency_overrides.pop(_require_admin, None)
|
||||
|
||||
@patch("app.api.dropbox.settings")
|
||||
def test_save_settings_env_not_found(self, mock_settings, client):
|
||||
"""Test that missing .env file is non-fatal — DB write still succeeds."""
|
||||
|
||||
@@ -490,12 +490,11 @@ class TestSaveGoogleDriveSettings:
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("os.path.exists")
|
||||
@patch("os.path.dirname")
|
||||
@patch("app.api.google_drive.os")
|
||||
@patch("app.config.settings")
|
||||
def test_save_settings_exception_handling(self, mock_settings, mock_dirname, mock_exists, client: TestClient):
|
||||
def test_save_settings_exception_handling(self, mock_settings, mock_os, client: TestClient):
|
||||
"""Test that exceptions in .env write are non-fatal — DB write still succeeds."""
|
||||
mock_exists.side_effect = Exception("Unexpected error")
|
||||
mock_os.path.exists.side_effect = Exception("Unexpected error")
|
||||
|
||||
response = client.post("/api/google-drive/save-settings", data={"refresh_token": "token", "use_oauth": "true"})
|
||||
|
||||
|
||||
@@ -342,6 +342,15 @@ class TestFormatTimeRemaining:
|
||||
class TestSaveOneDriveSettings:
|
||||
"""Tests for POST /onedrive/save-settings endpoint."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _admin_override(self):
|
||||
from app.api.onedrive import _require_admin
|
||||
from app.main import app as fastapi_app
|
||||
|
||||
fastapi_app.dependency_overrides[_require_admin] = lambda: {"is_admin": True}
|
||||
yield
|
||||
fastapi_app.dependency_overrides.pop(_require_admin, None)
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="# Existing config\n")
|
||||
@patch("os.path.exists")
|
||||
@patch("os.path.dirname")
|
||||
|
||||
@@ -294,6 +294,15 @@ class TestTokenRotationEnvAppendLine:
|
||||
class TestSaveSettingsException:
|
||||
"""Cover lines 324-326: save_onedrive_settings outer exception handler."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _admin_override(self):
|
||||
from app.api.onedrive import _require_admin
|
||||
from app.main import app as fastapi_app
|
||||
|
||||
fastapi_app.dependency_overrides[_require_admin] = lambda: {"is_admin": True}
|
||||
yield
|
||||
fastapi_app.dependency_overrides.pop(_require_admin, None)
|
||||
|
||||
def test_save_settings_outer_exception(self, client: TestClient):
|
||||
"""Trigger the outer exception handler in save_onedrive_settings."""
|
||||
with patch("app.api.onedrive.notify_settings_updated", side_effect=Exception("Unexpected boom")):
|
||||
|
||||
@@ -57,7 +57,7 @@ class TestFileViewPdfJs:
|
||||
pdf.write_bytes(b"%PDF-1.4 test")
|
||||
rec = _create_file_record(db_session, file_path=str(pdf), mime_type="application/pdf")
|
||||
|
||||
response = client.get(f"/files/{rec.id}")
|
||||
response = client.get(f"/files/{rec.id}/detail")
|
||||
assert response.status_code == 200
|
||||
html = response.text
|
||||
|
||||
@@ -80,7 +80,7 @@ class TestFileViewPdfJs:
|
||||
pdf.write_bytes(b"%PDF-1.4 test")
|
||||
rec = _create_file_record(db_session, file_path=str(pdf), mime_type="application/pdf")
|
||||
|
||||
response = client.get(f"/files/{rec.id}")
|
||||
response = client.get(f"/files/{rec.id}/detail")
|
||||
html = response.text
|
||||
|
||||
# The preview section should use pdf-viewer, not iframe
|
||||
@@ -93,7 +93,7 @@ class TestFileViewPdfJs:
|
||||
pdf.write_bytes(b"%PDF-1.4 test")
|
||||
rec = _create_file_record(db_session, file_path=str(pdf), mime_type="application/pdf")
|
||||
|
||||
response = client.get(f"/files/{rec.id}")
|
||||
response = client.get(f"/files/{rec.id}/detail")
|
||||
html = response.text
|
||||
|
||||
assert 'id="pdf-prev-btn"' in html
|
||||
@@ -116,7 +116,7 @@ class TestFileViewImagePreview:
|
||||
img.write_bytes(b"\xff\xd8\xff\xe0" + b"\x00" * 100) # minimal JPEG header
|
||||
rec = _create_file_record(db_session, filename="photo.jpg", mime_type="image/jpeg", file_path=str(img))
|
||||
|
||||
response = client.get(f"/files/{rec.id}")
|
||||
response = client.get(f"/files/{rec.id}/detail")
|
||||
assert response.status_code == 200
|
||||
html = response.text
|
||||
|
||||
@@ -132,7 +132,7 @@ class TestFileViewImagePreview:
|
||||
img.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 50)
|
||||
rec = _create_file_record(db_session, filename="photo.png", mime_type="image/png", file_path=str(img))
|
||||
|
||||
response = client.get(f"/files/{rec.id}")
|
||||
response = client.get(f"/files/{rec.id}/detail")
|
||||
html = response.text
|
||||
|
||||
assert 'aria-label="Zoom in"' in html
|
||||
@@ -146,7 +146,7 @@ class TestFileViewImagePreview:
|
||||
img.write_bytes(b"RIFF" + b"\x00" * 50)
|
||||
rec = _create_file_record(db_session, filename="wide.webp", mime_type="image/webp", file_path=str(img))
|
||||
|
||||
response = client.get(f"/files/{rec.id}")
|
||||
response = client.get(f"/files/{rec.id}/detail")
|
||||
html = response.text
|
||||
|
||||
# Pan support is implemented via JavaScript on img-wrap
|
||||
@@ -170,7 +170,7 @@ class TestFileViewTextPreview:
|
||||
txt.write_text("Hello world\nSecond line\n")
|
||||
rec = _create_file_record(db_session, filename="readme.txt", mime_type="text/plain", file_path=str(txt))
|
||||
|
||||
response = client.get(f"/files/{rec.id}")
|
||||
response = client.get(f"/files/{rec.id}/detail")
|
||||
assert response.status_code == 200
|
||||
html = response.text
|
||||
|
||||
@@ -184,7 +184,7 @@ class TestFileViewTextPreview:
|
||||
txt.write_text("print('hello')\n")
|
||||
rec = _create_file_record(db_session, filename="code.py", mime_type="text/x-python", file_path=str(txt))
|
||||
|
||||
response = client.get(f"/files/{rec.id}")
|
||||
response = client.get(f"/files/{rec.id}/detail")
|
||||
html = response.text
|
||||
|
||||
assert "copyTextPreview" in html
|
||||
@@ -196,7 +196,7 @@ class TestFileViewTextPreview:
|
||||
txt.write_text("a,b,c\n1,2,3\n")
|
||||
rec = _create_file_record(db_session, filename="data.csv", mime_type="text/csv", file_path=str(txt))
|
||||
|
||||
response = client.get(f"/files/{rec.id}")
|
||||
response = client.get(f"/files/{rec.id}/detail")
|
||||
html = response.text
|
||||
|
||||
# JS builds line-number spans
|
||||
@@ -218,7 +218,7 @@ class TestFileViewPreviewIcon:
|
||||
pdf.write_bytes(b"%PDF-1.4")
|
||||
rec = _create_file_record(db_session, file_path=str(pdf), mime_type="application/pdf")
|
||||
|
||||
html = client.get(f"/files/{rec.id}").text
|
||||
html = client.get(f"/files/{rec.id}/detail").text
|
||||
assert "fa-file-pdf" in html
|
||||
|
||||
def test_image_icon(self, client: TestClient, db_session, tmp_path):
|
||||
@@ -227,7 +227,7 @@ class TestFileViewPreviewIcon:
|
||||
img.write_bytes(b"\xff\xd8\xff\xe0" + b"\x00" * 10)
|
||||
rec = _create_file_record(db_session, filename="p.jpg", mime_type="image/jpeg", file_path=str(img))
|
||||
|
||||
html = client.get(f"/files/{rec.id}").text
|
||||
html = client.get(f"/files/{rec.id}/detail").text
|
||||
assert "fa-image" in html
|
||||
|
||||
def test_text_icon(self, client: TestClient, db_session, tmp_path):
|
||||
@@ -236,7 +236,7 @@ class TestFileViewPreviewIcon:
|
||||
txt.write_text("hello")
|
||||
rec = _create_file_record(db_session, filename="t.txt", mime_type="text/plain", file_path=str(txt))
|
||||
|
||||
html = client.get(f"/files/{rec.id}").text
|
||||
html = client.get(f"/files/{rec.id}/detail").text
|
||||
assert "fa-file-code" in html
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@ class TestFileDetailBottomPreview:
|
||||
pdf.write_bytes(b"%PDF-1.4")
|
||||
rec = _create_file_record(db_session, file_path=str(pdf), processed_path=str(pdf))
|
||||
|
||||
response = client.get(f"/files/{rec.id}/detail")
|
||||
response = client.get(f"/files/{rec.id}/process")
|
||||
assert response.status_code == 200
|
||||
html = response.text
|
||||
|
||||
@@ -372,7 +372,7 @@ class TestFileViewOcrText:
|
||||
rec.ocr_text = "Sample extracted OCR text content"
|
||||
db_session.commit()
|
||||
|
||||
html = client.get(f"/files/{rec.id}").text
|
||||
html = client.get(f"/files/{rec.id}/detail").text
|
||||
assert "toggleOcrText" in html
|
||||
assert "ocr-text-block" in html
|
||||
assert "Sample extracted OCR text content" in html
|
||||
@@ -383,7 +383,7 @@ class TestFileViewOcrText:
|
||||
pdf.write_bytes(b"%PDF-1.4")
|
||||
rec = _create_file_record(db_session, file_path=str(pdf))
|
||||
|
||||
html = client.get(f"/files/{rec.id}").text
|
||||
html = client.get(f"/files/{rec.id}/detail").text
|
||||
assert "loadText" in html or "Extract" in html
|
||||
|
||||
|
||||
@@ -409,5 +409,5 @@ class TestFileViewNoFile:
|
||||
db_session.commit()
|
||||
db_session.refresh(rec)
|
||||
|
||||
html = client.get(f"/files/{rec.id}").text
|
||||
html = client.get(f"/files/{rec.id}/detail").text
|
||||
assert "No file available for preview" in html
|
||||
|
||||
@@ -447,7 +447,7 @@ class TestFileDetailView:
|
||||
db_session.commit()
|
||||
|
||||
# Test detail view
|
||||
response = client.get(f"/files/{file_record.id}/detail")
|
||||
response = client.get(f"/files/{file_record.id}/process")
|
||||
assert response.status_code == 200
|
||||
# Check that response contains HTML with file information
|
||||
assert b"File Information" in response.content
|
||||
@@ -504,7 +504,7 @@ class TestFileDetailView:
|
||||
db_session.commit()
|
||||
|
||||
# Test detail view
|
||||
response = client.get(f"/files/{file_record.id}/detail")
|
||||
response = client.get(f"/files/{file_record.id}/process")
|
||||
assert response.status_code == 200
|
||||
# Check that response contains branching visualization elements
|
||||
assert b"Process Flow Visualization" in response.content
|
||||
|
||||
@@ -50,7 +50,7 @@ def test_file_detail_page_with_metadata(client: TestClient, db_session, sample_p
|
||||
db_session.refresh(file_record)
|
||||
|
||||
# Get detail page
|
||||
response = client.get(f"/files/{file_record.id}/detail")
|
||||
response = client.get(f"/files/{file_record.id}/process")
|
||||
assert response.status_code == 200
|
||||
|
||||
html = response.text
|
||||
@@ -85,7 +85,7 @@ def test_file_detail_with_gpt_metadata(client: TestClient, db_session, sample_pd
|
||||
db_session.refresh(file_record)
|
||||
|
||||
# Get detail page
|
||||
response = client.get(f"/files/{file_record.id}/detail")
|
||||
response = client.get(f"/files/{file_record.id}/process")
|
||||
assert response.status_code == 200
|
||||
|
||||
html = response.text
|
||||
@@ -190,7 +190,7 @@ def test_file_detail_shows_file_status_indicators(client: TestClient, db_session
|
||||
db_session.commit()
|
||||
db_session.refresh(file_record)
|
||||
|
||||
response = client.get(f"/files/{file_record.id}/detail")
|
||||
response = client.get(f"/files/{file_record.id}/process")
|
||||
assert response.status_code == 200
|
||||
|
||||
html = response.text
|
||||
|
||||
@@ -193,7 +193,7 @@ class TestFileDetailPage:
|
||||
db_session.commit()
|
||||
|
||||
# Test file detail page
|
||||
response = client.get(f"/files/{file_record.id}/detail")
|
||||
response = client.get(f"/files/{file_record.id}/process")
|
||||
assert response.status_code == 200
|
||||
content = response.text
|
||||
assert "create_file_record" in content
|
||||
|
||||
Reference in New Issue
Block a user