test_queue_cognito.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. import mock
  2. import pytest
  3. from boto3.dynamodb.conditions import Key
  4. pytestmark = [
  5. pytest.mark.acceptance_cognito,
  6. pytest.mark.api,
  7. pytest.mark.queue,
  8. pytest.mark.usefixtures("empty_jobs"),
  9. ]
  10. @pytest.mark.auth
  11. def test_auth(api_client_cognito, queue_base_endpoint):
  12. headers = {"Authorization": None}
  13. assert (
  14. 401
  15. == api_client_cognito.patch(
  16. queue_base_endpoint, json={}, headers=headers
  17. ).status_code
  18. )
  19. assert (
  20. 401 == api_client_cognito.get(queue_base_endpoint, headers=headers).status_code
  21. )
  22. assert (
  23. 401
  24. == api_client_cognito.delete(
  25. "{}/matches".format(queue_base_endpoint), json={}, headers=headers
  26. ).status_code
  27. )
  28. assert (
  29. 401
  30. == api_client_cognito.delete(queue_base_endpoint, headers=headers).status_code
  31. )
  32. def test_it_adds_to_queue(api_client_cognito, queue_base_endpoint, queue_table, stack):
  33. # Arrange
  34. key = "test"
  35. item = {
  36. "MatchId": key,
  37. "DataMappers": ["a", "b"],
  38. }
  39. expected = {
  40. "DeletionQueueItemId": mock.ANY,
  41. "MatchId": key,
  42. "CreatedAt": mock.ANY,
  43. "DataMappers": ["a", "b"],
  44. "CreatedBy": {"Username": "aws-uk-sa-builders@amazon.com", "Sub": mock.ANY},
  45. "Type": "Simple",
  46. }
  47. # Act
  48. response = api_client_cognito.patch(queue_base_endpoint, json=item)
  49. response_body = response.json()
  50. # Assert
  51. # Check the response is ok
  52. assert 201 == response.status_code
  53. assert expected == response_body
  54. assert (
  55. response.headers.get("Access-Control-Allow-Origin")
  56. == stack["APIAccessControlAllowOriginHeader"]
  57. )
  58. # Check the item exists in the DDB Table
  59. query_result = queue_table.get_item(
  60. Key={"DeletionQueueItemId": response_body["DeletionQueueItemId"]}
  61. )
  62. assert query_result["Item"]
  63. assert expected == query_result["Item"]
  64. def test_it_adds_composite_to_queue(
  65. api_client_cognito, queue_base_endpoint, queue_table, stack
  66. ):
  67. # Arrange
  68. key = [
  69. {"Column": "first_name", "Value": "John"},
  70. {"Column": "last_name", "Value": "Doe"},
  71. ]
  72. item = {
  73. "MatchId": key,
  74. "Type": "Composite",
  75. "DataMappers": ["a"],
  76. }
  77. expected = {
  78. "DeletionQueueItemId": mock.ANY,
  79. "MatchId": key,
  80. "CreatedAt": mock.ANY,
  81. "DataMappers": ["a"],
  82. "CreatedBy": {"Username": "aws-uk-sa-builders@amazon.com", "Sub": mock.ANY},
  83. "Type": "Composite",
  84. }
  85. # Act
  86. response = api_client_cognito.patch(queue_base_endpoint, json=item)
  87. response_body = response.json()
  88. # Assert
  89. # Check the response is ok
  90. assert 201 == response.status_code
  91. assert expected == response_body
  92. assert (
  93. response.headers.get("Access-Control-Allow-Origin")
  94. == stack["APIAccessControlAllowOriginHeader"]
  95. )
  96. # Check the item exists in the DDB Table
  97. query_result = queue_table.get_item(
  98. Key={"DeletionQueueItemId": response_body["DeletionQueueItemId"]}
  99. )
  100. assert query_result["Item"]
  101. assert expected == query_result["Item"]
  102. def test_it_adds_batch_to_queue(
  103. api_client_cognito, queue_base_endpoint, queue_table, stack
  104. ):
  105. # Arrange
  106. items = {
  107. "Matches": [
  108. {"MatchId": "test", "DataMappers": ["a", "b"]},
  109. {"MatchId": "test1", "DataMappers": ["a", "b"], "Type": "Simple"},
  110. {
  111. "MatchId": [
  112. {"Column": "first_name", "Value": "John"},
  113. {"Column": "last_name", "Value": "Doe"},
  114. ],
  115. "DataMappers": ["a"],
  116. "Type": "Composite",
  117. },
  118. ]
  119. }
  120. created_by_mock = {
  121. "Username": "aws-uk-sa-builders@amazon.com",
  122. "Sub": mock.ANY,
  123. }
  124. expected = {
  125. "Matches": [
  126. {
  127. "DeletionQueueItemId": mock.ANY,
  128. "MatchId": "test",
  129. "CreatedAt": mock.ANY,
  130. "DataMappers": ["a", "b"],
  131. "CreatedBy": created_by_mock,
  132. "Type": "Simple",
  133. },
  134. {
  135. "DeletionQueueItemId": mock.ANY,
  136. "MatchId": "test1",
  137. "CreatedAt": mock.ANY,
  138. "DataMappers": ["a", "b"],
  139. "CreatedBy": created_by_mock,
  140. "Type": "Simple",
  141. },
  142. {
  143. "DeletionQueueItemId": mock.ANY,
  144. "MatchId": [
  145. {"Column": "first_name", "Value": "John"},
  146. {"Column": "last_name", "Value": "Doe"},
  147. ],
  148. "CreatedAt": mock.ANY,
  149. "DataMappers": ["a"],
  150. "CreatedBy": created_by_mock,
  151. "Type": "Composite",
  152. },
  153. ]
  154. }
  155. # Act
  156. response = api_client_cognito.patch(
  157. "{}/matches".format(queue_base_endpoint), json=items
  158. )
  159. response_body = response.json()
  160. # Assert
  161. # Check the response is ok
  162. assert 201 == response.status_code
  163. assert expected == response_body
  164. assert (
  165. response.headers.get("Access-Control-Allow-Origin")
  166. == stack["APIAccessControlAllowOriginHeader"]
  167. )
  168. # Check the items exists in the DDB Table
  169. for i, match in enumerate(expected["Matches"]):
  170. query_result = queue_table.get_item(
  171. Key={
  172. "DeletionQueueItemId": response_body["Matches"][i][
  173. "DeletionQueueItemId"
  174. ]
  175. }
  176. )
  177. assert query_result["Item"]
  178. assert match == query_result["Item"]
  179. def test_it_rejects_invalid_add_to_queue(
  180. api_client_cognito, queue_base_endpoint, stack
  181. ):
  182. scenarios = [
  183. {"INVALID": "PAYLOAD"},
  184. {"Type": "Composite", "DataMappers": ["a"], "MatchId": ["a"]},
  185. {"Type": "Composite", "DataMappers": ["a"], "MatchId": [{}]},
  186. {"Type": "Composite", "DataMappers": ["a"], "MatchId": [{"Column": "a"}]},
  187. {"Type": "Composite", "DataMappers": ["a"], "MatchId": [{"Value": "a"}]},
  188. ]
  189. for scenario in scenarios:
  190. response = api_client_cognito.patch(queue_base_endpoint, json=scenario)
  191. assert 422 == response.status_code
  192. assert (
  193. response.headers.get("Access-Control-Allow-Origin")
  194. == stack["APIAccessControlAllowOriginHeader"]
  195. )
  196. def test_it_gets_queue(
  197. api_client_cognito, queue_base_endpoint, del_queue_factory, stack
  198. ):
  199. # Arrange
  200. del_queue_item = del_queue_factory()
  201. # Act
  202. response = api_client_cognito.get(queue_base_endpoint)
  203. response_body = response.json()
  204. # Assert
  205. assert response.status_code == 200
  206. assert isinstance(response_body.get("MatchIds"), list)
  207. assert del_queue_item in response_body["MatchIds"]
  208. assert (
  209. response.headers.get("Access-Control-Allow-Origin")
  210. == stack["APIAccessControlAllowOriginHeader"]
  211. )
  212. assert response.headers.get("Access-Control-Expose-Headers") == "content-length"
  213. def test_it_rejects_invalid_deletion(
  214. api_client_cognito, del_queue_factory, queue_base_endpoint, queue_table, stack
  215. ):
  216. # Arrange
  217. del_queue_item = del_queue_factory()
  218. match_id = del_queue_item["MatchId"]
  219. # Act
  220. response = api_client_cognito.delete(
  221. "{}/matches".format(queue_base_endpoint),
  222. json={"Matches": [{"MatchId": match_id}]},
  223. )
  224. # Assert
  225. assert 422 == response.status_code
  226. assert (
  227. response.headers.get("Access-Control-Allow-Origin")
  228. == stack["APIAccessControlAllowOriginHeader"]
  229. )
  230. def test_it_cancels_deletion(
  231. api_client_cognito, del_queue_factory, queue_base_endpoint, queue_table, stack
  232. ):
  233. # Arrange
  234. del_queue_item = del_queue_factory()
  235. deletion_queue_item_id = del_queue_item["DeletionQueueItemId"]
  236. # Act
  237. response = api_client_cognito.delete(
  238. "{}/matches".format(queue_base_endpoint),
  239. json={"Matches": [{"DeletionQueueItemId": deletion_queue_item_id}]},
  240. )
  241. # Assert
  242. assert 204 == response.status_code
  243. assert (
  244. response.headers.get("Access-Control-Allow-Origin")
  245. == stack["APIAccessControlAllowOriginHeader"]
  246. )
  247. # Check the item doesn't exist in the DDB Table
  248. query_result = queue_table.get_item(
  249. Key={"DeletionQueueItemId": deletion_queue_item_id}
  250. )
  251. assert not "Item" in query_result
  252. def test_it_handles_not_found(
  253. api_client_cognito, del_queue_factory, queue_base_endpoint, queue_table, stack
  254. ):
  255. # Arrange
  256. deletion_queue_item_id = "test"
  257. # Act
  258. response = api_client_cognito.delete(
  259. "{}/matches".format(queue_base_endpoint),
  260. json={"Matches": [{"DeletionQueueItemId": deletion_queue_item_id}]},
  261. )
  262. # Assert
  263. assert 204 == response.status_code
  264. assert (
  265. response.headers.get("Access-Control-Allow-Origin")
  266. == stack["APIAccessControlAllowOriginHeader"]
  267. )
  268. # Check the item doesn't exist in the DDB Table
  269. query_result = queue_table.get_item(
  270. Key={"DeletionQueueItemId": deletion_queue_item_id}
  271. )
  272. assert not "Item" in query_result
  273. def test_it_disables_cancel_deletion_whilst_job_in_progress(
  274. api_client_cognito,
  275. queue_base_endpoint,
  276. sf_client,
  277. job_table,
  278. execution_exists_waiter,
  279. job_finished_waiter,
  280. queue_table,
  281. del_queue_factory,
  282. stack,
  283. ):
  284. # Arrange
  285. del_queue_item = del_queue_factory()
  286. deletion_queue_item_id = del_queue_item["DeletionQueueItemId"]
  287. response = api_client_cognito.delete(queue_base_endpoint)
  288. response_body = response.json()
  289. job_id = response_body["Id"]
  290. execution_arn = "{}:{}".format(
  291. stack["StateMachineArn"].replace("stateMachine", "execution"), job_id
  292. )
  293. # Act
  294. response = api_client_cognito.delete(
  295. "{}/matches".format(queue_base_endpoint),
  296. json={"Matches": [{"DeletionQueueItemId": deletion_queue_item_id}]},
  297. )
  298. try:
  299. # Assert
  300. assert 400 == response.status_code
  301. # Check the item still exists in the DDB Table
  302. query_result = queue_table.get_item(
  303. Key={"DeletionQueueItemId": deletion_queue_item_id}
  304. )
  305. assert query_result["Item"]
  306. finally:
  307. execution_exists_waiter.wait(executionArn=execution_arn)
  308. sf_client.stop_execution(executionArn=execution_arn)
  309. job_finished_waiter.wait(
  310. TableName=job_table.name, Key={"Id": {"S": job_id}, "Sk": {"S": job_id}}
  311. )
  312. def test_it_processes_queue(
  313. api_client_cognito,
  314. queue_base_endpoint,
  315. sf_client,
  316. job_table,
  317. stack,
  318. job_complete_waiter,
  319. config_mutator,
  320. ):
  321. # Arrange
  322. config_mutator(JobDetailsRetentionDays=0)
  323. # Act
  324. response = api_client_cognito.delete(queue_base_endpoint)
  325. response_body = response.json()
  326. job_id = response_body["Id"]
  327. execution_arn = "{}:{}".format(
  328. stack["StateMachineArn"].replace("stateMachine", "execution"), job_id
  329. )
  330. try:
  331. # Assert
  332. assert 202 == response.status_code
  333. assert "Id" in response_body
  334. # Check the job was written to DynamoDB
  335. job_complete_waiter.wait(
  336. TableName=job_table.name, Key={"Id": {"S": job_id}, "Sk": {"S": job_id}}
  337. )
  338. query_result = job_table.query(
  339. KeyConditionExpression=Key("Id").eq(job_id) & Key("Sk").eq(job_id)
  340. )
  341. assert 1 == len(query_result["Items"])
  342. assert (
  343. response.headers.get("Access-Control-Allow-Origin")
  344. == stack["APIAccessControlAllowOriginHeader"]
  345. )
  346. finally:
  347. sf_client.stop_execution(executionArn=execution_arn)
  348. def test_it_sets_expiry(
  349. api_client_cognito,
  350. queue_base_endpoint,
  351. sf_client,
  352. job_table,
  353. stack,
  354. job_complete_waiter,
  355. config_mutator,
  356. ):
  357. # Arrange
  358. config_mutator(JobDetailsRetentionDays=1)
  359. # Act
  360. response = api_client_cognito.delete(queue_base_endpoint)
  361. response_body = response.json()
  362. job_id = response_body["Id"]
  363. execution_arn = "{}:{}".format(
  364. stack["StateMachineArn"].replace("stateMachine", "execution"), job_id
  365. )
  366. try:
  367. # Assert
  368. assert 202 == response.status_code
  369. assert "Id" in response_body
  370. # Check the job was written to DynamoDB
  371. job_complete_waiter.wait(
  372. TableName=job_table.name, Key={"Id": {"S": job_id}, "Sk": {"S": job_id}}
  373. )
  374. query_result = job_table.query(KeyConditionExpression=Key("Id").eq(job_id))
  375. assert len(query_result["Items"]) > 0
  376. assert all(["Expires" in i for i in query_result["Items"]])
  377. assert (
  378. response.headers.get("Access-Control-Allow-Origin")
  379. == stack["APIAccessControlAllowOriginHeader"]
  380. )
  381. finally:
  382. sf_client.stop_execution(executionArn=execution_arn)
  383. def test_it_only_allows_one_concurrent_execution(
  384. api_client_cognito, queue_base_endpoint, sf_client, stack, execution_exists_waiter
  385. ):
  386. # Arrange
  387. # Start a job
  388. response = api_client_cognito.delete(queue_base_endpoint)
  389. response_body = response.json()
  390. job_id = response_body["Id"]
  391. execution_arn = "{}:{}".format(
  392. stack["StateMachineArn"].replace("stateMachine", "execution"), job_id
  393. )
  394. # Act
  395. # Start a second job
  396. response = api_client_cognito.delete(queue_base_endpoint)
  397. try:
  398. # Assert
  399. assert 400 == response.status_code
  400. assert (
  401. response.headers.get("Access-Control-Allow-Origin")
  402. == stack["APIAccessControlAllowOriginHeader"]
  403. )
  404. finally:
  405. execution_exists_waiter.wait(executionArn=execution_arn)
  406. sf_client.stop_execution(executionArn=execution_arn)