|
2 | 2 | from copy import deepcopy |
3 | 3 |
|
4 | 4 | import pytest |
5 | | -from freezegun import freeze_time |
6 | 5 |
|
7 | 6 | from core.metadata_layer import ( |
8 | 7 | CirculationData, |
@@ -299,230 +298,50 @@ def test_apply_updates_existing_licenses(self, db: DatabaseTransactionFixture): |
299 | 298 | assert new_license.id == old_license.id |
300 | 299 | assert old_license.status == LicenseStatus.unavailable |
301 | 300 |
|
302 | | - @freeze_time("2025-03-13T07:00:00+00:00") |
303 | | - def test_apply_updates_multiple_licenses_with_partial_imports( |
| 301 | + def test_apply_updates_existing_license_when_removed_from_feed( |
304 | 302 | self, db: DatabaseTransactionFixture, caplog |
305 | 303 | ): |
306 | | - """This test covers: |
307 | | - - Licenses that used to be in the pool have disappeared from the feed in which case they should be made |
308 | | - unavailable |
309 | | - - The same work can appear several times in the feed and have a different license each time. |
310 | | - Make sure that all existing licenses are not incorrectly set as unavailable - unless they have really |
311 | | - expired.""" |
312 | 304 | edition, pool = db.edition(with_license_pool=True) |
313 | 305 |
|
314 | | - hour_ago = utc_now() - datetime.timedelta(hours=1) |
315 | | - |
316 | | - # Our licensepool in the database has four licenses: |
317 | | - # Valid license A |
318 | | - license_a = db.license( |
| 306 | + # Start with one license for this pool. |
| 307 | + existing_license = db.license( |
319 | 308 | pool, |
320 | | - identifier="A", |
321 | 309 | expires=None, |
322 | 310 | checkouts_left=2, |
323 | 311 | checkouts_available=3, |
324 | 312 | status=LicenseStatus.available, |
325 | | - is_missing=None, |
326 | | - last_checked=None, |
327 | | - ) |
328 | | - # Valid license B |
329 | | - license_b = db.license( |
330 | | - pool, |
331 | | - identifier="B", |
332 | | - expires=None, |
333 | | - checkouts_left=1, |
334 | | - checkouts_available=2, |
335 | | - status=LicenseStatus.available, |
336 | | - is_missing=None, |
337 | | - last_checked=None, |
338 | | - ) |
339 | | - # Valid license C |
340 | | - missing_license = db.license( |
341 | | - pool, |
342 | | - identifier="C", |
343 | | - expires=None, |
344 | | - checkouts_left=5, |
345 | | - checkouts_available=5, |
346 | | - status=LicenseStatus.available, |
347 | | - is_missing=None, |
348 | | - last_checked=None, |
349 | | - ) |
350 | | - # And license D that shows no more checkouts and an already unavailable status. |
351 | | - expired_license = db.license( |
352 | | - pool, |
353 | | - identifier="D", |
354 | | - expires=None, |
355 | | - checkouts_left=0, |
356 | | - checkouts_available=0, |
357 | | - status=LicenseStatus.unavailable, |
358 | | - is_missing=None, |
359 | | - last_checked=None, |
360 | 313 | ) |
361 | 314 |
|
362 | | - # First time we see this work (and pool) during import: Only license A and D appear. |
| 315 | + assert isinstance(existing_license.identifier, str) |
| 316 | + assert existing_license.checkouts_left == 2 |
| 317 | + assert existing_license.checkouts_available == 3 |
| 318 | + assert existing_license.status == LicenseStatus.available |
| 319 | + |
| 320 | + # The feed does not include the license |
363 | 321 | circulation_data = CirculationData( |
364 | | - licenses=[ |
365 | | - LicenseData( |
366 | | - identifier="A", |
367 | | - status=LicenseStatus.available, |
368 | | - checkouts_available=3, |
369 | | - checkout_url="whatever", |
370 | | - status_url="whatever", |
371 | | - ), |
372 | | - LicenseData( |
373 | | - identifier="D", |
374 | | - status=LicenseStatus.unavailable, |
375 | | - checkouts_available=0, |
376 | | - checkout_url="whatever", |
377 | | - status_url="whatever", |
378 | | - ), |
379 | | - ], |
| 322 | + licenses=[], # No licenses in the updated feed |
380 | 323 | data_source=edition.data_source, |
381 | 324 | primary_identifier=edition.primary_identifier, |
382 | 325 | ) |
383 | 326 | with caplog.at_level("WARNING"): |
384 | 327 | circulation_data.apply(db.session, pool.collection) |
385 | 328 | db.session.commit() |
386 | 329 |
|
387 | | - # License A is available |
388 | | - assert license_a.status == LicenseStatus.available |
389 | | - assert license_a.is_missing is False |
390 | | - assert license_a.last_checked is not None and license_a.last_checked >= hour_ago |
391 | | - # License B and missing_license are missing (plus they haven't been checked before) |
392 | | - assert license_b.status == LicenseStatus.unavailable |
393 | | - assert license_b.is_missing is True |
394 | | - assert license_b.last_checked is not None and license_b.last_checked >= hour_ago |
395 | | - assert ( |
396 | | - f"License {license_b.identifier} is missing from feed so set to be unavailable!" |
397 | | - in caplog.text |
398 | | - ) |
399 | | - assert missing_license.status == LicenseStatus.unavailable |
| 330 | + updated_license = pool.licenses[0] |
| 331 | + assert updated_license.id == existing_license.id |
400 | 332 | assert ( |
401 | | - missing_license.last_checked is not None |
402 | | - and missing_license.last_checked >= hour_ago |
403 | | - ) |
404 | | - assert missing_license.is_missing is True |
| 333 | + updated_license.status == LicenseStatus.unavailable |
| 334 | + ) # Status should have changed |
405 | 335 | assert ( |
406 | | - f"License {missing_license.identifier} is missing from feed so set to be unavailable!" |
407 | | - in caplog.text |
408 | | - ) |
409 | | - # License D is in the feed but has run out of checkouts and it's status remains unavailable |
410 | | - assert expired_license.status == LicenseStatus.unavailable |
411 | | - assert expired_license.is_missing is False |
412 | | - assert ( |
413 | | - expired_license.last_checked is not None |
414 | | - and expired_license.last_checked >= hour_ago |
415 | | - ) |
416 | | - |
417 | | - # Second time we see this work (and pool) during the same import: Only license B is in the feed |
418 | | - circulation_data = CirculationData( |
419 | | - licenses=[ |
420 | | - LicenseData( |
421 | | - identifier="B", |
422 | | - status=LicenseStatus.available, |
423 | | - checkouts_available=2, |
424 | | - checkout_url="whatever", |
425 | | - status_url="whatever", |
426 | | - ) |
427 | | - ], |
428 | | - data_source=edition.data_source, |
429 | | - primary_identifier=edition.primary_identifier, |
430 | | - ) |
431 | | - |
432 | | - with caplog.at_level("WARNING"): |
433 | | - circulation_data.apply(db.session, pool.collection) |
434 | | - db.session.commit() |
435 | | - |
436 | | - # License A should remain available |
437 | | - assert license_a.status == LicenseStatus.available |
438 | | - assert license_a.is_missing is False |
439 | | - assert license_a.last_checked is not None and license_a.last_checked >= hour_ago |
440 | | - # License B should now be available and not missing |
441 | | - assert license_b.status == LicenseStatus.available |
442 | | - assert license_b.is_missing is False # type: ignore |
443 | | - assert license_b.last_checked is not None and license_b.last_checked >= hour_ago |
444 | | - # License C is still missing and unavailable |
445 | | - assert missing_license.status == LicenseStatus.unavailable |
446 | | - assert missing_license.is_missing is True |
| 336 | + updated_license.checkouts_left == 2 |
| 337 | + ) # Checkouts left should remain unchanged. |
447 | 338 | assert ( |
448 | | - missing_license.last_checked is not None |
449 | | - and missing_license.last_checked >= hour_ago |
450 | | - ) |
| 339 | + updated_license.checkouts_available == 3 |
| 340 | + ) # Checkouts available should remain unchanged. |
451 | 341 | assert ( |
452 | | - f"License {missing_license.identifier} is missing from feed so set to be unavailable!" |
| 342 | + f"License {existing_license.identifier} has been removed from feed" |
453 | 343 | in caplog.text |
454 | 344 | ) |
455 | | - # The expired license is also missing this time - it remains unavailable but since we saw it |
456 | | - # when importing the work the first time, it remains not missing. |
457 | | - assert expired_license.status == LicenseStatus.unavailable |
458 | | - assert expired_license.is_missing is False |
459 | | - assert ( |
460 | | - expired_license.last_checked is not None |
461 | | - and expired_license.last_checked >= hour_ago |
462 | | - ) |
463 | | - |
464 | | - # Next day during import we see the title with only license A and the expired license D - again. |
465 | | - with freeze_time("2025-03-14T07:00:00+00:00"): |
466 | | - circulation_data = CirculationData( |
467 | | - licenses=[ |
468 | | - LicenseData( |
469 | | - identifier="A", |
470 | | - status=LicenseStatus.available, |
471 | | - checkouts_available=3, |
472 | | - checkout_url="whatever", |
473 | | - status_url="whatever", |
474 | | - ), |
475 | | - LicenseData( |
476 | | - identifier="D", |
477 | | - status=LicenseStatus.unavailable, |
478 | | - checkouts_available=0, |
479 | | - checkout_url="whatever", |
480 | | - status_url="whatever", |
481 | | - ), |
482 | | - ], |
483 | | - data_source=edition.data_source, |
484 | | - primary_identifier=edition.primary_identifier, |
485 | | - ) |
486 | | - circulation_data.apply(db.session, pool.collection) |
487 | | - db.session.commit() |
488 | | - |
489 | | - next_day = hour_ago + datetime.timedelta(days=1) |
490 | | - |
491 | | - # License A is available |
492 | | - assert license_a.status == LicenseStatus.available |
493 | | - assert license_a.is_missing is False |
494 | | - assert ( |
495 | | - license_a.last_checked is not None |
496 | | - and license_a.last_checked >= next_day |
497 | | - ) |
498 | | - # License B and missing_license are missing again |
499 | | - assert license_b.status == LicenseStatus.unavailable |
500 | | - assert license_b.is_missing is True |
501 | | - assert ( |
502 | | - license_b.last_checked is not None |
503 | | - and license_b.last_checked >= next_day |
504 | | - ) |
505 | | - assert ( |
506 | | - f"License {license_b.identifier} is missing from feed so set to be unavailable!" |
507 | | - in caplog.text |
508 | | - ) |
509 | | - assert missing_license.status == LicenseStatus.unavailable |
510 | | - assert ( |
511 | | - missing_license.last_checked is not None |
512 | | - and missing_license.last_checked >= next_day |
513 | | - ) |
514 | | - assert missing_license.is_missing is True |
515 | | - assert ( |
516 | | - f"License {missing_license.identifier} is missing from feed so set to be unavailable!" |
517 | | - in caplog.text |
518 | | - ) |
519 | | - # The expired license is still unavailable |
520 | | - assert expired_license.status == LicenseStatus.unavailable |
521 | | - assert expired_license.is_missing is False |
522 | | - assert ( |
523 | | - expired_license.last_checked is not None |
524 | | - and expired_license.last_checked >= next_day |
525 | | - ) |
526 | 345 |
|
527 | 346 | def test_apply_with_licenses_overrides_availability( |
528 | 347 | self, db: DatabaseTransactionFixture |
|
0 commit comments