diff --git a/papyrus/services/library_sync.py b/papyrus/services/library_sync.py index b8ece19..f9bf3c5 100644 --- a/papyrus/services/library_sync.py +++ b/papyrus/services/library_sync.py @@ -25,7 +25,7 @@ ) from papyrus.schemas.sync import PowerSyncCrudMutation from papyrus.services import media as media_service -from papyrus.services.library_validation import convert_value, normalize_book_payload, uuid_value +from papyrus.services.library_validation import convert_value, uuid_value MODELS: dict[str, Any] = { "reading_goals": SyncReadingGoal, @@ -233,9 +233,6 @@ async def apply_library_mutation( payload = dict(mutation.op_data or {}) payload = {key: value for key, value in payload.items() if key not in {"owner_user_id", "updated_at"}} - if table == "books": - payload = normalize_book_payload(payload) - if is_membership: payload = membership_values(table, mutation.id, payload) diff --git a/papyrus/services/library_validation.py b/papyrus/services/library_validation.py index dc68380..21f3bef 100644 --- a/papyrus/services/library_validation.py +++ b/papyrus/services/library_validation.py @@ -1,4 +1,4 @@ -"""Type conversion and backwards compatibility for library queue payloads.""" +"""Type conversion and validation for library queue payloads.""" from datetime import UTC, datetime from math import isfinite @@ -9,26 +9,6 @@ from sqlalchemy.dialects.postgresql import JSONB from papyrus.core.exceptions import ValidationError -from papyrus.models.sync import SyncBook - -PROMOTED_BOOK_FIELDS = frozenset( - { - "publication_date", - "file_format", - "file_size", - "file_hash", - "is_physical", - "physical_location", - "lent_to", - "lent_at", - "series_id", - "series_name", - "series_number", - "started_at", - "completed_at", - "last_read_at", - } -) def uuid_value(value: object, name: str) -> UUID: @@ -38,29 +18,6 @@ def uuid_value(value: object, name: str) -> UUID: raise ValidationError(f"{name} must be a valid UUID") from exc -def normalize_book_payload(payload: dict[str, Any]) -> dict[str, Any]: - """Keep the legacy envelope intact while promoting queued legacy values.""" - envelope = payload.get("custom_metadata") - - if isinstance(envelope, dict): - promoted = {} - - for key, value in envelope.items(): - if key not in PROMOTED_BOOK_FIELDS or key in payload: - continue - - try: - convert_value(SyncBook.__table__.columns[key], value) - except ValidationError: - continue - - promoted[key] = value - - return {**promoted, **payload} - - return payload - - def finite_number(value: Any) -> bool: if not isinstance(value, int | float) or isinstance(value, bool): return False diff --git a/tests/api/routes/test_library_sync.py b/tests/api/routes/test_library_sync.py index 52a1c92..4dd41ca 100644 --- a/tests/api/routes/test_library_sync.py +++ b/tests/api/routes/test_library_sync.py @@ -155,24 +155,21 @@ async def test_membership_remove_readd_and_pair_validation(client, auth_headers) ).status_code == 400 -async def test_legacy_envelope_normalizes_and_preserves_metadata(client, auth_headers, db_session): - envelope = { - "publication_date": "2020-01-01T00:00:00Z", - "series_id": "old-id", - "file_size": 42, - "is_physical": True, - "custom_metadata": {"key": "value"}, - } - +async def test_custom_metadata_does_not_override_book_fields(client, auth_headers, db_session): + metadata = {"file_size": 42, "is_physical": True, "reader_locator": {"version": 1}, "key": "value"} response = await upload( client, auth_headers, - mutation("books", uuid4(), {"title": "Book", "custom_metadata": envelope, "series_id": "explicit"}), + mutation( + "books", + uuid4(), + {"title": "Book", "custom_metadata": metadata, "series_id": "series", "file_size": 100}, + ), ) assert response.status_code == 200, response.text row = (await db_session.execute(text("SELECT series_id, file_size, is_physical, custom_metadata FROM books"))).one() - assert row == ("explicit", 42, True, envelope) + assert row == ("series", 100, False, metadata) async def test_foreign_reference_rolls_back_mixed_batch(client, auth_headers, db_session): @@ -286,7 +283,7 @@ async def test_concurrent_patches_preserve_unrelated_values(client, auth_headers mutation( "books", book, - {"title": "Book", "author": "Author", "custom_metadata": {"custom_metadata": {"keep": True}}}, + {"title": "Book", "author": "Author", "custom_metadata": {"keep": True}}, ), ) ).status_code == 200 @@ -316,7 +313,7 @@ async def test_concurrent_patches_preserve_unrelated_values(client, auth_headers assert [result.status_code for result in results] == [200, 200] row = (await db_session.execute(text("SELECT title, author, custom_metadata FROM books"))).one() - assert row == ("New title", "New author", {"custom_metadata": {"keep": True}}) + assert row == ("New title", "New author", {"keep": True}) async def test_library_field_validation_is_atomic(client, auth_headers, db_session): diff --git a/tests/services/test_library_validation.py b/tests/services/test_library_validation.py index 1c6d6f5..70164e7 100644 --- a/tests/services/test_library_validation.py +++ b/tests/services/test_library_validation.py @@ -2,7 +2,7 @@ from papyrus.core.exceptions import ValidationError from papyrus.models import SyncAnnotation, SyncBook -from papyrus.services.library_validation import convert_value, normalize_book_payload +from papyrus.services.library_validation import convert_value @pytest.mark.parametrize( @@ -16,17 +16,3 @@ def test_overflows_are_controlled_validation_errors(column, value): with pytest.raises(ValidationError): convert_value(column, value) - - -def test_legacy_invalid_values_remain_in_envelope_without_blocking_queue(): - envelope = {"is_physical": None, "series_number": "invalid", "file_size": 10**500, "lent_to": "Reader"} - normalized = normalize_book_payload({"custom_metadata": envelope}) - assert normalized == {"custom_metadata": envelope, "lent_to": "Reader"} - - -def test_explicit_promoted_value_wins_and_stays_subject_to_validation(): - normalized = normalize_book_payload({"custom_metadata": {"is_physical": True}, "is_physical": None}) - assert normalized["is_physical"] is None - - with pytest.raises(ValidationError): - convert_value(SyncBook.__table__.c.is_physical, normalized["is_physical"])