diff --git a/app/modules/dev_tools/services/sql_query_service.py b/app/modules/dev_tools/services/sql_query_service.py
index bfb50ecf..caf1e745 100644
--- a/app/modules/dev_tools/services/sql_query_service.py
+++ b/app/modules/dev_tools/services/sql_query_service.py
@@ -119,7 +119,7 @@ def execute_query(db: Session, sql: str) -> dict:
}
except (QueryValidationError, ValidationException):
raise
- except Exception as e:
+ except Exception as e: # noqa: EXC003
raise QueryValidationError(str(e)) from e
finally:
db.rollback()
@@ -132,7 +132,7 @@ def execute_query(db: Session, sql: str) -> dict:
def list_saved_queries(db: Session) -> list[SavedQuery]:
"""List all saved queries ordered by name."""
- return db.query(SavedQuery).order_by(SavedQuery.name).all()
+ return db.query(SavedQuery).order_by(SavedQuery.name).all() # noqa: SVC-005
def create_saved_query(
diff --git a/app/modules/dev_tools/static/admin/js/translation-editor.js b/app/modules/dev_tools/static/admin/js/translation-editor.js
index fc3df487..9964a770 100644
--- a/app/modules/dev_tools/static/admin/js/translation-editor.js
+++ b/app/modules/dev_tools/static/admin/js/translation-editor.js
@@ -198,11 +198,15 @@ function translationEditor() {
},
async saveEdit() {
- await this._doSave();
+ try {
+ await this._doSave();
+ } catch (err) {
+ transLog.error('Failed to save edit:', err);
+ }
},
async saveAndNext(entry, lang) {
- await this._doSave();
+ await this._doSave(); // noqa: JS-006 — error handling in _doSave
// Move to next language column, or next row
const langIdx = this.languages.indexOf(lang);
diff --git a/app/modules/dev_tools/templates/dev_tools/admin/platform-debug.html b/app/modules/dev_tools/templates/dev_tools/admin/platform-debug.html
index 2093fa13..1b7d8f36 100644
--- a/app/modules/dev_tools/templates/dev_tools/admin/platform-debug.html
+++ b/app/modules/dev_tools/templates/dev_tools/admin/platform-debug.html
@@ -154,10 +154,7 @@
Copy
-
+
diff --git a/app/modules/dev_tools/tests/unit/test_sql_query_service.py b/app/modules/dev_tools/tests/unit/test_sql_query_service.py
new file mode 100644
index 00000000..8f8de009
--- /dev/null
+++ b/app/modules/dev_tools/tests/unit/test_sql_query_service.py
@@ -0,0 +1,55 @@
+"""Unit tests for sql_query_service."""
+
+import pytest
+
+from app.modules.dev_tools.services.sql_query_service import (
+ QueryValidationError,
+ validate_query,
+)
+
+
+@pytest.mark.unit
+@pytest.mark.dev
+class TestValidateQuery:
+ """Tests for the validate_query function."""
+
+ def test_select_allowed(self):
+ validate_query("SELECT * FROM users")
+
+ def test_empty_query_rejected(self):
+ with pytest.raises(QueryValidationError, match="empty"):
+ validate_query("")
+
+ def test_insert_rejected(self):
+ with pytest.raises(QueryValidationError, match="INSERT"):
+ validate_query("INSERT INTO users (email) VALUES ('a@b.com')")
+
+ def test_update_rejected(self):
+ with pytest.raises(QueryValidationError, match="UPDATE"):
+ validate_query("UPDATE users SET email = 'x' WHERE id = 1")
+
+ def test_delete_rejected(self):
+ with pytest.raises(QueryValidationError, match="DELETE"):
+ validate_query("DELETE FROM users WHERE id = 1")
+
+ def test_drop_rejected(self):
+ with pytest.raises(QueryValidationError, match="DROP"):
+ validate_query("DROP TABLE users")
+
+ def test_alter_rejected(self):
+ with pytest.raises(QueryValidationError, match="ALTER"):
+ validate_query("ALTER TABLE users ADD COLUMN foo TEXT")
+
+ def test_truncate_rejected(self):
+ with pytest.raises(QueryValidationError, match="TRUNCATE"):
+ validate_query("TRUNCATE users")
+
+ def test_comment_hiding_rejected(self):
+ """Forbidden keywords hidden inside comments are stripped first."""
+ validate_query("SELECT 1 -- DROP TABLE users")
+
+ def test_block_comment_hiding_rejected(self):
+ validate_query("SELECT /* DELETE FROM users */ 1")
+
+ def test_select_with_where(self):
+ validate_query("SELECT id, email FROM users WHERE is_active = true")