Fix inconsistent filtering on AttributeProperties across typeclasses. Resolve #3194

This commit is contained in:
Griatch 2026-02-15 12:05:02 +01:00
parent 0dd2b595a0
commit 8b21527a8c
4 changed files with 94 additions and 3 deletions

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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