From 998624b1a5a498343bd7f37b5ca80402ba08e305 Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Thu, 16 Jun 2022 15:53:59 -0700 Subject: [PATCH] feat: allow setting db UUID (#20412) * WIP * feat: allow passing UUID when creating a DB * Test * Fix field --- superset/databases/schemas.py | 1 + tests/unit_tests/conftest.py | 20 +++++++ tests/unit_tests/databases/api_test.py | 53 +++++++++++++++++ tests/unit_tests/importexport/api_test.py | 70 ++++++++++------------- 4 files changed, 104 insertions(+), 40 deletions(-) create mode 100644 tests/unit_tests/databases/api_test.py diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py index cdce5578cd7d..bd30ad4ec5bd 100644 --- a/superset/databases/schemas.py +++ b/superset/databases/schemas.py @@ -397,6 +397,7 @@ class Meta: # pylint: disable=too-few-public-methods ) is_managed_externally = fields.Boolean(allow_none=True, default=False) external_url = fields.String(allow_none=True) + uuid = fields.String(required=False) class DatabasePutSchema(Schema, DatabaseParametersSchemaMixin): diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 86fb0127b84f..4560617d4bcb 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -25,6 +25,7 @@ from sqlalchemy.orm import sessionmaker from sqlalchemy.orm.session import Session +from superset import security_manager from superset.app import SupersetApp from superset.extensions import appbuilder from superset.initialization import SupersetAppInitializer @@ -69,6 +70,8 @@ def app() -> Iterator[SupersetApp]: app.config.from_object("superset.config") app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite://" + app.config["WTF_CSRF_ENABLED"] = False + app.config["PREVENT_UNSAFE_DB_CONNECTIONS"] = False app.config["TESTING"] = True # ``superset.extensions.appbuilder`` is a singleton, and won't rebuild the @@ -101,3 +104,20 @@ def app_context(app: SupersetApp) -> Iterator[None]: """ with app.app_context(): yield + + +@pytest.fixture +def full_api_access(mocker: MockFixture) -> Iterator[None]: + """ + Allow full access to the API. + + TODO (betodealmeida): we should replace this with user-fixtures, eg, ``admin`` or + ``gamma``, so that we have granular access to the APIs. + """ + mocker.patch( + "flask_appbuilder.security.decorators.verify_jwt_in_request", + return_value=True, + ) + mocker.patch.object(security_manager, "has_access", return_value=True) + + yield diff --git a/tests/unit_tests/databases/api_test.py b/tests/unit_tests/databases/api_test.py new file mode 100644 index 000000000000..f121b799fda6 --- /dev/null +++ b/tests/unit_tests/databases/api_test.py @@ -0,0 +1,53 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# pylint: disable=unused-argument, import-outside-toplevel + +from typing import Any +from uuid import UUID + +from pytest_mock import MockFixture +from sqlalchemy.orm.session import Session + + +def test_post_with_uuid( + mocker: MockFixture, + app_context: None, + session: Session, + client: Any, + full_api_access: None, +) -> None: + """ + Test that we can set the database UUID when creating it. + """ + from superset.models.core import Database + + # create table for databases + Database.metadata.create_all(session.get_bind()) # pylint: disable=no-member + + response = client.post( + "/api/v1/database/", + json={ + "database_name": "my_db", + "sqlalchemy_uri": "sqlite://", + "uuid": "7c1b7880-a59d-47cd-8bf1-f1eb8d2863cb", + }, + ) + assert response.status_code == 201 + + database = session.query(Database).one() + assert database.uuid == UUID("7c1b7880-a59d-47cd-8bf1-f1eb8d2863cb") diff --git a/tests/unit_tests/importexport/api_test.py b/tests/unit_tests/importexport/api_test.py index e5dee975d8cd..9c8c74025578 100644 --- a/tests/unit_tests/importexport/api_test.py +++ b/tests/unit_tests/importexport/api_test.py @@ -27,18 +27,16 @@ from superset import security_manager -def test_export_assets(mocker: MockFixture, client: Any) -> None: +def test_export_assets( + mocker: MockFixture, + client: Any, + full_api_access: None, +) -> None: """ Test exporting assets. """ from superset.commands.importers.v1.utils import get_contents_from_bundle - # grant access - mocker.patch( - "flask_appbuilder.security.decorators.verify_jwt_in_request", return_value=True - ) - mocker.patch.object(security_manager, "has_access", return_value=True) - mocked_contents = [ ( "metadata.yaml", @@ -62,16 +60,14 @@ def test_export_assets(mocker: MockFixture, client: Any) -> None: assert contents == dict(mocked_contents) -def test_import_assets(mocker: MockFixture, client: Any) -> None: +def test_import_assets( + mocker: MockFixture, + client: Any, + full_api_access: None, +) -> None: """ Test importing assets. """ - # grant access - mocker.patch( - "flask_appbuilder.security.decorators.verify_jwt_in_request", return_value=True - ) - mocker.patch.object(security_manager, "has_access", return_value=True) - mocked_contents = { "metadata.yaml": ( "version: 1.0.0\ntype: assets\ntimestamp: '2022-01-01T00:00:00+00:00'\n" @@ -105,16 +101,14 @@ def test_import_assets(mocker: MockFixture, client: Any) -> None: ImportAssetsCommand.assert_called_with(mocked_contents, passwords=passwords) -def test_import_assets_not_zip(mocker: MockFixture, client: Any) -> None: +def test_import_assets_not_zip( + mocker: MockFixture, + client: Any, + full_api_access: None, +) -> None: """ Test error message when the upload is not a ZIP file. """ - # grant access - mocker.patch( - "flask_appbuilder.security.decorators.verify_jwt_in_request", return_value=True - ) - mocker.patch.object(security_manager, "has_access", return_value=True) - buf = BytesIO(b"definitely_not_a_zip_file") form_data = { "bundle": (buf, "broken.txt"), @@ -145,14 +139,14 @@ def test_import_assets_not_zip(mocker: MockFixture, client: Any) -> None: } -def test_import_assets_no_form_data(mocker: MockFixture, client: Any) -> None: +def test_import_assets_no_form_data( + mocker: MockFixture, + client: Any, + full_api_access: None, +) -> None: """ Test error message when the upload has no form data. """ - # grant access - mocker.patch( - "flask_appbuilder.security.decorators.verify_jwt_in_request", return_value=True - ) mocker.patch.object(security_manager, "has_access", return_value=True) response = client.post("/api/v1/assets/import/", data="some_content") @@ -179,16 +173,14 @@ def test_import_assets_no_form_data(mocker: MockFixture, client: Any) -> None: } -def test_import_assets_incorrect_form_data(mocker: MockFixture, client: Any) -> None: +def test_import_assets_incorrect_form_data( + mocker: MockFixture, + client: Any, + full_api_access: None, +) -> None: """ Test error message when the upload form data has the wrong key. """ - # grant access - mocker.patch( - "flask_appbuilder.security.decorators.verify_jwt_in_request", return_value=True - ) - mocker.patch.object(security_manager, "has_access", return_value=True) - buf = BytesIO(b"definitely_not_a_zip_file") form_data = { "wrong": (buf, "broken.txt"), @@ -200,16 +192,14 @@ def test_import_assets_incorrect_form_data(mocker: MockFixture, client: Any) -> assert response.json == {"message": "Arguments are not correct"} -def test_import_assets_no_contents(mocker: MockFixture, client: Any) -> None: +def test_import_assets_no_contents( + mocker: MockFixture, + client: Any, + full_api_access: None, +) -> None: """ Test error message when the ZIP bundle has no contents. """ - # grant access - mocker.patch( - "flask_appbuilder.security.decorators.verify_jwt_in_request", return_value=True - ) - mocker.patch.object(security_manager, "has_access", return_value=True) - mocked_contents = { "README.txt": "Something is wrong", }