diff --git a/CHANGELOG.md b/CHANGELOG.md index 2601ea4edd..8044329380 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - [Fix][issue3649]: The `:j` command in EvEditor would squash empty lines (Griatch) - [Fix][issue3560]: Tutorial QuestHandler failed to load after server restart (Griatch) - [Fix][issue3601]: `CmdSet.add(..., allow_duplicates=True)` didn't allow duplicate cmd keys (Griatch) +- [Fix][issue3194]: Make filtering on AttributeProperties consistent across typeclasses (Griatch) - [Doc][pull3801]: Move Evennia doc build system to latest Sphinx/myST (PowershellNinja, also honorary mention to electroglyph) - [Doc][pull3800]: Describe support for Telnet SSH in HAProxy documentation (holl0wstar) @@ -69,6 +70,7 @@ [issue3649]: https://github.com/evennia/evennia/issues/3649 [issue3560]: https://github.com/evennia/evennia/issues/3560 [issue3601]: https://github.com/evennia/evennia/issues/3601 +[issue3194]: https://github.com/evennia/evennia/issues/3194 ## Evennia 5.0.1 diff --git a/evennia/objects/tests.py b/evennia/objects/tests.py index 0c75f82951..97896e0189 100644 --- a/evennia/objects/tests.py +++ b/evennia/objects/tests.py @@ -1,5 +1,3 @@ -from unittest import skip - from evennia.objects.models import ObjectDB from evennia.objects.objects import ( DefaultCharacter, @@ -656,7 +654,6 @@ class TestProperties(EvenniaTestCase): self.assertEqual(obj.cusattr, 5) self.assertEqual(obj.settest, 5) - @skip("TODO: Needs more research") def test_stored_object_queries(self): """, Test https://github.com/evennia/evennia/issues/3155, where AttributeProperties @@ -690,6 +687,42 @@ class TestProperties(EvenniaTestCase): obj1.delete() obj2.delete() + def test_stored_object_queries__self_reference(self): + """ + Regression test for querying on a stored self-reference. + + Related to https://github.com/evennia/evennia/issues/3194 comments. + """ + obj = create.create_object(TestObjectPropertiesClass, key="selfref") + try: + obj.attr1 = obj + query = TestObjectPropertiesClass.objects.filter( + db_attributes__db_key="attr1", db_attributes__db_value=obj + ) + self.assertEqual(list(query), [obj]) + finally: + obj.delete() + + def test_stored_object_queries__filter_family(self): + """ + Regression test for object-valued attribute filtering via filter_family. + + Related to https://github.com/evennia/evennia/issues/3194 comments. + """ + holder = create.create_object(DefaultObject, key="holder") + leg = create.create_object(DefaultObject, key="leg") + try: + holder.attributes.add("attached", leg, category="systems") + query = DefaultObject.objects.filter_family( + db_attributes__db_key="attached", + db_attributes__db_category="systems", + db_attributes__db_value=leg, + ) + self.assertIn(holder, query) + finally: + holder.delete() + leg.delete() + def test_not_create_attribute_with_autocreate_false(self): """ Test that AttributeProperty with autocreate=False does not create an attribute in the database. diff --git a/evennia/scripts/tests.py b/evennia/scripts/tests.py index a8b50b3f20..5e0f23c9bd 100644 --- a/evennia/scripts/tests.py +++ b/evennia/scripts/tests.py @@ -16,6 +16,7 @@ from evennia.scripts.monitorhandler import MonitorHandler from evennia.scripts.ondemandhandler import OnDemandHandler, OnDemandTask from evennia.scripts.scripts import DoNothing, ExtendedLoopingCall from evennia.scripts.tickerhandler import TickerHandler +from evennia.typeclasses.attributes import AttributeProperty from evennia.utils.create import create_script from evennia.utils.dbserialize import dbserialize from evennia.utils.test_resources import BaseEvenniaTest, EvenniaTest @@ -86,6 +87,10 @@ class TestingListIntervalScript(DefaultScript): self.repeats = 1 +class ScriptWithStoredRef(DefaultScript): + linked = AttributeProperty(default=None, autocreate=False) + + class TestScriptHandler(BaseEvenniaTest): """ Test the ScriptHandler class. @@ -162,6 +167,47 @@ class TestScriptDB(TestCase): self.assertFalse(self.scr in ScriptDB.objects.get_all_scripts()) +class TestIssue3194(BaseEvenniaTest): + """ + Regression test for inconsistent filtering of Script AttributeProperty refs. + https://github.com/evennia/evennia/issues/3194 + """ + + def test_script_attributeproperty_filtering_stored_dbobjs(self): + script_a = create_script(ScriptWithStoredRef, key="issue3194-script-a") + script_b = create_script(ScriptWithStoredRef, key="issue3194-script-b") + script_c = create_script(ScriptWithStoredRef, key="issue3194-script-c") + + try: + script_a.linked = script_b + script_c.linked = self.room1 + + self.assertEqual( + list(ScriptWithStoredRef.objects.get_by_attribute("linked", value=script_b)), + [script_a], + ) + self.assertEqual( + list( + ScriptWithStoredRef.objects.filter( + db_attributes__db_key="linked", db_attributes__db_value=script_b + ) + ), + [script_a], + ) + self.assertEqual( + list( + ScriptWithStoredRef.objects.filter( + db_attributes__db_key="linked", db_attributes__db_value=self.room1 + ) + ), + [script_c], + ) + finally: + script_a.delete() + script_b.delete() + script_c.delete() + + class TestExtendedLoopingCall(TestCase): """ Test the ExtendedLoopingCall class. diff --git a/evennia/utils/picklefield.py b/evennia/utils/picklefield.py index dffef0f3bb..5ce89f5f3b 100644 --- a/evennia/utils/picklefield.py +++ b/evennia/utils/picklefield.py @@ -92,6 +92,16 @@ def dbsafe_encode(value, compress_object=False, pickle_protocol=DEFAULT_PROTOCOL # simple string matches, thus the character streams must be the same # for the lookups to work properly. See tests.py for more information. try: + if isinstance(value, _ObjectWrapper): + # Keep conflict wrappers for regular values, but still normalize wrapped + # db objects to the same packed representation as Attribute storage. + packed = pack_dbobj(value._obj) + if packed is not value._obj: + value = packed + else: + # Make sure database objects are normalized before pickling for lookups. + value = pack_dbobj(value) + value = deepcopy(value) except CopyError: # this can happen on a manager query where the search query string is a