diff --git a/wrolpi/tags.py b/wrolpi/tags.py index 2d38c5c9..0c51c2e2 100644 --- a/wrolpi/tags.py +++ b/wrolpi/tags.py @@ -166,7 +166,9 @@ class Tag(ModelHelper, Base): # Replace forward-slash (linux directories) with Unicode Big Solidus (U+29F8) name = name.replace('/', '⧸') - name = INVALID_FILE_CHARS.sub('', name) + name = INVALID_FILE_CHARS.sub('', name).strip() + if not name: + raise InvalidTag('Tag name cannot be empty') if (other := self.get_by_name(session, name)) and other.id != self.id: raise InvalidTag('Tag name already taken') @@ -266,6 +268,7 @@ class Tag(ModelHelper, Base): @warning: does not commit""" if name and id_: raise RuntimeError('Tag name and id cannot be used together') + name = name.strip() if name else name if tag := session.query(Tag).filter(Tag.id == id_).one_or_none(): return tag diff --git a/wrolpi/test/test_tags.py b/wrolpi/test/test_tags.py index 2ca7d715..41d400fe 100644 --- a/wrolpi/test/test_tags.py +++ b/wrolpi/test/test_tags.py @@ -406,6 +406,32 @@ async def test_invalid_tag(async_client, test_session, test_directory): with pytest.raises(UnknownTag): await tags.upsert_tag(test_session, 'Tag ID does not exist', '#fff', 1) + with pytest.raises(InvalidTag): + await tags.upsert_tag(test_session, ' ', '#fff') + with pytest.raises(InvalidTag): + tags.Tag.get_or_create_tag(test_session, ' \t') + + +@pytest.mark.asyncio +async def test_tag_name_whitespace_stripped(test_session, test_directory, tag_factory): + """Leading and trailing whitespace is never part of a Tag's name.""" + await tags.upsert_tag(test_session, ' one ', '#fff') + tag = tags.Tag.find_by_name(test_session, 'one') + assert tag.name == 'one' + + # Renaming strips too. + await tags.upsert_tag(test_session, '\ttwo\n', '#fff', tag.id) + assert tag.name == 'two' + + # A padded name finds the existing Tag rather than creating a near-duplicate. + assert tags.Tag.get_or_create_tag(test_session, 'two ').id == tag.id + assert tags.Tag.get_or_create_tag(test_session, ' three ').name == 'three' + assert {i.name for i in test_session.query(tags.Tag)} == {'two', 'three'} + + # A padded name conflicts with an existing Tag. + with pytest.raises(InvalidTag): + await tags.upsert_tag(test_session, 'three ', '#fff', tag.id) + @pytest.mark.asyncio async def test_tags_directory(test_session, test_directory, tag_factory, video_factory, example_pdf, await_switches,