test_data_mappers_iam.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. import mock
  2. import pytest
  3. from copy import deepcopy
  4. from boto3.dynamodb.conditions import Key
  5. pytestmark = [
  6. pytest.mark.acceptance_iam,
  7. pytest.mark.api,
  8. pytest.mark.data_mappers,
  9. pytest.mark.usefixtures("empty_data_mappers"),
  10. ]
  11. @pytest.mark.auth
  12. def test_auth(api_client_iam, data_mapper_base_endpoint):
  13. headers = {"Authorization": None}
  14. assert (
  15. 403
  16. == api_client_iam.put(
  17. "{}/{}".format(data_mapper_base_endpoint, "a"), headers=headers
  18. ).status_code
  19. )
  20. assert (
  21. 403
  22. == api_client_iam.get(data_mapper_base_endpoint, headers=headers).status_code
  23. )
  24. assert (
  25. 403
  26. == api_client_iam.delete(
  27. "{}/{}".format(data_mapper_base_endpoint, "a"), headers=headers
  28. ).status_code
  29. )
  30. def test_it_creates_data_mapper(
  31. api_client_iam,
  32. data_mapper_base_endpoint,
  33. data_mapper_table,
  34. glue_table_factory,
  35. stack,
  36. iam_arn,
  37. ):
  38. # Arrange
  39. table = glue_table_factory()
  40. key = "test"
  41. data_mapper = {
  42. "DataMapperId": key,
  43. "Columns": ["a"],
  44. "QueryExecutor": "athena",
  45. "QueryExecutorParameters": {
  46. "DataCatalogProvider": "glue",
  47. "Database": table["Database"],
  48. "Table": table["Table"],
  49. },
  50. "Format": "parquet",
  51. "RoleArn": "arn:aws:iam::123456789012:role/S3F2DataAccessRole",
  52. "DeleteOldVersions": False,
  53. "IgnoreObjectNotFoundExceptions": True,
  54. }
  55. # Act
  56. response = api_client_iam.put(
  57. "{}/{}".format(data_mapper_base_endpoint, key), json=data_mapper
  58. )
  59. response_body = response.json()
  60. # Assert
  61. expected = deepcopy(data_mapper)
  62. expected["CreatedBy"] = {
  63. "Username": iam_arn,
  64. "Sub": mock.ANY,
  65. }
  66. expected["DeleteOldVersions"] = False
  67. # Check the response is ok
  68. assert 201 == response.status_code
  69. assert expected == response_body
  70. # Check the item exists in the DDB Table
  71. query_result = data_mapper_table.query(
  72. KeyConditionExpression=Key("DataMapperId").eq(key)
  73. )
  74. assert 1 == len(query_result["Items"])
  75. assert expected == query_result["Items"][0]
  76. assert (
  77. response.headers.get("Access-Control-Allow-Origin")
  78. == stack["APIAccessControlAllowOriginHeader"]
  79. )
  80. def test_it_modifies_data_mapper(
  81. api_client_iam,
  82. data_mapper_base_endpoint,
  83. data_mapper_table,
  84. glue_table_factory,
  85. stack,
  86. ):
  87. # Arrange
  88. table = glue_table_factory()
  89. key = "test"
  90. data_mapper = {
  91. "DataMapperId": key,
  92. "Columns": ["a"],
  93. "QueryExecutor": "athena",
  94. "QueryExecutorParameters": {
  95. "DataCatalogProvider": "glue",
  96. "Database": table["Database"],
  97. "Table": table["Table"],
  98. },
  99. "Format": "parquet",
  100. "RoleArn": "arn:aws:iam::123456789012:role/S3F2DataAccessRole",
  101. "DeleteOldVersions": False,
  102. "IgnoreObjectNotFoundExceptions": False,
  103. }
  104. # Act
  105. create_response = api_client_iam.put(
  106. "{}/{}".format(data_mapper_base_endpoint, key), json=data_mapper
  107. )
  108. data_mapper["Columns"] = ["b"]
  109. response = api_client_iam.put(
  110. "{}/{}".format(data_mapper_base_endpoint, key), json=data_mapper
  111. )
  112. response_body = response.json()
  113. # Assert
  114. assert 201 == response.status_code
  115. assert response_body["Columns"] == ["b"]
  116. # Check the item exists in the DDB Table
  117. query_result = data_mapper_table.query(
  118. KeyConditionExpression=Key("DataMapperId").eq(key)
  119. )
  120. assert 1 == len(query_result["Items"])
  121. assert query_result["Items"][0]["Columns"] == ["b"]
  122. assert (
  123. response.headers.get("Access-Control-Allow-Origin")
  124. == stack["APIAccessControlAllowOriginHeader"]
  125. )
  126. def test_it_creates_without_optionals(
  127. api_client_iam,
  128. data_mapper_base_endpoint,
  129. data_mapper_table,
  130. glue_table_factory,
  131. stack,
  132. iam_arn,
  133. ):
  134. # Arrange
  135. table = glue_table_factory()
  136. key = "test"
  137. data_mapper = {
  138. "DataMapperId": key,
  139. "Columns": ["a"],
  140. "QueryExecutor": "athena",
  141. "QueryExecutorParameters": {
  142. "DataCatalogProvider": "glue",
  143. "Database": table["Database"],
  144. "Table": table["Table"],
  145. },
  146. "RoleArn": "arn:aws:iam::123456789012:role/S3F2DataAccessRole",
  147. }
  148. # Act
  149. response = api_client_iam.put(
  150. "{}/{}".format(data_mapper_base_endpoint, key), json=data_mapper
  151. )
  152. response_body = response.json()
  153. # Assert
  154. expected = deepcopy(data_mapper)
  155. expected["Format"] = "parquet"
  156. expected["CreatedBy"] = {
  157. "Username": iam_arn,
  158. "Sub": mock.ANY,
  159. }
  160. expected["DeleteOldVersions"] = True
  161. expected["IgnoreObjectNotFoundExceptions"] = False
  162. # Check the response is ok
  163. assert 201 == response.status_code
  164. assert expected == response_body
  165. # Check the item exists in the DDB Table
  166. query_result = data_mapper_table.query(
  167. KeyConditionExpression=Key("DataMapperId").eq(key)
  168. )
  169. assert 1 == len(query_result["Items"])
  170. assert expected == query_result["Items"][0]
  171. assert (
  172. response.headers.get("Access-Control-Allow-Origin")
  173. == stack["APIAccessControlAllowOriginHeader"]
  174. )
  175. def test_it_rejects_invalid_data_mapper(
  176. api_client_iam, data_mapper_base_endpoint, glue_table_factory, stack
  177. ):
  178. # Arrange
  179. table = glue_table_factory()
  180. key = "test"
  181. data_mapper = {
  182. "DataMapperId": key,
  183. "Columns": ["a"],
  184. "QueryExecutor": "athena",
  185. "QueryExecutorParameters": {
  186. "DataCatalogProvider": "glue",
  187. "Database": table["Database"],
  188. "Table": table["Table"],
  189. },
  190. "RoleArn": "arn:aws:iam::123456789012:role/WrongRoleName",
  191. }
  192. # Act
  193. response = api_client_iam.put(
  194. "{}/{}".format(data_mapper_base_endpoint, key), json=data_mapper
  195. )
  196. response_body = response.json()
  197. # Assert
  198. assert 422 == response.status_code
  199. assert (
  200. response.headers.get("Access-Control-Allow-Origin")
  201. == stack["APIAccessControlAllowOriginHeader"]
  202. )
  203. def test_it_rejects_invalid_role(api_client_iam, data_mapper_base_endpoint, stack):
  204. key = "test"
  205. response = api_client_iam.put(
  206. "{}/{}".format(data_mapper_base_endpoint, key), json={"INVALID": "PAYLOAD"}
  207. )
  208. assert 422 == response.status_code
  209. assert (
  210. response.headers.get("Access-Control-Allow-Origin")
  211. == stack["APIAccessControlAllowOriginHeader"]
  212. )
  213. def test_it_rejects_invalid_data_source(
  214. api_client_iam, data_mapper_base_endpoint, stack
  215. ):
  216. key = "test"
  217. response = api_client_iam.put(
  218. "{}/{}".format(data_mapper_base_endpoint, key),
  219. json={
  220. "Columns": ["column"],
  221. "QueryExecutor": "unsupported",
  222. "QueryExecutorParameters": {},
  223. "Format": "parquet",
  224. },
  225. )
  226. assert 422 == response.status_code
  227. assert (
  228. response.headers.get("Access-Control-Allow-Origin")
  229. == stack["APIAccessControlAllowOriginHeader"]
  230. )
  231. def test_it_rejects_invalid_data_catalog_provider(
  232. api_client_iam, data_mapper_base_endpoint, stack
  233. ):
  234. key = "test"
  235. response = api_client_iam.put(
  236. "{}/{}".format(data_mapper_base_endpoint, key),
  237. json={
  238. "Columns": ["column"],
  239. "QueryExecutor": "athena",
  240. "QueryExecutorParameters": {
  241. "Database": "database",
  242. "Table": "table",
  243. "DataCatalogProvider": "invalid",
  244. },
  245. "Format": "parquet",
  246. "RoleArn": "arn:aws:iam::123456789012:role/S3F2DataAccessRole",
  247. },
  248. )
  249. assert 422 == response.status_code
  250. assert (
  251. response.headers.get("Access-Control-Allow-Origin")
  252. == stack["APIAccessControlAllowOriginHeader"]
  253. )
  254. def test_it_rejects_missing_glue_catalog(
  255. api_client_iam, data_mapper_base_endpoint, stack
  256. ):
  257. key = "test"
  258. response = api_client_iam.put(
  259. "{}/{}".format(data_mapper_base_endpoint, key),
  260. json={
  261. "Columns": ["column"],
  262. "QueryExecutor": "athena",
  263. "QueryExecutorParameters": {
  264. "Database": "non_existent",
  265. "Table": "non_existent",
  266. "DataCatalogProvider": "glue",
  267. },
  268. "Format": "parquet",
  269. "RoleArn": "arn:aws:iam::123456789012:role/S3F2DataAccessRole",
  270. },
  271. )
  272. assert 400 == response.status_code
  273. assert (
  274. response.headers.get("Access-Control-Allow-Origin")
  275. == stack["APIAccessControlAllowOriginHeader"]
  276. )
  277. def test_it_gets_all_data_mappers(
  278. api_client_iam, data_mapper_base_endpoint, glue_data_mapper_factory, stack
  279. ):
  280. # Arrange
  281. item = glue_data_mapper_factory()
  282. # Act
  283. response = api_client_iam.get(data_mapper_base_endpoint)
  284. response_body = response.json()
  285. # Assert
  286. assert response.status_code == 200
  287. assert isinstance(response_body.get("DataMappers"), list)
  288. assert item in response_body["DataMappers"]
  289. assert (
  290. response.headers.get("Access-Control-Allow-Origin")
  291. == stack["APIAccessControlAllowOriginHeader"]
  292. )
  293. def test_it_gets_data_mapper(
  294. api_client_iam, data_mapper_base_endpoint, glue_data_mapper_factory, stack
  295. ):
  296. # Arrange
  297. item = glue_data_mapper_factory()
  298. key = item["DataMapperId"]
  299. # Act
  300. response = api_client_iam.get("{}/{}".format(data_mapper_base_endpoint, key))
  301. response_body = response.json()
  302. # Assert
  303. assert response.status_code == 200
  304. assert item == response_body
  305. assert (
  306. response.headers.get("Access-Control-Allow-Origin")
  307. == stack["APIAccessControlAllowOriginHeader"]
  308. )
  309. def test_it_deletes_data_mapper(
  310. api_client_iam,
  311. glue_data_mapper_factory,
  312. data_mapper_base_endpoint,
  313. data_mapper_table,
  314. stack,
  315. ):
  316. # Arrange
  317. item = glue_data_mapper_factory()
  318. key = item["DataMapperId"]
  319. # Act
  320. response = api_client_iam.delete("{}/{}".format(data_mapper_base_endpoint, key))
  321. # Assert
  322. assert 204 == response.status_code
  323. # Check the item doesn't exist in the DDB Table
  324. query_result = data_mapper_table.query(
  325. KeyConditionExpression=Key("DataMapperId").eq(key)
  326. )
  327. assert 0 == len(query_result["Items"])
  328. assert (
  329. response.headers.get("Access-Control-Allow-Origin")
  330. == stack["APIAccessControlAllowOriginHeader"]
  331. )