invenio-app-ils 1.0.0a54__py2.py3-none-any.whl → 6.1.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. invenio_app_ils/__init__.py +2 -2
  2. invenio_app_ils/acquisition/api.py +3 -1
  3. invenio_app_ils/acquisition/config.py +10 -21
  4. invenio_app_ils/acquisition/jsonresolvers/order_order_lines.py +1 -0
  5. invenio_app_ils/acquisition/mappings/os-v1/acq_orders/order-v1.0.0.json +364 -0
  6. invenio_app_ils/acquisition/mappings/os-v2/__init__.py +0 -0
  7. invenio_app_ils/acquisition/mappings/os-v2/acq_orders/order-v1.0.0.json +364 -0
  8. invenio_app_ils/acquisition/mappings/v7/acq_orders/order-v1.0.0.json +14 -0
  9. invenio_app_ils/acquisition/search.py +5 -15
  10. invenio_app_ils/assets/semantic-ui/less/theme.config +103 -0
  11. invenio_app_ils/assets/semantic-ui/templates/.gitkeep +0 -0
  12. invenio_app_ils/circulation/api.py +153 -67
  13. invenio_app_ils/circulation/config.py +29 -25
  14. invenio_app_ils/circulation/indexer.py +14 -19
  15. invenio_app_ils/circulation/loaders/__init__.py +2 -0
  16. invenio_app_ils/circulation/loaders/schemas/json/loan_checkout.py +2 -3
  17. invenio_app_ils/circulation/loaders/schemas/json/loan_request.py +82 -16
  18. invenio_app_ils/circulation/loaders/schemas/json/loan_self_checkout.py +19 -0
  19. invenio_app_ils/circulation/notifications/api.py +23 -19
  20. invenio_app_ils/circulation/notifications/messages.py +6 -6
  21. invenio_app_ils/circulation/notifications/tasks.py +1 -2
  22. invenio_app_ils/circulation/receivers.py +2 -5
  23. invenio_app_ils/circulation/search.py +23 -22
  24. invenio_app_ils/circulation/serializers/__init__.py +2 -2
  25. invenio_app_ils/circulation/serializers/csv.py +1 -3
  26. invenio_app_ils/circulation/serializers/custom_fields.py +5 -9
  27. invenio_app_ils/circulation/serializers/json.py +2 -6
  28. invenio_app_ils/circulation/serializers/response.py +12 -12
  29. invenio_app_ils/circulation/stats/views.py +5 -15
  30. invenio_app_ils/circulation/tasks.py +2 -6
  31. invenio_app_ils/circulation/templates/invenio_app_ils_circulation/notifications/self_checkout.html +19 -0
  32. invenio_app_ils/circulation/templates/invenio_app_ils_circulation/notifications/update_dates.html +19 -0
  33. invenio_app_ils/circulation/utils.py +23 -17
  34. invenio_app_ils/circulation/views.py +103 -39
  35. invenio_app_ils/cli.py +220 -260
  36. invenio_app_ils/closures/api.py +72 -7
  37. invenio_app_ils/closures/serializers/__init__.py +16 -0
  38. invenio_app_ils/closures/serializers/response.py +35 -0
  39. invenio_app_ils/closures/serializers/schema.py +28 -0
  40. invenio_app_ils/closures/tasks.py +4 -11
  41. invenio_app_ils/closures/views.py +66 -0
  42. invenio_app_ils/config.py +207 -150
  43. invenio_app_ils/document_requests/api.py +6 -21
  44. invenio_app_ils/document_requests/loaders/__init__.py +4 -12
  45. invenio_app_ils/document_requests/mappings/{v6 → os-v1}/__init__.py +2 -2
  46. invenio_app_ils/document_requests/mappings/os-v1/document_requests/document_request-v1.0.0.json +196 -0
  47. invenio_app_ils/document_requests/mappings/os-v2/__init__.py +8 -0
  48. invenio_app_ils/document_requests/mappings/os-v2/document_requests/document_request-v1.0.0.json +196 -0
  49. invenio_app_ils/document_requests/notifications/api.py +2 -7
  50. invenio_app_ils/document_requests/notifications/messages.py +3 -7
  51. invenio_app_ils/document_requests/search.py +2 -6
  52. invenio_app_ils/document_requests/views.py +4 -10
  53. invenio_app_ils/documents/api.py +19 -25
  54. invenio_app_ils/documents/indexer.py +13 -20
  55. invenio_app_ils/documents/jsonresolvers/document_circulation.py +11 -33
  56. invenio_app_ils/documents/jsonresolvers/document_eitem.py +1 -0
  57. invenio_app_ils/documents/jsonresolvers/document_item.py +8 -9
  58. invenio_app_ils/documents/jsonresolvers/document_stock.py +7 -7
  59. invenio_app_ils/documents/loaders/jsonschemas/document.py +3 -14
  60. invenio_app_ils/documents/mappings/{v6 → os-v1}/__init__.py +1 -1
  61. invenio_app_ils/documents/mappings/os-v1/documents/document-v1.0.0.json +932 -0
  62. invenio_app_ils/documents/mappings/os-v1/documents/document-v2.0.0.json +1066 -0
  63. invenio_app_ils/documents/mappings/os-v2/__init__.py +8 -0
  64. invenio_app_ils/documents/mappings/os-v2/documents/document-v1.0.0.json +932 -0
  65. invenio_app_ils/documents/mappings/os-v2/documents/document-v2.0.0.json +1066 -0
  66. invenio_app_ils/documents/mappings/v7/documents/document-v1.0.0.json +102 -11
  67. invenio_app_ils/documents/mappings/v7/documents/document-v2.0.0.json +1066 -0
  68. invenio_app_ils/documents/schemas/documents/document-v2.0.0.json +732 -0
  69. invenio_app_ils/eitems/api.py +41 -7
  70. invenio_app_ils/eitems/loaders/jsonschemas/eitems.py +6 -6
  71. invenio_app_ils/eitems/mappings/{v6 → os-v1}/__init__.py +1 -1
  72. invenio_app_ils/eitems/mappings/os-v1/eitems/eitem-v1.0.0.json +139 -0
  73. invenio_app_ils/eitems/mappings/os-v1/eitems/eitem-v2.0.0.json +142 -0
  74. invenio_app_ils/eitems/mappings/os-v1/eitems/eitem-v3.0.0.json +179 -0
  75. invenio_app_ils/{series/mappings/v6 → eitems/mappings/os-v2}/__init__.py +1 -1
  76. invenio_app_ils/eitems/mappings/os-v2/eitems/eitem-v1.0.0.json +139 -0
  77. invenio_app_ils/eitems/mappings/os-v2/eitems/eitem-v2.0.0.json +142 -0
  78. invenio_app_ils/eitems/mappings/os-v2/eitems/eitem-v3.0.0.json +179 -0
  79. invenio_app_ils/eitems/mappings/v7/eitems/eitem-v1.0.0.json +12 -2
  80. invenio_app_ils/eitems/mappings/v7/eitems/eitem-v2.0.0.json +142 -0
  81. invenio_app_ils/eitems/mappings/v7/eitems/eitem-v3.0.0.json +179 -0
  82. invenio_app_ils/eitems/schemas/eitems/eitem-v2.0.0.json +120 -0
  83. invenio_app_ils/eitems/schemas/eitems/eitem-v3.0.0.json +120 -0
  84. invenio_app_ils/eitems/search.py +3 -9
  85. invenio_app_ils/errors.py +119 -6
  86. invenio_app_ils/ext.py +15 -18
  87. invenio_app_ils/facets.py +13 -15
  88. invenio_app_ils/files/receivers.py +1 -1
  89. invenio_app_ils/files/views.py +1 -2
  90. invenio_app_ils/ill/api.py +1 -2
  91. invenio_app_ils/ill/config.py +10 -16
  92. invenio_app_ils/ill/jsonresolvers/borrowing_request_document.py +2 -3
  93. invenio_app_ils/ill/jsonresolvers/borrowing_request_patron.py +1 -3
  94. invenio_app_ils/ill/jsonresolvers/borrowing_request_provider.py +1 -3
  95. invenio_app_ils/ill/loaders/__init__.py +3 -9
  96. invenio_app_ils/ill/loaders/jsonschemas/patron_loan_actions.py +1 -8
  97. invenio_app_ils/ill/loaders/jsonschemas/patron_loan_extension_actions.py +3 -8
  98. invenio_app_ils/ill/mappings/{v6 → os-v1}/__init__.py +1 -1
  99. invenio_app_ils/ill/mappings/os-v1/ill_borrowing_requests/borrowing_request-v1.0.0.json +254 -0
  100. invenio_app_ils/{acquisition/mappings/v6 → ill/mappings/os-v2}/__init__.py +1 -1
  101. invenio_app_ils/ill/mappings/os-v2/ill_borrowing_requests/borrowing_request-v1.0.0.json +254 -0
  102. invenio_app_ils/ill/mappings/v7/ill_borrowing_requests/borrowing_request-v1.0.0.json +14 -0
  103. invenio_app_ils/ill/notifications/api.py +1 -4
  104. invenio_app_ils/ill/search.py +3 -9
  105. invenio_app_ils/ill/serializers/__init__.py +1 -3
  106. invenio_app_ils/ill/views.py +2 -4
  107. invenio_app_ils/internal_locations/api.py +1 -4
  108. invenio_app_ils/internal_locations/mappings/{v6 → os-v1}/__init__.py +1 -1
  109. invenio_app_ils/internal_locations/mappings/os-v1/internal_locations/internal_location-v1.0.0.json +108 -0
  110. invenio_app_ils/internal_locations/mappings/os-v2/__init__.py +8 -0
  111. invenio_app_ils/internal_locations/mappings/os-v2/internal_locations/internal_location-v1.0.0.json +108 -0
  112. invenio_app_ils/internal_locations/search.py +1 -3
  113. invenio_app_ils/items/api.py +18 -15
  114. invenio_app_ils/items/indexer.py +1 -3
  115. invenio_app_ils/items/jsonresolvers/item_document.py +1 -1
  116. invenio_app_ils/items/jsonresolvers/item_internal_location.py +2 -6
  117. invenio_app_ils/items/loaders/jsonschemas/items.py +2 -0
  118. {tests/api/circulation → invenio_app_ils/items/mappings/os-v1}/__init__.py +1 -1
  119. invenio_app_ils/items/mappings/os-v1/items/item-v1.0.0.json +285 -0
  120. invenio_app_ils/items/mappings/os-v1/items/item-v2.0.0.json +326 -0
  121. {tests/api/ill → invenio_app_ils/items/mappings/os-v2}/__init__.py +1 -1
  122. invenio_app_ils/items/mappings/os-v2/items/item-v1.0.0.json +285 -0
  123. invenio_app_ils/items/mappings/os-v2/items/item-v2.0.0.json +326 -0
  124. invenio_app_ils/items/mappings/v7/items/item-v1.0.0.json +11 -0
  125. invenio_app_ils/items/mappings/v7/items/item-v2.0.0.json +326 -0
  126. invenio_app_ils/items/schemas/items/item-v1.0.0.json +22 -0
  127. invenio_app_ils/items/schemas/items/item-v2.0.0.json +172 -0
  128. invenio_app_ils/items/search.py +18 -11
  129. invenio_app_ils/items/serializers/item.py +7 -6
  130. invenio_app_ils/literature/api.py +1 -3
  131. invenio_app_ils/literature/search.py +3 -5
  132. invenio_app_ils/literature/serializers/csv.py +1 -3
  133. invenio_app_ils/literature/serializers/json.py +1 -3
  134. invenio_app_ils/locations/api.py +2 -4
  135. invenio_app_ils/locations/indexer.py +3 -9
  136. invenio_app_ils/locations/loaders/__init__.py +1 -3
  137. invenio_app_ils/locations/loaders/jsonschemas/location.py +4 -11
  138. invenio_app_ils/locations/mappings/{v6 → os-v1}/__init__.py +1 -1
  139. invenio_app_ils/locations/mappings/os-v1/locations/location-v1.0.0.json +90 -0
  140. invenio_app_ils/locations/mappings/os-v2/__init__.py +8 -0
  141. invenio_app_ils/locations/mappings/os-v2/locations/location-v1.0.0.json +90 -0
  142. invenio_app_ils/notifications/messages.py +4 -3
  143. invenio_app_ils/notifications/tasks.py +1 -3
  144. invenio_app_ils/notifications/views.py +2 -2
  145. invenio_app_ils/patrons/anonymization.py +53 -32
  146. invenio_app_ils/patrons/api.py +3 -5
  147. invenio_app_ils/patrons/cli.py +1 -2
  148. invenio_app_ils/patrons/indexer.py +4 -13
  149. invenio_app_ils/patrons/mappings/os-v1/__init__.py +8 -0
  150. invenio_app_ils/patrons/mappings/os-v1/patrons/patron-v1.0.0.json +42 -0
  151. invenio_app_ils/patrons/mappings/os-v2/__init__.py +8 -0
  152. invenio_app_ils/patrons/mappings/os-v2/patrons/patron-v1.0.0.json +42 -0
  153. invenio_app_ils/patrons/views.py +4 -14
  154. invenio_app_ils/permissions.py +93 -35
  155. invenio_app_ils/providers/api.py +2 -6
  156. invenio_app_ils/providers/config.py +11 -19
  157. invenio_app_ils/providers/indexer.py +2 -6
  158. invenio_app_ils/providers/mappings/{v6 → os-v1}/__init__.py +1 -1
  159. invenio_app_ils/providers/mappings/os-v1/providers/provider-v1.0.0.json +57 -0
  160. invenio_app_ils/providers/mappings/os-v2/__init__.py +8 -0
  161. invenio_app_ils/providers/mappings/os-v2/providers/provider-v1.0.0.json +57 -0
  162. invenio_app_ils/providers/mappings/v7/__init__.py +1 -1
  163. invenio_app_ils/providers/proxies.py +1 -3
  164. invenio_app_ils/records/api.py +3 -9
  165. invenio_app_ils/records/loaders/schemas/changed_by.py +1 -3
  166. invenio_app_ils/records/loaders/schemas/identifiers.py +23 -0
  167. invenio_app_ils/records/metadata_extensions.py +1 -4
  168. invenio_app_ils/records/permissions.py +8 -2
  169. invenio_app_ils/records/serializers/__init__.py +1 -4
  170. invenio_app_ils/records/views.py +4 -7
  171. invenio_app_ils/records_relations/api.py +11 -34
  172. invenio_app_ils/records_relations/indexer.py +1 -3
  173. invenio_app_ils/records_relations/retriever.py +14 -9
  174. invenio_app_ils/records_relations/views.py +16 -50
  175. invenio_app_ils/relations/api.py +17 -16
  176. invenio_app_ils/search_permissions.py +24 -24
  177. invenio_app_ils/series/api.py +10 -12
  178. invenio_app_ils/series/loaders/jsonschemas/series.py +3 -2
  179. invenio_app_ils/{items/mappings/v6 → series/mappings/os-v1}/__init__.py +1 -1
  180. invenio_app_ils/series/mappings/os-v1/series/series-v1.0.0.json +324 -0
  181. invenio_app_ils/series/mappings/os-v1/series/series-v2.0.0.json +396 -0
  182. {tests/api/acquisition → invenio_app_ils/series/mappings/os-v2}/__init__.py +1 -1
  183. invenio_app_ils/series/mappings/os-v2/series/series-v1.0.0.json +324 -0
  184. invenio_app_ils/series/mappings/os-v2/series/series-v2.0.0.json +396 -0
  185. invenio_app_ils/series/mappings/v7/series/series-v1.0.0.json +35 -3
  186. invenio_app_ils/series/mappings/v7/series/series-v2.0.0.json +396 -0
  187. invenio_app_ils/series/schemas/series/series-v2.0.0.json +354 -0
  188. invenio_app_ils/stats/aggregations/aggr_file_download/os-v1/aggr-file-download-v1.json +60 -0
  189. invenio_app_ils/stats/aggregations/aggr_file_download/os-v2/__init__.py +9 -0
  190. invenio_app_ils/stats/aggregations/aggr_file_download/os-v2/aggr-file-download-v1.json +60 -0
  191. invenio_app_ils/stats/event_builders.py +73 -0
  192. invenio_app_ils/stats/file_download/os-v1/file-download-v1.json +72 -0
  193. invenio_app_ils/stats/file_download/os-v2/__init__.py +9 -0
  194. invenio_app_ils/stats/file_download/os-v2/file-download-v1.json +72 -0
  195. invenio_app_ils/stats/processors.py +30 -0
  196. invenio_app_ils/stats/templates/aggregations/__init__.py +8 -0
  197. invenio_app_ils/stats/templates/aggregations/ils_record_changes/__init__.py +8 -0
  198. invenio_app_ils/stats/templates/aggregations/ils_record_changes/os-v2/__init__.py +8 -0
  199. invenio_app_ils/stats/templates/aggregations/ils_record_changes/os-v2/aggr-ils-record-changes-v1.json +41 -0
  200. {tests → invenio_app_ils/stats/templates/events}/__init__.py +2 -2
  201. invenio_app_ils/stats/templates/events/ils_record_changes/__init__.py +8 -0
  202. invenio_app_ils/stats/templates/events/ils_record_changes/os-v2/__init__.py +8 -0
  203. invenio_app_ils/stats/templates/events/ils_record_changes/os-v2/ils-record-changes-v1.json +41 -0
  204. invenio_app_ils/templates/logged_out.html +12 -0
  205. invenio_app_ils/views.py +18 -4
  206. invenio_app_ils/vocabularies/api.py +1 -1
  207. invenio_app_ils/vocabularies/cli.py +6 -18
  208. invenio_app_ils/vocabularies/data/acq_order_line_payment_modes.json +5 -0
  209. invenio_app_ils/vocabularies/data/identifier_schemes.json +5 -0
  210. invenio_app_ils/vocabularies/indexer.py +1 -4
  211. invenio_app_ils/vocabularies/jsonresolvers/licenses.py +2 -11
  212. invenio_app_ils/vocabularies/mappings/os-v1/__init__.py +8 -0
  213. invenio_app_ils/vocabularies/mappings/os-v1/vocabularies/vocabulary-v1.0.0.json +23 -0
  214. invenio_app_ils/vocabularies/mappings/os-v2/__init__.py +8 -0
  215. invenio_app_ils/vocabularies/mappings/os-v2/vocabularies/vocabulary-v1.0.0.json +23 -0
  216. invenio_app_ils/vocabularies/sources/base.py +1 -3
  217. invenio_app_ils/webpack.py +27 -0
  218. invenio_app_ils-6.1.0.dist-info/METADATA +627 -0
  219. {invenio_app_ils-1.0.0a54.dist-info → invenio_app_ils-6.1.0.dist-info}/RECORD +227 -249
  220. {invenio_app_ils-1.0.0a54.dist-info → invenio_app_ils-6.1.0.dist-info}/WHEEL +1 -1
  221. {invenio_app_ils-1.0.0a54.dist-info → invenio_app_ils-6.1.0.dist-info}/entry_points.txt +9 -5
  222. {invenio_app_ils-1.0.0a54.dist-info → invenio_app_ils-6.1.0.dist-info}/top_level.txt +0 -1
  223. invenio_app_ils/acquisition/mappings/v6/acq_orders/order-v1.0.0.json +0 -352
  224. invenio_app_ils/document_requests/mappings/v6/document_requests/document_request-v1.0.0.json +0 -199
  225. invenio_app_ils/documents/mappings/v6/documents/document-v1.0.0.json +0 -843
  226. invenio_app_ils/eitems/mappings/v6/eitems/eitem-v1.0.0.json +0 -131
  227. invenio_app_ils/ill/mappings/v6/ill_borrowing_requests/borrowing_request-v1.0.0.json +0 -242
  228. invenio_app_ils/internal_locations/mappings/v6/internal_locations/internal_location-v1.0.0.json +0 -110
  229. invenio_app_ils/items/mappings/v6/items/item-v1.0.0.json +0 -276
  230. invenio_app_ils/locations/mappings/v6/locations/location-v1.0.0.json +0 -92
  231. invenio_app_ils/notifications/admin.py +0 -57
  232. invenio_app_ils/patrons/mappings/v6/__init__.py +0 -8
  233. invenio_app_ils/patrons/mappings/v6/patrons/patron-v1.0.0.json +0 -44
  234. invenio_app_ils/providers/mappings/v6/providers/provider-v1.0.0.json +0 -59
  235. invenio_app_ils/series/mappings/v6/series/series-v1.0.0.json +0 -298
  236. invenio_app_ils/stats/aggregations/aggr_file_download/v6/aggr-file-download-v1.json +0 -59
  237. invenio_app_ils/stats/file_download/v6/file-download-v1.json +0 -74
  238. invenio_app_ils/version.py +0 -14
  239. invenio_app_ils/vocabularies/mappings/v6/__init__.py +0 -8
  240. invenio_app_ils/vocabularies/mappings/v6/vocabularies/vocabulary-v1.0.0.json +0 -25
  241. invenio_app_ils-1.0.0a54.dist-info/METADATA +0 -95
  242. tests/api/__init__.py +0 -8
  243. tests/api/acquisition/test_acq_orders_crud.py +0 -102
  244. tests/api/acquisition/test_acq_orders_permissions.py +0 -104
  245. tests/api/acquisition/test_acq_providers_permissions.py +0 -83
  246. tests/api/circulation/test_expired_loans.py +0 -88
  247. tests/api/circulation/test_loan_checkout.py +0 -216
  248. tests/api/circulation/test_loan_default_durations.py +0 -57
  249. tests/api/circulation/test_loan_document_resolver.py +0 -18
  250. tests/api/circulation/test_loan_extend.py +0 -132
  251. tests/api/circulation/test_loan_item_permissions.py +0 -139
  252. tests/api/circulation/test_loan_item_resolver.py +0 -26
  253. tests/api/circulation/test_loan_list_permissions.py +0 -104
  254. tests/api/circulation/test_loan_patron_resolver.py +0 -38
  255. tests/api/circulation/test_loan_request.py +0 -234
  256. tests/api/circulation/test_loan_update.py +0 -150
  257. tests/api/circulation/test_notifications.py +0 -229
  258. tests/api/conftest.py +0 -236
  259. tests/api/document_requests/__init__.py +0 -8
  260. tests/api/document_requests/test_document_requests.py +0 -131
  261. tests/api/document_requests/test_document_requests_permissions.py +0 -177
  262. tests/api/document_requests/test_notifications_filter.py +0 -36
  263. tests/api/ill/test_ill_brw_crud.py +0 -84
  264. tests/api/ill/test_ill_brw_reqs_patron_loan_create_action.py +0 -207
  265. tests/api/ill/test_ill_brw_reqs_patron_loan_extension_actions.py +0 -298
  266. tests/api/ill/test_ill_brw_reqs_permissions.py +0 -169
  267. tests/api/ill/test_ill_providers_permissions.py +0 -82
  268. tests/api/ils/__init__.py +0 -8
  269. tests/api/ils/documents/__init__.py +0 -8
  270. tests/api/ils/documents/test_document_crud.py +0 -57
  271. tests/api/ils/documents/test_document_permissions.py +0 -106
  272. tests/api/ils/documents/test_document_resolvers.py +0 -35
  273. tests/api/ils/eitems/__init__.py +0 -8
  274. tests/api/ils/eitems/test_eitems_crud.py +0 -41
  275. tests/api/ils/eitems/test_eitems_permissions.py +0 -84
  276. tests/api/ils/eitems/test_files.py +0 -172
  277. tests/api/ils/items/__init__.py +0 -8
  278. tests/api/ils/items/test_apis.py +0 -43
  279. tests/api/ils/items/test_items_crud.py +0 -60
  280. tests/api/ils/items/test_items_permissions.py +0 -107
  281. tests/api/ils/items/test_items_update.py +0 -47
  282. tests/api/ils/records_relations/__init__.py +0 -8
  283. tests/api/ils/records_relations/helpers.py +0 -115
  284. tests/api/ils/records_relations/test_records_relations_parentchild.py +0 -560
  285. tests/api/ils/records_relations/test_records_relations_sequence.py +0 -302
  286. tests/api/ils/records_relations/test_records_relations_siblings.py +0 -678
  287. tests/api/ils/series/__init__.py +0 -8
  288. tests/api/ils/series/test_series_permissions.py +0 -95
  289. tests/api/ils/test_anonymization.py +0 -189
  290. tests/api/ils/test_apis.py +0 -76
  291. tests/api/ils/test_closures.py +0 -324
  292. tests/api/ils/test_errors.py +0 -126
  293. tests/api/ils/test_facets.py +0 -84
  294. tests/api/ils/test_internal_locations.py +0 -96
  295. tests/api/ils/test_loaders.py +0 -55
  296. tests/api/ils/test_metadata_extensions.py +0 -205
  297. tests/api/ils/test_notifications.py +0 -183
  298. tests/api/ils/test_notifications_mails.py +0 -39
  299. tests/api/ils/test_notifications_permissions.py +0 -55
  300. tests/api/ils/test_patrons.py +0 -106
  301. tests/api/ils/test_record_delete.py +0 -46
  302. tests/api/ils/test_record_permissions.py +0 -135
  303. tests/api/ils/test_resolvers.py +0 -207
  304. tests/api/ils/test_stats.py +0 -160
  305. tests/api/ils/test_tasks.py +0 -211
  306. tests/api/ils/test_vocabularies.py +0 -35
  307. tests/conftest.py +0 -123
  308. tests/data/acq_orders.json +0 -89
  309. tests/data/acq_providers.json +0 -12
  310. tests/data/document_requests.json +0 -77
  311. tests/data/documents.json +0 -237
  312. tests/data/eitems.json +0 -66
  313. tests/data/ill_borrowing_requests.json +0 -77
  314. tests/data/ill_providers.json +0 -12
  315. tests/data/internal_locations.json +0 -22
  316. tests/data/items.json +0 -271
  317. tests/data/loans.json +0 -133
  318. tests/data/loans_most_loaned.json +0 -128
  319. tests/data/locations.json +0 -20
  320. tests/data/series.json +0 -33
  321. tests/helpers.py +0 -104
  322. tests/templates/notifications/title_body.html +0 -8
  323. tests/templates/notifications/title_body_html.html +0 -13
  324. tests/templates/notifications/title_body_html_ctx.html +0 -13
  325. tests/templates/notifications/title_only.html +0 -3
  326. tests/test_version.py +0 -17
  327. /tests/templates/notifications/blank.html → /invenio_app_ils/acquisition/mappings/os-v1/__init__.py +0 -0
  328. /invenio_app_ils/stats/aggregations/aggr_file_download/{v6 → os-v1}/__init__.py +0 -0
  329. /invenio_app_ils/stats/file_download/{v6 → os-v1}/__init__.py +0 -0
  330. {invenio_app_ils-1.0.0a54.dist-info → invenio_app_ils-6.1.0.dist-info/licenses}/AUTHORS.rst +0 -0
  331. {invenio_app_ils-1.0.0a54.dist-info → invenio_app_ils-6.1.0.dist-info/licenses}/LICENSE +0 -0
@@ -1,150 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- #
3
- # Copyright (C) 2020 CERN.
4
- #
5
- # invenio-app-ils is free software; you can redistribute it and/or modify it
6
- # under the terms of the MIT License; see LICENSE file for more details.
7
-
8
- """Test loan update."""
9
- import json
10
- from copy import deepcopy
11
- from datetime import date, timedelta
12
-
13
- from flask import url_for
14
- from invenio_circulation.api import Loan
15
-
16
- from tests.helpers import user_login, user_logout
17
-
18
-
19
- def _url_loan(pid_value):
20
- return url_for("invenio_records_rest.loanid_item", pid_value=pid_value)
21
-
22
-
23
- def _post_loan_update(
24
- client,
25
- json_headers,
26
- pid_value,
27
- start_date=None,
28
- end_date=None,
29
- request_start_date=None,
30
- request_expire_date=None,
31
- ):
32
- url = url_for(
33
- "invenio_app_ils_circulation.loanid_update_dates", pid_value=pid_value
34
- )
35
- data = {}
36
- if start_date:
37
- data["start_date"] = start_date
38
- if end_date:
39
- data["end_date"] = end_date
40
- if request_start_date:
41
- data["request_start_date"] = request_start_date
42
- if request_expire_date:
43
- data["request_expire_date"] = request_expire_date
44
- res = client.post(url, headers=json_headers, data=json.dumps(data))
45
- return res
46
-
47
-
48
- def _load_result(res):
49
- return json.loads(res.data.decode("utf-8"))
50
-
51
-
52
- def _today(dt=0):
53
- return (date.today() + timedelta(days=dt)).strftime("%Y-%m-%d")
54
-
55
-
56
- def test_loan_access_permission(client, json_headers, users, testdata):
57
- """
58
- Test that a patron should not be able to update their loan;
59
- and a fortiori, other people's.
60
- """
61
- loan = testdata["loans"][0]
62
- user = user_login(client, "patron1", users)
63
- url = _url_loan(loan["pid"])
64
- res = client.get(url, headers=json_headers)
65
- assert res.status_code == 200 # Can access their own loan
66
- metadata = _load_result(res)["metadata"]
67
- metadata["transaction_user_pid"] = str(user.id)
68
- res = client.put(url, headers=json_headers, data=json.dumps(metadata))
69
- assert res.status_code == 403 # Cannot modify the loan
70
-
71
-
72
- def test_loan_update_date(client, json_headers, users, testdata):
73
- """Test the edition of the dates on a loan."""
74
- pid = testdata["loans"][4]["pid"] # Item on loan
75
-
76
- res = _post_loan_update(client, json_headers, pid)
77
- assert res.status_code == 401
78
-
79
- # Patron cannot update loan
80
- user_login(client, "patron1", users)
81
- res = _post_loan_update(client, json_headers, pid)
82
- assert res.status_code == 403
83
- user_logout(client)
84
-
85
- # Librarian can
86
- user_login(client, "librarian", users)
87
- old_loan = _load_result(client.get(_url_loan(pid), headers=json_headers))
88
- res = _post_loan_update(client, json_headers, pid)
89
- assert res.status_code == 202
90
- new_loan = _load_result(res)
91
- assert old_loan["updated"] < new_loan["updated"] # Liveness check
92
-
93
- # Update both values
94
- start_date = "2020-01-01"
95
- end_date = _today(+1)
96
- res = _post_loan_update(
97
- client, json_headers, pid, start_date=start_date, end_date=end_date
98
- )
99
- assert res.status_code == 202
100
- new_loan_meta = _load_result(res)["metadata"]
101
- assert new_loan_meta["start_date"] == start_date
102
- assert new_loan_meta["end_date"] == end_date
103
-
104
- # Start date after today
105
- start_date = _today(+2)
106
- end_date = _today(+3)
107
- res = _post_loan_update(
108
- client, json_headers, pid, start_date=start_date, end_date=end_date
109
- )
110
- assert res.status_code == 400
111
-
112
- # No relative constraints on non-active loans
113
- pid = testdata["loans"][3]["pid"] # Pending
114
- res = _post_loan_update(
115
- client,
116
- json_headers,
117
- pid,
118
- request_start_date=start_date,
119
- request_expire_date=end_date,
120
- )
121
- assert res.status_code == 202
122
- new_loan_meta = _load_result(res)["metadata"]
123
- assert new_loan_meta["request_start_date"] == start_date
124
- assert new_loan_meta["request_expire_date"] == end_date
125
-
126
- # Negative date range
127
- start_date = "2000-02-01"
128
- end_date = "2000-01-01"
129
- res = _post_loan_update(
130
- client,
131
- json_headers,
132
- pid,
133
- request_start_date=start_date,
134
- request_expire_date=end_date,
135
- )
136
- assert res.status_code == 400
137
-
138
- # Illegal combination of parameters
139
- start_date = _today(-2)
140
- end_date = _today(+2)
141
- res = _post_loan_update(
142
- client,
143
- json_headers,
144
- pid,
145
- start_date=start_date,
146
- end_date=end_date,
147
- request_start_date=start_date,
148
- request_expire_date=end_date,
149
- )
150
- assert res.status_code == 400
@@ -1,229 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- #
3
- # Copyright (C) 2020 CERN.
4
- #
5
- # invenio-app-ils is free software; you can redistribute it and/or modify it
6
- # under the terms of the MIT License; see LICENSE file for more details.
7
-
8
- """Test email notifications on transitions."""
9
-
10
- import random
11
- from datetime import timedelta
12
-
13
- import arrow
14
- from flask import current_app, url_for
15
- from invenio_circulation.api import Loan
16
- from invenio_circulation.proxies import current_circulation
17
- from invenio_indexer.api import RecordIndexer
18
- from invenio_search import current_search
19
-
20
- from invenio_app_ils.circulation.notifications.tasks import (
21
- send_expiring_loans_notification_reminder,
22
- send_overdue_loans_notification_reminder,
23
- )
24
- from invenio_app_ils.documents.api import Document
25
- from invenio_app_ils.patrons.api import Patron
26
- from tests.helpers import user_login, user_logout
27
-
28
-
29
- def test_notifs_on_loan_checkout(
30
- client, app_with_notifs, users, testdata, loan_params
31
- ):
32
- """Test that an email is sent on loan checkout."""
33
- loan_data = testdata["loans"][1]
34
- loan = Loan.get_record_by_pid(loan_data["pid"])
35
- with app_with_notifs.extensions["mail"].record_messages() as outbox:
36
- user_login(client, "admin", users)
37
-
38
- assert len(outbox) == 0
39
- current_circulation.circulation.trigger(
40
- loan, **dict(loan_params, trigger="checkout")
41
- )
42
- assert len(outbox) == 1
43
- msg = outbox[0]
44
-
45
- doc = Document.get_record_by_pid(loan_data["document_pid"])
46
- expected_subject = (
47
- "InvenioILS: loan started for \"{0}\"".format(doc["title"])
48
- )
49
- assert msg.subject == expected_subject
50
-
51
- edition_year = " ({edition} - {year})".format(
52
- edition=doc["edition"], year=doc["publication_year"]
53
- )
54
- full_title = "{title}, {author}{edition_year}".format(
55
- title=doc["title"],
56
- author=doc["authors"][0]["full_name"],
57
- edition_year=edition_year,
58
- )
59
-
60
- literature_url = "{host}{path}".format(
61
- host=current_app.config["SPA_HOST"],
62
- path=current_app.config["SPA_PATHS"]["literature"]
63
- % {"pid": doc["pid"]},
64
- )
65
- profile_url = "{host}{path}".format(
66
- host=current_app.config["SPA_HOST"],
67
- path=current_app.config["SPA_PATHS"]["profile"],
68
- )
69
- expected_body_plain = """Dear Patron One,
70
-
71
- your loan for "{doc_full_title}" <{literature_url}> has started.
72
-
73
- The due date is {loan_end_date}.
74
-
75
- You can see your ongoing and past loans in your profile page <{profile_url}>.
76
-
77
- Kind regards,
78
- InvenioILS""".format(
79
- doc_full_title=full_title,
80
- literature_url=literature_url,
81
- loan_end_date=loan_data["end_date"],
82
- profile_url=profile_url,
83
- )
84
- assert msg.body == expected_body_plain
85
-
86
-
87
- def test_notification_on_overdue_permissions(client, testdata, json_headers,
88
- users):
89
- """Test that only the backoffice can send a reminder."""
90
- pid = testdata["loans"][0]["pid"]
91
- url = url_for("invenio_app_ils_circulation.loanid_notification",
92
- pid_value=pid)
93
- tests = [("patron1", 403), ("anonymous", 401)]
94
- for username, expected_status in tests:
95
- user_login(client, username, users)
96
- res = client.post(url, headers=json_headers)
97
- assert res.status_code == expected_status
98
-
99
-
100
- def test_notification_on_overdue_loans(
101
- app_with_notifs, db, users, testdata, mocker, client, json_headers
102
- ):
103
- """Test that an email is sent for a loan that is overdue."""
104
- mocker.patch(
105
- "invenio_app_ils.patrons.api.Patron.get_patron",
106
- return_value=Patron(users["patron1"].id),
107
- )
108
-
109
- def prepare_data():
110
- """Prepare data."""
111
- days = current_app.config[
112
- "ILS_CIRCULATION_NOTIFICATION_OVERDUE_REMINDER_INTERVAL"
113
- ]
114
- loans = testdata["loans"]
115
-
116
- recs = []
117
- now = arrow.utcnow()
118
-
119
- def new_end_date(loan, date):
120
- loan["end_date"] = date.date().isoformat()
121
- loan["state"] = "ITEM_ON_LOAN"
122
- loan.commit()
123
- recs.append(loan)
124
-
125
- # overdue loans
126
- date = now - timedelta(days=days)
127
- new_end_date(loans[0], date)
128
-
129
- date = now - timedelta(days=days * 2)
130
- new_end_date(loans[1], date)
131
-
132
- # not overdue
133
- date = now - timedelta(days=-1)
134
- new_end_date(loans[2], date)
135
-
136
- # not overdue or overdue but not to be notified
137
- remaining_not_overdue = loans[3:]
138
- for loan in remaining_not_overdue:
139
- days = random.choice([-1, 0, 1])
140
- date = now - timedelta(days=days)
141
- new_end_date(loan, date)
142
- db.session.commit()
143
-
144
- indexer = RecordIndexer()
145
- for rec in recs:
146
- indexer.index(rec)
147
-
148
- current_search.flush_and_refresh(index="*")
149
-
150
- user_login(client, "librarian", users)
151
-
152
- # test individual overdue loan
153
- prepare_data()
154
- loans = testdata["loans"]
155
-
156
- notification_url = url_for(
157
- "invenio_app_ils_circulation.loanid_notification",
158
- pid_value=loans[0]["pid"],
159
- )
160
-
161
- res = client.post(notification_url, headers=json_headers)
162
- assert res.status_code == 202
163
-
164
- # test individual not overdue loan
165
- notification_url = url_for(
166
- "invenio_app_ils_circulation.loanid_notification",
167
- pid_value=loans[2]["pid"],
168
- )
169
-
170
- res = client.post(notification_url, headers=json_headers)
171
- assert res.status_code == 400
172
-
173
- user_logout(client)
174
-
175
- # test all loans
176
- with app_with_notifs.extensions["mail"].record_messages() as outbox:
177
- assert len(outbox) == 0
178
- send_overdue_loans_notification_reminder.apply_async()
179
- assert len(outbox) == 2
180
-
181
-
182
- def test_notification_on_expiring_loans(app_with_notifs, db, users,
183
- testdata, mocker):
184
- """Test that an email is sent for a loan that is about to expire."""
185
- mocker.patch(
186
- "invenio_app_ils.patrons.api.Patron.get_patron",
187
- return_value=Patron(users["patron1"].id),
188
- )
189
-
190
- def prepare_data():
191
- """Prepare data."""
192
- max_days = current_app.config["ILS_CIRCULATION_LOAN_WILL_EXPIRE_DAYS"]
193
- loans = testdata["loans"]
194
-
195
- recs = []
196
- now = arrow.utcnow()
197
-
198
- def new_end_date(loan, date):
199
- loan["end_date"] = date.date().isoformat()
200
- loan["state"] = "ITEM_ON_LOAN"
201
- loan.commit()
202
- recs.append(loan)
203
-
204
- # expiring loans
205
- date = now + timedelta(days=max_days)
206
- new_end_date(loans[0], date)
207
- new_end_date(loans[1], date)
208
- new_end_date(loans[2], date)
209
-
210
- # not expiring
211
- remaining_not_overdue = loans[3:]
212
- for loan in remaining_not_overdue:
213
- days = random.choice([-2, -1, 1, max_days + 1])
214
- date = now + timedelta(days=days)
215
- new_end_date(loan, date)
216
- db.session.commit()
217
-
218
- indexer = RecordIndexer()
219
- for rec in recs:
220
- indexer.index(rec)
221
-
222
- current_search.flush_and_refresh(index="*")
223
-
224
- prepare_data()
225
-
226
- with app_with_notifs.extensions["mail"].record_messages() as outbox:
227
- assert len(outbox) == 0
228
- send_expiring_loans_notification_reminder.apply_async()
229
- assert len(outbox) == 3
tests/api/conftest.py DELETED
@@ -1,236 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- #
3
- # Copyright (C) 2018-2020 CERN.
4
- #
5
- # invenio-app-ils is free software; you can redistribute it and/or modify it
6
- # under the terms of the MIT License; see LICENSE file for more details.
7
-
8
- """Pytest fixtures and plugins for the API application."""
9
-
10
- import tempfile
11
-
12
- import pytest
13
- from invenio_app.factory import create_api
14
- from invenio_circulation.api import Loan
15
- from invenio_circulation.pidstore.pids import CIRCULATION_LOAN_PID_TYPE
16
- from invenio_indexer.api import RecordIndexer
17
- from invenio_search import current_search
18
-
19
- from invenio_app_ils.acquisition.api import ORDER_PID_TYPE, Order
20
- from invenio_app_ils.document_requests.api import (
21
- DOCUMENT_REQUEST_PID_TYPE,
22
- DocumentRequest,
23
- )
24
- from invenio_app_ils.documents.api import DOCUMENT_PID_TYPE, Document
25
- from invenio_app_ils.eitems.api import EITEM_PID_TYPE, EItem
26
- from invenio_app_ils.ill.api import (
27
- BORROWING_REQUEST_PID_TYPE,
28
- BorrowingRequest,
29
- )
30
- from invenio_app_ils.internal_locations.api import (
31
- INTERNAL_LOCATION_PID_TYPE,
32
- InternalLocation,
33
- )
34
- from invenio_app_ils.items.api import ITEM_PID_TYPE, Item
35
- from invenio_app_ils.locations.api import LOCATION_PID_TYPE, Location
36
- from invenio_app_ils.providers.api import PROVIDER_PID_TYPE, Provider
37
- from invenio_app_ils.series.api import SERIES_PID_TYPE, Series
38
- from tests.helpers import (
39
- document_ref_builder,
40
- internal_location_ref_builder,
41
- load_json_from_datadir,
42
- mint_record_pid,
43
- )
44
-
45
-
46
- @pytest.fixture(scope="module")
47
- def app_config(app_config):
48
- """Get app config."""
49
- tests_config = {
50
- "REST_CSRF_ENABLED": False,
51
- "ACCOUNTS_SESSION_REDIS_URL": "", # in-memory
52
- "RATELIMIT_GUEST_USER": "1000 per minute",
53
- "RATELIMIT_AUTHENTICATED_USER": "1000 per minute",
54
- "CIRCULATION_TRANSACTION_USER_VALIDATOR": lambda x: True,
55
- "EXTEND_LOANS_LOCATION_UPDATED": False,
56
- "ILS_NOTIFICATIONS_TEMPLATES": {"footer": "footer.html"},
57
- }
58
- app_config.update(tests_config)
59
- return app_config
60
-
61
-
62
- @pytest.fixture(scope="module")
63
- def create_app():
64
- """Create test app."""
65
- return create_api
66
-
67
-
68
- @pytest.fixture()
69
- def json_headers():
70
- """JSON headers."""
71
- return [
72
- ("Content-Type", "application/json"),
73
- ("Accept", "application/json"),
74
- ]
75
-
76
-
77
- def _create_records(db, objs, cls, pid_type):
78
- """Create records and index."""
79
- recs = []
80
- for obj in objs:
81
- record = cls.create(obj)
82
- mint_record_pid(pid_type, "pid", record)
83
- recs.append(record)
84
- db.session.commit()
85
- return recs
86
-
87
-
88
- @pytest.fixture()
89
- def testdata(app, db, es_clear, users):
90
- """Create, index and return test data."""
91
- data = load_json_from_datadir("locations.json")
92
- locations = _create_records(db, data, Location, LOCATION_PID_TYPE)
93
-
94
- data = load_json_from_datadir("internal_locations.json")
95
- int_locs = _create_records(
96
- db, data, InternalLocation, INTERNAL_LOCATION_PID_TYPE
97
- )
98
-
99
- data = load_json_from_datadir("series.json")
100
- series = _create_records(db, data, Series, SERIES_PID_TYPE)
101
-
102
- data = load_json_from_datadir("documents.json")
103
- documents = _create_records(db, data, Document, DOCUMENT_PID_TYPE)
104
-
105
- data = load_json_from_datadir("items.json")
106
- items = _create_records(db, data, Item, ITEM_PID_TYPE)
107
-
108
- data = load_json_from_datadir("eitems.json")
109
- eitems = _create_records(db, data, EItem, EITEM_PID_TYPE)
110
-
111
- data = load_json_from_datadir("loans.json")
112
- loans = _create_records(db, data, Loan, CIRCULATION_LOAN_PID_TYPE)
113
-
114
- data = load_json_from_datadir("acq_providers.json")
115
- acq_providers = _create_records(db, data, Provider, PROVIDER_PID_TYPE)
116
-
117
- data = load_json_from_datadir("acq_orders.json")
118
- acq_orders = _create_records(db, data, Order, ORDER_PID_TYPE)
119
-
120
- data = load_json_from_datadir("ill_providers.json")
121
- ill_providers = _create_records(db, data, Provider, PROVIDER_PID_TYPE)
122
-
123
- data = load_json_from_datadir("ill_borrowing_requests.json")
124
- ill_brw_reqs = _create_records(
125
- db, data, BorrowingRequest, BORROWING_REQUEST_PID_TYPE
126
- )
127
-
128
- data = load_json_from_datadir("document_requests.json")
129
- doc_reqs = _create_records(
130
- db, data, DocumentRequest, DOCUMENT_REQUEST_PID_TYPE
131
- )
132
-
133
- # index
134
- ri = RecordIndexer()
135
- for rec in (
136
- locations
137
- + int_locs
138
- + series
139
- + documents
140
- + items
141
- + eitems
142
- + loans
143
- + doc_reqs
144
- + acq_providers
145
- + acq_orders
146
- + ill_providers
147
- + ill_brw_reqs
148
- ):
149
- ri.index(rec)
150
-
151
- current_search.flush_and_refresh(index="*")
152
-
153
- return {
154
- "document_requests": doc_reqs,
155
- "documents": documents,
156
- "internal_locations": int_locs,
157
- "items": items,
158
- "eitems": eitems,
159
- "loans": loans,
160
- "locations": locations,
161
- "series": series,
162
- "acq_providers": acq_providers,
163
- "acq_orders": acq_orders,
164
- "ill_providers": ill_providers,
165
- "ill_brw_reqs": ill_brw_reqs,
166
- }
167
-
168
-
169
- @pytest.fixture()
170
- def testdata_most_loaned(db, testdata):
171
- """Create, index and return test data for most loans tests."""
172
- most_loaned = load_json_from_datadir("loans_most_loaned.json")
173
- recs = _create_records(db, most_loaned, Loan, CIRCULATION_LOAN_PID_TYPE)
174
-
175
- ri = RecordIndexer()
176
- for rec in recs:
177
- ri.index(rec)
178
-
179
- current_search.flush_and_refresh(index="loans")
180
-
181
- return {
182
- "locations": testdata["locations"],
183
- "internal_locations": testdata["internal_locations"],
184
- "documents": testdata["documents"],
185
- "items": testdata["items"],
186
- "loans": most_loaned,
187
- "series": testdata["series"],
188
- }
189
-
190
-
191
- @pytest.fixture()
192
- def item_record(app):
193
- """Fixture to return an Item payload."""
194
- return {
195
- "pid": "itemid-1",
196
- "document_pid": "docid-1",
197
- "document": {"$ref": document_ref_builder(app, "itemid-1")},
198
- "barcode": "123456789-1",
199
- "title": "Test item x",
200
- "internal_location_pid": "ilocid-1",
201
- "internal_location": {
202
- "$ref": internal_location_ref_builder(app, "itemid-1")
203
- },
204
- "medium": "NOT_SPECIFIED",
205
- "status": "CAN_CIRCULATE",
206
- "circulation_restriction": "NO_RESTRICTION",
207
- }
208
-
209
-
210
- @pytest.fixture()
211
- def loan_params():
212
- """Params for API REST payload."""
213
- return dict(
214
- transaction_user_pid="4",
215
- patron_pid="1",
216
- document_pid="docid-1",
217
- item_pid=dict(type="pitmid", value="itemid-2"),
218
- transaction_location_pid="locid-1",
219
- transaction_date="2018-02-01T09:30:00+02:00",
220
- )
221
-
222
-
223
- @pytest.fixture()
224
- def bucket(bucket_from_dir):
225
- """Create temporary bucket fixture."""
226
- with tempfile.TemporaryDirectory(prefix="ils-test-") as temp_dir:
227
- bucket = bucket_from_dir(temp_dir)
228
- yield bucket
229
-
230
-
231
- @pytest.fixture()
232
- def with_access(app):
233
- """Enable explicit permission check (`_access`)."""
234
- app.config["ILS_RECORDS_EXPLICIT_PERMISSIONS_ENABLED"] = True
235
- yield
236
- app.config["ILS_RECORDS_EXPLICIT_PERMISSIONS_ENABLED"] = False
@@ -1,8 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- #
3
- # Copyright (C) 2020 CERN.
4
- #
5
- # invenio-app-ils is free software; you can redistribute it and/or modify it
6
- # under the terms of the MIT License; see LICENSE file for more details.
7
-
8
- """Document Requests API Tests."""