123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981 |
- #
- # Copyright 2018-2022 Elyra Authors
- #
- # Licensed 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.
- #
- from collections import OrderedDict
- import copy
- import json
- import os
- import shutil
- import time
- from jsonschema import ValidationError
- import pytest
- from elyra.metadata.error import MetadataExistsError
- from elyra.metadata.error import MetadataNotFoundError
- from elyra.metadata.error import SchemaNotFoundError
- from elyra.metadata.manager import MetadataManager
- from elyra.metadata.metadata import Metadata
- from elyra.metadata.schema import METADATA_TEST_SCHEMASPACE
- from elyra.metadata.schema import METADATA_TEST_SCHEMASPACE_ID
- from elyra.metadata.storage import FileMetadataCache
- from elyra.metadata.storage import FileMetadataStore
- from elyra.metadata.storage import MetadataStore
- from elyra.tests.metadata.test_utils import byo_metadata_json
- from elyra.tests.metadata.test_utils import create_instance
- from elyra.tests.metadata.test_utils import create_json_file
- from elyra.tests.metadata.test_utils import invalid_metadata_json
- from elyra.tests.metadata.test_utils import invalid_no_display_name_json
- from elyra.tests.metadata.test_utils import MockMetadataStore
- from elyra.tests.metadata.test_utils import valid_display_name_json
- from elyra.tests.metadata.test_utils import valid_metadata2_json
- from elyra.tests.metadata.test_utils import valid_metadata_json
- os.environ["METADATA_TESTING"] = "1" # Enable metadata-tests schemaspace
- # ########################## MetadataManager Tests ###########################
- def test_manager_add_invalid(tests_manager):
- with pytest.raises(ValueError):
- MetadataManager(schemaspace="invalid")
- # Attempt with non Metadata instance
- with pytest.raises(TypeError):
- tests_manager.create(valid_metadata_json)
- # and invalid parameters
- with pytest.raises(TypeError):
- tests_manager.create(None, invalid_no_display_name_json)
- with pytest.raises(ValueError):
- tests_manager.create("foo", None)
- def test_manager_add_no_name(tests_manager, schemaspace_location):
- metadata_name = "valid_metadata_instance"
- metadata = Metadata.from_dict(METADATA_TEST_SCHEMASPACE_ID, {**valid_metadata_json})
- instance = tests_manager.create(None, metadata)
- assert instance is not None
- assert instance.name == metadata_name
- assert instance.pre_property == instance.metadata.get("required_test")
- assert instance.post_property == instance.display_name
- # Ensure file was created using store_manager
- instance_list = tests_manager.metadata_store.fetch_instances(metadata_name)
- assert len(instance_list) == 1
- instance = Metadata.from_dict(METADATA_TEST_SCHEMASPACE, instance_list[0])
- metadata_location = _compose_instance_location(tests_manager.metadata_store, schemaspace_location, metadata_name)
- assert instance.resource == metadata_location
- assert instance.pre_property == instance.metadata.get("required_test")
- # This will be None because the hooks don't get called when fetched directly from the store
- assert instance.post_property is None
- # And finally, remove it.
- tests_manager.remove(metadata_name)
- # Verify removal using metadata_store
- with pytest.raises(MetadataNotFoundError):
- tests_manager.metadata_store.fetch_instances(metadata_name)
- def test_manager_add_short_name(tests_manager, schemaspace_location):
- # Found that single character names were failing validation
- metadata_name = "a"
- metadata = Metadata(**valid_metadata_json)
- instance = tests_manager.create(metadata_name, metadata)
- assert instance is not None
- assert instance.name == metadata_name
- # Ensure file was created using store_manager
- instance_list = tests_manager.metadata_store.fetch_instances(metadata_name)
- assert len(instance_list) == 1
- instance = Metadata.from_dict(METADATA_TEST_SCHEMASPACE_ID, instance_list[0])
- metadata_location = _compose_instance_location(tests_manager.metadata_store, schemaspace_location, metadata_name)
- assert instance.resource == metadata_location
- # And finally, remove it.
- tests_manager.remove(metadata_name)
- # Verify removal using metadata_store
- with pytest.raises(MetadataNotFoundError):
- tests_manager.metadata_store.fetch_instances(metadata_name)
- def test_manager_add_empty_display_name(tests_manager):
- # Found that empty display_name values were passing validation, so minLength=1 was added
- metadata_name = "empty_display_name"
- metadata = Metadata(**valid_metadata_json)
- metadata.display_name = ""
- with pytest.raises(ValidationError):
- tests_manager.create(metadata_name, metadata)
- # Ensure file was not created using storage manager
- with pytest.raises(MetadataNotFoundError):
- tests_manager.metadata_store.fetch_instances(metadata_name)
- def test_manager_add_display_name(tests_manager, schemaspace_location):
- metadata_display_name = '1 teste "rápido"'
- metadata_name = "a_1_teste_rpido"
- metadata = Metadata(**valid_display_name_json)
- instance = tests_manager.create(None, metadata)
- assert instance is not None
- assert instance.name == metadata_name
- assert instance.display_name == metadata_display_name
- # Ensure file was created using store_manager
- instance_list = tests_manager.metadata_store.fetch_instances(metadata_name)
- assert len(instance_list) == 1
- instance = Metadata.from_dict(METADATA_TEST_SCHEMASPACE, instance_list[0])
- metadata_location = _compose_instance_location(tests_manager.metadata_store, schemaspace_location, metadata_name)
- assert instance.resource == metadata_location
- assert instance.display_name == metadata_display_name
- # And finally, remove it.
- tests_manager.remove(metadata_name)
- # Verify removal using metadata_store
- with pytest.raises(MetadataNotFoundError):
- tests_manager.metadata_store.fetch_instances(metadata_name)
- @pytest.mark.parametrize(
- "complex_string, valid",
- [
- ("", False),
- (" ", False),
- (" starting-whitespace", False),
- ("ending-whitespace ", False),
- (" whitespace-both-ends ", False),
- ("whitespace in between", True),
- ("no-whitespace", True),
- ],
- )
- def test_manager_complex_string_schema(tests_manager, schemaspace_location, complex_string, valid):
- metadata_name = "valid_metadata_instance"
- metadata_dict = {**valid_metadata_json}
- metadata_dict["metadata"]["string_complex_test"] = complex_string
- metadata = Metadata.from_dict(METADATA_TEST_SCHEMASPACE_ID, metadata_dict)
- if not valid:
- with pytest.raises(ValidationError):
- tests_manager.create(metadata_name, metadata)
- else:
- instance = tests_manager.create(metadata_name, metadata)
- assert instance.metadata.get("string_complex_test") == complex_string
- # And finally, remove it.
- tests_manager.remove(metadata_name)
- # Verify removal using metadata_store
- with pytest.raises(MetadataNotFoundError):
- tests_manager.metadata_store.fetch_instances(metadata_name)
- def test_manager_get_include_invalid(tests_manager):
- metadata_list = tests_manager.get_all(include_invalid=False)
- assert len(metadata_list) == 2
- metadata_list = tests_manager.get_all(include_invalid=True)
- assert len(metadata_list) == 5
- def test_manager_get_of_schema(tests_manager):
- metadata_list = tests_manager.get_all(include_invalid=True)
- assert len(metadata_list) == 5
- metadata_list = tests_manager.get_all(include_invalid=True, of_schema="metadata-test")
- assert len(metadata_list) == 3 # does not include metadata with schema {unknown} and metadata-testxxx
- def test_manager_get_bad_json(tests_manager):
- with pytest.raises(ValueError) as ve:
- tests_manager.get("bad")
- assert "JSON failed to load for instance 'bad'" in str(ve.value)
- def test_manager_get_all(tests_manager):
- metadata_list = tests_manager.get_all()
- assert len(metadata_list) == 2
- # Ensure name is getting derived from resource and not from contents
- for metadata in metadata_list:
- if metadata.display_name == "Another Metadata Instance (2)":
- assert metadata.name == "another"
- else:
- assert metadata.name == "valid"
- def test_manager_get_none(tests_manager, schemaspace_location):
- # Attempt to get a metadata instance using `None` (error expected)
- with pytest.raises(ValueError, match="The 'name' parameter requires a value."):
- tests_manager.get(name=None)
- def test_manager_get_all_none(tests_manager, schemaspace_location):
- # Delete the schemaspace contents and attempt listing metadata
- _remove_schemaspace(tests_manager.metadata_store, schemaspace_location)
- assert tests_manager.schemaspace_exists() is False
- _create_schemaspace(tests_manager.metadata_store, schemaspace_location)
- assert tests_manager.schemaspace_exists()
- metadata_list = tests_manager.get_all()
- assert len(metadata_list) == 0
- def test_manager_add_remove_valid(tests_manager, schemaspace_location):
- metadata_name = "valid_add_remove"
- # Remove schemaspace_location and ensure it gets created
- _remove_schemaspace(tests_manager.metadata_store, schemaspace_location)
- metadata = Metadata(**valid_metadata_json)
- instance = tests_manager.create(metadata_name, metadata)
- assert instance is not None
- # Attempt to create again w/o replace, then replace it.
- with pytest.raises(MetadataExistsError):
- tests_manager.create(metadata_name, metadata)
- instance = tests_manager.update(metadata_name, metadata)
- assert instance is not None
- # And finally, remove it.
- tests_manager.remove(metadata_name)
- # Verify removal using metadata_store
- with pytest.raises(MetadataNotFoundError):
- tests_manager.metadata_store.fetch_instances(metadata_name)
- def test_manager_remove_invalid(tests_manager, schemaspace_location):
- # Ensure invalid metadata file isn't validated and is removed.
- create_instance(tests_manager.metadata_store, schemaspace_location, "remove_invalid", invalid_metadata_json)
- metadata_name = "remove_invalid"
- tests_manager.remove(metadata_name)
- # Verify removal using metadata_store
- with pytest.raises(MetadataNotFoundError):
- tests_manager.metadata_store.fetch_instances(metadata_name)
- def test_manager_remove_missing(tests_manager):
- # Ensure removal of missing metadata file is handled.
- metadata_name = "missing"
- with pytest.raises(MetadataNotFoundError):
- tests_manager.remove(metadata_name)
- def test_manager_read_valid_by_name(tests_manager, schemaspace_location):
- metadata_name = "valid"
- some_metadata = tests_manager.get(metadata_name)
- assert some_metadata.name == metadata_name
- assert some_metadata.schema_name == "metadata-test"
- metadata_location = _compose_instance_location(tests_manager.metadata_store, schemaspace_location, metadata_name)
- assert metadata_location == some_metadata.resource
- def test_manager_read_invalid_by_name(tests_manager):
- metadata_name = "invalid"
- with pytest.raises(ValidationError):
- tests_manager.get(metadata_name)
- def test_manager_read_missing_by_name(tests_manager):
- metadata_name = "missing"
- with pytest.raises(MetadataNotFoundError):
- tests_manager.get(metadata_name)
- def test_manager_rollback_create(tests_manager):
- metadata_name = "rollback_create"
- metadata = Metadata(**valid_metadata2_json)
- os.environ["METADATA_TEST_HOOK_OP"] = "create" # Tell test class which op to raise
- # Create post-save hook will throw NotImplementedError
- with pytest.raises(NotImplementedError):
- tests_manager.create(metadata_name, metadata)
- # Ensure nothing got created
- with pytest.raises(MetadataNotFoundError):
- tests_manager.get(metadata_name)
- os.environ.pop("METADATA_TEST_HOOK_OP") # Restore normal operation
- instance = tests_manager.create(metadata_name, metadata)
- instance2 = tests_manager.get(metadata_name)
- assert instance.name == instance2.name
- assert instance.schema_name == instance2.schema_name
- assert instance.post_property == instance2.post_property
- def test_manager_rollback_update(tests_manager):
- metadata_name = "rollback_update"
- metadata = Metadata(**valid_metadata2_json)
- # Create the instance
- instance = tests_manager.create(metadata_name, metadata)
- original_display_name = instance.display_name
- instance.display_name = "Updated_" + original_display_name
- os.environ["METADATA_TEST_HOOK_OP"] = "update" # Tell test class which op to raise
- # Update post-save hook will throw ModuleNotFoundError
- with pytest.raises(ModuleNotFoundError):
- tests_manager.update(metadata_name, instance)
- # Ensure the display_name is still the original value.
- instance2 = tests_manager.get(metadata_name)
- assert instance2.display_name == original_display_name
- os.environ.pop("METADATA_TEST_HOOK_OP") # Restore normal operation
- # Ensure we can still update
- instance = tests_manager.update(metadata_name, instance)
- assert instance.display_name == "Updated_" + original_display_name
- def test_manager_rollback_delete(tests_manager):
- metadata_name = "rollback_delete"
- metadata = Metadata(**valid_metadata2_json)
- # Create the instance
- instance = tests_manager.create(metadata_name, metadata)
- os.environ["METADATA_TEST_HOOK_OP"] = "delete" # Tell test class which op to raise
- # Delete post-save hook will throw FileNotFoundError
- with pytest.raises(FileNotFoundError):
- tests_manager.remove(metadata_name)
- # Ensure the instance still exists
- instance2 = tests_manager.get(metadata_name)
- assert instance2.display_name == instance.display_name
- os.environ.pop("METADATA_TEST_HOOK_OP") # Restore normal operation
- # Ensure we can still delete
- tests_manager.remove(metadata_name)
- # Ensure the instance was deleted
- with pytest.raises(MetadataNotFoundError):
- tests_manager.get(metadata_name)
- def test_manager_hierarchy_fetch(tests_hierarchy_manager, factory_location, shared_location, schemaspace_location):
- # fetch initial instances, only factory data should be present
- metadata_list = tests_hierarchy_manager.get_all()
- assert len(metadata_list) == 3
- # Ensure these are all factory instances
- for metadata in metadata_list:
- assert metadata.display_name == "factory"
- byo_3 = tests_hierarchy_manager.get("byo_3")
- assert byo_3.resource.startswith(str(factory_location))
- # add a shared instance and confirm list count is still the same, but
- # only that instance is present in shared directory...
- byo_instance = byo_metadata_json
- byo_instance["display_name"] = "shared"
- create_json_file(shared_location, "byo_3.json", byo_instance)
- metadata_list = tests_hierarchy_manager.get_all()
- assert len(metadata_list) == 3
- # Ensure the proper instances exist
- for metadata in metadata_list:
- if metadata.name == "byo_3":
- assert metadata.display_name == "shared"
- else:
- assert metadata.display_name == "factory"
- byo_3 = tests_hierarchy_manager.get("byo_3")
- assert byo_3.resource.startswith(str(shared_location))
- # add a shared and a user instance confirm list count is still the same, but
- # both the user and shared instances are correct.
- byo_instance = byo_metadata_json
- byo_instance["display_name"] = "shared"
- create_json_file(shared_location, "byo_2.json", byo_instance)
- byo_instance["display_name"] = "user"
- create_json_file(schemaspace_location, "byo_2.json", byo_instance)
- metadata_list = tests_hierarchy_manager.get_all()
- assert len(metadata_list) == 3
- # Ensure the proper instances exist
- for metadata in metadata_list:
- if metadata.name == "byo_1":
- assert metadata.display_name == "factory"
- if metadata.name == "byo_2":
- assert metadata.display_name == "user"
- if metadata.name == "byo_3":
- assert metadata.display_name == "shared"
- byo_2 = tests_hierarchy_manager.get("byo_2")
- assert byo_2.resource.startswith(str(schemaspace_location))
- # delete the user instance and ensure its shared copy is now exposed
- tests_hierarchy_manager.metadata_store.delete_instance(byo_2.to_dict())
- metadata_list = tests_hierarchy_manager.get_all()
- assert len(metadata_list) == 3
- # Ensure the proper instances exist
- for metadata in metadata_list:
- if metadata.name == "byo_1":
- assert metadata.display_name == "factory"
- if metadata.name == "byo_2":
- assert metadata.display_name == "shared"
- if metadata.name == "byo_3":
- assert metadata.display_name == "shared"
- byo_2 = tests_hierarchy_manager.get("byo_2")
- assert byo_2.resource.startswith(str(shared_location))
- # delete both shared copies and ensure only factory is left
- # Note: because we can only delete user instances via the APIs, this
- # code is metadata_store-sensitive. If other stores implement this
- # hierachy scheme, similar storage-specific code will be necessary.
- if isinstance(tests_hierarchy_manager.metadata_store, FileMetadataStore):
- os.remove(os.path.join(shared_location, "byo_2.json"))
- os.remove(os.path.join(shared_location, "byo_3.json"))
- # fetch initial instances, only factory data should be present
- metadata_list = tests_hierarchy_manager.get_all()
- assert len(metadata_list) == 3
- # Ensure these are all factory instances
- for metadata in metadata_list:
- assert metadata.display_name == "factory"
- byo_2 = tests_hierarchy_manager.get("byo_2")
- assert byo_2.resource.startswith(str(factory_location))
- def test_manager_hierarchy_create(tests_hierarchy_manager, schemaspace_location):
- # Note, this is really more of an update test (replace = True), since you cannot "create" an
- # instance if it already exists - which, in this case, it exists in the factory area
- metadata = Metadata(**byo_metadata_json)
- metadata.display_name = "user"
- with pytest.raises(MetadataExistsError):
- tests_hierarchy_manager.create("byo_2", metadata)
- instance = tests_hierarchy_manager.update("byo_2", metadata)
- assert instance is not None
- assert instance.resource.startswith(str(schemaspace_location))
- metadata_list = tests_hierarchy_manager.get_all()
- assert len(metadata_list) == 3
- # Ensure the proper instances exist
- for metadata in metadata_list:
- if metadata.name == "byo_1":
- assert metadata.display_name == "factory"
- if metadata.name == "byo_2":
- assert metadata.display_name == "user"
- if metadata.name == "byo_3":
- assert metadata.display_name == "factory"
- byo_2 = tests_hierarchy_manager.get("byo_2")
- assert byo_2.resource.startswith(str(schemaspace_location))
- metadata = Metadata(**byo_metadata_json)
- metadata.display_name = "user"
- instance = tests_hierarchy_manager.update("byo_3", metadata)
- assert instance is not None
- assert instance.resource.startswith(str(schemaspace_location))
- metadata_list = tests_hierarchy_manager.get_all()
- assert len(metadata_list) == 3
- # Ensure the proper instances exist
- for metadata in metadata_list:
- if metadata.name == "byo_1":
- assert metadata.display_name == "factory"
- if metadata.name == "byo_2":
- assert metadata.display_name == "user"
- if metadata.name == "byo_3":
- assert metadata.display_name == "user"
- byo_2 = tests_hierarchy_manager.get("byo_2")
- assert byo_2.resource.startswith(str(schemaspace_location))
- def test_manager_hierarchy_update(tests_hierarchy_manager, factory_location, shared_location, schemaspace_location):
- # Create a copy of existing factory instance and ensure its in the user area
- byo_2 = tests_hierarchy_manager.get("byo_2")
- assert byo_2.resource.startswith(str(factory_location))
- byo_2.display_name = "user"
- with pytest.raises(MetadataExistsError):
- tests_hierarchy_manager.create("byo_2", byo_2)
- # Repeat with replacement enabled
- instance = tests_hierarchy_manager.update("byo_2", byo_2)
- assert instance is not None
- assert instance.resource.startswith(str(schemaspace_location))
- # now "slip in" a shared instance behind the updated version and ensure
- # the updated version is what's returned.
- byo_instance = byo_metadata_json
- byo_instance["display_name"] = "shared"
- create_json_file(shared_location, "byo_2.json", byo_instance)
- byo_2 = tests_hierarchy_manager.get("byo_2")
- assert byo_2.resource.startswith(str(schemaspace_location))
- # now remove the updated instance and ensure the shared instance appears
- tests_hierarchy_manager.remove("byo_2")
- byo_2 = tests_hierarchy_manager.get("byo_2")
- assert byo_2.resource.startswith(str(shared_location))
- def test_manager_update(tests_hierarchy_manager, schemaspace_location):
- # Create some metadata, then attempt to update it with a known schema violation
- # and ensure the previous copy still exists...
- # Create a user instance...
- metadata = Metadata.from_dict(METADATA_TEST_SCHEMASPACE_ID, {**byo_metadata_json})
- metadata.display_name = "user1"
- instance = tests_hierarchy_manager.create("update", metadata)
- assert instance is not None
- assert instance.resource.startswith(str(schemaspace_location))
- assert instance.pre_property == instance.metadata["required_test"]
- assert instance.post_property == instance.display_name
- # Now update the user instance - add a field - and ensure that the original renamed file is not present.
- instance2 = tests_hierarchy_manager.get("update")
- instance2.display_name = "user2"
- instance2.metadata["number_range_test"] = 7
- instance = tests_hierarchy_manager.update("update", instance2)
- assert instance.pre_property == instance.metadata["required_test"]
- assert instance.post_property == instance2.display_name
- _ensure_single_instance(tests_hierarchy_manager, schemaspace_location, "update.json")
- instance2 = tests_hierarchy_manager.get("update")
- assert instance2.display_name == "user2"
- assert instance2.metadata["number_range_test"] == 7
- def test_manager_default_value(tests_hierarchy_manager, schemaspace_location):
- # Create some metadata, then attempt to update it with a known schema violation
- # and ensure the previous copy still exists...
- # Create a user instance...
- metadata = Metadata.from_dict(METADATA_TEST_SCHEMASPACE, {**byo_metadata_json})
- metadata.display_name = "user1"
- instance = tests_hierarchy_manager.create("default_value", metadata)
- assert instance.metadata["number_default_test"] == 42 # Ensure default value was applied when not present
- instance2 = tests_hierarchy_manager.get("default_value")
- instance2.metadata["number_default_test"] = 37
- tests_hierarchy_manager.update("default_value", instance2)
- instance3 = tests_hierarchy_manager.get("default_value")
- assert instance3.metadata["number_default_test"] == 37
- # Now remove the updated value and ensure it comes back with the default
- instance3.metadata.pop("number_default_test")
- assert "number_default_test" not in instance3.metadata
- tests_hierarchy_manager.update("default_value", instance3)
- instance4 = tests_hierarchy_manager.get("default_value")
- assert instance4.metadata["number_default_test"] == 42
- def test_manager_bad_update(tests_hierarchy_manager, schemaspace_location):
- # Create some metadata, then attempt to update it with a known schema violation
- # and ensure the previous copy still exists...
- # Create a user instance...
- metadata = Metadata(**byo_metadata_json)
- metadata.display_name = "user1"
- instance = tests_hierarchy_manager.create("bad_update", metadata)
- assert instance is not None
- assert instance.resource.startswith(str(schemaspace_location))
- # Now, attempt to update the user instance, but include a schema violation.
- # Verify the update failed, but also ensure the previous instance is still there.
- instance2 = tests_hierarchy_manager.get("bad_update")
- instance2.display_name = "user2"
- instance2.metadata["number_range_test"] = 42 # number is out of range
- with pytest.raises(ValidationError):
- tests_hierarchy_manager.update("bad_update", instance2)
- _ensure_single_instance(tests_hierarchy_manager, schemaspace_location, "bad_update.json")
- instance2 = tests_hierarchy_manager.get("bad_update")
- assert instance2.display_name == instance.display_name
- assert "number_range_test" not in instance2.metadata
- # Now try update without providing a name, ValueError expected
- instance2 = tests_hierarchy_manager.get("bad_update")
- instance2.display_name = "user update with no name"
- with pytest.raises(ValueError):
- tests_hierarchy_manager.update(None, instance2)
- _ensure_single_instance(tests_hierarchy_manager, schemaspace_location, "bad_update.json")
- def test_manager_hierarchy_remove(tests_hierarchy_manager, factory_location, shared_location, schemaspace_location):
- # Create additional instances in shared and user areas
- byo_2 = byo_metadata_json
- byo_2["display_name"] = "shared"
- create_json_file(shared_location, "byo_2.json", byo_2)
- metadata = Metadata(**byo_metadata_json)
- metadata.display_name = "user"
- instance = tests_hierarchy_manager.update("byo_2", metadata)
- assert instance is not None
- assert instance.resource.startswith(str(schemaspace_location))
- # Confirm on in user is found...
- metadata_list = tests_hierarchy_manager.get_all()
- assert len(metadata_list) == 3
- # Ensure the proper instances exist
- for metadata in metadata_list:
- if metadata.name == "byo_1":
- assert metadata.display_name == "factory"
- if metadata.name == "byo_2":
- assert metadata.display_name == "user"
- if metadata.name == "byo_3":
- assert metadata.display_name == "factory"
- byo_2 = tests_hierarchy_manager.get("byo_2")
- assert byo_2.resource.startswith(str(schemaspace_location))
- # Now remove instance. Should be allowed since it resides in user area
- tests_hierarchy_manager.remove("byo_2")
- _ensure_single_instance(tests_hierarchy_manager, schemaspace_location, "byo_2.json", expected_count=0)
- # Attempt to remove instance from shared area and its protected
- with pytest.raises(PermissionError) as pe:
- tests_hierarchy_manager.remove("byo_2")
- assert "Removal of instance 'byo_2'" in str(pe.value)
- # Ensure the one that exists is the one in the shared area
- byo_2 = tests_hierarchy_manager.get("byo_2")
- assert byo_2.resource.startswith(str(shared_location))
- # Attempt to remove instance from factory area and its protected as well
- with pytest.raises(PermissionError) as pe:
- tests_hierarchy_manager.remove("byo_1")
- assert "Removal of instance 'byo_1'" in str(pe.value)
- byo_1 = tests_hierarchy_manager.get("byo_1")
- assert byo_1.resource.startswith(str(factory_location))
- # ########################## MetadataStore Tests ###########################
- def test_store_schemaspace(store_manager, schemaspace_location):
- # Delete the metadata dir contents and attempt listing metadata
- _remove_schemaspace(store_manager, schemaspace_location)
- assert store_manager.schemaspace_exists() is False
- # create some metadata
- store_manager.store_instance("ensure_schemaspace_exists", Metadata(**valid_metadata_json).prepare_write())
- assert store_manager.schemaspace_exists()
- def test_store_fetch_instances(store_manager):
- instances_list = store_manager.fetch_instances()
- assert len(instances_list) == 4
- def test_store_fetch_no_schemaspace(store_manager, schemaspace_location):
- # Delete the schemaspace contents and attempt listing metadata
- _remove_schemaspace(store_manager, schemaspace_location)
- instance_list = store_manager.fetch_instances()
- assert len(instance_list) == 0
- def test_store_fetch_by_name(store_manager):
- metadata_name = "valid"
- instance_list = store_manager.fetch_instances(name=metadata_name)
- assert instance_list[0].get("name") == metadata_name
- def test_store_fetch_missing(store_manager):
- metadata_name = "missing"
- with pytest.raises(MetadataNotFoundError):
- store_manager.fetch_instances(name=metadata_name)
- def test_store_store_instance(store_manager, schemaspace_location):
- # Remove schemaspace to test raw creation and confirm perms
- _remove_schemaspace(store_manager, schemaspace_location)
- metadata_name = "persist"
- metadata = Metadata(**valid_metadata_json)
- metadata_dict = metadata.prepare_write()
- instance = store_manager.store_instance(metadata_name, metadata_dict)
- assert instance is not None
- if isinstance(store_manager, FileMetadataStore):
- dir_mode = oct(os.stat(schemaspace_location).st_mode & 0o777777) # Be sure to include other attributes
- assert dir_mode == "0o40700" # and ensure this is a directory with only rwx by owner enabled
- # Ensure file was created
- metadata_file = os.path.join(schemaspace_location, "persist.json")
- assert os.path.exists(metadata_file)
- file_mode = oct(os.stat(metadata_file).st_mode & 0o777777) # Be sure to include other attributes
- assert file_mode == "0o100600" # and ensure this is a regular file with only rw by owner enabled
- with open(metadata_file, "r", encoding="utf-8") as f:
- valid_add = json.loads(f.read())
- assert "resource" not in valid_add
- assert "name" not in valid_add
- assert "display_name" in valid_add
- assert valid_add["display_name"] == "valid metadata instance"
- assert "schema_name" in valid_add
- assert valid_add["schema_name"] == "metadata-test"
- # Attempt to create again w/o replace, then replace it.
- with pytest.raises(MetadataExistsError):
- store_manager.store_instance(metadata_name, metadata.prepare_write())
- metadata.metadata["number_range_test"] = 10
- instance = store_manager.store_instance(metadata_name, metadata.prepare_write(), for_update=True)
- assert instance is not None
- assert instance.get("metadata")["number_range_test"] == 10
- def test_store_delete_instance(store_manager, schemaspace_location):
- metadata_name = "valid"
- instance_list = store_manager.fetch_instances(name=metadata_name)
- metadata = instance_list[0]
- store_manager.delete_instance(metadata)
- with pytest.raises(MetadataNotFoundError):
- store_manager.fetch_instances(name=metadata_name)
- if isinstance(store_manager, FileMetadataStore):
- # Ensure file was physically deleted
- metadata_file = os.path.join(schemaspace_location, "valid.json")
- assert not os.path.exists(metadata_file)
- # ########################## Error Tests ###########################
- def test_error_metadata_not_found():
- schemaspace = METADATA_TEST_SCHEMASPACE
- resource = "missing_metadata"
- try:
- raise MetadataNotFoundError(schemaspace, resource)
- except MetadataNotFoundError as mnfe:
- assert str(mnfe) == f"No such instance named '{resource}' was found in the {schemaspace} schemaspace."
- def test_error_metadata_exists():
- schemaspace = METADATA_TEST_SCHEMASPACE
- resource = "existing_metadata"
- try:
- raise MetadataExistsError(schemaspace, resource)
- except MetadataExistsError as mee:
- assert str(mee) == f"An instance named '{resource}' already exists in the {schemaspace} schemaspace."
- def test_error_schema_not_found():
- schemaspace = METADATA_TEST_SCHEMASPACE
- resource = "missing_schema"
- try:
- raise SchemaNotFoundError(schemaspace, resource)
- except SchemaNotFoundError as snfe:
- assert str(snfe) == f"No such schema named '{resource}' was found in the {schemaspace} schemaspace."
- def test_cache_init():
- FileMetadataCache.clear_instance()
- cache = FileMetadataCache.instance()
- assert cache.max_size == 128
- FileMetadataCache.clear_instance()
- cache = FileMetadataCache.instance(max_size=3)
- assert cache.max_size == 3
- FileMetadataCache.clear_instance()
- def test_cache_ops(tests_manager, schemaspace_location):
- FileMetadataCache.clear_instance()
- test_items = OrderedDict({"a": 3, "b": 4, "c": 5, "d": 6, "e": 7})
- test_resources = {}
- test_content = {}
- # Setup test data
- for name, number in test_items.items():
- content = copy.deepcopy(valid_metadata_json)
- content["display_name"] = name
- content["metadata"]["number_range_test"] = number
- resource = create_instance(tests_manager.metadata_store, schemaspace_location, name, content)
- test_resources[name] = resource
- test_content[name] = content
- # Add initial entries
- cache = FileMetadataCache.instance(max_size=3)
- for name in test_items: # Add the items to the cache
- cache.add_item(test_resources[name], test_content[name])
- assert len(cache) == 3
- assert cache.trims == 2
- assert cache.get_item(test_resources.get("a")) is None
- assert cache.get_item(test_resources.get("b")) is None
- assert cache.get_item(test_resources.get("c")) is not None
- assert cache.get_item(test_resources.get("d")) is not None
- assert cache.get_item(test_resources.get("e")) is not None
- assert cache.misses == 2
- assert cache.hits == 3
- cache.add_item(test_resources.get("a"), test_content.get("a"))
- assert len(cache) == 3
- assert cache.trims == 3
- assert cache.get_item(test_resources.get("c")) is None # since 'c' was aged out
- assert cache.get_item(test_resources.get("a")) is not None
- assert cache.misses == 3
- assert cache.hits == 4
- e_val = cache.remove_item(test_resources.get("e"))
- assert len(cache) == 2
- assert e_val["metadata"]["number_range_test"] == test_items.get("e")
- assert cache.get_item(test_resources.get("e")) is None
- assert cache.misses == 4
- assert cache.hits == 4
- assert cache.trims == 3
- a_val = cache.remove_item(test_resources.get("a"))
- assert len(cache) == 1
- assert a_val["metadata"]["number_range_test"] == test_items.get("a")
- assert cache.get_item(test_resources.get("a")) is None
- assert cache.misses == 5
- assert cache.hits == 4
- assert cache.trims == 3
- d_val = cache.get_item(test_resources.get("d"))
- assert len(cache) == 1
- assert d_val["metadata"]["number_range_test"] == test_items.get("d")
- assert cache.misses == 5
- assert cache.hits == 5
- assert cache.trims == 3
- if isinstance(tests_manager.metadata_store, FileMetadataStore):
- # Exercise delete from filesystem and ensure cached item is removed
- assert os.path.exists(test_resources.get("d"))
- os.remove(test_resources.get("d"))
- recorded = 0.0
- for i in range(1, 6): # allow up to a second for delete to record in cache
- time.sleep(0.2) # initial tests are showing only one sub-second delay is necessary
- recorded += 0.2
- if len(cache) == 0:
- break
- assert len(cache) == 0
- print(f"\ntest_cache_ops: Delete recorded after {recorded} seconds")
- assert cache.get_item(test_resources.get("d")) is None
- assert cache.misses == 6
- assert cache.hits == 5
- assert cache.trims == 3
- def test_cache_disabled(tests_manager, schemaspace_location):
- FileMetadataCache.clear_instance()
- test_items = OrderedDict({"a": 3, "b": 4, "c": 5, "d": 6, "e": 7})
- test_resources = {}
- test_content = {}
- # Setup test data
- for name, number in test_items.items():
- content = copy.deepcopy(valid_metadata_json)
- content["display_name"] = name
- content["metadata"]["number_range_test"] = number
- resource = create_instance(tests_manager.metadata_store, schemaspace_location, name, content)
- test_resources[name] = resource
- test_content[name] = content
- # Add initial entries
- cache = FileMetadataCache.instance(max_size=3, enabled=False)
- assert hasattr(cache, "observer") is False
- assert hasattr(cache, "observed_dirs") is False
- for name in test_items: # Add the items to the cache
- cache.add_item(test_resources[name], test_content[name])
- assert len(cache) == 0
- assert cache.trims == 0
- assert cache.get_item(test_resources.get("a")) is None
- assert cache.get_item(test_resources.get("b")) is None
- assert cache.get_item(test_resources.get("c")) is None
- assert cache.get_item(test_resources.get("d")) is None
- assert cache.get_item(test_resources.get("e")) is None
- assert cache.misses == 0
- assert cache.hits == 0
- def _ensure_single_instance(tests_hierarchy_manager, schemaspace_location, name, expected_count=1):
- """Because updates can trigger the copy of the original, this methods ensures that
- only the named instance (`name`) exists after the operation. The expected_count
- can be altered so that it can also be used to ensure clean removals.
- """
- if isinstance(tests_hierarchy_manager.metadata_store, FileMetadataStore):
- # Ensure only the actual metadata file exists. The renamed instance will start with 'name' but have
- # a timestamp appended to it.
- count = 0
- actual = 0
- for f in os.listdir(str(schemaspace_location)):
- if name in f:
- count = count + 1
- if name == f:
- actual = actual + 1
- assert count == expected_count, "Temporarily renamed file was not removed"
- assert actual == expected_count
- def _create_schemaspace(store_manager: MetadataStore, schemaspace_location: str):
- """Creates schemaspace in a storage-independent manner"""
- if isinstance(store_manager, FileMetadataStore):
- os.makedirs(schemaspace_location)
- elif isinstance(store_manager, MockMetadataStore):
- instances = store_manager.instances
- if instances is None:
- setattr(store_manager, "instances", dict())
- def _remove_schemaspace(store_manager: MetadataStore, schemaspace_location: str):
- """Removes schemaspace in a storage-independent manner"""
- if isinstance(store_manager, FileMetadataStore):
- shutil.rmtree(schemaspace_location)
- elif isinstance(store_manager, MockMetadataStore):
- setattr(store_manager, "instances", None)
- def _compose_instance_location(store_manager: MetadataStore, location: str, name: str) -> str:
- """Compose location of the named instance in a storage-independent manner"""
- if isinstance(store_manager, FileMetadataStore):
- location = os.path.join(location, f"{name}.json")
- elif isinstance(store_manager, MockMetadataStore):
- location = None
- return location
|