# 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, unused-import from __future__ import annotations import pickle from datetime import datetime, timedelta from typing import TYPE_CHECKING from uuid import UUID import pytest from flask.ctx import AppContext from flask_appbuilder.security.sqla.models import User from superset.extensions import db from superset.key_value.exceptions import ( KeyValueCreateFailedError, KeyValueUpdateFailedError, ) from superset.key_value.types import ( JsonKeyValueCodec, KeyValueResource, PickleKeyValueCodec, ) from superset.utils import json from superset.utils.core import override_user from tests.unit_tests.fixtures.common import admin_user, after_each # noqa: F401 if TYPE_CHECKING: from superset.key_value.models import KeyValueEntry ID_KEY = 123 UUID_KEY = UUID("3e7a2ab8-bcaf-49b0-a5df-dfb432f291cc") RESOURCE = KeyValueResource.APP JSON_VALUE = {"foo": "bar"} PICKLE_VALUE = object() JSON_CODEC = JsonKeyValueCodec() PICKLE_CODEC = PickleKeyValueCodec() NEW_VALUE = {"foo": "baz"} @pytest.fixture def key_value_entry() -> KeyValueEntry: from superset.key_value.models import KeyValueEntry entry = KeyValueEntry( id=ID_KEY, uuid=UUID_KEY, resource=RESOURCE, value=JSON_CODEC.encode(JSON_VALUE), ) db.session.add(entry) db.session.flush() return entry def test_create_id_entry( app_context: AppContext, admin_user: User, # noqa: F811 after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO from superset.key_value.models import KeyValueEntry with override_user(admin_user): created_entry = KeyValueDAO.create_entry( resource=RESOURCE, value=JSON_VALUE, codec=JSON_CODEC, ) db.session.flush() found_entry = ( db.session.query(KeyValueEntry).filter_by(id=created_entry.id).one() ) assert json.loads(found_entry.value) == JSON_VALUE assert found_entry.created_by_fk == admin_user.id def test_create_uuid_entry( app_context: AppContext, admin_user: User, # noqa: F811 after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO from superset.key_value.models import KeyValueEntry with override_user(admin_user): created_entry = KeyValueDAO.create_entry( resource=RESOURCE, value=JSON_VALUE, codec=JSON_CODEC ) db.session.flush() found_entry = ( db.session.query(KeyValueEntry).filter_by(uuid=created_entry.uuid).one() ) assert json.loads(found_entry.value) == JSON_VALUE assert found_entry.created_by_fk == admin_user.id def test_create_fail_json_entry( app_context: AppContext, after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO with pytest.raises(KeyValueCreateFailedError): KeyValueDAO.create_entry( resource=RESOURCE, value=PICKLE_VALUE, codec=JSON_CODEC, ) def test_create_pickle_entry( app_context: AppContext, admin_user: User, # noqa: F811 after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO from superset.key_value.models import KeyValueEntry with override_user(admin_user): created_entry = KeyValueDAO.create_entry( resource=RESOURCE, value=PICKLE_VALUE, codec=PICKLE_CODEC, ) db.session.flush() found_entry = ( db.session.query(KeyValueEntry).filter_by(id=created_entry.id).one() ) assert isinstance(pickle.loads(found_entry.value), type(PICKLE_VALUE)) # noqa: S301 assert found_entry.created_by_fk == admin_user.id def test_get_value( app_context: AppContext, key_value_entry: KeyValueEntry, after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO value = KeyValueDAO.get_value( resource=RESOURCE, key=key_value_entry.id, codec=JSON_CODEC, ) assert value == JSON_VALUE def test_get_id_entry( app_context: AppContext, key_value_entry: KeyValueEntry, after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=key_value_entry.id) assert found_entry is not None assert found_entry.id == key_value_entry.id def test_get_uuid_entry( app_context: AppContext, key_value_entry: KeyValueEntry, # noqa: F811 after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO assert key_value_entry.uuid is not None found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=key_value_entry.uuid) assert found_entry is not None assert JSON_CODEC.decode(found_entry.value) == JSON_VALUE def test_get_id_entry_missing( app_context: AppContext, after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO entry = KeyValueDAO.get_entry(resource=RESOURCE, key=456) assert entry is None def test_get_expired_entry( app_context: AppContext, after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO created_entry = KeyValueDAO.create_entry( resource=RESOURCE, value=JSON_VALUE, codec=JSON_CODEC, key=ID_KEY, expires_on=datetime.now() - timedelta(days=1), ) found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=created_entry.id) assert found_entry is not None assert found_entry.is_expired() is True def test_get_future_expiring_entry( app_context: AppContext, after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO created_entry = KeyValueDAO.create_entry( resource=RESOURCE, value=JSON_VALUE, codec=JSON_CODEC, key=ID_KEY, expires_on=datetime.now() + timedelta(days=1), ) found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=created_entry.id) assert found_entry is not None assert found_entry.is_expired() is False def test_update_id_entry( app_context: AppContext, key_value_entry: KeyValueEntry, # noqa: F811 admin_user: User, # noqa: F811 after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO with override_user(admin_user): updated_entry = KeyValueDAO.update_entry( resource=RESOURCE, key=ID_KEY, value=NEW_VALUE, codec=JSON_CODEC, ) db.session.flush() assert updated_entry is not None assert JSON_CODEC.decode(updated_entry.value) == NEW_VALUE assert updated_entry.id == ID_KEY assert updated_entry.uuid == UUID_KEY found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=ID_KEY) assert found_entry is not None assert JSON_CODEC.decode(found_entry.value) == NEW_VALUE assert found_entry.changed_by_fk == admin_user.id def test_update_uuid_entry( app_context: AppContext, key_value_entry: KeyValueEntry, # noqa: F811 admin_user: User, # noqa: F811 after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO with override_user(admin_user): updated_entry = KeyValueDAO.update_entry( resource=RESOURCE, key=UUID_KEY, value=NEW_VALUE, codec=JSON_CODEC, ) db.session.flush() assert updated_entry is not None assert JSON_CODEC.decode(updated_entry.value) == NEW_VALUE assert updated_entry.id == ID_KEY assert updated_entry.uuid == UUID_KEY found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=UUID_KEY) assert found_entry is not None assert JSON_CODEC.decode(found_entry.value) == NEW_VALUE assert found_entry.changed_by_fk == admin_user.id def test_update_missing_entry( app_context: AppContext, admin_user: User, # noqa: F811 after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO with override_user(admin_user): with pytest.raises(KeyValueUpdateFailedError): KeyValueDAO.update_entry( resource=RESOURCE, key=456, value=NEW_VALUE, codec=JSON_CODEC, ) def test_upsert_id_entry( app_context: AppContext, key_value_entry: KeyValueEntry, # noqa: F811 admin_user: User, # noqa: F811 after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO with override_user(admin_user): entry = KeyValueDAO.upsert_entry( resource=RESOURCE, key=ID_KEY, value=NEW_VALUE, codec=JSON_CODEC, ) found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=ID_KEY) assert found_entry is not None assert JSON_CODEC.decode(found_entry.value) == NEW_VALUE assert entry.changed_by_fk == admin_user.id def test_upsert_uuid_entry( app_context: AppContext, key_value_entry: KeyValueEntry, # noqa: F811 admin_user: User, # noqa: F811 after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO with override_user(admin_user): entry = KeyValueDAO.upsert_entry( resource=RESOURCE, key=UUID_KEY, value=NEW_VALUE, codec=JSON_CODEC, ) db.session.flush() assert entry is not None assert entry.id == ID_KEY assert entry.uuid == UUID_KEY found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=UUID_KEY) assert found_entry is not None assert JSON_CODEC.decode(found_entry.value) == NEW_VALUE assert entry.changed_by_fk == admin_user.id def test_upsert_missing_entry( app_context: AppContext, after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO entry = KeyValueDAO.get_entry(resource=RESOURCE, key=ID_KEY) assert entry is None KeyValueDAO.upsert_entry( resource=RESOURCE, key=ID_KEY, value=NEW_VALUE, codec=JSON_CODEC, ) entry = KeyValueDAO.get_entry(resource=RESOURCE, key=ID_KEY) assert entry is not None assert JSON_CODEC.decode(entry.value) == NEW_VALUE def test_delete_id_entry( app_context: AppContext, key_value_entry: KeyValueEntry, after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO assert KeyValueDAO.delete_entry(resource=RESOURCE, key=ID_KEY) is True def test_delete_uuid_entry( app_context: AppContext, key_value_entry: KeyValueEntry, after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO assert KeyValueDAO.delete_entry(resource=RESOURCE, key=UUID_KEY) is True def test_delete_entry_missing( app_context: AppContext, after_each: None, # noqa: F811 ) -> None: from superset.daos.key_value import KeyValueDAO assert KeyValueDAO.delete_entry(resource=RESOURCE, key=12345678) is False