test_stats_updater.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. import boto3
  2. import pytest
  3. from mock import patch
  4. from backend.lambdas.jobs.stats_updater import update_stats
  5. pytestmark = [pytest.mark.unit, pytest.mark.jobs]
  6. @patch("backend.lambdas.jobs.stats_updater.table")
  7. def test_it_handles_successful_queries(table):
  8. resp = update_stats(
  9. "job123",
  10. [
  11. {
  12. "Id": "job123",
  13. "Sk": "123456",
  14. "Type": "JobEvent",
  15. "CreatedAt": 123.0,
  16. "EventName": "QuerySucceeded",
  17. "EventData": {
  18. "Statistics": {
  19. "DataScannedInBytes": 10,
  20. "EngineExecutionTimeInMillis": 100,
  21. }
  22. },
  23. }
  24. ],
  25. )
  26. table.update_item.assert_called_with(
  27. Key={
  28. "Id": "job123",
  29. "Sk": "job123",
  30. },
  31. ConditionExpression="#Id = :Id AND #Sk = :Sk",
  32. UpdateExpression="set #qt = if_not_exists(#qt, :z) + :qt, "
  33. "#qs = if_not_exists(#qs, :z) + :qs, "
  34. "#qf = if_not_exists(#qf, :z) + :qf, "
  35. "#qb = if_not_exists(#qb, :z) + :qb, "
  36. "#qm = if_not_exists(#qm, :z) + :qm, "
  37. "#ou = if_not_exists(#ou, :z) + :ou, "
  38. "#os = if_not_exists(#os, :z) + :os, "
  39. "#of = if_not_exists(#of, :z) + :of, "
  40. "#or = if_not_exists(#or, :z) + :or",
  41. ExpressionAttributeNames={
  42. "#Id": "Id",
  43. "#Sk": "Sk",
  44. "#qt": "TotalQueryCount",
  45. "#qs": "TotalQuerySucceededCount",
  46. "#qf": "TotalQueryFailedCount",
  47. "#qb": "TotalQueryScannedInBytes",
  48. "#qm": "TotalQueryTimeInMillis",
  49. "#ou": "TotalObjectUpdatedCount",
  50. "#os": "TotalObjectUpdateSkippedCount",
  51. "#of": "TotalObjectUpdateFailedCount",
  52. "#or": "TotalObjectRollbackFailedCount",
  53. },
  54. ExpressionAttributeValues={
  55. ":Id": "job123",
  56. ":Sk": "job123",
  57. ":qt": 1,
  58. ":qs": 1,
  59. ":qf": 0,
  60. ":qb": 10,
  61. ":qm": 100,
  62. ":ou": 0,
  63. ":os": 0,
  64. ":of": 0,
  65. ":or": 0,
  66. ":z": 0,
  67. },
  68. ReturnValues="ALL_NEW",
  69. )
  70. @patch("backend.lambdas.jobs.stats_updater.table")
  71. def test_it_handles_failed_queries(table):
  72. resp = update_stats(
  73. "job123",
  74. [
  75. {
  76. "Id": "job123",
  77. "Sk": "123456",
  78. "Type": "JobEvent",
  79. "CreatedAt": 123.0,
  80. "EventName": "QueryFailed",
  81. "EventData": {},
  82. }
  83. ],
  84. )
  85. table.update_item.assert_called_with(
  86. Key={
  87. "Id": "job123",
  88. "Sk": "job123",
  89. },
  90. ConditionExpression="#Id = :Id AND #Sk = :Sk",
  91. UpdateExpression="set #qt = if_not_exists(#qt, :z) + :qt, "
  92. "#qs = if_not_exists(#qs, :z) + :qs, "
  93. "#qf = if_not_exists(#qf, :z) + :qf, "
  94. "#qb = if_not_exists(#qb, :z) + :qb, "
  95. "#qm = if_not_exists(#qm, :z) + :qm, "
  96. "#ou = if_not_exists(#ou, :z) + :ou, "
  97. "#os = if_not_exists(#os, :z) + :os, "
  98. "#of = if_not_exists(#of, :z) + :of, "
  99. "#or = if_not_exists(#or, :z) + :or",
  100. ExpressionAttributeNames={
  101. "#Id": "Id",
  102. "#Sk": "Sk",
  103. "#qt": "TotalQueryCount",
  104. "#qs": "TotalQuerySucceededCount",
  105. "#qf": "TotalQueryFailedCount",
  106. "#qb": "TotalQueryScannedInBytes",
  107. "#qm": "TotalQueryTimeInMillis",
  108. "#ou": "TotalObjectUpdatedCount",
  109. "#os": "TotalObjectUpdateSkippedCount",
  110. "#of": "TotalObjectUpdateFailedCount",
  111. "#or": "TotalObjectRollbackFailedCount",
  112. },
  113. ExpressionAttributeValues={
  114. ":Id": "job123",
  115. ":Sk": "job123",
  116. ":qt": 1,
  117. ":qs": 0,
  118. ":qf": 1,
  119. ":qb": 0,
  120. ":qm": 0,
  121. ":ou": 0,
  122. ":os": 0,
  123. ":of": 0,
  124. ":or": 0,
  125. ":z": 0,
  126. },
  127. ReturnValues="ALL_NEW",
  128. )
  129. @patch("backend.lambdas.jobs.stats_updater.table")
  130. def test_it_handles_successful_updates(table):
  131. resp = update_stats(
  132. "job123",
  133. [
  134. {
  135. "Id": "job123",
  136. "Sk": "123456",
  137. "Type": "JobEvent",
  138. "CreatedAt": 123.0,
  139. "EventName": "ObjectUpdated",
  140. "EventData": {},
  141. }
  142. ],
  143. )
  144. table.update_item.assert_called_with(
  145. Key={"Id": "job123", "Sk": "job123"},
  146. ConditionExpression="#Id = :Id AND #Sk = :Sk",
  147. UpdateExpression="set #qt = if_not_exists(#qt, :z) + :qt, "
  148. "#qs = if_not_exists(#qs, :z) + :qs, "
  149. "#qf = if_not_exists(#qf, :z) + :qf, "
  150. "#qb = if_not_exists(#qb, :z) + :qb, "
  151. "#qm = if_not_exists(#qm, :z) + :qm, "
  152. "#ou = if_not_exists(#ou, :z) + :ou, "
  153. "#os = if_not_exists(#os, :z) + :os, "
  154. "#of = if_not_exists(#of, :z) + :of, "
  155. "#or = if_not_exists(#or, :z) + :or",
  156. ExpressionAttributeNames={
  157. "#Id": "Id",
  158. "#Sk": "Sk",
  159. "#qt": "TotalQueryCount",
  160. "#qs": "TotalQuerySucceededCount",
  161. "#qf": "TotalQueryFailedCount",
  162. "#qb": "TotalQueryScannedInBytes",
  163. "#qm": "TotalQueryTimeInMillis",
  164. "#ou": "TotalObjectUpdatedCount",
  165. "#os": "TotalObjectUpdateSkippedCount",
  166. "#of": "TotalObjectUpdateFailedCount",
  167. "#or": "TotalObjectRollbackFailedCount",
  168. },
  169. ExpressionAttributeValues={
  170. ":Id": "job123",
  171. ":Sk": "job123",
  172. ":qt": 0,
  173. ":qs": 0,
  174. ":qf": 0,
  175. ":qb": 0,
  176. ":qm": 0,
  177. ":ou": 1,
  178. ":os": 0,
  179. ":of": 0,
  180. ":or": 0,
  181. ":z": 0,
  182. },
  183. ReturnValues="ALL_NEW",
  184. )
  185. @patch("backend.lambdas.jobs.stats_updater.table")
  186. def test_it_handles_failed_updates(table):
  187. resp = update_stats(
  188. "job123",
  189. [
  190. {
  191. "Id": "job123",
  192. "Sk": "123456",
  193. "Type": "JobEvent",
  194. "CreatedAt": 123.0,
  195. "EventName": "ObjectUpdateFailed",
  196. "EventData": {},
  197. }
  198. ],
  199. )
  200. table.update_item.assert_called_with(
  201. Key={"Id": "job123", "Sk": "job123"},
  202. ConditionExpression="#Id = :Id AND #Sk = :Sk",
  203. UpdateExpression="set #qt = if_not_exists(#qt, :z) + :qt, "
  204. "#qs = if_not_exists(#qs, :z) + :qs, "
  205. "#qf = if_not_exists(#qf, :z) + :qf, "
  206. "#qb = if_not_exists(#qb, :z) + :qb, "
  207. "#qm = if_not_exists(#qm, :z) + :qm, "
  208. "#ou = if_not_exists(#ou, :z) + :ou, "
  209. "#os = if_not_exists(#os, :z) + :os, "
  210. "#of = if_not_exists(#of, :z) + :of, "
  211. "#or = if_not_exists(#or, :z) + :or",
  212. ExpressionAttributeNames={
  213. "#Id": "Id",
  214. "#Sk": "Sk",
  215. "#qt": "TotalQueryCount",
  216. "#qs": "TotalQuerySucceededCount",
  217. "#qf": "TotalQueryFailedCount",
  218. "#qb": "TotalQueryScannedInBytes",
  219. "#qm": "TotalQueryTimeInMillis",
  220. "#ou": "TotalObjectUpdatedCount",
  221. "#os": "TotalObjectUpdateSkippedCount",
  222. "#of": "TotalObjectUpdateFailedCount",
  223. "#or": "TotalObjectRollbackFailedCount",
  224. },
  225. ExpressionAttributeValues={
  226. ":Id": "job123",
  227. ":Sk": "job123",
  228. ":qt": 0,
  229. ":qs": 0,
  230. ":qf": 0,
  231. ":qb": 0,
  232. ":qm": 0,
  233. ":ou": 0,
  234. ":os": 0,
  235. ":of": 1,
  236. ":or": 0,
  237. ":z": 0,
  238. },
  239. ReturnValues="ALL_NEW",
  240. )
  241. @patch("backend.lambdas.jobs.stats_updater.table")
  242. def test_it_handles_skipped_updates(table):
  243. resp = update_stats(
  244. "job123",
  245. [
  246. {
  247. "Id": "job123",
  248. "Sk": "123456",
  249. "Type": "JobEvent",
  250. "CreatedAt": 123.0,
  251. "EventName": "ObjectUpdateSkipped",
  252. "EventData": {},
  253. }
  254. ],
  255. )
  256. table.update_item.assert_called_with(
  257. Key={"Id": "job123", "Sk": "job123"},
  258. ConditionExpression="#Id = :Id AND #Sk = :Sk",
  259. UpdateExpression="set #qt = if_not_exists(#qt, :z) + :qt, "
  260. "#qs = if_not_exists(#qs, :z) + :qs, "
  261. "#qf = if_not_exists(#qf, :z) + :qf, "
  262. "#qb = if_not_exists(#qb, :z) + :qb, "
  263. "#qm = if_not_exists(#qm, :z) + :qm, "
  264. "#ou = if_not_exists(#ou, :z) + :ou, "
  265. "#os = if_not_exists(#os, :z) + :os, "
  266. "#of = if_not_exists(#of, :z) + :of, "
  267. "#or = if_not_exists(#or, :z) + :or",
  268. ExpressionAttributeNames={
  269. "#Id": "Id",
  270. "#Sk": "Sk",
  271. "#qt": "TotalQueryCount",
  272. "#qs": "TotalQuerySucceededCount",
  273. "#qf": "TotalQueryFailedCount",
  274. "#qb": "TotalQueryScannedInBytes",
  275. "#qm": "TotalQueryTimeInMillis",
  276. "#ou": "TotalObjectUpdatedCount",
  277. "#os": "TotalObjectUpdateSkippedCount",
  278. "#of": "TotalObjectUpdateFailedCount",
  279. "#or": "TotalObjectRollbackFailedCount",
  280. },
  281. ExpressionAttributeValues={
  282. ":Id": "job123",
  283. ":Sk": "job123",
  284. ":qt": 0,
  285. ":qs": 0,
  286. ":qf": 0,
  287. ":qb": 0,
  288. ":qm": 0,
  289. ":ou": 0,
  290. ":os": 1,
  291. ":of": 0,
  292. ":or": 0,
  293. ":z": 0,
  294. },
  295. ReturnValues="ALL_NEW",
  296. )
  297. @patch("backend.lambdas.jobs.stats_updater.table")
  298. def test_it_handles_failed_rollbacks(table):
  299. resp = update_stats(
  300. "job123",
  301. [
  302. {
  303. "Id": "job123",
  304. "Sk": "123456",
  305. "Type": "JobEvent",
  306. "CreatedAt": 123.0,
  307. "EventName": "ObjectRollbackFailed",
  308. "EventData": {},
  309. }
  310. ],
  311. )
  312. table.update_item.assert_called_with(
  313. Key={"Id": "job123", "Sk": "job123"},
  314. ConditionExpression="#Id = :Id AND #Sk = :Sk",
  315. UpdateExpression="set #qt = if_not_exists(#qt, :z) + :qt, "
  316. "#qs = if_not_exists(#qs, :z) + :qs, "
  317. "#qf = if_not_exists(#qf, :z) + :qf, "
  318. "#qb = if_not_exists(#qb, :z) + :qb, "
  319. "#qm = if_not_exists(#qm, :z) + :qm, "
  320. "#ou = if_not_exists(#ou, :z) + :ou, "
  321. "#os = if_not_exists(#os, :z) + :os, "
  322. "#of = if_not_exists(#of, :z) + :of, "
  323. "#or = if_not_exists(#or, :z) + :or",
  324. ExpressionAttributeNames={
  325. "#Id": "Id",
  326. "#Sk": "Sk",
  327. "#qt": "TotalQueryCount",
  328. "#qs": "TotalQuerySucceededCount",
  329. "#qf": "TotalQueryFailedCount",
  330. "#qb": "TotalQueryScannedInBytes",
  331. "#qm": "TotalQueryTimeInMillis",
  332. "#ou": "TotalObjectUpdatedCount",
  333. "#os": "TotalObjectUpdateSkippedCount",
  334. "#of": "TotalObjectUpdateFailedCount",
  335. "#or": "TotalObjectRollbackFailedCount",
  336. },
  337. ExpressionAttributeValues={
  338. ":Id": "job123",
  339. ":Sk": "job123",
  340. ":qt": 0,
  341. ":qs": 0,
  342. ":qf": 0,
  343. ":qb": 0,
  344. ":qm": 0,
  345. ":ou": 0,
  346. ":os": 0,
  347. ":of": 0,
  348. ":or": 1,
  349. ":z": 0,
  350. },
  351. ReturnValues="ALL_NEW",
  352. )
  353. @patch("backend.lambdas.jobs.stats_updater.table")
  354. def test_it_handles_multiple_events(table):
  355. resp = update_stats(
  356. "job123",
  357. [
  358. {
  359. "Id": "job123",
  360. "Sk": "123456",
  361. "Type": "JobEvent",
  362. "CreatedAt": 123.0,
  363. "EventName": "QuerySucceeded",
  364. "EventData": {
  365. "Statistics": {
  366. "DataScannedInBytes": 10,
  367. "EngineExecutionTimeInMillis": 100,
  368. }
  369. },
  370. },
  371. {
  372. "Id": "job123",
  373. "Sk": "123456",
  374. "Type": "JobEvent",
  375. "CreatedAt": 123.0,
  376. "EventName": "ObjectUpdated",
  377. "EventData": {},
  378. },
  379. ],
  380. )
  381. table.update_item.assert_called_with(
  382. Key={"Id": "job123", "Sk": "job123"},
  383. ConditionExpression="#Id = :Id AND #Sk = :Sk",
  384. UpdateExpression="set #qt = if_not_exists(#qt, :z) + :qt, "
  385. "#qs = if_not_exists(#qs, :z) + :qs, "
  386. "#qf = if_not_exists(#qf, :z) + :qf, "
  387. "#qb = if_not_exists(#qb, :z) + :qb, "
  388. "#qm = if_not_exists(#qm, :z) + :qm, "
  389. "#ou = if_not_exists(#ou, :z) + :ou, "
  390. "#os = if_not_exists(#os, :z) + :os, "
  391. "#of = if_not_exists(#of, :z) + :of, "
  392. "#or = if_not_exists(#or, :z) + :or",
  393. ExpressionAttributeNames={
  394. "#Id": "Id",
  395. "#Sk": "Sk",
  396. "#qt": "TotalQueryCount",
  397. "#qs": "TotalQuerySucceededCount",
  398. "#qf": "TotalQueryFailedCount",
  399. "#qb": "TotalQueryScannedInBytes",
  400. "#qm": "TotalQueryTimeInMillis",
  401. "#ou": "TotalObjectUpdatedCount",
  402. "#os": "TotalObjectUpdateSkippedCount",
  403. "#of": "TotalObjectUpdateFailedCount",
  404. "#or": "TotalObjectRollbackFailedCount",
  405. },
  406. ExpressionAttributeValues={
  407. ":Id": "job123",
  408. ":Sk": "job123",
  409. ":qt": 1,
  410. ":qs": 1,
  411. ":qf": 0,
  412. ":qb": 10,
  413. ":qm": 100,
  414. ":ou": 1,
  415. ":os": 0,
  416. ":of": 0,
  417. ":or": 0,
  418. ":z": 0,
  419. },
  420. ReturnValues="ALL_NEW",
  421. )
  422. @patch("backend.lambdas.jobs.stats_updater.ddb")
  423. @patch("backend.lambdas.jobs.stats_updater.table")
  424. def test_it_handles_already_failed_jobs(table, ddb):
  425. e = boto3.client("dynamodb").exceptions.ConditionalCheckFailedException
  426. ddb.meta.client.exceptions.ConditionalCheckFailedException = e
  427. table.update_item.side_effect = e({}, "ConditionalCheckFailedException")
  428. update_stats(
  429. "job123",
  430. [
  431. {
  432. "Id": "job123",
  433. "Sk": "123456",
  434. "Type": "JobEvent",
  435. "CreatedAt": 123.0,
  436. "EventName": "QuerySucceeded",
  437. "EventData": {
  438. "Statistics": {
  439. "DataScannedInBytes": 10,
  440. "EngineExecutionTimeInMillis": 100,
  441. }
  442. },
  443. }
  444. ],
  445. )
  446. table.update_item.assert_called()