test_handlers.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. #
  2. # Copyright 2018-2022 Elyra Authors
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. import copy
  17. import json
  18. import os
  19. import shutil
  20. from jupyter_server.utils import url_path_join
  21. import pytest
  22. from tornado.httpclient import HTTPClientError
  23. from elyra.metadata.schema import METADATA_TEST_SCHEMASPACE
  24. from elyra.metadata.schema import METADATA_TEST_SCHEMASPACE_ID
  25. from elyra.tests.metadata.test_utils import byo_metadata_json
  26. from elyra.tests.metadata.test_utils import create_json_file
  27. from elyra.tests.metadata.test_utils import get_instance
  28. from elyra.tests.metadata.test_utils import invalid_metadata_json
  29. from elyra.tests.metadata.test_utils import valid_metadata_json
  30. from elyra.tests.util.handlers_utils import expected_http_error
  31. os.environ["METADATA_TESTING"] = "1" # Enable metadata-tests schemaspace
  32. async def test_bogus_schemaspace(jp_fetch, bogus_location):
  33. # Validate missing is not found. Remove the bogus location to ensure its not created
  34. shutil.rmtree(bogus_location)
  35. with pytest.raises(HTTPClientError) as e:
  36. await jp_fetch("elyra", "metadata", "bogus", "missing")
  37. assert expected_http_error(e, 400)
  38. assert not os.path.exists(bogus_location)
  39. async def test_missing_instance(jp_fetch, setup_data):
  40. # Validate missing is not found
  41. name = "missing"
  42. with pytest.raises(HTTPClientError) as e:
  43. await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, name)
  44. assert expected_http_error(e, 404)
  45. async def test_invalid_instance(jp_fetch, setup_data):
  46. # Validate invalid throws 404 with validation message
  47. with pytest.raises(HTTPClientError) as e:
  48. await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "invalid")
  49. assert expected_http_error(e, 400)
  50. async def test_valid_instance(jp_fetch, setup_data):
  51. # Ensure valid metadata can be found
  52. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID, "valid")
  53. assert r.code == 200
  54. metadata = json.loads(r.body.decode())
  55. assert "schema_name" in metadata
  56. assert metadata["display_name"] == "valid metadata instance"
  57. async def test_get_instances(jp_fetch, setup_data):
  58. # Ensure all valid metadata can be found
  59. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID)
  60. assert r.code == 200
  61. metadata = json.loads(r.body.decode())
  62. assert isinstance(metadata, dict)
  63. assert len(metadata) == 1
  64. instances = metadata[METADATA_TEST_SCHEMASPACE_ID]
  65. assert len(instances) == 2
  66. assert isinstance(instances, list)
  67. assert get_instance(instances, "name", "another")
  68. assert get_instance(instances, "name", "valid")
  69. async def test_get_empty_schemaspace_instances(jp_fetch, schemaspace_location, setup_data):
  70. # Delete the metadata dir contents and attempt listing metadata
  71. shutil.rmtree(schemaspace_location)
  72. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE)
  73. assert r.code == 200
  74. metadata = json.loads(r.body.decode())
  75. assert isinstance(metadata, dict)
  76. assert len(metadata) == 1
  77. instances = metadata[METADATA_TEST_SCHEMASPACE]
  78. assert len(instances) == 0
  79. # Now create empty schemaspace
  80. os.makedirs(schemaspace_location)
  81. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID)
  82. assert r.code == 200
  83. metadata = json.loads(r.body.decode())
  84. assert isinstance(metadata, dict)
  85. assert len(metadata) == 1
  86. instances = metadata[METADATA_TEST_SCHEMASPACE_ID]
  87. assert len(instances) == 0
  88. async def test_get_hierarchy_instances(jp_fetch, setup_hierarchy):
  89. # Ensure all valid metadata can be found
  90. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE)
  91. assert r.code == 200
  92. metadata = json.loads(r.body.decode())
  93. assert isinstance(metadata, dict)
  94. assert len(metadata) == 1
  95. instances = metadata[METADATA_TEST_SCHEMASPACE]
  96. assert len(instances) == 3
  97. assert isinstance(instances, list)
  98. assert get_instance(instances, "name", "byo_1")
  99. assert get_instance(instances, "name", "byo_2")
  100. assert get_instance(instances, "name", "byo_3")
  101. byo_3 = get_instance(instances, "name", "byo_3")
  102. assert byo_3["display_name"] == "factory"
  103. async def test_create_instance(jp_base_url, jp_fetch):
  104. """Create a simple instance - not conflicting with factory instances."""
  105. valid = copy.deepcopy(valid_metadata_json)
  106. valid["name"] = "valid"
  107. body = json.dumps(valid)
  108. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID, body=body, method="POST")
  109. assert r.code == 201
  110. assert r.headers.get("Location") == url_path_join(
  111. jp_base_url, "/elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID, "valid"
  112. )
  113. metadata = json.loads(r.body.decode())
  114. # Add expected "extra" fields to 'valid' so whole-object comparison is satisfied.
  115. # These are added during the pre_save(), post_save() and on_load() hooks on the
  116. # MockMetadataTest class instance or when default values for missing properties are applied.
  117. valid["pre_property"] = valid["metadata"]["required_test"]
  118. valid["post_property"] = valid["display_name"]
  119. valid["metadata"]["number_default_test"] = 42
  120. assert metadata == valid
  121. async def test_create_hierarchy_instance(jp_fetch, setup_hierarchy):
  122. """Attempts to create an instance from one in the hierarchy."""
  123. byo_instance = copy.deepcopy(byo_metadata_json)
  124. byo_instance["display_name"] = "user"
  125. byo_instance["name"] = "byo_2"
  126. body = json.dumps(byo_instance)
  127. with pytest.raises(HTTPClientError) as e:
  128. await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, body=body, method="POST")
  129. assert expected_http_error(e, 409)
  130. # Confirm the instance was not changed
  131. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE)
  132. assert r.code == 200
  133. metadata = json.loads(r.body.decode())
  134. assert isinstance(metadata, dict)
  135. assert len(metadata) == 1
  136. instances = metadata[METADATA_TEST_SCHEMASPACE]
  137. assert len(instances) == 3
  138. assert isinstance(instances, list)
  139. byo_2 = get_instance(instances, "name", "byo_2")
  140. assert byo_2["display_name"] == "factory"
  141. async def test_create_invalid_instance(jp_fetch):
  142. """Create a simple instance - not conflicting with factory instances."""
  143. invalid = copy.deepcopy(invalid_metadata_json)
  144. invalid["name"] = "invalid"
  145. body = json.dumps(invalid)
  146. with pytest.raises(HTTPClientError) as e:
  147. await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, body=body, method="POST")
  148. assert expected_http_error(e, 400)
  149. async def test_create_instance_missing_schema(jp_fetch, schemaspace_location):
  150. """Attempt to create an instance using an invalid schema"""
  151. missing_schema = copy.deepcopy(valid_metadata_json)
  152. missing_schema["name"] = "missing_schema"
  153. missing_schema["schema_name"] = "missing_schema"
  154. missing_schema.pop("display_name")
  155. body = json.dumps(missing_schema)
  156. with pytest.raises(HTTPClientError) as e:
  157. await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, body=body, method="POST")
  158. assert expected_http_error(e, 404)
  159. # Ensure instance was not created. Can't use REST here since it will correctly trigger 404
  160. # even though an instance was created and not removed due to failure to validate (due to
  161. # missing schema). Fixed by trapping the FileNotFoundError raised due to no schema.
  162. assert not os.path.exists(os.path.join(schemaspace_location, "missing_schema.json"))
  163. with pytest.raises(HTTPClientError) as e:
  164. await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "missing_schema")
  165. assert expected_http_error(e, 404)
  166. async def test_update_non_existent(jp_fetch, schemaspace_location):
  167. """Attempt to update a non-existent instance."""
  168. # Try to update a non-existent instance - 404 expected...
  169. valid = copy.deepcopy(valid_metadata_json)
  170. valid["name"] = "valid"
  171. valid["metadata"]["number_range_test"] = 7
  172. body = json.dumps(valid)
  173. # Update (non-existent) instance
  174. with pytest.raises(HTTPClientError) as e:
  175. await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "valid", body=body, method="PUT")
  176. assert expected_http_error(e, 404)
  177. async def test_update_instance(jp_fetch, schemaspace_location):
  178. """Update a simple instance."""
  179. # Create an instance, then update
  180. create_json_file(schemaspace_location, "valid.json", valid_metadata_json)
  181. valid = copy.deepcopy(valid_metadata_json)
  182. valid["name"] = "valid"
  183. valid["metadata"]["number_range_test"] = 7
  184. body = json.dumps(valid)
  185. # Update instance
  186. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID, "valid", body=body, method="PUT")
  187. assert r.code == 200
  188. instance = json.loads(r.body.decode())
  189. assert instance["metadata"]["number_range_test"] == 7
  190. # Confirm update via jp_fetch
  191. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "valid")
  192. assert r.code == 200
  193. instance = json.loads(r.body.decode())
  194. assert instance["metadata"]["number_range_test"] == 7
  195. async def test_invalid_update(jp_fetch, schemaspace_location):
  196. """Update a simple instance with invalid metadata."""
  197. # Create an instance, then update with invalid metadata
  198. create_json_file(schemaspace_location, "update_bad_md.json", valid_metadata_json)
  199. # Fetch it to get the valid instance
  200. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "update_bad_md")
  201. assert r.code == 200
  202. instance = json.loads(r.body.decode())
  203. # Now attempt the update with bad metadata and ensure previous still exists
  204. valid2 = copy.deepcopy(valid_metadata_json)
  205. valid2["name"] = "valid"
  206. valid2["metadata"]["number_range_test"] = 42
  207. body2 = json.dumps(valid2)
  208. with pytest.raises(HTTPClientError) as e:
  209. await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID, "update_bad_md", body=body2, method="PUT")
  210. assert expected_http_error(e, 400)
  211. # Fetch again and ensure it matches the previous instance
  212. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE_ID, "update_bad_md")
  213. assert r.code == 200
  214. instance2 = json.loads(r.body.decode())
  215. assert instance2 == instance
  216. async def test_update_fields(jp_fetch, schemaspace_location):
  217. # Create an instance, then update with a new field
  218. create_json_file(schemaspace_location, "update_fields.json", valid_metadata_json)
  219. valid = copy.deepcopy(valid_metadata_json)
  220. valid["metadata"]["number_range_test"] = 7
  221. body = json.dumps(valid)
  222. # Update instance adding number_range_test
  223. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "update_fields", body=body, method="PUT")
  224. assert r.code == 200
  225. instance = json.loads(r.body.decode())
  226. assert instance["metadata"]["number_range_test"] == 7
  227. # Add a new field (per schema) and remove another -
  228. valid["metadata"].pop("number_range_test")
  229. valid["metadata"]["string_length_test"] = "valid len"
  230. body = json.dumps(valid)
  231. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "update_fields", body=body, method="PUT")
  232. assert r.code == 200
  233. instance = json.loads(r.body.decode())
  234. assert instance["metadata"]["string_length_test"] == "valid len"
  235. assert "number_range_test" not in instance["metadata"]
  236. async def test_update_hierarchy_instance(jp_fetch, setup_hierarchy):
  237. """Update a simple instance - that's conflicting with factory instances."""
  238. # Do not name intentionally, since this is an update
  239. byo_instance = copy.deepcopy(byo_metadata_json)
  240. byo_instance["display_name"] = "user"
  241. byo_instance["metadata"]["number_range_test"] = 7
  242. body = json.dumps(byo_instance)
  243. # Because this is considered an update, replacement is enabled.
  244. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "byo_2", body=body, method="PUT")
  245. assert r.code == 200
  246. # Confirm the instances and ensure byo_2 is in USER area
  247. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE)
  248. assert r.code == 200
  249. metadata = json.loads(r.body.decode())
  250. assert isinstance(metadata, dict)
  251. assert len(metadata) == 1
  252. instances = metadata[METADATA_TEST_SCHEMASPACE]
  253. assert len(instances) == 3
  254. assert isinstance(instances, list)
  255. byo_2 = get_instance(instances, "name", "byo_2")
  256. assert byo_2["schema_name"] == byo_metadata_json["schema_name"]
  257. assert byo_2["metadata"]["number_range_test"] == 7
  258. # Attempt to rename the resource, exception expected.
  259. byo_2["name"] = "byo_2_renamed"
  260. body = json.dumps(byo_2)
  261. with pytest.raises(HTTPClientError) as e:
  262. await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "byo_2", body=body, method="PUT")
  263. assert expected_http_error(e, 400)
  264. # Confirm no update occurred
  265. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "byo_2")
  266. assert r.code == 200
  267. instance = json.loads(r.body.decode())
  268. assert instance["name"] == "byo_2"
  269. async def test_delete_instance(jp_fetch, schemaspace_location, setup_data):
  270. """Create a simple instance - not conflicting with factory instances and delete it."""
  271. # First, attempt to delete non-existent resource, exception expected.
  272. with pytest.raises(HTTPClientError) as e:
  273. await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "missing", method="DELETE")
  274. assert expected_http_error(e, 404)
  275. create_json_file(schemaspace_location, "valid.json", valid_metadata_json)
  276. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "valid", method="DELETE")
  277. assert r.code == 204
  278. # Confirm deletion
  279. with pytest.raises(HTTPClientError) as e:
  280. await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "valid", method="DELETE")
  281. assert expected_http_error(e, 404)
  282. async def test_delete_hierarchy_instance(jp_fetch, schemaspace_location, setup_hierarchy):
  283. """Create a simple instance - that conflicts with factory instances and delete it only if local."""
  284. with pytest.raises(HTTPClientError) as e:
  285. await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "byo_2", method="DELETE")
  286. assert expected_http_error(e, 403)
  287. # create local instance, delete should succeed
  288. create_json_file(schemaspace_location, "byo_2.json", byo_metadata_json)
  289. r = await jp_fetch("elyra", "metadata", METADATA_TEST_SCHEMASPACE, "byo_2", method="DELETE")
  290. assert r.code == 204
  291. async def test_bogus_schema(jp_fetch):
  292. # Validate missing is not found
  293. # Remove self.request (and other 'self.' prefixes) once transition to jupyter_server occurs
  294. with pytest.raises(HTTPClientError) as e:
  295. await jp_fetch("elyra", "schema", "bogus")
  296. assert expected_http_error(e, 404)
  297. async def test_missing_runtimes_schema(jp_fetch):
  298. # Validate missing is not found
  299. with pytest.raises(HTTPClientError) as e:
  300. await jp_fetch("elyra", "schema", "runtimes", "missing")
  301. assert expected_http_error(e, 404)
  302. async def test_get_runtimes_schemas(jp_fetch):
  303. # Ensure all schema for runtimes can be found
  304. await _get_schemaspace_schemas(jp_fetch, "runtimes", ["kfp", "airflow"])
  305. async def test_get_code_snippets_schemas(jp_fetch):
  306. # Ensure all schema for code-snippets can be found
  307. await _get_schemaspace_schemas(jp_fetch, "code-snippets", ["code-snippet"])
  308. async def test_get_test_schemas(jp_fetch):
  309. # Ensure all schema for metadata_tests can be found
  310. await _get_schemaspace_schemas(jp_fetch, METADATA_TEST_SCHEMASPACE, ["metadata-test", "metadata-test2"])
  311. async def test_get_runtimes_schema(jp_fetch):
  312. # Ensure all schema for runtimes can be found
  313. await _get_schemaspace_schema(jp_fetch, "runtimes", "kfp")
  314. async def test_get_code_snippets_schema(jp_fetch):
  315. # Ensure all schema for code-snippets can be found
  316. await _get_schemaspace_schema(jp_fetch, "code-snippets", "code-snippet")
  317. async def test_get_test_schema(jp_fetch):
  318. # Ensure all schema for metadata-test can be found
  319. await _get_schemaspace_schema(jp_fetch, METADATA_TEST_SCHEMASPACE, "metadata-test")
  320. async def _get_schemaspace_schemas(jp_fetch, schemaspace, expected):
  321. r = await jp_fetch(
  322. "elyra",
  323. "schema",
  324. schemaspace,
  325. )
  326. assert r.code == 200
  327. schemaspace_schemas = json.loads(r.body.decode())
  328. assert isinstance(schemaspace_schemas, dict)
  329. assert len(schemaspace_schemas) == 1
  330. schemas = schemaspace_schemas[schemaspace]
  331. assert len(schemas) == len(expected)
  332. for expected_schema in expected:
  333. assert get_instance(schemas, "name", expected_schema)
  334. async def _get_schemaspace_schema(jp_fetch, schemaspace, expected):
  335. r = await jp_fetch("elyra", "schema", schemaspace, expected)
  336. assert r.code == 200
  337. schemaspace_schema = json.loads(r.body.decode())
  338. assert isinstance(schemaspace_schema, dict)
  339. assert expected == schemaspace_schema["name"]
  340. assert schemaspace == schemaspace_schema["schemaspace"]
  341. async def test_get_schemaspaces(jp_fetch):
  342. expected_schemaspaces = ["runtimes", "code-snippets"]
  343. r = await jp_fetch("elyra", "schemaspace")
  344. assert r.code == 200
  345. schemaspaces = json.loads(r.body.decode("utf-8"))
  346. assert isinstance(schemaspaces, dict)
  347. assert len(schemaspaces["schemaspaces"]) >= len(expected_schemaspaces)
  348. for expected_schemaspace in expected_schemaspaces:
  349. assert expected_schemaspace in schemaspaces["schemaspaces"]
  350. async def test_get_schemaspace_info(jp_fetch):
  351. r = await jp_fetch("elyra", "schemaspace", METADATA_TEST_SCHEMASPACE)
  352. assert r.code == 200
  353. schemaspace_info = json.loads(r.body.decode("utf-8"))
  354. assert "name" in schemaspace_info
  355. assert "id" in schemaspace_info
  356. assert "display_name" in schemaspace_info
  357. assert "description" in schemaspace_info
  358. assert "schemas" not in schemaspace_info
  359. assert schemaspace_info["name"] == METADATA_TEST_SCHEMASPACE
  360. assert schemaspace_info["id"] == METADATA_TEST_SCHEMASPACE_ID
  361. async def test_get_missing_schemaspace_info(jp_fetch):
  362. with pytest.raises(HTTPClientError) as e:
  363. await jp_fetch("elyra", "schemaspace", "missing-schemaspace")
  364. assert expected_http_error(e, 404)