#!/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 . from twisted.words.xish import domish from tx_xmpp.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 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