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")