123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- #
- # 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.
- #
- import copy
- import json
- import os
- import shutil
- from jupyter_server.utils import url_path_join
- import pytest
- from tornado.httpclient import HTTPClientError
- from elyra.metadata.schema import METADATA_TEST_SCHEMASPACE
- from elyra.metadata.schema import METADATA_TEST_SCHEMASPACE_ID
- from elyra.tests.metadata.test_utils import byo_metadata_json
- from elyra.tests.metadata.test_utils import create_json_file
- from elyra.tests.metadata.test_utils import get_instance
- from elyra.tests.metadata.test_utils import invalid_metadata_json
- from elyra.tests.metadata.test_utils import valid_metadata_json
- from elyra.tests.util.handlers_utils import expected_http_error
- os.environ["METADATA_TESTING"] = "1" # Enable metadata-tests schemaspace
- async def test_bogus_schemaspace(jp_fetch, bogus_location):
- # Validate missing is not found. Remove the bogus location to ensure its not created
- shutil.rmtree(bogus_location)
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", "bogus", "missing")
- assert expected_http_error(e, 400)
- assert not os.path.exists(bogus_location)
- async def test_missing_instance(jp_fetch, setup_data):
- # Validate missing is not found
- name = "missing"
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, name)
- assert expected_http_error(e, 404)
- async def test_invalid_instance(jp_fetch, setup_data):
- # Validate invalid throws 404 with validation message
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "invalid")
- assert expected_http_error(e, 400)
- async def test_valid_instance(jp_fetch, setup_data):
- # Ensure valid metadata can be found
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID, "valid")
- assert r.code == 200
- metadata = json.loads(r.body.decode())
- assert "schema_name" in metadata
- assert metadata["display_name"] == "valid metadata instance"
- async def test_get_instances(jp_fetch, setup_data):
- # Ensure all valid metadata can be found
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID)
- assert r.code == 200
- metadata = json.loads(r.body.decode())
- assert isinstance(metadata, dict)
- assert len(metadata) == 1
- instances = metadata[METADATA_TEST_SCHEMASPACE_ID]
- assert len(instances) == 2
- assert isinstance(instances, list)
- assert get_instance(instances, "name", "another")
- assert get_instance(instances, "name", "valid")
- async def test_get_empty_schemaspace_instances(jp_fetch, schemaspace_location, setup_data):
- # Delete the metadata dir contents and attempt listing metadata
- shutil.rmtree(schemaspace_location)
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE)
- assert r.code == 200
- metadata = json.loads(r.body.decode())
- assert isinstance(metadata, dict)
- assert len(metadata) == 1
- instances = metadata[METADATA_TEST_SCHEMASPACE]
- assert len(instances) == 0
- # Now create empty schemaspace
- os.makedirs(schemaspace_location)
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID)
- assert r.code == 200
- metadata = json.loads(r.body.decode())
- assert isinstance(metadata, dict)
- assert len(metadata) == 1
- instances = metadata[METADATA_TEST_SCHEMASPACE_ID]
- assert len(instances) == 0
- async def test_get_hierarchy_instances(jp_fetch, setup_hierarchy):
- # Ensure all valid metadata can be found
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE)
- assert r.code == 200
- metadata = json.loads(r.body.decode())
- assert isinstance(metadata, dict)
- assert len(metadata) == 1
- instances = metadata[METADATA_TEST_SCHEMASPACE]
- assert len(instances) == 3
- assert isinstance(instances, list)
- assert get_instance(instances, "name", "byo_1")
- assert get_instance(instances, "name", "byo_2")
- assert get_instance(instances, "name", "byo_3")
- byo_3 = get_instance(instances, "name", "byo_3")
- assert byo_3["display_name"] == "factory"
- async def test_create_instance(jp_base_url, jp_fetch):
- """Create a simple instance - not conflicting with factory instances."""
- valid = copy.deepcopy(valid_metadata_json)
- valid["name"] = "valid"
- body = json.dumps(valid)
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID, body=body, method="POST")
- assert r.code == 201
- assert r.headers.get("Location") == url_path_join(
- jp_base_url, "/elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID, "valid"
- )
- metadata = json.loads(r.body.decode())
- # Add expected "extra" fields to 'valid' so whole-object comparison is satisfied.
- # These are added during the pre_save(), post_save() and on_load() hooks on the
- # MockMetadataTest class instance or when default values for missing properties are applied.
- valid["pre_property"] = valid["metadata"]["required_test"]
- valid["post_property"] = valid["display_name"]
- valid["metadata"]["number_default_test"] = 42
- assert metadata == valid
- async def test_create_hierarchy_instance(jp_fetch, setup_hierarchy):
- """Attempts to create an instance from one in the hierarchy."""
- byo_instance = copy.deepcopy(byo_metadata_json)
- byo_instance["display_name"] = "user"
- byo_instance["name"] = "byo_2"
- body = json.dumps(byo_instance)
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, body=body, method="POST")
- assert expected_http_error(e, 409)
- # Confirm the instance was not changed
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE)
- assert r.code == 200
- metadata = json.loads(r.body.decode())
- assert isinstance(metadata, dict)
- assert len(metadata) == 1
- instances = metadata[METADATA_TEST_SCHEMASPACE]
- assert len(instances) == 3
- assert isinstance(instances, list)
- byo_2 = get_instance(instances, "name", "byo_2")
- assert byo_2["display_name"] == "factory"
- async def test_create_invalid_instance(jp_fetch):
- """Create a simple instance - not conflicting with factory instances."""
- invalid = copy.deepcopy(invalid_metadata_json)
- invalid["name"] = "invalid"
- body = json.dumps(invalid)
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, body=body, method="POST")
- assert expected_http_error(e, 400)
- async def test_create_instance_missing_schema(jp_fetch, schemaspace_location):
- """Attempt to create an instance using an invalid schema"""
- missing_schema = copy.deepcopy(valid_metadata_json)
- missing_schema["name"] = "missing_schema"
- missing_schema["schema_name"] = "missing_schema"
- missing_schema.pop("display_name")
- body = json.dumps(missing_schema)
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, body=body, method="POST")
- assert expected_http_error(e, 404)
- # Ensure instance was not created. Can't use REST here since it will correctly trigger 404
- # even though an instance was created and not removed due to failure to validate (due to
- # missing schema). Fixed by trapping the FileNotFoundError raised due to no schema.
- assert not os.path.exists(os.path.join(schemaspace_location, "missing_schema.json"))
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "missing_schema")
- assert expected_http_error(e, 404)
- async def test_update_non_existent(jp_fetch, schemaspace_location):
- """Attempt to update a non-existent instance."""
- # Try to update a non-existent instance - 404 expected...
- valid = copy.deepcopy(valid_metadata_json)
- valid["name"] = "valid"
- valid["metadata"]["number_range_test"] = 7
- body = json.dumps(valid)
- # Update (non-existent) instance
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "valid", body=body, method="PUT")
- assert expected_http_error(e, 404)
- async def test_update_instance(jp_fetch, schemaspace_location):
- """Update a simple instance."""
- # Create an instance, then update
- create_json_file(schemaspace_location, "valid.json", valid_metadata_json)
- valid = copy.deepcopy(valid_metadata_json)
- valid["name"] = "valid"
- valid["metadata"]["number_range_test"] = 7
- body = json.dumps(valid)
- # Update instance
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID, "valid", body=body, method="PUT")
- assert r.code == 200
- instance = json.loads(r.body.decode())
- assert instance["metadata"]["number_range_test"] == 7
- # Confirm update via jp_fetch
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "valid")
- assert r.code == 200
- instance = json.loads(r.body.decode())
- assert instance["metadata"]["number_range_test"] == 7
- async def test_invalid_update(jp_fetch, schemaspace_location):
- """Update a simple instance with invalid metadata."""
- # Create an instance, then update with invalid metadata
- create_json_file(schemaspace_location, "update_bad_md.json", valid_metadata_json)
- # Fetch it to get the valid instance
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "update_bad_md")
- assert r.code == 200
- instance = json.loads(r.body.decode())
- # Now attempt the update with bad metadata and ensure previous still exists
- valid2 = copy.deepcopy(valid_metadata_json)
- valid2["name"] = "valid"
- valid2["metadata"]["number_range_test"] = 42
- body2 = json.dumps(valid2)
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID, "update_bad_md", body=body2, method="PUT")
- assert expected_http_error(e, 400)
- # Fetch again and ensure it matches the previous instance
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID, "update_bad_md")
- assert r.code == 200
- instance2 = json.loads(r.body.decode())
- assert instance2 == instance
- async def test_update_fields(jp_fetch, schemaspace_location):
- # Create an instance, then update with a new field
- create_json_file(schemaspace_location, "update_fields.json", valid_metadata_json)
- valid = copy.deepcopy(valid_metadata_json)
- valid["metadata"]["number_range_test"] = 7
- body = json.dumps(valid)
- # Update instance adding number_range_test
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "update_fields", body=body, method="PUT")
- assert r.code == 200
- instance = json.loads(r.body.decode())
- assert instance["metadata"]["number_range_test"] == 7
- # Add a new field (per schema) and remove another -
- valid["metadata"].pop("number_range_test")
- valid["metadata"]["string_length_test"] = "valid len"
- body = json.dumps(valid)
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "update_fields", body=body, method="PUT")
- assert r.code == 200
- instance = json.loads(r.body.decode())
- assert instance["metadata"]["string_length_test"] == "valid len"
- assert "number_range_test" not in instance["metadata"]
- async def test_update_hierarchy_instance(jp_fetch, setup_hierarchy):
- """Update a simple instance - that's conflicting with factory instances."""
- # Do not name intentionally, since this is an update
- byo_instance = copy.deepcopy(byo_metadata_json)
- byo_instance["display_name"] = "user"
- byo_instance["metadata"]["number_range_test"] = 7
- body = json.dumps(byo_instance)
- # Because this is considered an update, replacement is enabled.
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "byo_2", body=body, method="PUT")
- assert r.code == 200
- # Confirm the instances and ensure byo_2 is in USER area
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE)
- assert r.code == 200
- metadata = json.loads(r.body.decode())
- assert isinstance(metadata, dict)
- assert len(metadata) == 1
- instances = metadata[METADATA_TEST_SCHEMASPACE]
- assert len(instances) == 3
- assert isinstance(instances, list)
- byo_2 = get_instance(instances, "name", "byo_2")
- assert byo_2["schema_name"] == byo_metadata_json["schema_name"]
- assert byo_2["metadata"]["number_range_test"] == 7
- # Attempt to rename the resource, exception expected.
- byo_2["name"] = "byo_2_renamed"
- body = json.dumps(byo_2)
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "byo_2", body=body, method="PUT")
- assert expected_http_error(e, 400)
- # Confirm no update occurred
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "byo_2")
- assert r.code == 200
- instance = json.loads(r.body.decode())
- assert instance["name"] == "byo_2"
- async def test_delete_instance(jp_fetch, schemaspace_location, setup_data):
- """Create a simple instance - not conflicting with factory instances and delete it."""
- # First, attempt to delete non-existent resource, exception expected.
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "missing", method="DELETE")
- assert expected_http_error(e, 404)
- create_json_file(schemaspace_location, "valid.json", valid_metadata_json)
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "valid", method="DELETE")
- assert r.code == 204
- # Confirm deletion
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "valid", method="DELETE")
- assert expected_http_error(e, 404)
- async def test_delete_hierarchy_instance(jp_fetch, schemaspace_location, setup_hierarchy):
- """Create a simple instance - that conflicts with factory instances and delete it only if local."""
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "byo_2", method="DELETE")
- assert expected_http_error(e, 403)
- # create local instance, delete should succeed
- create_json_file(schemaspace_location, "byo_2.json", byo_metadata_json)
- r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "byo_2", method="DELETE")
- assert r.code == 204
- async def test_bogus_schema(jp_fetch):
- # Validate missing is not found
- # Remove self.request (and other 'self.' prefixes) once transition to jupyter_server occurs
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "schema", "bogus")
- assert expected_http_error(e, 404)
- async def test_missing_runtimes_schema(jp_fetch):
- # Validate missing is not found
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "schema", "runtimes", "missing")
- assert expected_http_error(e, 404)
- async def test_get_runtimes_schemas(jp_fetch):
- # Ensure all schema for runtimes can be found
- await _get_schemaspace_schemas(jp_fetch, "runtimes", ["kfp", "airflow"])
- async def test_get_code_snippets_schemas(jp_fetch):
- # Ensure all schema for code-snippets can be found
- await _get_schemaspace_schemas(jp_fetch, "code-snippets", ["code-snippet"])
- async def test_get_test_schemas(jp_fetch):
- # Ensure all schema for metadata_tests can be found
- await _get_schemaspace_schemas(jp_fetch, METADATA_TEST_SCHEMASPACE, ["metadata-test", "metadata-test2"])
- async def test_get_runtimes_schema(jp_fetch):
- # Ensure all schema for runtimes can be found
- await _get_schemaspace_schema(jp_fetch, "runtimes", "kfp")
- async def test_get_code_snippets_schema(jp_fetch):
- # Ensure all schema for code-snippets can be found
- await _get_schemaspace_schema(jp_fetch, "code-snippets", "code-snippet")
- async def test_get_test_schema(jp_fetch):
- # Ensure all schema for metadata-test can be found
- await _get_schemaspace_schema(jp_fetch, METADATA_TEST_SCHEMASPACE, "metadata-test")
- async def _get_schemaspace_schemas(jp_fetch, schemaspace, expected):
- r = await jp_fetch(
- "elyra",
- "schema",
- schemaspace,
- )
- assert r.code == 200
- schemaspace_schemas = json.loads(r.body.decode())
- assert isinstance(schemaspace_schemas, dict)
- assert len(schemaspace_schemas) == 1
- schemas = schemaspace_schemas[schemaspace]
- assert len(schemas) == len(expected)
- for expected_schema in expected:
- assert get_instance(schemas, "name", expected_schema)
- async def _get_schemaspace_schema(jp_fetch, schemaspace, expected):
- r = await jp_fetch("elyra", "schema", schemaspace, expected)
- assert r.code == 200
- schemaspace_schema = json.loads(r.body.decode())
- assert isinstance(schemaspace_schema, dict)
- assert expected == schemaspace_schema["name"]
- assert schemaspace == schemaspace_schema["schemaspace"]
- async def test_get_schemaspaces(jp_fetch):
- expected_schemaspaces = ["runtimes", "code-snippets"]
- r = await jp_fetch("elyra", "schemaspace")
- assert r.code == 200
- schemaspaces = json.loads(r.body.decode("utf-8"))
- assert isinstance(schemaspaces, dict)
- assert len(schemaspaces["schemaspaces"]) >= len(expected_schemaspaces)
- for expected_schemaspace in expected_schemaspaces:
- assert expected_schemaspace in schemaspaces["schemaspaces"]
- async def test_get_schemaspace_info(jp_fetch):
- r = await jp_fetch("elyra", "schemaspace", METADATA_TEST_SCHEMASPACE)
- assert r.code == 200
- schemaspace_info = json.loads(r.body.decode("utf-8"))
- assert "name" in schemaspace_info
- assert "id" in schemaspace_info
- assert "display_name" in schemaspace_info
- assert "description" in schemaspace_info
- assert "schemas" not in schemaspace_info
- assert schemaspace_info["name"] == METADATA_TEST_SCHEMASPACE
- assert schemaspace_info["id"] == METADATA_TEST_SCHEMASPACE_ID
- async def test_get_missing_schemaspace_info(jp_fetch):
- with pytest.raises(HTTPClientError) as e:
- await jp_fetch("elyra", "schemaspace", "missing-schemaspace")
- assert expected_http_error(e, 404)
|