# 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. # isort:skip_file """Unit tests for Superset""" import pytest import prison from datetime import datetime from freezegun import freeze_time from sqlalchemy.sql import func import tests.integration_tests.test_app # noqa: F401 from superset import db from superset.models.core import Theme from superset.utils.database import get_example_database # noqa: F401 from superset.utils import json from tests.integration_tests.base_tests import SupersetTestCase from tests.integration_tests.constants import ADMIN_USERNAME THEMES_FIXTURE_COUNT = 3 class TestThemeApi(SupersetTestCase): def insert_theme( self, theme_name: str, json_data: str, created_by_username: str = "admin", ) -> Theme: admin = self.get_user(created_by_username) theme = Theme( theme_name=theme_name, json_data=json_data, created_by=admin, changed_by=admin, ) db.session.add(theme) db.session.commit() return theme @pytest.fixture def create_themes(self): with self.create_app().app_context(): themes = [] for cx in range(THEMES_FIXTURE_COUNT): themes.append( self.insert_theme( theme_name=f"theme_name{cx}", json_data=f'{{"color": "theme{cx}"}}', ) ) yield themes # rollback changes for theme in themes: db.session.delete(theme) db.session.commit() @pytest.mark.usefixtures("create_themes") def test_get_list_theme(self): """ Theme API: Test get list themes """ themes = db.session.query(Theme).all() self.login(ADMIN_USERNAME) uri = "api/v1/theme/" rv = self.get_assert_metric(uri, "get_list") assert rv.status_code == 200 data = json.loads(rv.data.decode("utf-8")) assert data["count"] == len(themes) expected_columns = [ "changed_by", "changed_by_name", "changed_on_delta_humanized", "created_by", "created_on", "id", "is_system", "is_system_default", "is_system_dark", "json_data", "theme_name", "uuid", ] result_columns = list(data["result"][0].keys()) assert set(expected_columns) == set(result_columns) @pytest.mark.usefixtures("create_themes") def test_get_list_sort_theme(self): """ Theme API: Test get list and sort themes """ themes = db.session.query(Theme).order_by(Theme.theme_name.asc()).all() self.login(ADMIN_USERNAME) query_string = {"order_column": "theme_name", "order_direction": "asc"} uri = f"api/v1/theme/?q={prison.dumps(query_string)}" rv = self.get_assert_metric(uri, "get_list") assert rv.status_code == 200 data = json.loads(rv.data.decode("utf-8")) assert data["count"] == len(themes) for i, theme in enumerate(themes): assert theme.theme_name == data["result"][i]["theme_name"] @pytest.mark.usefixtures("create_themes") def test_get_list_custom_filter_theme(self): """ Theme API: Test get list and custom filter """ self.login(ADMIN_USERNAME) # Test filtering by JSON data all_themes = ( db.session.query(Theme).filter(Theme.json_data.ilike("%theme1%")).all() ) query_string = { "filters": [ { "col": "theme_name", "opr": "theme_all_text", "value": "theme1", } ], } uri = f"api/v1/theme/?q={prison.dumps(query_string)}" rv = self.get_assert_metric(uri, "get_list") assert rv.status_code == 200 data = json.loads(rv.data.decode("utf-8")) assert data["count"] == len(all_themes) # Test filtering by theme name all_themes = ( db.session.query(Theme) .filter(Theme.theme_name.ilike("%theme_name2%")) .all() ) query_string = { "filters": [ { "col": "theme_name", "opr": "theme_all_text", "value": "theme_name2", } ], } uri = f"api/v1/theme/?q={prison.dumps(query_string)}" rv = self.get_assert_metric(uri, "get_list") assert rv.status_code == 200 data = json.loads(rv.data.decode("utf-8")) assert data["count"] == len(all_themes) def test_info_theme(self): """ Theme API: Test info """ self.login(ADMIN_USERNAME) uri = "api/v1/theme/_info" rv = self.get_assert_metric(uri, "info") assert rv.status_code == 200 def test_info_security_theme(self): """ Theme API: Test info security """ self.login(ADMIN_USERNAME) params = {"keys": ["permissions"]} uri = f"api/v1/theme/_info?q={prison.dumps(params)}" rv = self.get_assert_metric(uri, "info") data = json.loads(rv.data.decode("utf-8")) assert rv.status_code == 200 assert "can_read" in data["permissions"] assert "can_write" in data["permissions"] assert len(data["permissions"]) == 3 @pytest.mark.usefixtures("create_themes") def test_get_theme(self): """ Theme API: Test get theme """ with freeze_time(datetime.now()): theme = ( db.session.query(Theme) .filter(Theme.theme_name == "theme_name1") .one_or_none() ) self.login(ADMIN_USERNAME) uri = f"api/v1/theme/{theme.id}" rv = self.get_assert_metric(uri, "get") assert rv.status_code == 200 expected_result = { "id": theme.id, "theme_name": "theme_name1", "json_data": '{"color": "theme1"}', "is_system": False, "is_system_default": False, "is_system_dark": False, "uuid": str(theme.uuid), "changed_by": { "first_name": theme.created_by.first_name, "id": theme.created_by.id, "last_name": theme.created_by.last_name, }, "changed_on_delta_humanized": "now", "created_by": { "first_name": theme.created_by.first_name, "id": theme.created_by.id, "last_name": theme.created_by.last_name, }, } data = json.loads(rv.data.decode("utf-8")) for key, value in data["result"].items(): assert value == expected_result[key] @pytest.mark.usefixtures("create_themes") def test_get_theme_not_found(self): """ Theme API: Test get theme not found """ max_id = db.session.query(func.max(Theme.id)).scalar() self.login(ADMIN_USERNAME) uri = f"api/v1/theme/{max_id + 1}" rv = self.get_assert_metric(uri, "get") assert rv.status_code == 404 def test_create_theme(self): """ Theme API: Test create """ post_data = { "theme_name": "test_theme", "json_data": '{"primary": "#007bff", "secondary": "#6c757d"}', } self.login(ADMIN_USERNAME) uri = "api/v1/theme/" rv = self.post_assert_metric(uri, post_data, "post") data = json.loads(rv.data.decode("utf-8")) assert rv.status_code == 201 theme_id = data.get("id") model = db.session.query(Theme).get(theme_id) for key in post_data: assert getattr(model, key) == data["result"][key] # Rollback changes db.session.delete(model) db.session.commit() @pytest.mark.usefixtures("create_themes") def test_update_theme(self): """ Theme API: Test update """ theme = ( db.session.query(Theme).filter(Theme.theme_name == "theme_name1").all()[0] ) put_data = { "theme_name": "updated_theme_name", "json_data": '{"primary": "#28a745", "secondary": "#ffc107"}', } self.login(ADMIN_USERNAME) uri = f"api/v1/theme/{theme.id}" rv = self.put_assert_metric(uri, put_data, "put") assert rv.status_code == 200 model = db.session.query(Theme).get(theme.id) assert model.theme_name == "updated_theme_name" assert model.json_data == '{"primary": "#28a745", "secondary": "#ffc107"}' @pytest.mark.usefixtures("create_themes") def test_update_theme_not_found(self): """ Theme API: Test update not found """ max_id = db.session.query(func.max(Theme.id)).scalar() self.login(ADMIN_USERNAME) put_data = { "theme_name": "updated_theme_name", "json_data": '{"primary": "#28a745"}', } uri = f"api/v1/theme/{max_id + 1}" rv = self.put_assert_metric(uri, put_data, "put") assert rv.status_code == 404 @pytest.mark.usefixtures("create_themes") @pytest.mark.skip( "DELETE endpoint not properly registered due to route method exclusion" ) def test_delete_theme(self): """ Theme API: Test delete """ theme = ( db.session.query(Theme) .filter(Theme.theme_name == "theme_name1") .one_or_none() ) self.login(ADMIN_USERNAME) uri = f"api/v1/theme/{theme.id}" rv = self.delete_assert_metric(uri, "delete") assert rv.status_code == 200 model = db.session.query(Theme).get(theme.id) assert model is None @pytest.mark.usefixtures("create_themes") @pytest.mark.skip( "DELETE endpoint not properly registered due to route method exclusion" ) def test_delete_theme_not_found(self): """ Theme API: Test delete not found """ max_id = db.session.query(func.max(Theme.id)).scalar() self.login(ADMIN_USERNAME) uri = f"api/v1/theme/{max_id + 1}" rv = self.delete_assert_metric(uri, "delete") assert rv.status_code == 404 @pytest.mark.usefixtures("create_themes") def test_delete_bulk_themes(self): """ Theme API: Test delete bulk """ # Only delete non-system themes to avoid 403 errors themes = db.session.query(Theme).filter(~Theme.is_system).all() theme_ids = [theme.id for theme in themes] self.login(ADMIN_USERNAME) uri = f"api/v1/theme/?q={prison.dumps(theme_ids)}" rv = self.delete_assert_metric(uri, "bulk_delete") assert rv.status_code == 200 response = json.loads(rv.data.decode("utf-8")) expected_response = {"message": f"Deleted {len(theme_ids)} themes"} assert response == expected_response # Verify only non-system themes are deleted, system themes remain remaining_themes = db.session.query(Theme).filter(~Theme.is_system).all() assert remaining_themes == [] @pytest.mark.usefixtures("create_themes") def test_delete_one_bulk_themes(self): """ Theme API: Test delete one in bulk """ # Only delete non-system themes to avoid 403 errors theme = db.session.query(Theme).filter(~Theme.is_system).first() theme_ids = [theme.id] self.login(ADMIN_USERNAME) uri = f"api/v1/theme/?q={prison.dumps(theme_ids)}" rv = self.delete_assert_metric(uri, "bulk_delete") assert rv.status_code == 200 response = json.loads(rv.data.decode("utf-8")) expected_response = {"message": f"Deleted {len(theme_ids)} theme"} assert response == expected_response theme_ = db.session.query(Theme).get(theme_ids[0]) assert theme_ is None def test_delete_bulk_theme_bad_request(self): """ Theme API: Test delete bulk bad request """ theme_ids = [1, "a"] self.login(ADMIN_USERNAME) uri = f"api/v1/theme/?q={prison.dumps(theme_ids)}" rv = self.delete_assert_metric(uri, "bulk_delete") assert rv.status_code == 400 @pytest.mark.usefixtures("create_themes") def test_delete_bulk_theme_not_found(self): """ Theme API: Test delete bulk not found """ max_id = db.session.query(func.max(Theme.id)).scalar() theme_ids = [max_id + 1, max_id + 2] self.login(ADMIN_USERNAME) uri = f"api/v1/theme/?q={prison.dumps(theme_ids)}" rv = self.delete_assert_metric(uri, "bulk_delete") assert rv.status_code == 404