421 lines
14 KiB
Python
421 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# Tests for Libervia's Pubsub Contacts plugin
|
|
# Copyright (C) 2009-2026 Jérôme Poisson (goffi@goffi.org)
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from twisted.words.xish import domish
|
|
from wokkel.pubsub import NS_PUBSUB
|
|
|
|
from libervia.backend.plugins.plugin_misc_pubsub_contacts import (
|
|
Contact,
|
|
Contacts,
|
|
ContactsData,
|
|
Group,
|
|
Groups,
|
|
NS_CONTACTS,
|
|
)
|
|
|
|
|
|
class TestGroup:
|
|
"""Tests for Group model."""
|
|
|
|
def test_from_element(self):
|
|
"""Group is correctly parsed from element."""
|
|
element = domish.Element((NS_CONTACTS, "group"))
|
|
element["id"] = "group123"
|
|
element["name"] = "My Group"
|
|
desc_elt = element.addElement((NS_CONTACTS, "description"))
|
|
desc_elt.addContent("A test group")
|
|
|
|
group = Group.from_element(element)
|
|
|
|
assert group.id == "group123"
|
|
assert group.name == "My Group"
|
|
assert group.description == "A test group"
|
|
|
|
def test_from_element_nested_in_parent(self):
|
|
"""Group is correctly parsed from <group> element's parent."""
|
|
parent = domish.Element((None, "parent"))
|
|
group_elt = parent.addElement((NS_CONTACTS, "group"))
|
|
group_elt["id"] = "group123"
|
|
group_elt["name"] = "Test Group"
|
|
|
|
group = Group.from_element(parent)
|
|
|
|
assert group.id == "group123"
|
|
assert group.name == "Test Group"
|
|
|
|
def test_from_element_set_encrypted(self):
|
|
""" "encrypted" flag is correctly set when present in metadata."""
|
|
element = domish.Element((NS_CONTACTS, "group"))
|
|
element["id"] = "group123"
|
|
metadata = {"encrypted": {"group123": True}}
|
|
|
|
group = Group.from_element(element, metadata)
|
|
|
|
assert group.encrypted is True
|
|
|
|
def test_from_element_invalid_type_raises_error(self):
|
|
"""ValueError is raised when element is not a group element."""
|
|
element = domish.Element((NS_CONTACTS, "contact"))
|
|
|
|
try:
|
|
Group.from_element(element)
|
|
assert False, "Expected ValueError"
|
|
except ValueError as e:
|
|
assert "Invalid element" in str(e)
|
|
|
|
def test_to_element(self):
|
|
"""Group element is correctly created with all fields."""
|
|
group = Group(
|
|
id="group123", name="My Group", description="A test group", encrypted=True
|
|
)
|
|
element = group.to_element()
|
|
|
|
assert element.uri == NS_CONTACTS
|
|
assert element.name == "group"
|
|
assert element["id"] == "group123"
|
|
assert element["name"] == "My Group"
|
|
|
|
desc_elements = list(element.elements(NS_CONTACTS, "description"))
|
|
assert len(desc_elements) == 1
|
|
assert str(desc_elements[0]) == "A test group"
|
|
|
|
def test_to_element_missing_name_raises_error(self):
|
|
"""ValueError is raised when name is missing and id_only is False."""
|
|
group = Group(id="group123")
|
|
|
|
try:
|
|
group.to_element(id_only=False)
|
|
assert False, "Expected ValueError"
|
|
except ValueError as e:
|
|
assert "name must be set" in str(e)
|
|
|
|
|
|
class TestGroups:
|
|
"""Tests for Groups model."""
|
|
|
|
def test_append_and_iterate(self):
|
|
"""Groups can be appended and iterated."""
|
|
groups = Groups([])
|
|
groups.append(Group(id="g1", name="Group 1"))
|
|
groups.append(Group(id="g2", name="Group 2"))
|
|
|
|
group_list = list(groups)
|
|
assert len(group_list) == 2
|
|
assert group_list[0].id == "g1"
|
|
assert group_list[1].id == "g2"
|
|
|
|
def test_len_and_remove(self):
|
|
"""Groups len and remove work correctly."""
|
|
groups = Groups([])
|
|
groups.append(Group(id="g1", name="Group 1"))
|
|
groups.append(Group(id="g2", name="Group 2"))
|
|
|
|
assert len(groups) == 2
|
|
groups.remove(groups[0])
|
|
assert len(groups) == 1
|
|
assert groups[0].id == "g2"
|
|
|
|
def test_extend(self):
|
|
"""Groups extend works correctly."""
|
|
groups = Groups([])
|
|
groups.extend([Group(id="g1"), Group(id="g2")])
|
|
|
|
assert len(groups) == 2
|
|
|
|
def test_clear(self):
|
|
"""Groups clear works correctly."""
|
|
groups = Groups([Group(id="g1"), Group(id="g2")])
|
|
groups.clear()
|
|
|
|
assert len(groups) == 0
|
|
|
|
def test_sort(self):
|
|
"""Groups sort works correctly."""
|
|
groups = Groups([Group(id="g2"), Group(id="g1")])
|
|
groups.sort(key=lambda g: g.id)
|
|
|
|
assert groups[0].id == "g1"
|
|
assert groups[1].id == "g2"
|
|
|
|
def test_getitem(self):
|
|
"""Groups __getitem__ works correctly."""
|
|
groups = Groups([Group(id="g1"), Group(id="g2")])
|
|
assert groups[0].id == "g1"
|
|
assert groups[1].id == "g2"
|
|
|
|
|
|
class TestContact:
|
|
"""Tests for Contact model."""
|
|
|
|
def test_from_element_with_jid_id(self):
|
|
"""Contact is correctly parsed from element with jid identity."""
|
|
contact_elt = domish.Element((NS_CONTACTS, "contact"))
|
|
contact_elt["name"] = "Test Name"
|
|
identity_elt = contact_elt.addElement((NS_CONTACTS, "identity"))
|
|
identity_elt["type"] = "jid"
|
|
identity_elt.addContent("user@example.com")
|
|
|
|
contact = Contact.from_element(contact_elt, ContactsData())
|
|
|
|
assert contact.identities == {"jid": "user@example.com"}
|
|
assert contact.name == "Test Name"
|
|
assert len(contact.groups) == 0
|
|
|
|
def test_from_element_with_group(self):
|
|
"""Contact is correctly parsed with group reference."""
|
|
parent = domish.Element((NS_PUBSUB, "items"))
|
|
item_elt = parent.addElement((NS_PUBSUB, "item"))
|
|
item_elt["id"] = "contact123"
|
|
contact_elt = item_elt.addElement((NS_CONTACTS, "contact"))
|
|
identity_elt = contact_elt.addElement((NS_CONTACTS, "identity"))
|
|
identity_elt["type"] = "jid"
|
|
identity_elt.addContent("user@example.com")
|
|
group_elt = contact_elt.addElement((NS_CONTACTS, "group"))
|
|
group_elt["id"] = "g1"
|
|
|
|
contacts_data = ContactsData()
|
|
contacts_data.groups = Groups([Group(id="g1", name="Friends")])
|
|
|
|
contact = Contact.from_element(contact_elt, contacts_data)
|
|
|
|
assert contact.id == "contact123"
|
|
assert len(contact.groups) == 1
|
|
assert contact.groups[0].id == "g1"
|
|
assert contact.groups[0].name == "Friends"
|
|
|
|
def test_from_element_with_ancestor_item_id(self):
|
|
"""Contact id is extracted from ancestor item element."""
|
|
parent = domish.Element((NS_PUBSUB, "items"))
|
|
item_elt = parent.addElement((NS_PUBSUB, "item"))
|
|
item_elt["id"] = "contact123"
|
|
contact_elt = item_elt.addElement((NS_CONTACTS, "contact"))
|
|
identity_elt = contact_elt.addElement((NS_CONTACTS, "identity"))
|
|
identity_elt["type"] = "jid"
|
|
identity_elt.addContent("user@example.com")
|
|
|
|
contact = Contact.from_element(contact_elt, ContactsData())
|
|
|
|
assert contact.id == "contact123"
|
|
|
|
def test_from_element_with_ancestor_item_encrypted(self):
|
|
"""Contact encrypted flag is set from item metadata."""
|
|
parent = domish.Element((NS_PUBSUB, "items"))
|
|
item_elt = parent.addElement((NS_PUBSUB, "item"))
|
|
item_elt["id"] = "contact123"
|
|
item_elt["encrypted"] = "true"
|
|
contact_elt = item_elt.addElement((NS_CONTACTS, "contact"))
|
|
identity_elt = contact_elt.addElement((NS_CONTACTS, "identity"))
|
|
identity_elt["type"] = "jid"
|
|
identity_elt.addContent("user@example.com")
|
|
|
|
metadata = {"encrypted": {"contact123": True}}
|
|
contact = Contact.from_element(contact_elt, ContactsData(), metadata)
|
|
|
|
assert contact.encrypted is True
|
|
|
|
def test_to_element(self):
|
|
"""Contact element is correctly created."""
|
|
contact = Contact(
|
|
id="contact123",
|
|
name="Test Name",
|
|
identities={"jid": "user@example.com"},
|
|
groups=Groups([Group(id="g1", name="Friends")]),
|
|
)
|
|
element = contact.to_element()
|
|
|
|
assert element.uri == NS_CONTACTS
|
|
assert element.name == "contact"
|
|
assert element["name"] == "Test Name"
|
|
|
|
identity_elements = list(element.elements(NS_CONTACTS, "identity"))
|
|
assert len(identity_elements) == 1
|
|
assert identity_elements[0]["type"] == "jid"
|
|
assert str(identity_elements[0]) == "user@example.com"
|
|
|
|
group_elements = list(element.elements(NS_CONTACTS, "group"))
|
|
assert len(group_elements) == 1
|
|
group_element = group_elements[0]
|
|
assert group_element["id"] == "g1"
|
|
assert not group_element.hasAttribute(
|
|
"name"
|
|
), "name attribute should not be present in group with id_only"
|
|
|
|
|
|
class TestContacts:
|
|
"""Tests for Contacts root model."""
|
|
|
|
def test_append_and_iterate(self):
|
|
"""Contacts can be appended and iterated."""
|
|
contacts = Contacts([])
|
|
contacts.append(Contact(identities={"jid": "user1@example.com"}, id="c1"))
|
|
contacts.append(Contact(identities={"jid": "user2@example.com"}, id="c2"))
|
|
|
|
contact_list = list(contacts)
|
|
assert len(contact_list) == 2
|
|
assert contact_list[0].id == "c1"
|
|
assert contact_list[1].id == "c2"
|
|
|
|
def test_len_and_remove(self):
|
|
"""Contacts len and remove work correctly."""
|
|
contacts = Contacts([])
|
|
contacts.append(Contact(identities={"jid": "user1@example.com"}, id="c1"))
|
|
contacts.append(Contact(identities={"jid": "user2@example.com"}, id="c2"))
|
|
|
|
assert len(contacts) == 2
|
|
contacts.remove(contacts[0])
|
|
assert len(contacts) == 1
|
|
assert contacts[0].id == "c2"
|
|
|
|
def test_extend(self):
|
|
"""Contacts extend works correctly."""
|
|
contacts = Contacts([])
|
|
contacts.extend(
|
|
[
|
|
Contact(identities={"jid": "user1@example.com"}),
|
|
Contact(identities={"jid": "user2@example.com"}),
|
|
]
|
|
)
|
|
|
|
assert len(contacts) == 2
|
|
|
|
def test_clear(self):
|
|
"""Contacts clear works correctly."""
|
|
contacts = Contacts(
|
|
[
|
|
Contact(identities={"jid": "user1@example.com"}),
|
|
Contact(identities={"jid": "user2@example.com"}),
|
|
]
|
|
)
|
|
contacts.clear()
|
|
|
|
assert len(contacts) == 0
|
|
|
|
def test_sort(self):
|
|
"""Contacts sort works correctly."""
|
|
contacts = Contacts(
|
|
[
|
|
Contact(identities={"jid": "user2@example.com"}, id="c2"),
|
|
Contact(identities={"jid": "user1@example.com"}, id="c1"),
|
|
]
|
|
)
|
|
contacts.sort(key=lambda c: c.id)
|
|
|
|
assert contacts[0].id == "c1"
|
|
assert contacts[1].id == "c2"
|
|
|
|
def test_getitem(self):
|
|
"""Contacts __getitem__ works correctly."""
|
|
contacts = Contacts(
|
|
[
|
|
Contact(identities={"jid": "user1@example.com"}, id="c1"),
|
|
Contact(identities={"jid": "user2@example.com"}, id="c2"),
|
|
]
|
|
)
|
|
assert contacts[0].id == "c1"
|
|
assert contacts[1].id == "c2"
|
|
|
|
|
|
class TestContactsData:
|
|
"""Tests for ContactsData class."""
|
|
|
|
def test_jid_to_contacts_empty(self):
|
|
"""jid_to_contacts returns empty dict when contacts is empty."""
|
|
data = ContactsData()
|
|
assert data.jid_to_contacts == {}
|
|
|
|
def test_jid_to_contacts(self):
|
|
"""jid_to_contacts correctly maps JIDs to contacts."""
|
|
contact1 = Contact(
|
|
id="c1",
|
|
identities={"jid": "user1@example.com"},
|
|
)
|
|
contact2 = Contact(
|
|
id="c2",
|
|
identities={"jid": "user2@example.com"},
|
|
)
|
|
data = ContactsData(contacts=Contacts([contact1, contact2]))
|
|
|
|
jid_map = data.jid_to_contacts
|
|
|
|
assert len(jid_map) == 2
|
|
assert jid_map["user1@example.com"].id == "c1"
|
|
assert jid_map["user2@example.com"].id == "c2"
|
|
|
|
def test_jid_to_contacts_skips_contact_without_id(self):
|
|
"""jid_to_contacts skips contacts without id."""
|
|
contact1 = Contact(
|
|
id="c1",
|
|
identities={"jid": "user1@example.com"},
|
|
)
|
|
contact2 = Contact(
|
|
id="c2",
|
|
identities={"jid": "user2@example.com"},
|
|
)
|
|
contact_no_id = Contact(
|
|
id=None,
|
|
identities={"jid": "user3@example.com"},
|
|
)
|
|
data = ContactsData(contacts=Contacts([contact1, contact2, contact_no_id]))
|
|
|
|
jid_map = data.jid_to_contacts
|
|
|
|
assert len(jid_map) == 2
|
|
assert "user1@example.com" in jid_map
|
|
assert "user2@example.com" in jid_map
|
|
assert "user3@example.com" not in jid_map
|
|
|
|
def test_ids_to_groups(self):
|
|
"""ids_to_groups correctly maps group IDs to groups."""
|
|
group1 = Group(id="g1", name="Group 1")
|
|
group2 = Group(id="g2", name="Group 2")
|
|
data = ContactsData(groups=Groups([group1, group2]))
|
|
|
|
id_map = data.ids_to_groups
|
|
|
|
assert len(id_map) == 2
|
|
assert id_map["g1"].name == "Group 1"
|
|
assert id_map["g2"].name == "Group 2"
|
|
|
|
def test_ids_to_groups_skips_missing_id(self):
|
|
"""ids_to_groups skips groups without ID."""
|
|
group = Group(id="g1", name="Group 1")
|
|
group_no_id = Group(name="No ID Group")
|
|
data = ContactsData(groups=Groups([group, group_no_id]))
|
|
|
|
id_map = data.ids_to_groups
|
|
|
|
assert len(id_map) == 1
|
|
assert "g1" in id_map
|
|
|
|
def test_reset(self):
|
|
"""Reset clears cached mappings."""
|
|
contact = Contact(
|
|
id="c1",
|
|
identities={"jid": "user1@example.com"},
|
|
)
|
|
group = Group(id="g1", name="Group 1")
|
|
data = ContactsData(contacts=Contacts([contact]), groups=Groups([group]))
|
|
|
|
# Access the mappings to populate caches, and check that they are not empty.
|
|
assert len(data.jid_to_contacts) == 1
|
|
assert len(data.ids_to_groups) == 1
|
|
|
|
data.reset()
|
|
|
|
assert data._jid_to_contacts is None
|
|
assert data._ids_to_groups is None
|