invenio-app-ils 4.6.0__py2.py3-none-any.whl → 5.0.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 (115) hide show
  1. invenio_app_ils/__init__.py +1 -1
  2. invenio_app_ils/assets/semantic-ui/less/theme.config +103 -0
  3. invenio_app_ils/circulation/api.py +9 -5
  4. invenio_app_ils/circulation/config.py +1 -0
  5. invenio_app_ils/circulation/loaders/schemas/json/loan_checkout.py +1 -1
  6. invenio_app_ils/circulation/loaders/schemas/json/loan_request.py +2 -2
  7. invenio_app_ils/circulation/notifications/api.py +9 -1
  8. invenio_app_ils/circulation/notifications/messages.py +2 -1
  9. invenio_app_ils/circulation/templates/invenio_app_ils_circulation/notifications/update_dates.html +19 -0
  10. invenio_app_ils/cli.py +92 -64
  11. invenio_app_ils/config.py +10 -2
  12. invenio_app_ils/documents/loaders/jsonschemas/document.py +1 -1
  13. invenio_app_ils/eitems/loaders/jsonschemas/eitems.py +2 -2
  14. invenio_app_ils/patrons/anonymization.py +0 -4
  15. invenio_app_ils/series/loaders/jsonschemas/series.py +1 -1
  16. invenio_app_ils/webpack.py +27 -0
  17. {invenio_app_ils-4.6.0.dist-info → invenio_app_ils-5.0.0.dist-info}/METADATA +52 -42
  18. {invenio_app_ils-4.6.0.dist-info → invenio_app_ils-5.0.0.dist-info}/RECORD +24 -112
  19. {invenio_app_ils-4.6.0.dist-info → invenio_app_ils-5.0.0.dist-info}/entry_points.txt +3 -0
  20. {invenio_app_ils-4.6.0.dist-info → invenio_app_ils-5.0.0.dist-info}/top_level.txt +0 -1
  21. invenio_app_ils/notifications/admin.py +0 -57
  22. tests/__init__.py +0 -8
  23. tests/api/__init__.py +0 -8
  24. tests/api/acquisition/__init__.py +0 -8
  25. tests/api/acquisition/test_acq_orders_crud.py +0 -99
  26. tests/api/acquisition/test_acq_orders_permissions.py +0 -104
  27. tests/api/acquisition/test_acq_providers_permissions.py +0 -83
  28. tests/api/circulation/__init__.py +0 -8
  29. tests/api/circulation/test_expired_loans.py +0 -88
  30. tests/api/circulation/test_loan_bulk_extend.py +0 -169
  31. tests/api/circulation/test_loan_checkout.py +0 -422
  32. tests/api/circulation/test_loan_default_durations.py +0 -43
  33. tests/api/circulation/test_loan_document_resolver.py +0 -18
  34. tests/api/circulation/test_loan_extend.py +0 -122
  35. tests/api/circulation/test_loan_item_permissions.py +0 -135
  36. tests/api/circulation/test_loan_item_resolver.py +0 -26
  37. tests/api/circulation/test_loan_list_permissions.py +0 -98
  38. tests/api/circulation/test_loan_patron_resolver.py +0 -38
  39. tests/api/circulation/test_loan_request.py +0 -338
  40. tests/api/circulation/test_loan_update.py +0 -150
  41. tests/api/circulation/test_notifications.py +0 -221
  42. tests/api/conftest.py +0 -227
  43. tests/api/document_requests/__init__.py +0 -8
  44. tests/api/document_requests/test_document_requests.py +0 -131
  45. tests/api/document_requests/test_document_requests_permissions.py +0 -159
  46. tests/api/document_requests/test_notifications_filter.py +0 -35
  47. tests/api/ill/__init__.py +0 -8
  48. tests/api/ill/test_ill_brw_crud.py +0 -74
  49. tests/api/ill/test_ill_brw_reqs_patron_loan_create_action.py +0 -198
  50. tests/api/ill/test_ill_brw_reqs_patron_loan_extension_actions.py +0 -280
  51. tests/api/ill/test_ill_brw_reqs_permissions.py +0 -163
  52. tests/api/ill/test_ill_providers_permissions.py +0 -82
  53. tests/api/ils/__init__.py +0 -8
  54. tests/api/ils/documents/__init__.py +0 -8
  55. tests/api/ils/documents/test_document_crud.py +0 -57
  56. tests/api/ils/documents/test_document_permissions.py +0 -100
  57. tests/api/ils/documents/test_document_resolvers.py +0 -35
  58. tests/api/ils/eitems/__init__.py +0 -8
  59. tests/api/ils/eitems/test_eitems_crud.py +0 -42
  60. tests/api/ils/eitems/test_eitems_permissions.py +0 -85
  61. tests/api/ils/eitems/test_files.py +0 -162
  62. tests/api/ils/items/__init__.py +0 -8
  63. tests/api/ils/items/test_apis.py +0 -43
  64. tests/api/ils/items/test_items_crud.py +0 -99
  65. tests/api/ils/items/test_items_permissions.py +0 -107
  66. tests/api/ils/records_relations/__init__.py +0 -8
  67. tests/api/ils/records_relations/helpers.py +0 -115
  68. tests/api/ils/records_relations/test_records_relations_parentchild.py +0 -560
  69. tests/api/ils/records_relations/test_records_relations_sequence.py +0 -294
  70. tests/api/ils/records_relations/test_records_relations_siblings.py +0 -751
  71. tests/api/ils/series/__init__.py +0 -8
  72. tests/api/ils/series/test_series_permissions.py +0 -95
  73. tests/api/ils/test_anonymization.py +0 -181
  74. tests/api/ils/test_apis.py +0 -76
  75. tests/api/ils/test_closures.py +0 -353
  76. tests/api/ils/test_errors.py +0 -125
  77. tests/api/ils/test_facets.py +0 -88
  78. tests/api/ils/test_internal_locations.py +0 -96
  79. tests/api/ils/test_loaders.py +0 -51
  80. tests/api/ils/test_metadata_extensions.py +0 -206
  81. tests/api/ils/test_notifications.py +0 -173
  82. tests/api/ils/test_notifications_mails.py +0 -37
  83. tests/api/ils/test_notifications_permissions.py +0 -55
  84. tests/api/ils/test_patrons.py +0 -102
  85. tests/api/ils/test_record_delete.py +0 -42
  86. tests/api/ils/test_record_permissions.py +0 -132
  87. tests/api/ils/test_resolvers.py +0 -205
  88. tests/api/ils/test_stats.py +0 -142
  89. tests/api/ils/test_tasks.py +0 -209
  90. tests/api/ils/test_vocabularies.py +0 -35
  91. tests/conftest.py +0 -221
  92. tests/data/acq_orders.json +0 -110
  93. tests/data/acq_providers.json +0 -12
  94. tests/data/document_requests.json +0 -77
  95. tests/data/documents.json +0 -238
  96. tests/data/eitems.json +0 -71
  97. tests/data/ill_borrowing_requests.json +0 -77
  98. tests/data/ill_providers.json +0 -12
  99. tests/data/internal_locations.json +0 -22
  100. tests/data/items.json +0 -304
  101. tests/data/loans.json +0 -133
  102. tests/data/loans_most_loaned.json +0 -128
  103. tests/data/locations.json +0 -26
  104. tests/data/series.json +0 -33
  105. tests/helpers.py +0 -107
  106. tests/templates/notifications/title_body.html +0 -8
  107. tests/templates/notifications/title_body_html.html +0 -13
  108. tests/templates/notifications/title_body_html_ctx.html +0 -13
  109. tests/templates/notifications/title_only.html +0 -3
  110. tests/test_post_logout_redirect.py +0 -23
  111. tests/test_version.py +0 -17
  112. /tests/templates/notifications/blank.html → /invenio_app_ils/assets/semantic-ui/templates/.gitkeep +0 -0
  113. {invenio_app_ils-4.6.0.dist-info → invenio_app_ils-5.0.0.dist-info}/WHEEL +0 -0
  114. {invenio_app_ils-4.6.0.dist-info → invenio_app_ils-5.0.0.dist-info}/licenses/AUTHORS.rst +0 -0
  115. {invenio_app_ils-4.6.0.dist-info → invenio_app_ils-5.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,422 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- #
3
- # Copyright (C) 2018-2025 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 loans item permissions."""
9
-
10
- import json
11
- from copy import deepcopy
12
- from datetime import timedelta
13
-
14
- import arrow
15
- from flask import url_for
16
- from flask_principal import UserNeed
17
- from invenio_access.permissions import Permission
18
- from invenio_search import current_search
19
-
20
- from invenio_app_ils.errors import (
21
- LoanSelfCheckoutDocumentOverbooked,
22
- LoanSelfCheckoutItemActiveLoan,
23
- LoanSelfCheckoutItemInvalidStatus,
24
- )
25
- from invenio_app_ils.items.api import Item
26
- from invenio_app_ils.items.serializers import item
27
- from invenio_app_ils.proxies import current_app_ils
28
- from tests.helpers import user_login, user_logout
29
-
30
- NEW_LOAN = {
31
- "item_pid": "CHANGE ME IN EACH TEST",
32
- "document_pid": "docid-1",
33
- "patron_pid": "3",
34
- "transaction_location_pid": "locid-1",
35
- "pickup_location_pid": "locid-1",
36
- "delivery": {"method": "PICKUP"},
37
- }
38
-
39
- NEW_LOAN_REQUEST = {
40
- "document_pid": "CHANGE ME IN EACH TEST",
41
- "patron_pid": "3",
42
- "transaction_location_pid": "locid-1",
43
- "pickup_location_pid": "locid-1",
44
- "delivery": {"method": "PICKUP"},
45
- }
46
-
47
-
48
- def test_anonymous_cannot_request_loan_on_item(client, json_headers, testdata):
49
- """Test that anonymous users cannot request a loan on a item."""
50
- url = url_for("invenio_app_ils_circulation.loan_checkout")
51
- res = client.post(url, headers=json_headers, data=json.dumps(NEW_LOAN))
52
- assert res.status_code == 401
53
-
54
-
55
- def test_librarian_can_checkout_item_for_user(client, json_headers, users, testdata):
56
- """Test that librarian can checkout a loan on a item for a patron."""
57
- user_login(client, "librarian", users)
58
- url = url_for("invenio_app_ils_circulation.loan_checkout")
59
- params = deepcopy(NEW_LOAN)
60
- params["transaction_user_pid"] = str(users["librarian"].id)
61
- params["item_pid"] = dict(type="pitmid", value="itemid-60")
62
- res = client.post(url, headers=json_headers, data=json.dumps(params))
63
- assert res.status_code == 202
64
- loan = res.get_json()["metadata"]
65
- assert loan["state"] == "ITEM_ON_LOAN"
66
- assert loan["item_pid"] == params["item_pid"]
67
- assert loan["document_pid"] == params["document_pid"]
68
- assert loan["patron_pid"] == params["patron_pid"]
69
-
70
-
71
- def test_force_checkout_specific_permissions(
72
- app, client, json_headers, users, testdata
73
- ):
74
- """Test that only allowed users can perform a force checkout."""
75
- default_factory = app.config["ILS_VIEWS_PERMISSIONS_FACTORY"]
76
- librarian2 = users["librarian2"]
77
-
78
- # override default permission factory to require specific permission for
79
- # force-checkout action
80
- def custom_views_permissions_factory(action):
81
- if action == "circulation-loan-force-checkout":
82
- # fake permission for a specific user
83
- return Permission(UserNeed(librarian2.id))
84
- else:
85
- return default_factory(action)
86
-
87
- app.config["ILS_VIEWS_PERMISSIONS_FACTORY"] = custom_views_permissions_factory
88
-
89
- # prepare request
90
- url = url_for("invenio_app_ils_circulation.loan_checkout")
91
- params = deepcopy(NEW_LOAN)
92
- params["force"] = True
93
- params["item_pid"] = dict(type="pitmid", value="itemid-MISSING")
94
- params["transaction_user_pid"] = str(librarian2.id)
95
-
96
- # force-checkout as librarian should fail
97
- user_login(client, "librarian", users)
98
- res = client.post(url, headers=json_headers, data=json.dumps(params))
99
- assert res.status_code == 403
100
-
101
- # force-checkout as librarian2 should succeed
102
- user_login(client, "librarian2", users)
103
- res = client.post(url, headers=json_headers, data=json.dumps(params))
104
- assert res.status_code == 202
105
- loan = res.get_json()["metadata"]
106
- assert loan["state"] == "ITEM_ON_LOAN"
107
- assert loan["item_pid"] == params["item_pid"]
108
- assert loan["document_pid"] == params["document_pid"]
109
- assert loan["patron_pid"] == params["patron_pid"]
110
-
111
- # restore default config
112
- app.config["ILS_VIEWS_PERMISSIONS_FACTORY"] = default_factory
113
-
114
-
115
- def test_checkout_conditions_librarian(client, json_headers, users, testdata):
116
- """Test checkout conditions user."""
117
- librarian = users["librarian"]
118
- user_login(client, "librarian", users)
119
- url = url_for("invenio_app_ils_circulation.loan_checkout")
120
-
121
- params = deepcopy(NEW_LOAN)
122
- params["item_pid"] = dict(type="pitmid", value="itemid-61")
123
- params["transaction_user_pid"] = str(librarian.id)
124
- res = client.post(url, headers=json_headers, data=json.dumps(params))
125
- assert res.status_code == 202
126
-
127
- params = deepcopy(NEW_LOAN)
128
- params["item_pid"] = dict(type="pitmid", value="itemid-MISSING")
129
- params["transaction_user_pid"] = str(librarian.id)
130
- res = client.post(url, headers=json_headers, data=json.dumps(params))
131
- # missing
132
- assert res.status_code == 400
133
-
134
- params = deepcopy(NEW_LOAN)
135
- params["force"] = True
136
- params["item_pid"] = dict(type="pitmid", value="itemid-MISSING")
137
- params["transaction_user_pid"] = str(librarian.id)
138
- res = client.post(url, headers=json_headers, data=json.dumps(params))
139
- # missing but force
140
- assert res.status_code == 202
141
- item = Item.get_record_by_pid(params["item_pid"]["value"])
142
- assert item["status"] == "CAN_CIRCULATE"
143
-
144
-
145
- def test_checkout_loader_start_end_dates(app, client, json_headers, users, testdata):
146
- """Test that start and end dates request parameters."""
147
- librarian = users["librarian"]
148
- user_login(client, "librarian", users)
149
- item_pid = dict(type="pitmid", value="itemid-61")
150
- url = url_for("invenio_app_ils_circulation.loan_checkout")
151
- # fake duration for first item
152
- loan_duration_timedelta = app.config["CIRCULATION_POLICIES"]["checkout"][
153
- "duration_default"
154
- ](dict(item_pid=item_pid), dict())
155
- now = arrow.utcnow()
156
-
157
- # it should succeed when no start/end dates provided
158
- start_date = now.date().isoformat()
159
- end_date = (now + loan_duration_timedelta).date().isoformat()
160
-
161
- params = deepcopy(NEW_LOAN)
162
- params["item_pid"] = item_pid
163
- params["transaction_user_pid"] = str(librarian.id)
164
- res = client.post(url, headers=json_headers, data=json.dumps(params))
165
- assert res.status_code == 202
166
- loan = res.get_json()["metadata"]
167
- assert loan["state"] == "ITEM_ON_LOAN"
168
- assert loan["item_pid"] == params["item_pid"]
169
- assert loan["start_date"] == start_date
170
- assert loan["end_date"] == end_date
171
- assert loan["transaction_date"]
172
-
173
- # it should succeed when no end date provided
174
- _start_date = now + timedelta(days=3)
175
- start_date = _start_date.date().isoformat()
176
- end_date = (_start_date + loan_duration_timedelta).date().isoformat()
177
-
178
- params = deepcopy(NEW_LOAN)
179
- params["item_pid"] = dict(type="pitmid", value="itemid-62")
180
- params["start_date"] = start_date
181
- params["transaction_user_pid"] = str(librarian.id)
182
- res = client.post(url, headers=json_headers, data=json.dumps(params))
183
- assert res.status_code == 202
184
- loan = res.get_json()["metadata"]
185
- assert loan["state"] == "ITEM_ON_LOAN"
186
- assert loan["item_pid"] == params["item_pid"]
187
- assert loan["start_date"] == start_date
188
- assert loan["end_date"] == end_date
189
- assert loan["transaction_date"]
190
-
191
- start_date = (now + timedelta(days=3)).date().isoformat()
192
- end_date = (now + timedelta(days=15)).date().isoformat()
193
-
194
- # it should succeed when start/end dates provided
195
- params = deepcopy(NEW_LOAN)
196
- params["item_pid"] = dict(type="pitmid", value="itemid-63")
197
- params["start_date"] = start_date
198
- params["end_date"] = end_date
199
- params["transaction_user_pid"] = str(librarian.id)
200
- res = client.post(url, headers=json_headers, data=json.dumps(params))
201
- assert res.status_code == 202
202
- loan = res.get_json()["metadata"]
203
- assert loan["state"] == "ITEM_ON_LOAN"
204
- assert loan["item_pid"] == params["item_pid"]
205
- assert loan["start_date"] == start_date
206
- assert loan["end_date"] == end_date
207
- assert loan["transaction_date"]
208
-
209
- # it should fail when only end date provided
210
- params = deepcopy(NEW_LOAN)
211
- params["item_pid"] = dict(type="pitmid", value="itemid-63")
212
- params["end_date"] = end_date
213
- params["transaction_user_pid"] = str(librarian.id)
214
- res = client.post(url, headers=json_headers, data=json.dumps(params))
215
- assert res.status_code == 400
216
-
217
- # it should fail when wrong date provided
218
- start_date = (now + timedelta(days=3)).date().isoformat()
219
- past_end_date = now - timedelta(days=30)
220
- params = deepcopy(NEW_LOAN)
221
- params["item_pid"] = dict(type="pitmid", value="itemid-63")
222
- params["start_date"] = start_date
223
- params["end_date"] = past_end_date.date().isoformat()
224
- params["transaction_user_pid"] = str(librarian.id)
225
- res = client.post(url, headers=json_headers, data=json.dumps(params))
226
- assert res.status_code == 400
227
-
228
-
229
- def test_self_checkout_search(app, client, json_headers, users, testdata):
230
- """Test self-checkout search."""
231
- app.config["ILS_SELF_CHECKOUT_ENABLED"] = True
232
-
233
- # test that anonymous user cannot search for barcodes
234
- url = url_for("invenio_app_ils_circulation.loan_self_checkout")
235
- res = client.get(f"{url}?barcode=123456", headers=json_headers)
236
- assert res.status_code == 401
237
-
238
- user_login(client, "patron2", users)
239
-
240
- # test the missing parameter error
241
- url = url_for("invenio_app_ils_circulation.loan_self_checkout")
242
- res = client.get(url, headers=json_headers)
243
- assert res.status_code == 400
244
-
245
- # test that authenticated user can search for barcodes, but it will return
246
- # 400 when not found
247
- unexisting_barcode = "123456"
248
- res = client.get(f"{url}?barcode={unexisting_barcode}", headers=json_headers)
249
- assert res.status_code == 400
250
-
251
- # test that an error is returned when the item cannot circulate
252
- in_binding_item_barcode = "123456789-74"
253
- url = url_for("invenio_app_ils_circulation.loan_self_checkout")
254
- res = client.get(f"{url}?barcode={in_binding_item_barcode}", headers=json_headers)
255
- assert res.status_code == 400
256
- # assert that the payload will contain the key error with a msg
257
- response = res.get_json()
258
- assert LoanSelfCheckoutItemInvalidStatus.description in response["message"]
259
- assert LoanSelfCheckoutItemInvalidStatus.supportCode in response["supportCode"]
260
-
261
- # test that no error is returned when the item is marked as missing
262
- missing_item_barcode = "123456789-1"
263
- missing_item_pid = "itemid-1"
264
- url = url_for("invenio_app_ils_circulation.loan_self_checkout")
265
- res = client.get(f"{url}?barcode={missing_item_barcode}", headers=json_headers)
266
- assert res.status_code == 200
267
-
268
- # assert item is no longer marked as missing
269
- response = res.get_json()
270
- item_pid = response["metadata"]["pid"]
271
- assert item_pid == missing_item_pid
272
- item = Item.get_record_by_pid(item_pid)
273
- assert item["status"] == "CAN_CIRCULATE"
274
-
275
- # create a loan on the same patron, and another one on another patron
276
- user_login(client, "librarian", users)
277
- url = url_for("invenio_app_ils_circulation.loan_checkout")
278
-
279
- for item_pid, patron_pid in [
280
- ("itemid-60", "2"), # barcode 123456789-60
281
- ("itemid-61", "1"), # barcode 123456789-61
282
- ]:
283
- params = deepcopy(NEW_LOAN)
284
- params["transaction_user_pid"] = str(users["librarian"].id)
285
- params["item_pid"] = dict(type="pitmid", value=item_pid)
286
- params["patron_pid"] = patron_pid
287
- res = client.post(url, headers=json_headers, data=json.dumps(params))
288
- assert res.status_code == 202
289
-
290
- # ensure new loans and related items are fully indexed
291
- current_search.flush_and_refresh(index="*")
292
-
293
- user_login(client, "patron2", users)
294
-
295
- # test that an error is returned when the item is already on loan by the same user
296
- on_loan_same_patron_barcode = "123456789-60"
297
- url = url_for("invenio_app_ils_circulation.loan_self_checkout")
298
- res = client.get(
299
- f"{url}?barcode={on_loan_same_patron_barcode}", headers=json_headers
300
- )
301
- assert res.status_code == 400
302
- # assert that the payload will contain the key error with a msg
303
- response = res.get_json()
304
- assert LoanSelfCheckoutItemActiveLoan.description in response["message"]
305
- assert LoanSelfCheckoutItemActiveLoan.supportCode in response["supportCode"]
306
-
307
- # test that an error is returned when the item is already on loan by another user
308
- on_loan_other_patron_barcode = "123456789-61"
309
- url = url_for("invenio_app_ils_circulation.loan_self_checkout")
310
- res = client.get(
311
- f"{url}?barcode={on_loan_other_patron_barcode}", headers=json_headers
312
- )
313
- assert res.status_code == 400
314
- # assert that the payload will contain the key error with a msg
315
- response = res.get_json()
316
- assert LoanSelfCheckoutItemActiveLoan.description in response["message"]
317
- assert LoanSelfCheckoutItemActiveLoan.supportCode in response["supportCode"]
318
-
319
- # test happy path
320
- available_barcode = "123456789-10"
321
- url = url_for("invenio_app_ils_circulation.loan_self_checkout")
322
- res = client.get(f"{url}?barcode={available_barcode}", headers=json_headers)
323
- assert res.status_code == 200
324
- response = res.get_json()
325
- assert response["metadata"]["pid"] == "itemid-10"
326
-
327
-
328
- def test_self_checkout(app, client, json_headers, users, testdata):
329
- """Test self-checkout."""
330
-
331
- def _create_request(patron, document_pid):
332
- url = url_for("invenio_app_ils_circulation.loan_request")
333
- user = user_login(client, patron, users)
334
- params = deepcopy(NEW_LOAN_REQUEST)
335
- params["document_pid"] = document_pid
336
- params["patron_pid"] = str(user.id)
337
- params["transaction_user_pid"] = str(user.id)
338
- res = client.post(url, headers=json_headers, data=json.dumps(params))
339
- assert res.status_code == 202
340
- current_search.flush_and_refresh(index="*")
341
-
342
- def _self_checkout(patron, item_pid, document_pid):
343
- url = url_for("invenio_app_ils_circulation.loan_self_checkout")
344
- params = deepcopy(NEW_LOAN)
345
- params["document_pid"] = document_pid
346
- params["item_pid"] = dict(type="pitmid", value=item_pid)
347
- params["patron_pid"] = str(patron.id)
348
- params["transaction_user_pid"] = str(patron.id)
349
- return client.post(url, headers=json_headers, data=json.dumps(params))
350
-
351
- url = url_for("invenio_app_ils_circulation.loan_self_checkout")
352
-
353
- app.config["ILS_SELF_CHECKOUT_ENABLED"] = False
354
-
355
- # test a logged in user cannot self-checkout when the feature is disabled
356
- patron2 = user_login(client, "patron2", users)
357
- res = client.post(url, headers=json_headers)
358
- assert res.status_code == 403
359
-
360
- user_logout(client)
361
- app.config["ILS_SELF_CHECKOUT_ENABLED"] = True
362
-
363
- # test that anonymous user cannot self-checkout
364
- res = client.post(url, headers=json_headers)
365
- assert res.status_code == 401
366
-
367
- # test overbooked books
368
- # create multiple requests from different patrons
369
- _create_request("patron1", "docid-15")
370
- _create_request("patron3", "docid-15")
371
-
372
- document_rec = current_app_ils.document_record_cls.get_record_by_pid("docid-15")
373
- document = document_rec.replace_refs()
374
- assert document["circulation"]["overbooked"]
375
-
376
- # test that user cannot self-checkout an overbooked book, without having a request
377
- patron2 = user_login(client, "patron2", users)
378
- res = _self_checkout(patron2, "itemid-71", "docid-15")
379
- assert res.status_code == 400
380
- response = res.get_json()
381
- assert LoanSelfCheckoutDocumentOverbooked.description in response["message"]
382
- assert LoanSelfCheckoutDocumentOverbooked.supportCode in response["supportCode"]
383
-
384
- # test that user cannot self-checkout an overbooked book, having a request
385
- # create request from the same patron
386
- _create_request("patron2", "docid-15")
387
- patron2 = user_login(client, "patron2", users)
388
- res = _self_checkout(patron2, "itemid-71", "docid-15")
389
- assert res.status_code == 400
390
- response = res.get_json()
391
- assert LoanSelfCheckoutDocumentOverbooked.description in response["message"]
392
- assert LoanSelfCheckoutDocumentOverbooked.supportCode in response["supportCode"]
393
-
394
- # test that user can self-checkout having a prior request
395
- _create_request("patron2", "docid-16")
396
-
397
- patron2 = user_login(client, "patron2", users)
398
- res = _self_checkout(patron2, "itemid-72", "docid-16")
399
- assert res.status_code == 202
400
- response = res.get_json()
401
- assert response["metadata"]["delivery"]["method"] == "SELF-CHECKOUT"
402
-
403
- # test that user can self-checkout without having a prior request, even if there
404
- # are other requests on the book (but not overbooked)
405
- _create_request("patron1", "docid-17")
406
-
407
- patron2 = user_login(client, "patron2", users)
408
- res = _self_checkout(patron2, "itemid-73", "docid-17")
409
- assert res.status_code == 202
410
- response = res.get_json()
411
- assert response["metadata"]["delivery"]["method"] == "SELF-CHECKOUT"
412
-
413
- # test self-checkout with item marked as missing raises no error and marks the item as CAN_CIRCULATE
414
- missing_item_pid = "itemid-1"
415
- app.config["ILS_SELF_CHECKOUT_ENABLED"] = True
416
- patron2 = user_login(client, "patron2", users)
417
- res = _self_checkout(patron2, missing_item_pid, "docid-1")
418
- assert res.status_code == 202
419
- response = res.get_json()
420
- assert response["metadata"]["delivery"]["method"] == "SELF-CHECKOUT"
421
- item = Item.get_record_by_pid(missing_item_pid)
422
- assert item["status"] == "CAN_CIRCULATE"
@@ -1,43 +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 loans default durations."""
9
-
10
- from datetime import timedelta
11
-
12
- import arrow
13
-
14
- from invenio_app_ils.ill.api import (
15
- circulation_default_extension_duration,
16
- circulation_default_loan_duration,
17
- )
18
-
19
- FAKE_LOAN_ITEM_NO_RESTRICTIONS = {"item_pid": {"type": "pitmid", "value": "itemid-1"}}
20
- FAKE_LOAN_ITEM_ONE_WEEK = {"item_pid": {"type": "pitmid", "value": "itemid-2"}}
21
- FAKE_LOAN_ITEM_TWO_WEEKS = {"item_pid": {"type": "pitmid", "value": "itemid-3"}}
22
- FAKE_LOAN_ITEM_THREE_WEEKS = {"item_pid": {"type": "pitmid", "value": "itemid-4"}}
23
-
24
-
25
- def test_loans_default_durations(testdata):
26
- """Test loans default durations."""
27
- tomorrow = arrow.utcnow() + timedelta(days=1)
28
- not_overdue_end_date = tomorrow.date().isoformat()
29
- for duration_func in (
30
- circulation_default_loan_duration,
31
- circulation_default_extension_duration,
32
- ):
33
- FAKE_LOAN_ITEM_NO_RESTRICTIONS["end_date"] = not_overdue_end_date
34
- assert duration_func(FAKE_LOAN_ITEM_NO_RESTRICTIONS, None) == timedelta(weeks=4)
35
-
36
- FAKE_LOAN_ITEM_ONE_WEEK["end_date"] = not_overdue_end_date
37
- assert duration_func(FAKE_LOAN_ITEM_ONE_WEEK, None) == timedelta(weeks=1)
38
-
39
- FAKE_LOAN_ITEM_TWO_WEEKS["end_date"] = not_overdue_end_date
40
- assert duration_func(FAKE_LOAN_ITEM_TWO_WEEKS, None) == timedelta(weeks=2)
41
-
42
- FAKE_LOAN_ITEM_THREE_WEEKS["end_date"] = not_overdue_end_date
43
- assert duration_func(FAKE_LOAN_ITEM_THREE_WEEKS, None) == timedelta(weeks=3)
@@ -1,18 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- #
3
- # Copyright (C) 2019 CERN.
4
- #
5
- # Invenio-Circulation is free software; you can redistribute it and/or modify
6
- # it under the terms of the MIT License; see LICENSE file for more details.
7
-
8
- """Tests for loan item resolver."""
9
-
10
- from invenio_circulation.api import Loan
11
-
12
-
13
- def test_loan_document_resolver(app, testdata):
14
- """Test item resolving from loan."""
15
- loan_pid = testdata["loans"][1]["pid"]
16
- loan = Loan.get_record_by_pid(loan_pid)
17
- loan = loan.replace_refs()
18
- assert loan["document"]["pid"] == loan["document_pid"]
@@ -1,122 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- #
3
- # Copyright (C) 2019-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 extend."""
9
-
10
- import json
11
- from copy import deepcopy
12
- from datetime import timedelta
13
-
14
- import arrow
15
- from flask import url_for
16
- from invenio_circulation.api import Loan
17
- from invenio_db import db
18
-
19
- from invenio_app_ils.circulation.api import circulation_default_loan_duration_for_item
20
- from invenio_app_ils.items.api import Item
21
- from tests.helpers import user_login
22
-
23
-
24
- def _checkout_loan_pid1(client, json_headers, users, loan_params):
25
- """Create an ongoing loan."""
26
-
27
- def _checkout(loan_pid, params):
28
- """Perform checkout action."""
29
- user_login(client, "librarian", users)
30
- checkout_url = url_for(
31
- "invenio_circulation_loan_actions.loanid_actions",
32
- pid_value=loan_pid,
33
- action="checkout",
34
- )
35
- resp = client.post(checkout_url, headers=json_headers, data=json.dumps(params))
36
- assert resp.status_code == 202
37
- return resp.get_json()
38
-
39
- loan_pid = "loanid-1"
40
- params = deepcopy(loan_params)
41
- params["document_pid"] = "docid-1"
42
- params["item_pid"]["value"] = "itemid-2"
43
-
44
- return _checkout(loan_pid, params)
45
-
46
-
47
- def test_loan_extend_permissions(client, json_headers, users, testdata, loan_params):
48
- """Test loan can be extended."""
49
- params = deepcopy(loan_params)
50
- del params["transaction_date"]
51
- loan = _checkout_loan_pid1(client, json_headers, users, params)
52
-
53
- tests = [
54
- ("admin", 202),
55
- ("librarian", 202),
56
- ("patron1", 202),
57
- ("patron3", 403),
58
- ]
59
-
60
- for username, expected_resp_code in tests:
61
- extend_url = loan["links"]["actions"]["extend"]
62
-
63
- user_login(client, username, users)
64
- res = client.post(
65
- extend_url,
66
- headers=json_headers,
67
- data=json.dumps(params),
68
- )
69
- assert res.status_code == expected_resp_code
70
-
71
-
72
- def test_loan_extension_end_date(
73
- app, client, json_headers, users, testdata, loan_params
74
- ):
75
- """Test loan end date after extension."""
76
- params = deepcopy(loan_params)
77
- del params["transaction_date"]
78
- record = _checkout_loan_pid1(client, json_headers, users, params)
79
- extend_url = record["links"]["actions"]["extend"]
80
- loan = record["metadata"]
81
-
82
- item = Item.get_record_by_pid(loan["item_pid"]["value"])
83
- item_loan_duration = circulation_default_loan_duration_for_item(item)
84
-
85
- user_login(client, "patron1", users)
86
-
87
- def _set_loan_end_date(loan_pid, new_end_date):
88
- """Set loan end date."""
89
- loan = Loan.get_record_by_pid(loan_pid)
90
- loan["end_date"] = new_end_date.date().isoformat()
91
- loan.commit()
92
- db.session.commit()
93
-
94
- def test_extension_end_date_loan_not_overdue(loan_pid):
95
- """Test that new end date is added to the current end date."""
96
- now = arrow.utcnow()
97
- new_end_date = now + timedelta(days=10) # loan is not overdue
98
- _set_loan_end_date(loan_pid, new_end_date)
99
-
100
- expected_end_date = new_end_date + item_loan_duration
101
-
102
- res = client.post(extend_url, headers=json_headers, data=json.dumps(params))
103
- assert res.status_code == 202
104
- new_loan = res.get_json()["metadata"]
105
- assert new_loan["end_date"] == expected_end_date.date().isoformat()
106
-
107
- def test_extension_end_date_loan_overdue(loan_pid):
108
- """Test that new end date is added to today."""
109
- now = arrow.utcnow()
110
-
111
- new_end_date = now - timedelta(days=2) # loan is overdue since 2 days
112
- _set_loan_end_date(loan_pid, new_end_date)
113
-
114
- expected_end_date = now + item_loan_duration
115
- res = client.post(extend_url, headers=json_headers, data=json.dumps(params))
116
- assert res.status_code == 202
117
- new_loan = res.get_json()["metadata"]
118
- # loan new end date should start from now
119
- assert new_loan["end_date"] == expected_end_date.date().isoformat()
120
-
121
- test_extension_end_date_loan_not_overdue(loan["pid"])
122
- test_extension_end_date_loan_overdue(loan["pid"])