invenio-app-ils 4.2.0__py2.py3-none-any.whl → 4.3.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.
@@ -15,13 +15,16 @@ import arrow
15
15
  from flask import url_for
16
16
  from flask_principal import UserNeed
17
17
  from invenio_access.permissions import Permission
18
+ from invenio_search import current_search
18
19
 
19
- from invenio_app_ils.items.api import Item
20
- from invenio_app_ils.permissions import (
21
- authenticated_user_permission,
22
- loan_checkout_permission,
23
- views_permissions_factory,
20
+ from invenio_app_ils.errors import (
21
+ LoanSelfCheckoutDocumentOverbooked,
22
+ LoanSelfCheckoutItemActiveLoan,
23
+ LoanSelfCheckoutItemInvalidStatus,
24
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
25
28
  from tests.helpers import user_login, user_logout
26
29
 
27
30
  NEW_LOAN = {
@@ -30,7 +33,15 @@ NEW_LOAN = {
30
33
  "patron_pid": "3",
31
34
  "transaction_location_pid": "locid-1",
32
35
  "pickup_location_pid": "locid-1",
33
- "delivery": {"method": "PICK_UP"},
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"},
34
45
  }
35
46
 
36
47
 
@@ -215,77 +226,171 @@ def test_checkout_loader_start_end_dates(app, client, json_headers, users, testd
215
226
  assert res.status_code == 400
216
227
 
217
228
 
218
- def _views_permissions_factory(action):
219
- """Override ILS views permissions factory."""
220
- if action == "circulation-loan-checkout":
221
- return authenticated_user_permission()
222
- return views_permissions_factory(action)
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
223
237
 
238
+ user_login(client, "patron2", users)
224
239
 
225
- def test_self_checkout(app, client, json_headers, users, testdata):
226
- """Tests for self checkout feature."""
227
- app.config["ILS_SELF_CHECKOUT_ENABLED"] = True
228
- app.config["ILS_VIEWS_PERMISSIONS_FACTORY"] = _views_permissions_factory
229
- app.config["RECORDS_REST_ENDPOINTS"]["pitmid"][
230
- "list_permission_factory_imp"
231
- ] = authenticated_user_permission
232
- app.config["ILS_CIRCULATION_RECORDS_REST_ENDPOINTS"]["loanid"][
233
- "update_permission_factory_imp"
234
- ] = loan_checkout_permission
235
-
236
- # Self checkout by librarian should pass
237
- librarian = users["librarian"]
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
+ missing_item_barcode = "123456789-1"
253
+ url = url_for("invenio_app_ils_circulation.loan_self_checkout")
254
+ res = client.get(f"{url}?barcode={missing_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
+ # create a loan on the same patron, and another one on another patron
238
262
  user_login(client, "librarian", users)
239
- params = deepcopy(NEW_LOAN)
240
- params["item_pid"] = dict(type="pitmid", value="itemid-60")
241
- params["transaction_user_pid"] = str(librarian.id)
242
- params["patron_pid"] = str(librarian.id)
243
263
  url = url_for("invenio_app_ils_circulation.loan_checkout")
244
- res = client.post(url, headers=json_headers, data=json.dumps(params))
245
264
 
246
- assert res.status_code == 202
247
- loan = res.get_json()["metadata"]
248
- assert loan["state"] == "ITEM_ON_LOAN"
249
- assert loan["item_pid"] == params["item_pid"]
250
- assert loan["patron_pid"] == str(librarian.id)
251
- user_logout(client)
265
+ for item_pid, patron_pid in [
266
+ ("itemid-60", "2"), # barcode 123456789-60
267
+ ("itemid-61", "1"), # barcode 123456789-61
268
+ ]:
269
+ params = deepcopy(NEW_LOAN)
270
+ params["transaction_user_pid"] = str(users["librarian"].id)
271
+ params["item_pid"] = dict(type="pitmid", value=item_pid)
272
+ params["patron_pid"] = patron_pid
273
+ res = client.post(url, headers=json_headers, data=json.dumps(params))
274
+ assert res.status_code == 202
275
+
276
+ # ensure new loans and related items are fully indexed
277
+ current_search.flush_and_refresh(index="*")
278
+
279
+ user_login(client, "patron2", users)
280
+
281
+ # test that an error is returned when the item is already on loan by the same user
282
+ on_loan_same_patron_barcode = "123456789-60"
283
+ url = url_for("invenio_app_ils_circulation.loan_self_checkout")
284
+ res = client.get(
285
+ f"{url}?barcode={on_loan_same_patron_barcode}", headers=json_headers
286
+ )
287
+ assert res.status_code == 400
288
+ # assert that the payload will contain the key error with a msg
289
+ response = res.get_json()
290
+ assert LoanSelfCheckoutItemActiveLoan.description in response["message"]
291
+ assert LoanSelfCheckoutItemActiveLoan.supportCode in response["supportCode"]
292
+
293
+ # test that an error is returned when the item is already on loan by another user
294
+ on_loan_other_patron_barcode = "123456789-61"
295
+ url = url_for("invenio_app_ils_circulation.loan_self_checkout")
296
+ res = client.get(
297
+ f"{url}?barcode={on_loan_other_patron_barcode}", headers=json_headers
298
+ )
299
+ assert res.status_code == 400
300
+ # assert that the payload will contain the key error with a msg
301
+ response = res.get_json()
302
+ assert LoanSelfCheckoutItemActiveLoan.description in response["message"]
303
+ assert LoanSelfCheckoutItemActiveLoan.supportCode in response["supportCode"]
252
304
 
253
- # Self checkout by patron should pass if patron_pid matches
254
- patron3 = users["patron3"]
255
- user_login(client, "patron3", users)
256
- params = deepcopy(NEW_LOAN)
257
- params["item_pid"] = dict(type="pitmid", value="itemid-61")
258
- params["transaction_user_pid"] = str(patron3.id)
259
- params["patron_pid"] = str(patron3.id)
260
- url = url_for("invenio_app_ils_circulation.loan_checkout")
261
- res = client.post(url, headers=json_headers, data=json.dumps(params))
305
+ # test happy path
306
+ available_barcode = "123456789-10"
307
+ url = url_for("invenio_app_ils_circulation.loan_self_checkout")
308
+ res = client.get(f"{url}?barcode={available_barcode}", headers=json_headers)
309
+ assert res.status_code == 200
310
+ response = res.get_json()
311
+ assert response["metadata"]["pid"] == "itemid-10"
262
312
 
263
- assert res.status_code == 202
264
- loan = res.get_json()["metadata"]
265
- assert loan["state"] == "ITEM_ON_LOAN"
266
- assert loan["item_pid"] == params["item_pid"]
267
- assert loan["patron_pid"] == str(patron3.id)
268
313
 
269
- # Self checkout should fail if feature flag is not set to true
314
+ def test_self_checkout(app, client, json_headers, users, testdata):
315
+ """Test self-checkout."""
316
+
317
+ def _create_request(patron, document_pid):
318
+ url = url_for("invenio_app_ils_circulation.loan_request")
319
+ user = user_login(client, patron, users)
320
+ params = deepcopy(NEW_LOAN_REQUEST)
321
+ params["document_pid"] = document_pid
322
+ params["patron_pid"] = str(user.id)
323
+ params["transaction_user_pid"] = str(user.id)
324
+ res = client.post(url, headers=json_headers, data=json.dumps(params))
325
+ assert res.status_code == 202
326
+ current_search.flush_and_refresh(index="*")
327
+
328
+ def _self_checkout(patron, item_pid, document_pid):
329
+ params = deepcopy(NEW_LOAN)
330
+ params["document_pid"] = document_pid
331
+ params["item_pid"] = dict(type="pitmid", value=item_pid)
332
+ params["patron_pid"] = str(patron.id)
333
+ params["transaction_user_pid"] = str(patron.id)
334
+ return client.post(url, headers=json_headers, data=json.dumps(params))
335
+
336
+ url = url_for("invenio_app_ils_circulation.loan_self_checkout")
337
+
270
338
  app.config["ILS_SELF_CHECKOUT_ENABLED"] = False
271
- params = deepcopy(NEW_LOAN)
272
- params["item_pid"] = dict(type="pitmid", value="itemid-62")
273
- params["transaction_user_pid"] = str(patron3.id)
274
- params["patron_pid"] = str(patron3.id)
275
- url = url_for("invenio_app_ils_circulation.loan_checkout")
276
- res = client.post(url, headers=json_headers, data=json.dumps(params))
277
339
 
340
+ # test a logged in user cannot self-checkout when the feature is disabled
341
+ patron2 = user_login(client, "patron2", users)
342
+ res = client.post(url, headers=json_headers)
278
343
  assert res.status_code == 403
344
+
279
345
  user_logout(client)
346
+ app.config["ILS_SELF_CHECKOUT_ENABLED"] = True
280
347
 
281
- # Self checkout should fail if if patron_pid doesn't match
282
- patron1 = users["patron1"]
283
- user_login(client, "patron1", users)
284
- params = deepcopy(NEW_LOAN)
285
- params["item_pid"] = dict(type="pitmid", value="itemid-63")
286
- params["transaction_user_pid"] = str(patron1.id)
287
- params["patron_pid"] = str(patron3.id)
288
- url = url_for("invenio_app_ils_circulation.loan_checkout")
289
- res = client.post(url, headers=json_headers, data=json.dumps(params))
348
+ # test that anonymous user cannot self-checkout
349
+ res = client.post(url, headers=json_headers)
350
+ assert res.status_code == 401
290
351
 
291
- assert res.status_code == 403
352
+ # test overbooked books
353
+ # create multiple requests from different patrons
354
+ _create_request("patron1", "docid-15")
355
+ _create_request("patron3", "docid-15")
356
+
357
+ document_rec = current_app_ils.document_record_cls.get_record_by_pid("docid-15")
358
+ document = document_rec.replace_refs()
359
+ assert document["circulation"]["overbooked"]
360
+
361
+ # test that user cannot self-checkout an overbooked book, without having a request
362
+ patron2 = user_login(client, "patron2", users)
363
+ res = _self_checkout(patron2, "itemid-71", "docid-15")
364
+ assert res.status_code == 400
365
+ response = res.get_json()
366
+ assert LoanSelfCheckoutDocumentOverbooked.description in response["message"]
367
+ assert LoanSelfCheckoutDocumentOverbooked.supportCode in response["supportCode"]
368
+
369
+ # test that user cannot self-checkout an overbooked book, having a request
370
+ # create request from the same patron
371
+ _create_request("patron2", "docid-15")
372
+ patron2 = user_login(client, "patron2", users)
373
+ res = _self_checkout(patron2, "itemid-71", "docid-15")
374
+ assert res.status_code == 400
375
+ response = res.get_json()
376
+ assert LoanSelfCheckoutDocumentOverbooked.description in response["message"]
377
+ assert LoanSelfCheckoutDocumentOverbooked.supportCode in response["supportCode"]
378
+
379
+ # test that user can self-checkout having a prior request
380
+ _create_request("patron2", "docid-16")
381
+
382
+ patron2 = user_login(client, "patron2", users)
383
+ res = _self_checkout(patron2, "itemid-72", "docid-16")
384
+ assert res.status_code == 202
385
+ response = res.get_json()
386
+ assert response["metadata"]["delivery"]["method"] == "SELF-CHECKOUT"
387
+
388
+ # test that user can self-checkout without having a prior request, even if there
389
+ # are other requests on the book (but not overbooked)
390
+ _create_request("patron1", "docid-17")
391
+
392
+ patron2 = user_login(client, "patron2", users)
393
+ res = _self_checkout(patron2, "itemid-73", "docid-17")
394
+ assert res.status_code == 202
395
+ response = res.get_json()
396
+ assert response["metadata"]["delivery"]["method"] == "SELF-CHECKOUT"
tests/data/items.json CHANGED
@@ -1,7 +1,7 @@
1
1
  [
2
2
  {
3
3
  "pid": "itemid-1",
4
- "created_by": {"type": "script", "value": "demo"},
4
+ "created_by": { "type": "script", "value": "demo" },
5
5
  "barcode": "123456789-1",
6
6
  "document_pid": "docid-1",
7
7
  "internal_location_pid": "ilocid-1",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  {
14
14
  "pid": "itemid-2",
15
- "created_by": {"type": "script", "value": "demo"},
15
+ "created_by": { "type": "script", "value": "demo" },
16
16
  "barcode": "123456789-2",
17
17
  "document_pid": "docid-1",
18
18
  "internal_location_pid": "ilocid-2",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  {
25
25
  "pid": "itemid-3",
26
- "created_by": {"type": "script", "value": "demo"},
26
+ "created_by": { "type": "script", "value": "demo" },
27
27
  "barcode": "123456789-3",
28
28
  "document_pid": "docid-2",
29
29
  "internal_location_pid": "ilocid-1",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  {
36
36
  "pid": "itemid-4",
37
- "created_by": {"type": "script", "value": "demo"},
37
+ "created_by": { "type": "script", "value": "demo" },
38
38
  "barcode": "123456789-4",
39
39
  "document_pid": "docid-3",
40
40
  "internal_location_pid": "ilocid-2",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  {
47
47
  "pid": "itemid-5",
48
- "created_by": {"type": "script", "value": "demo"},
48
+ "created_by": { "type": "script", "value": "demo" },
49
49
  "barcode": "123456789-5",
50
50
  "document_pid": "docid-3",
51
51
  "internal_location_pid": "ilocid-1",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  {
58
58
  "pid": "itemid-6",
59
- "created_by": {"type": "script", "value": "demo"},
59
+ "created_by": { "type": "script", "value": "demo" },
60
60
  "barcode": "123456789-6",
61
61
  "document_pid": "docid-3",
62
62
  "internal_location_pid": "ilocid-1",
@@ -67,7 +67,7 @@
67
67
  },
68
68
  {
69
69
  "pid": "itemid-7",
70
- "created_by": {"type": "script", "value": "demo"},
70
+ "created_by": { "type": "script", "value": "demo" },
71
71
  "barcode": "123456789-7",
72
72
  "document_pid": "docid-5",
73
73
  "internal_location_pid": "ilocid-2",
@@ -78,7 +78,7 @@
78
78
  },
79
79
  {
80
80
  "pid": "itemid-8",
81
- "created_by": {"type": "script", "value": "demo"},
81
+ "created_by": { "type": "script", "value": "demo" },
82
82
  "barcode": "123456789-8",
83
83
  "document_pid": "docid-5",
84
84
  "internal_location_pid": "ilocid-1",
@@ -89,7 +89,7 @@
89
89
  },
90
90
  {
91
91
  "pid": "itemid-9",
92
- "created_by": {"type": "script", "value": "demo"},
92
+ "created_by": { "type": "script", "value": "demo" },
93
93
  "barcode": "123456789-9",
94
94
  "document_pid": "docid-5",
95
95
  "internal_location_pid": "ilocid-1",
@@ -100,7 +100,7 @@
100
100
  },
101
101
  {
102
102
  "pid": "itemid-10",
103
- "created_by": {"type": "script", "value": "demo"},
103
+ "created_by": { "type": "script", "value": "demo" },
104
104
  "barcode": "123456789-10",
105
105
  "document_pid": "docid-5",
106
106
  "internal_location_pid": "ilocid-1",
@@ -111,7 +111,7 @@
111
111
  },
112
112
  {
113
113
  "pid": "itemid-50",
114
- "created_by": {"type": "script", "value": "demo"},
114
+ "created_by": { "type": "script", "value": "demo" },
115
115
  "barcode": "123456789-50",
116
116
  "document_pid": "docid-5",
117
117
  "internal_location_pid": "ilocid-1",
@@ -123,7 +123,7 @@
123
123
  },
124
124
  {
125
125
  "pid": "itemid-51",
126
- "created_by": {"type": "script", "value": "demo"},
126
+ "created_by": { "type": "script", "value": "demo" },
127
127
  "barcode": "123456789-51",
128
128
  "document_pid": "docid-5",
129
129
  "internal_location_pid": "ilocid-2",
@@ -135,7 +135,7 @@
135
135
  },
136
136
  {
137
137
  "pid": "itemid-52",
138
- "created_by": {"type": "script", "value": "demo"},
138
+ "created_by": { "type": "script", "value": "demo" },
139
139
  "barcode": "123456789-52",
140
140
  "document_pid": "docid-5",
141
141
  "internal_location_pid": "ilocid-1",
@@ -147,7 +147,7 @@
147
147
  },
148
148
  {
149
149
  "pid": "itemid-53",
150
- "created_by": {"type": "script", "value": "demo"},
150
+ "created_by": { "type": "script", "value": "demo" },
151
151
  "barcode": "123456789-53",
152
152
  "document_pid": "docid-5",
153
153
  "internal_location_pid": "ilocid-1",
@@ -159,7 +159,7 @@
159
159
  },
160
160
  {
161
161
  "pid": "itemid-54",
162
- "created_by": {"type": "script", "value": "demo"},
162
+ "created_by": { "type": "script", "value": "demo" },
163
163
  "barcode": "123456789-54",
164
164
  "document_pid": "docid-5",
165
165
  "internal_location_pid": "ilocid-1",
@@ -171,7 +171,7 @@
171
171
  },
172
172
  {
173
173
  "pid": "itemid-55",
174
- "created_by": {"type": "script", "value": "demo"},
174
+ "created_by": { "type": "script", "value": "demo" },
175
175
  "barcode": "123456789-55",
176
176
  "document_pid": "docid-1",
177
177
  "internal_location_pid": "ilocid-1",
@@ -182,7 +182,7 @@
182
182
  },
183
183
  {
184
184
  "pid": "itemid-56",
185
- "created_by": {"type": "script", "value": "demo"},
185
+ "created_by": { "type": "script", "value": "demo" },
186
186
  "barcode": "123456789-54",
187
187
  "document_pid": "docid-5",
188
188
  "internal_location_pid": "ilocid-1",
@@ -193,7 +193,7 @@
193
193
  },
194
194
  {
195
195
  "pid": "itemid-57",
196
- "created_by": {"type": "script", "value": "demo"},
196
+ "created_by": { "type": "script", "value": "demo" },
197
197
  "barcode": "123456789-55",
198
198
  "document_pid": "docid-1",
199
199
  "internal_location_pid": "ilocid-1",
@@ -204,7 +204,7 @@
204
204
  },
205
205
  {
206
206
  "pid": "itemid-MISSING",
207
- "created_by": {"type": "script", "value": "demo"},
207
+ "created_by": { "type": "script", "value": "demo" },
208
208
  "barcode": "123456789-55",
209
209
  "document_pid": "docid-1",
210
210
  "internal_location_pid": "ilocid-1",
@@ -215,8 +215,8 @@
215
215
  },
216
216
  {
217
217
  "pid": "itemid-60",
218
- "created_by": {"type": "script", "value": "demo"},
219
- "barcode": "123456789-55",
218
+ "created_by": { "type": "script", "value": "demo" },
219
+ "barcode": "123456789-60",
220
220
  "document_pid": "docid-1",
221
221
  "internal_location_pid": "ilocid-1",
222
222
  "circulation_restriction": "NO_RESTRICTION",
@@ -226,8 +226,8 @@
226
226
  },
227
227
  {
228
228
  "pid": "itemid-61",
229
- "created_by": {"type": "script", "value": "demo"},
230
- "barcode": "123456789-55",
229
+ "created_by": { "type": "script", "value": "demo" },
230
+ "barcode": "123456789-61",
231
231
  "document_pid": "docid-1",
232
232
  "internal_location_pid": "ilocid-1",
233
233
  "circulation_restriction": "NO_RESTRICTION",
@@ -237,7 +237,7 @@
237
237
  },
238
238
  {
239
239
  "pid": "itemid-62",
240
- "created_by": {"type": "script", "value": "demo"},
240
+ "created_by": { "type": "script", "value": "demo" },
241
241
  "barcode": "123456789-55",
242
242
  "document_pid": "docid-1",
243
243
  "internal_location_pid": "ilocid-1",
@@ -248,7 +248,7 @@
248
248
  },
249
249
  {
250
250
  "pid": "itemid-63",
251
- "created_by": {"type": "script", "value": "demo"},
251
+ "created_by": { "type": "script", "value": "demo" },
252
252
  "barcode": "123456789-55",
253
253
  "document_pid": "docid-1",
254
254
  "internal_location_pid": "ilocid-1",
@@ -259,13 +259,35 @@
259
259
  },
260
260
  {
261
261
  "pid": "itemid-71",
262
- "created_by": {"type": "script", "value": "demo"},
262
+ "created_by": { "type": "script", "value": "demo" },
263
263
  "barcode": "123456789-55",
264
- "document_pid": "docid-17",
264
+ "document_pid": "docid-15",
265
265
  "internal_location_pid": "ilocid-4",
266
266
  "circulation_restriction": "NO_RESTRICTION",
267
267
  "medium": "NOT_SPECIFIED",
268
268
  "status": "CAN_CIRCULATE",
269
269
  "document": {}
270
+ },
271
+ {
272
+ "pid": "itemid-72",
273
+ "created_by": { "type": "script", "value": "demo" },
274
+ "barcode": "123456789-72",
275
+ "document_pid": "docid-16",
276
+ "internal_location_pid": "ilocid-1",
277
+ "circulation_restriction": "NO_RESTRICTION",
278
+ "medium": "NOT_SPECIFIED",
279
+ "status": "CAN_CIRCULATE",
280
+ "document": {}
281
+ },
282
+ {
283
+ "pid": "itemid-73",
284
+ "created_by": { "type": "script", "value": "demo" },
285
+ "barcode": "123456789-73",
286
+ "document_pid": "docid-17",
287
+ "internal_location_pid": "ilocid-1",
288
+ "circulation_restriction": "NO_RESTRICTION",
289
+ "medium": "NOT_SPECIFIED",
290
+ "status": "CAN_CIRCULATE",
291
+ "document": {}
270
292
  }
271
293
  ]