Browse Source

[Core] Refactor the URL pattern to allow special characters in the table and column names. (#3866)

Ayush Goyal 1 year ago
parent
commit
7f6aab62aa

+ 119 - 11
desktop/core/src/desktop/api_public_tests.py

@@ -16,24 +16,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import json
-import pytest
 import sys
+import json
+from unittest.mock import MagicMock, Mock, patch
 
+import pytest
 from django.http import HttpResponse
 from django.urls import reverse
 
-
-
-from useradmin.models import User
-from desktop.conf import CUSTOM
 from desktop.api_public import get_django_request
+from desktop.conf import CUSTOM
 from desktop.lib.django_test_util import Client, make_logged_in_client
-
-if sys.version_info[0] > 2:
-  from unittest.mock import patch, Mock, MagicMock
-else:
-  from mock import patch, Mock, MagicMock
+from useradmin.models import User
 
 
 @pytest.mark.django_db
@@ -103,3 +97,117 @@ class TestEditorApi():
     django_request = get_django_request(request)
 
     assert hasattr(django_request.user, 'has_hue_permission')
+
+
+@pytest.mark.django_db
+class TestPublicURLPatterns():
+
+  def setup_method(self):
+    self.client = make_logged_in_client(username="api_user", recreate=True, is_superuser=False)
+    self.client_not_me = make_logged_in_client(username="not_api_user", recreate=True, is_superuser=False)
+
+    self.user = User.objects.get(username="api_user")
+    self.user_not_me = User.objects.get(username="not_api_user")
+
+  def test_autocomplete_databases(self):
+    """
+    Test the autocomplete URL for databases
+    """
+    response = self.client.post('/api/v1/editor/autocomplete/')
+    assert response.status_code == 200
+
+  def test_autocomplete_tables(self):
+    """
+    Test the autocomplete URL for tables in a specific database
+    """
+    # Test with a valid database name
+    response = self.client.post('/api/v1/editor/autocomplete/test_db')
+    assert response.status_code == 200
+
+    # Test with a special character in the database name
+    response = self.client.post('/api/v1/editor/autocomplete/test_db:-;test')
+    assert response.status_code == 200
+
+  def test_autocomplete_columns(self):
+    """
+    Test the autocomplete URL for columns in a specific table within a database
+    """
+    # Test with valid database and table names
+    response = self.client.post('/api/v1/editor/autocomplete/test_db/test_table')
+    assert response.status_code == 200
+
+    # Test with special characters in the database and table names
+    response = self.client.post('/api/v1/editor/autocomplete/test_db:-$@test/test_table:-$@test')
+    assert response.status_code == 200
+
+  def test_describe_database(self):
+    """
+    Test the describe URL for a specific database
+    """
+    # Test with a valid database name
+    response = self.client.post('/api/v1/editor/describe/test_db/')
+    assert response.status_code == 200
+
+    # Test with a special character in the database name
+    response = self.client.post('/api/v1/editor/describe/test_db:-$@test/')
+    assert response.status_code == 200
+
+  def test_describe_table(self):
+    """
+    Test the describe URL for a specific table in a database
+    """
+    # Test with valid database and table names
+    response = self.client.post('/api/v1/editor/describe/test_db/test_table/')
+    assert response.status_code == 200
+
+    # Test with special characters in the database and table names
+    response = self.client.post('/api/v1/editor/describe/test_db:-$@test/test_table:-$@test/')
+    assert response.status_code == 200
+
+  def test_describe_column(self):
+    """
+    Test the describe URL for a specific column in a table within a database
+    """
+    # Test with valid database, table, and column names
+    response = self.client.post('/api/v1/editor/describe/test_db/test_table/stats/test_column/')
+    assert response.status_code == 200
+
+    # Test with special characters in the database, table, and column names
+    response = self.client.post('/api/v1/editor/describe/test_db:-$@test/test_table:-$@test/stats/test_column:-$@test/')
+    assert response.status_code == 200
+
+  def test_sample_data_for_table(self):
+    """
+    Test the sample data URL for a specific table in a database
+    """
+    # Test with valid database and table names
+    response = self.client.post('/api/v1/editor/sample/test_db/test_table/')
+    assert response.status_code == 200
+
+    # Test with special characters in the database and table names
+    response = self.client.post('/api/v1/editor/sample/test_db:-$@test/test_table:-$@test/')
+    assert response.status_code == 200
+
+  def test_sample_data_for_column(self):
+    """
+    Test the sample data URL for a specific column in a table within a database
+    """
+    # Test with valid database, table, and column names
+    response = self.client.post('/api/v1/editor/sample/test_db/test_table/test_column/')
+    assert response.status_code == 200
+
+    # Test with special characters in the database, table, and column names
+    response = self.client.post('/api/v1/editor/sample/test_db:-$@test/test_table:-$@test/test_column:-$@test/')
+    assert response.status_code == 200
+
+  def test_sample_data_for_nested(self):
+    """
+    Test the sample data URL for a specific nested field in a column of a table within a database
+    """
+    # Test with valid database, table, column, and nested field
+    response = self.client.post('/api/v1/editor/sample/test_db/test_table/test_column/test_nested/')
+    assert response.status_code == 200
+
+    # Test with special characters in the database, table, column, and nested field names
+    response = self.client.post('/api/v1/editor/sample/test_db:-$@test/test_table:-$@test/test_column:-$@test/test_nested:-$@test/')
+    assert response.status_code == 200

+ 11 - 9
desktop/core/src/desktop/api_public_urls_v1.py

@@ -54,10 +54,12 @@ urlpatterns += [
   re_path(r'^editor/close_statement/?$', api_public.close_statement, name='editor_close_statement'),
   re_path(r'^editor/get_logs/?$', api_public.get_logs, name='editor_get_logs'),
   re_path(r'^editor/get_history/?', api_public.get_history, name='editor_get_history'),
-  re_path(r'^editor/describe/(?P<database>[^/]*)/?$', api_public.describe, name='editor_describe_database'),
-  re_path(r'^editor/describe/(?P<database>[^/]*)/(?P<table>[\w_\-]+)/?$', api_public.describe, name='editor_describe_table'),
+  re_path(r'^editor/describe/(?P<database>[^/?]*)/?$', api_public.describe, name='editor_describe_database'),
+  re_path(r'^editor/describe/(?P<database>[^/?]*)/(?P<table>[^/?]+)/?$', api_public.describe, name='editor_describe_table'),
   re_path(
-    r'^editor/describe/(?P<database>[^/]*)/(?P<table>\w+)/stats(?:/(?P<column>\w+))?/?$', api_public.describe, name='editor_describe_column'
+    r'^editor/describe/(?P<database>[^/?]*)/(?P<table>[^/?]+)/stats(?:/(?P<column>[^/?]+))?/?$',
+    api_public.describe,
+    name='editor_describe_column'
   ),
   re_path(r'^editor/autocomplete/?$', api_public.autocomplete, name='editor_autocomplete_databases'),
   re_path(
@@ -66,28 +68,28 @@ urlpatterns += [
     name="editor_autocomplete_tables",
   ),
   re_path(
-    r"^editor/autocomplete/(?P<database>[^/?]*)/(?P<table>[\w_\-]+)/?$",
+    r"^editor/autocomplete/(?P<database>[^/?]*)/(?P<table>[^/?]+)/?$",
     api_public.autocomplete,
     name="editor_autocomplete_columns",
   ),
   re_path(
-    r"^editor/autocomplete/(?P<database>[^/?]*)/(?P<table>[\w_\-]+)/(?P<column>\w+)/?$",
+    r"^editor/autocomplete/(?P<database>[^/?]*)/(?P<table>[^/?]+)/(?P<column>[^/?]+)/?$",
     api_public.autocomplete,
     name="editor_autocomplete_column",
   ),
   re_path(
-    r"^editor/autocomplete/(?P<database>[^/?]*)/(?P<table>[\w_\-]+)/(?P<column>\w+)/(?P<nested>.+)/?$",
+    r"^editor/autocomplete/(?P<database>[^/?]*)/(?P<table>[^/?]+)/(?P<column>[^/?]+)/(?P<nested>.+)/?$",
     api_public.autocomplete,
     name="editor_autocomplete_nested",
   ),
-  re_path(r'^editor/sample/(?P<database>[^/?]*)/(?P<table>[\w_\-]+)/?$', api_public.get_sample_data, name='editor_sample_data'),
+  re_path(r'^editor/sample/(?P<database>[^/?]*)/(?P<table>[^/?]+)/?$', api_public.get_sample_data, name='editor_sample_data'),
   re_path(
-    r'^editor/sample/(?P<database>[^/?]*)/(?P<table>[\w_\-]+)/(?P<column>\w+)/?$',
+    r'^editor/sample/(?P<database>[^/?]*)/(?P<table>[^/?]+)/(?P<column>[^/?]+)/?$',
     api_public.get_sample_data,
     name='editor_sample_data_column',
   ),
   re_path(
-      r"^editor/sample/(?P<database>[^/?]*)/(?P<table>[\w_\-]+)/(?P<column>\w+)/(?P<nested>.+)/?$",
+      r"^editor/sample/(?P<database>[^/?]*)/(?P<table>[^/?]+)/(?P<column>[^/?]+)/(?P<nested>.+)/?$",
       api_public.get_sample_data,
       name="editor_sample_data_nested",
   ),

+ 114 - 0
desktop/libs/notebook/src/notebook/api_tests.py

@@ -973,3 +973,117 @@ class TestEditor(object):
         assert 200 == response.status_code
     finally:
       doc.delete()
+
+
+@pytest.mark.django_db
+class TestPrivateURLPatterns():
+
+  def setup_method(self):
+    self.client = make_logged_in_client(username="api_user", recreate=True, is_superuser=False)
+    self.client_not_me = make_logged_in_client(username="not_api_user", recreate=True, is_superuser=False)
+
+    self.user = User.objects.get(username="api_user")
+    self.user_not_me = User.objects.get(username="not_api_user")
+
+  def test_autocomplete_databases(self):
+    """
+    Test the autocomplete URL for databases
+    """
+    response = self.client.post('/notebook/api/autocomplete/')
+    assert response.status_code == 200
+
+  def test_autocomplete_tables(self):
+    """
+    Test the autocomplete URL for tables in a specific database
+    """
+    # Test with a valid database name
+    response = self.client.post('/notebook/api/autocomplete/test_db')
+    assert response.status_code == 200
+
+    # Test with a special character in the database name
+    response = self.client.post('/notebook/api/autocomplete/test_db:-;test')
+    assert response.status_code == 200
+
+  def test_autocomplete_columns(self):
+    """
+    Test the autocomplete URL for columns in a specific table within a database
+    """
+    # Test with valid database and table names
+    response = self.client.post('/notebook/api/autocomplete/test_db/test_table')
+    assert response.status_code == 200
+
+    # Test with special characters in the database and table names
+    response = self.client.post('/notebook/api/autocomplete/test_db:-$@test/test_table:-$@test')
+    assert response.status_code == 200
+
+  def test_describe_database(self):
+    """
+    Test the describe URL for a specific database
+    """
+    # Test with a valid database name
+    response = self.client.post('/notebook/api/describe/test_db/')
+    assert response.status_code == 200
+
+    # Test with a special character in the database name
+    response = self.client.post('/notebook/api/describe/test_db:-$@test/')
+    assert response.status_code == 200
+
+  def test_describe_table(self):
+    """
+    Test the describe URL for a specific table in a database
+    """
+    # Test with valid database and table names
+    response = self.client.post('/notebook/api/describe/test_db/test_table/')
+    assert response.status_code == 200
+
+    # Test with special characters in the database and table names
+    response = self.client.post('/notebook/api/describe/test_db:-$@test/test_table:-$@test/')
+    assert response.status_code == 200
+
+  def test_describe_column(self):
+    """
+    Test the describe URL for a specific column in a table within a database
+    """
+    # Test with valid database, table, and column names
+    response = self.client.post('/notebook/api/describe/test_db/test_table/stats/test_column/')
+    assert response.status_code == 200
+
+    # Test with special characters in the database, table, and column names
+    response = self.client.post('/notebook/api/describe/test_db:-$@test/test_table:-$@test/stats/test_column:-$@test/')
+    assert response.status_code == 200
+
+  def test_sample_data_for_table(self):
+    """
+    Test the sample data URL for a specific table in a database
+    """
+    # Test with valid database and table names
+    response = self.client.post('/notebook/api/sample/test_db/test_table/')
+    assert response.status_code == 200
+
+    # Test with special characters in the database and table names
+    response = self.client.post('/notebook/api/sample/test_db:-$@test/test_table:-$@test/')
+    assert response.status_code == 200
+
+  def test_sample_data_for_column(self):
+    """
+    Test the sample data URL for a specific column in a table within a database
+    """
+    # Test with valid database, table, and column names
+    response = self.client.post('/notebook/api/sample/test_db/test_table/test_column/')
+    assert response.status_code == 200
+
+    # Test with special characters in the database, table, and column names
+    response = self.client.post('/notebook/api/sample/test_db:-$@test/test_table:-$@test/test_column:-$@test/')
+    assert response.status_code == 200
+
+  def test_sample_data_for_nested(self):
+    """
+    Test the sample data URL for a specific nested field in a column of a table within a database
+    """
+    # Test with valid database, table, column, and nested field
+    response = self.client.post('/notebook/api/sample/test_db/test_table/test_column/test_nested/')
+    assert response.status_code == 200
+
+    # Test with special characters in the database, table, column, and nested field names
+    response = self.client.post('/notebook/api/sample/test_db:-$@test/test_table:-$@test/test_column:-$@test/test_nested:-$@test/')
+    assert response.status_code == 200

+ 11 - 9
desktop/libs/notebook/src/notebook/urls.py

@@ -75,25 +75,25 @@ urlpatterns += [
   # HS2, RDBMS, JDBC
   re_path(r'^api/autocomplete/?$', notebook_api.autocomplete, name='api_autocomplete_databases'),
   re_path(r'^api/autocomplete/(?P<database>[^/?]*)/?$', notebook_api.autocomplete, name='api_autocomplete_tables'),
-  re_path(r'^api/autocomplete/(?P<database>[^/?]*)/(?P<table>[\w_\-]+)/?$', notebook_api.autocomplete, name='api_autocomplete_columns'),
+  re_path(r'^api/autocomplete/(?P<database>[^/?]*)/(?P<table>[^/?]+)/?$', notebook_api.autocomplete, name='api_autocomplete_columns'),
   re_path(
-    r'^api/autocomplete/(?P<database>[^/?]*)/(?P<table>[\w_\-]+)/(?P<column>\w+)/?$',
+    r'^api/autocomplete/(?P<database>[^/?]*)/(?P<table>[^/?]+)/(?P<column>[^/?]+)/?$',
     notebook_api.autocomplete,
     name='api_autocomplete_column'
   ),
   re_path(
-    r'^api/autocomplete/(?P<database>[^/?]*)/(?P<table>[\w_\-]+)/(?P<column>\w+)/(?P<nested>.+)/?$',
+    r'^api/autocomplete/(?P<database>[^/?]*)/(?P<table>[^/?]+)/(?P<column>[^/?]+)/(?P<nested>.+)/?$',
     notebook_api.autocomplete,
     name='api_autocomplete_nested'
   ),
-  re_path(r'^api/sample/(?P<database>[^/?]*)/(?P<table>[\w_\-]+)/?$', notebook_api.get_sample_data, name='api_sample_data'),
+  re_path(r'^api/sample/(?P<database>[^/?]*)/(?P<table>[^/?]+)/?$', notebook_api.get_sample_data, name='api_sample_data'),
   re_path(
-    r'^api/sample/(?P<database>[^/?]*)/(?P<table>[\w_\-]+)/(?P<column>\w+)/?$',
+    r'^api/sample/(?P<database>[^/?]*)/(?P<table>[^/?]+)/(?P<column>[^/?]+)/?$',
     notebook_api.get_sample_data,
     name='api_sample_data_column'
   ),
   re_path(
-    r'^api/sample/(?P<database>[^/?]*)/(?P<table>[\w_\-]+)/(?P<column>\w+)/(?P<nested>.+)/?$',
+    r'^api/sample/(?P<database>[^/?]*)/(?P<table>[^/?]+)/(?P<column>[^/?]+)/(?P<nested>.+)/?$',
     notebook_api.get_sample_data,
     name='api_sample_data_nested'
   ),
@@ -122,9 +122,11 @@ urlpatterns += [
 
 # Table API
 urlpatterns += [
-  re_path(r'^api/describe/(?P<database>[^/]*)/?$', notebook_api.describe, name='api_describe_database'),
-  re_path(r'^api/describe/(?P<database>[^/]*)/(?P<table>[\w_\-]+)/?$', notebook_api.describe, name='api_describe_table'),
+  re_path(r'^api/describe/(?P<database>[^/?]*)/?$', notebook_api.describe, name='api_describe_database'),
+  re_path(r'^api/describe/(?P<database>[^/?]*)/(?P<table>[^/?]+)/?$', notebook_api.describe, name='api_describe_table'),
   re_path(
-    r'^api/describe/(?P<database>[^/]*)/(?P<table>\w+)/stats(?:/(?P<column>\w+))?/?$', notebook_api.describe, name='api_describe_column'
+    r'^api/describe/(?P<database>[^/?]*)/(?P<table>[^/?]+)/stats(?:/(?P<column>[^/?]+))?/?$',
+    notebook_api.describe,
+    name='api_describe_column'
   ),
 ]