diff --git a/tests/middleware_tests/test_sorting_entry_fields.py b/tests/middleware_tests/test_sorting_entry_fields.py index b60ede9..ac178a3 100644 --- a/tests/middleware_tests/test_sorting_entry_fields.py +++ b/tests/middleware_tests/test_sorting_entry_fields.py @@ -1,27 +1,32 @@ import pytest from bibtexparser import Library +from bibtexparser import write_string from bibtexparser.middlewares.sorting_entry_fields import SortFieldsAlphabeticallyMiddleware from bibtexparser.middlewares.sorting_entry_fields import SortFieldsCustomMiddleware from bibtexparser.model import Entry from bibtexparser.model import Field -TEST_ENTRY = Entry( - "article", - "Cesar2013", - [ - Field("author", "Cesar, J.", 1), - Field("title", "A title", 2), - Field("note", "A note", 3), - Field("journal", "A journal", 3), - Field("year", "2013", 4), - ], -) + +def _test_entry(): + """Return a fresh entry so in-place middleware tests cannot affect each other.""" + return Entry( + "article", + "Cesar2013", + [ + Field("author", "Cesar, J.", 1), + Field("title", "A title", 2), + Field("note", "A note", 3), + Field("journal", "A journal", 3), + Field("year", "2013", 4), + ], + ) @pytest.mark.parametrize("inplace", [True, False]) def test_sort_alphabetically(inplace: bool): - library = Library(blocks=[TEST_ENTRY]) + original_entry = _test_entry() + library = Library(blocks=[original_entry]) m = SortFieldsAlphabeticallyMiddleware(allow_inplace_modification=inplace) transformed = m.transform(library) entry = transformed.entries[0] @@ -34,16 +39,17 @@ def test_sort_alphabetically(inplace: bool): # Sanity Checks against unintended external effects assert len(transformed.blocks) == 1 if inplace: - assert entry is TEST_ENTRY + assert entry is original_entry else: - assert entry is not TEST_ENTRY + assert entry is not original_entry @pytest.mark.parametrize("inplace", [True, False]) def test_sort_custom_order_case_insensitive(inplace: bool): custom_order = ("author", "yEar", "title", "Journal") # assess case insensitive - library = Library(blocks=[TEST_ENTRY]) + original_entry = _test_entry() + library = Library(blocks=[original_entry]) # case insensitivity is default m = SortFieldsCustomMiddleware(order=custom_order, allow_inplace_modification=inplace) transformed = m.transform(library) @@ -57,28 +63,60 @@ def test_sort_custom_order_case_insensitive(inplace: bool): # Sanity Checks against unintended external effects assert len(transformed.blocks) == 1 if inplace: - assert entry is TEST_ENTRY + assert entry is original_entry else: - assert entry is not TEST_ENTRY + assert entry is not original_entry def test_sort_order_custom_case_sensitive(): custom_order = ("author", "yEar", "title", "Journal") # assess case sensitive - library = Library(blocks=[TEST_ENTRY]) + library = Library(blocks=[_test_entry()]) m = SortFieldsCustomMiddleware(order=custom_order, case_sensitive=True) transformed = m.transform(library) entry = transformed.entries[0] assert entry.fields[0].key == "author" assert entry.fields[1].key == "title" - # Unspecified fields (due to case sensitivity) are appended - assert entry.fields[2].key == "year" - - # Unspecified fields (due to case sensitivity) are appended + # Unspecified fields are appended in their original relative order. + assert entry.fields[2].key == "note" assert entry.fields[3].key == "journal" - - # Unspecified fields are appended - assert entry.fields[4].key == "note" + assert entry.fields[4].key == "year" assert len(transformed.blocks) == 1 + + +def test_custom_order_applies_during_public_write(): + """The public writer applies custom ordering without mutating the source library.""" + original_entry = _test_entry() + library = Library(blocks=[original_entry]) + middleware = SortFieldsCustomMiddleware( + order=("year", "author"), allow_inplace_modification=False + ) + + written = write_string(library, prepend_middleware=[middleware]) + written_field_keys = [ + line.split("=", maxsplit=1)[0].strip() for line in written.splitlines() if "=" in line + ] + + assert written_field_keys == ["year", "author", "title", "note", "journal"] + assert [field.key for field in original_entry.fields] == [ + "author", + "title", + "note", + "journal", + "year", + ] + + +@pytest.mark.parametrize( + "order", + [ + pytest.param(("title", "title"), id="identical"), + pytest.param(("title", "TITLE"), id="case-insensitive"), + ], +) +def test_custom_order_rejects_duplicate_keys(order): + """Ambiguous custom orders fail early, including case-only duplicates by default.""" + with pytest.raises(ValueError, match="title"): + SortFieldsCustomMiddleware(order=order)