import unittest.mock

from slixmpp import JID, Iq
from slixmpp.exceptions import XMPPError

from slidge import (
    BaseGateway,
    BaseSession,
    LegacyBookmarks,
    LegacyContact,
    LegacyMUC,
    LegacyParticipant,
)
from slidge.command.user import _get_updated_spaces
from slidge.util.test import SlidgeTest
from slidge.util.types import SpaceMetadata


class Session(BaseSession):
    async def login(self):
        pass


class Gateway(BaseGateway):
    COMPONENT_NAME = "Megacorp Chat XMPP Gateway"
    GROUPS = True
    SPACES = True


class Contact(LegacyContact):
    async def update_info(self):
        if self.legacy_id in ("programming-socks", "boring-software", "editors"):
            raise XMPPError("item-not-found", "Not a contact")


class Bookmarks(LegacyBookmarks["MUC"]):
    async def fill(self):
        for legacy_id in (
            "programming-socks",
            "boring-software",
            "editors",
            "fancy-hotels",
            "finance",
        ):
            await self.by_legacy_id(legacy_id)

    async def fetch_space_metadata(self, legacy_id: str):
        match legacy_id:
            case "dev":
                return SpaceMetadata(
                    creator_legacy_id="lead-dev",
                    owner_legacy_ids=["lead-dev", "lead-dev-friend"],
                    name="The developers corner",
                    description="Nerds welcome!",
                    member_count=666,
                )
            case "sales":
                return SpaceMetadata(
                    creator_legacy_id="ferrari-guy",
                    name="Sales team",
                )
            case "management":
                return SpaceMetadata(
                    creator_legacy_id="ceo",
                    name="The bosses lounge",
                )
            case _:
                raise XMPPError("item-not-found", "Not a space")


class MUC(LegacyMUC[LegacyParticipant]):
    async def update_info(self):
        match self.legacy_id:
            case "programming-socks":
                self.name = "Share your best programming socks deal HERE"
                self.space_legacy_id = "dev"
            case "boring-software":
                self.name = "ERP 3000"
                self.space_legacy_id = "dev"
            case "editors":
                self.name = "Settling the text editor wars"
                self.space_legacy_id = "dev"
            case "fancy-hotels":
                self.space_legacy_id = "sales"
            case "finance":
                self.space_legacy_id = "management"
            case "new-dev":
                self.space_legacy_id = "dev"


class TestSpaces(SlidgeTest):
    plugin = globals()

    def setUp(self):
        super().setUp()
        self._add_slidge_user("emacs-user@shakespeare.lit")

    def user_session(
        self, jid: str | JID = "emacs-user@shakespeare.lit"
    ) -> "BaseSession":
        return super().user_session(jid)

    def test_query_spaces_not_implemented(self):
        Gateway.SPACES = False
        self.recv(  # language=XML
            """
            <iq type='get'
                from='emacs-user@shakespeare.lit/cool-client'
                to='aim.shakespeare.lit'
                id='spaces-items'>
              <query xmlns='http://jabber.org/protocol/disco#items'>
                <filter xmlns='urn:xmpp:pubsub-filter:0'>
                  <x xmlns='jabber:x:data'
                     type='submit'>
                    <field var='FORM_TYPE'
                           type='hidden'>
                      <value>urn:xmpp:pubsub-filter:0</value>
                    </field>
                    <field type='list-multi'
                           var='included-types'>
                      <value>urn:xmpp:spaces:0</value>
                    </field>
                  </x>
                </filter>
              </query>
            </iq>
            """
        )
        self.send(  # language=XML
            """
            <iq xmlns="jabber:component:accept"
                type="error"
                from="aim.shakespeare.lit"
                to="emacs-user@shakespeare.lit/cool-client"
                id="spaces-items">
              <error type="cancel">
                <feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
                <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">This gateway does not support XEP-0503</text>
              </error>
            </iq>
            """
        )
        Gateway.SPACES = True

    def test_query_spaces(self):
        self.recv(  # language=XML
            """
            <iq type='get'
                from='emacs-user@shakespeare.lit/cool-client'
                to='aim.shakespeare.lit'
                id='spaces-items'>
              <query xmlns='http://jabber.org/protocol/disco#items'>
                <filter xmlns='urn:xmpp:pubsub-filter:0'>
                  <x xmlns='jabber:x:data'
                     type='submit'>
                    <field var='FORM_TYPE'
                           type='hidden'>
                      <value>urn:xmpp:pubsub-filter:0</value>
                    </field>
                    <field type='list-multi'
                           var='included-types'>
                      <value>urn:xmpp:spaces:0</value>
                    </field>
                  </x>
                </filter>
              </query>
            </iq>
            """
        )
        self.send(  # language=XML
            """
            <iq type='result'
                to='emacs-user@shakespeare.lit/cool-client'
                from='aim.shakespeare.lit'
                id='spaces-items'>
              <query xmlns='http://jabber.org/protocol/disco#items'>
                <item jid='aim.shakespeare.lit'
                      node='dev'
                      name='The developers corner' />
                <item jid='aim.shakespeare.lit'
                      node='sales'
                      name='Sales team' />
                <item jid='aim.shakespeare.lit'
                      node='management'
                      name='The bosses lounge' />
              </query>
            </iq>
            """
        )

    def test_query_space_metadata(self):
        self.recv(  # language=XML
            """
            <iq type='get'
                from='emacs-user@shakespeare.lit/cool-client'
                to='aim.shakespeare.lit'
                id='space-metadata'>
              <query xmlns='http://jabber.org/protocol/disco#info'
                     node='dev' />
            </iq>
            """
        )
        self.send(  # language=XML
            """
            <iq type='result'
                from='aim.shakespeare.lit'
                to='emacs-user@shakespeare.lit/cool-client'
                id='space-metadata'>
              <query xmlns='http://jabber.org/protocol/disco#info'
                     node='dev'>
                <identity category='pubsub'
                          type='leaf' />
                <feature var='http://jabber.org/protocol/pubsub' />
                <feature var='http://jabber.org/protocol/pubsub#meta-data' />
                <feature var='http://jabber.org/protocol/pubsub#item-ids' />
                <feature var='http://jabber.org/protocol/pubsub#manage-subscriptions' />
                <feature var='http://jabber.org/protocol/pubsub#modify-affiliations' />
                <feature var='http://jabber.org/protocol/pubsub#outcast-affiliation' />
                <feature var='http://jabber.org/protocol/pubsub#retract-items' />
                <feature var='http://jabber.org/protocol/pubsub#retrieve-affiliations' />
                <feature var='http://jabber.org/protocol/pubsub#retrieve-items' />
                <feature var='http://jabber.org/protocol/pubsub#retrieve-subscriptions' />
                <feature var='http://jabber.org/protocol/pubsub#subscribe' />
                <feature var='http://jabber.org/protocol/pubsub#subscription-notifications' />
                <x xmlns='jabber:x:data'
                   type='result'>
                  <field var='FORM_TYPE'
                         type='hidden'>
                    <value>http://jabber.org/protocol/pubsub#meta-data</value>
                  </field>
                  <field var='pubsub#type'>
                    <value>urn:xmpp:spaces:0</value>
                  </field>
                  <field var='pubsub#creator'
                         type='jid-single'>
                    <value>lead-dev@aim.shakespeare.lit</value>
                  </field>
                  <field var='pubsub#title'>
                    <value>The developers corner</value>
                  </field>
                  <field var='pubsub#owner'
                         type='jid-multi'>
                    <value>lead-dev@aim.shakespeare.lit</value>
                    <value>lead-dev-friend@aim.shakespeare.lit</value>
                  </field>
                  <field var='pubsub#num_subscribers'>
                    <value>666</value>
                  </field>
                  <field var='pubsub#description'>
                    <value>Nerds welcome!</value>
                  </field>
                </x>
              </query>
            </iq>
            """
        )

    def test_get_all_items(self):
        self.recv(  # language=XML
            """
            <iq type='get'
                from='emacs-user@shakespeare.lit/cool-client'
                to='aim.shakespeare.lit'
                id='space-items'>
              <pubsub xmlns='http://jabber.org/protocol/pubsub'>
                <items node='dev' />
              </pubsub>
            </iq>
            """
        )
        self.send(  # language=XML
            """
            <iq type='result'
                from='aim.shakespeare.lit'
                to='emacs-user@shakespeare.lit/cool-client'
                id='space-items'>
              <pubsub xmlns='http://jabber.org/protocol/pubsub'>
                <items node='dev'>
                  <item id='boring-software@aim.shakespeare.lit'>
                    <conference xmlns='urn:xmpp:bookmarks:1'
                                name='ERP 3000' />
                  </item>
                  <item id='programming-socks@aim.shakespeare.lit'>
                    <conference xmlns='urn:xmpp:bookmarks:1'
                                name='Share your best programming socks deal HERE'></conference>
                  </item>
                  <item id='editors@aim.shakespeare.lit'>
                    <conference xmlns='urn:xmpp:bookmarks:1'
                                name='Settling the text editor wars'></conference>
                  </item>
                </items>
              </pubsub>
            </iq>
            """,
            use_values=False,
        )

    def test_get_single_item(self) -> None:
        self.__recv_get_item(["programming-socks@aim.shakespeare.lit"])
        self.__send_items(
            [
                (
                    "programming-socks@aim.shakespeare.lit",
                    "Share your best programming socks deal HERE",
                )
            ]
        )

    def test_get_multiple_item(self) -> None:
        self.__recv_get_item(
            ["programming-socks@aim.shakespeare.lit", "editors@aim.shakespeare.lit"]
        )
        self.__send_items(
            [
                (
                    "programming-socks@aim.shakespeare.lit",
                    "Share your best programming socks deal HERE",
                ),
                (
                    "editors@aim.shakespeare.lit",
                    "Settling the text editor wars",
                ),
            ]
        )

    def test_get_nonexistent_item(self) -> None:
        self.__recv_get_item(["nope"])
        self.send(  # language=XML
            """
            <iq type="error"
                from="aim.shakespeare.lit"
                to="emacs-user@shakespeare.lit/cool-client"
                id="0">
              <error type="cancel">
                <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
                <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Slidge only supports items that are MUCs.</text>
              </error>
            </iq>
            """
        )

    def test_get_nonexistent_item_multiple(self) -> None:
        self.__recv_get_item(["nope", "editors@aim.shakespeare.lit"])
        self.send(  # language=XML
            """
            <iq type="error"
                from="aim.shakespeare.lit"
                to="emacs-user@shakespeare.lit/cool-client"
                id="0">
              <error type="cancel">
                <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
                <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Slidge only supports items that are MUCs.</text>
              </error>
            </iq>
            """
        )

    def test_get_nonexistent_item_multiple2(self) -> None:
        self.__recv_get_item(["xxx@aim.shakespeare.lit", "editors@aim.shakespeare.lit"])
        self.send(  # language=XML
            """
            <iq type="error"
                from="aim.shakespeare.lit"
                to="emacs-user@shakespeare.lit/cool-client"
                id="0">
              <error type="cancel">
                <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
                <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Could not find items: {'xxx@aim.shakespeare.lit'}</text>
              </error>
            </iq>
            """
        )

    def __recv_get_item(self, ids: list[str], node: str = "dev") -> None:
        items = ""
        for i in ids:
            items += f"<item id='{i}' />\n"
        self.recv(  # language=XML
            f"""
            <iq type='get'
                from='emacs-user@shakespeare.lit/cool-client'
                to='aim.shakespeare.lit'
                id='0'>
              <pubsub xmlns='http://jabber.org/protocol/pubsub'>
                <items node='{node}'>{items}</items>
              </pubsub>
            </iq>
            """
        )

    def __send_items(self, data: list[tuple[str, str]], node: str = "dev") -> None:
        items = ""
        for id_, name in data:
            items += f"<item id='{id_}'>\n<conference xmlns='urn:xmpp:bookmarks:1' name='{name}'/></item>"
        self.send(  # language=XML
            f"""
            <iq type='result'
                from='aim.shakespeare.lit'
                to='emacs-user@shakespeare.lit/cool-client'
                id='0'>
              <pubsub xmlns='http://jabber.org/protocol/pubsub'>
                <items node='{node}'>{items}</items>
              </pubsub>
            </iq>
            """
        )

    def test_muc_advertises_membership(self):
        self.recv(  # language=XML
            """
            <iq from='emacs-user@shakespeare.lit/cool-client'
                to='programming-socks@aim.shakespeare.lit'
                type='get'>
              <query xmlns='http://jabber.org/protocol/disco#info' />
            </iq>
            """
        )
        self.send(  # language=XML
            """
            <iq xmlns="jabber:component:accept"
                from="programming-socks@aim.shakespeare.lit"
                to="emacs-user@shakespeare.lit/cool-client"
                type="result"
                id="1">
              <query xmlns="http://jabber.org/protocol/disco#info">
                <identity category="conference"
                          type="text"
                          name="Share your best programming socks deal HERE" />
                <feature var="http://jabber.org/protocol/muc" />
                <feature var="http://jabber.org/protocol/muc#stable_id" />
                <feature var="http://jabber.org/protocol/muc#self-ping-optimization" />
                <feature var="urn:xmpp:mam:2" />
                <feature var="urn:xmpp:mam:2#extended" />
                <feature var="urn:xmpp:sid:0" />
                <feature var="muc_persistent" />
                <feature var="vcard-temp" />
                <feature var="urn:xmpp:ping" />
                <feature var="urn:xmpp:occupant-id:0" />
                <feature var="jabber:iq:register" />
                <feature var="urn:xmpp:message-moderate:1" />
                <feature var="muc_open" />
                <feature var="muc_semianonymous" />
                <feature var="muc_public" />
                <feature var="urn:xmpp:spaces:0" />
                <feature var="http://jabber.org/protocol/commands" />
                <feature var="urn:xmpp:muc:affiliations:1" />
                <x xmlns="jabber:x:data"
                   type="result">
                  <field var="FORM_TYPE"
                         type="hidden">
                    <value>http://jabber.org/protocol/muc#roominfo</value>
                  </field>
                  <field var="muc#roomconfig_persistentroom"
                         type="boolean">
                    <value>1</value>
                  </field>
                  <field var="muc#roomconfig_changesubject"
                         type="boolean">
                    <value>0</value>
                  </field>
                  <field var="muc#maxhistoryfetch">
                    <value>100</value>
                  </field>
                  <field var="muc#roominfo_subjectmod"
                         type="boolean">
                    <value>0</value>
                  </field>
                  <field var="muc#roomconfig_roomname">
                    <value>Share your best programming socks deal HERE</value>
                  </field>
                  <field var="muc#roomconfig_membersonly"
                         type="boolean">
                    <value>0</value>
                  </field>
                  <field var="muc#roomconfig_whois"
                         type="list-single">
                    <value>moderators</value>
                  </field>
                  <field var="muc#roomconfig_publicroom"
                         type="boolean">
                    <value>1</value>
                  </field>
                  <field var="muc#roomconfig_allowpm"
                         type="boolean">
                    <value>0</value>
                  </field>
                  <field var="muc#roominfo_pubsub">
                    <value>xmpp:aim.shakespeare.lit?node=dev</value>
                  </field>
                </x>
              </query>
            </iq>
            """
        )

    def test_user_subscribe_success(self) -> None:
        self.__recv_subscription_request("dev")
        self.send(  # language=XML
            """
            <iq type="result"
                from="aim.shakespeare.lit"
                to="emacs-user@shakespeare.lit/cool-client"
                id="1">
              <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <subscription node="dev"
                              jid="emacs-user@shakespeare.lit"
                              subscription="subscribed" />
              </pubsub>
            </iq>
            """
        )

    def test_user_subscribe_non_existing_space(self) -> None:
        self.__recv_subscription_request("non-existing")
        iq = self.next_sent()
        assert isinstance(iq, Iq)
        assert iq["type"] == "error"
        assert iq["error"]["type"] == "cancel"
        assert iq["error"]["condition"] == "item-not-found"

    def __recv_subscription_request(self, node: str) -> None:
        self.recv(  # language=XML
            f"""
            <iq to="aim.shakespeare.lit"
                from="emacs-user@shakespeare.lit/cool-client"
                type="set">
              <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <subscribe jid="emacs-user@shakespeare.lit"
                           node="{node}" />
              </pubsub>
            </iq>
            """
        )

    def test_user_leave_space(self) -> None:
        with unittest.mock.patch("slidge.BaseSession.on_leave_space") as on_leave_space:
            self.recv(  # language=XML
                """
            <iq to="aim.shakespeare.lit"
                from="emacs-user@shakespeare.lit/cool-client"
                type="set">
              <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <unsubscribe jid="emacs-user@shakespeare.lit"
                             node="dev" />
              </pubsub>
            </iq>
            """
            )
        on_leave_space.assert_awaited_once_with("dev")
        self.send(  # language=XML
            """
            <iq type="result"
                from="aim.shakespeare.lit"
                to="emacs-user@shakespeare.lit/cool-client"
                id="1">
              <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <subscription node="dev"
                              jid="emacs-user@shakespeare.lit"
                              subscription="none" />
              </pubsub>
            </iq>
            """
        )

    def test_new_room_in_existing_space(self) -> None:
        self.run_coro(self.user_session().bookmarks.by_legacy_id("new-dev"))
        self.send(  # language=XML
            """
            <message type="headline"
                     from="aim.shakespeare.lit"
                     to="emacs-user@shakespeare.lit">
              <event xmlns='http://jabber.org/protocol/pubsub#event'>
                <items node='dev'>
                  <item id='boring-software@aim.shakespeare.lit'>
                    <conference xmlns='urn:xmpp:bookmarks:1'
                                name='ERP 3000' />
                  </item>
                  <item id='programming-socks@aim.shakespeare.lit'>
                    <conference xmlns='urn:xmpp:bookmarks:1'
                                name='Share your best programming socks deal HERE' />
                  </item>
                  <item id='editors@aim.shakespeare.lit'>
                    <conference xmlns='urn:xmpp:bookmarks:1'
                                name='Settling the text editor wars' />
                  </item>
                  <item id='new-dev@aim.shakespeare.lit'>
                    <conference xmlns='urn:xmpp:bookmarks:1' />
                  </item>
                </items>
              </event>
            </message>
            """,
            use_values=False,
        )

    def test_get_owner_affiliations(self) -> None:
        self.recv(  # language=XML
            """
            <iq type="get"
                from="emacs-user@shakespeare.lit/cool-client"
                to="aim.shakespeare.lit"
                id="get">
              <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
                <affiliations node="dev" />
              </pubsub>
            </iq>
            """
        )
        self.send(  # language=XML
            """
            <iq xmlns="jabber:component:accept"
                type="error"
                from="aim.shakespeare.lit"
                to="emacs-user@shakespeare.lit/cool-client"
                id="get">
              <error type="auth">
                <forbidden xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
                <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Slidge does not implement managing space affiliations.</text>
              </error>
            </iq>
            """,
        )

        # For slidge v0.5.0…
        # self.send(
        #     """
        #     <iq type='result'
        #         from='aim.shakespeare.lit'
        #         to='emacs-user@shakespeare.lit/cool-client'>
        #       <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
        #         <affiliations>
        #           <affiliation jid='lead-dev@aim.shakespeare.lit' affiliation='owner'/>
        #           <affiliation jid='lead-dev-friend@aim.shakespeare.lit' affiliation='owner'/>
        #           <affiliation jid='emacs-user@shakespeare.lit' affiliation='member'/>
        #         </affiliations>
        #       </pubsub>
        #     </iq>
        #     """
        # )

    def test_get_node_affiliation(self) -> None:
        self.recv(  # language=XML
            """
            <iq type="get"
                from="emacs-user@shakespeare.lit/cool-client"
                to="aim.shakespeare.lit"
                id="0">
              <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <affiliations node="dev" />
              </pubsub>
            </iq>
            """
        )
        self.send(  # language=XML
            """
            <iq type="result"
                to="emacs-user@shakespeare.lit/cool-client"
                from="aim.shakespeare.lit"
                id="0">
              <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <affiliations>
                  <affiliation node='dev'
                               affiliation='subscriber' />
                </affiliations>
              </pubsub>
            </iq>
            """
        )

    def test_get_all_affiliations(self) -> None:
        self.recv(  # language=XML
            """
            <iq type="get"
                from="emacs-user@shakespeare.lit/cool-client"
                to="aim.shakespeare.lit"
                id="0">
              <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <affiliations />
              </pubsub>
            </iq>
            """
        )
        self.send(  # language=XML
            """
            <iq type="result"
                to="emacs-user@shakespeare.lit/cool-client"
                from="aim.shakespeare.lit"
                id="0">
              <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <affiliations>
                  <affiliation node='dev'
                               affiliation='subscriber' />
                  <affiliation node='sales'
                               affiliation='subscriber' />
                  <affiliation node='management'
                               affiliation='subscriber' />
                </affiliations>
              </pubsub>
            </iq>
            """
        )

    def test_metadata_change(self) -> None:
        self.run_coro(
            self.user_session().bookmarks.update_space_metadata(
                "dev",
                SpaceMetadata(name="The new name"),
            )
        )
        self.send(  # language=XML
            """
            <message type="headline"
                     from="aim.shakespeare.lit"
                     to="emacs-user@shakespeare.lit">
              <event xmlns='http://jabber.org/protocol/pubsub#event'>
                <configuration node='dev'>
                  <x xmlns='jabber:x:data'
                     type='result'>
                    <field var='FORM_TYPE'
                           type='hidden'>
                      <value>http://jabber.org/protocol/pubsub#node_config</value>
                    </field>
                    <field var='pubsub#title'>
                      <value>The new name</value>
                    </field>
                    <!-- MUST: identifies this node as a space -->
                    <field var='pubsub#type'>
                      <value>urn:xmpp:spaces:0</value>
                    </field>
                    <!-- MUST: subscribers are notified when items are retracted -->
                    <field var='pubsub#notify_retract'>
                      <value>true</value>
                    </field>
                    <!-- MUST: items are persisted on the node -->
                    <field var='pubsub#persist_items'>
                      <value>true</value>
                    </field>
                    <!-- MUST: items are NOT purged when the node goes offline -->
                    <field var='pubsub#purge_offline'>
                      <value>false</value>
                    </field>
                    <!-- MUST (public): anyone can subscribe/read -->
                    <field var='pubsub#access_model'>
                      <value>authorize</value>
                    </field>
                    <!-- SHOULD: subscribers notified of new subscriptions -->
                    <field var='pubsub#notify_sub'>
                      <value>true</value>
                    </field>
                    <!-- SHOULD: subscribers notified of configuration changes -->
                    <field var='pubsub#notify_config'>
                      <value>true</value>
                    </field>
                    <!-- SHOULD: subscribers notified when the node is deleted -->
                    <field var='pubsub#notify_delete'>
                      <value>true</value>
                    </field>
                    <!-- SHOULD NOT be open: only owners/publishers should publish -->
                    <field var='pubsub#publish_model'>
                      <value>publishers</value>
                    </field>
                  </x>
                </configuration>
              </event>
            </message>
            """
        )

    def test_get_owner_subscriptions(self) -> None:
        self.recv(  # language=XML
            """
            <iq type="get"
                from="emacs-user@shakespeare.lit/cool-client"
                to="aim.shakespeare.lit"
                id="0">
              <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
                <subscriptions node="dev" />
              </pubsub>
            </iq>
            """
        )

        self.send(  # language=XML
            """
            <iq type='result'
                from="aim.shakespeare.lit"
                to="emacs-user@shakespeare.lit/cool-client"
                id='0'>
              <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
                <subscriptions node='dev'>
                  <subscription jid='emacs-user@shakespeare.lit'
                                subscription='subscribed' />
                </subscriptions>
              </pubsub>
            </iq>
            """
        )

    def test_get_subscriptions(self) -> None:
        self.recv(  # language=XML
            """
            <iq type="get"
                from="emacs-user@shakespeare.lit/cool-client"
                to="aim.shakespeare.lit"
                id="0">
              <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <subscriptions />
              </pubsub>
            </iq>
            """
        )
        self.send(  # language=XML
            """
            <iq type="result"
                to="emacs-user@shakespeare.lit/cool-client"
                from="aim.shakespeare.lit"
                id="0">
              <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <subscriptions>
                  <subscription node='dev'
                                jid='emacs-user@shakespeare.lit'
                                subscription='subscribed' />
                  <subscription node='sales'
                                jid='emacs-user@shakespeare.lit'
                                subscription='subscribed' />
                  <subscription node='management'
                                jid='emacs-user@shakespeare.lit'
                                subscription='subscribed' />
                </subscriptions>
              </pubsub>
            </iq>
            """
        )

    def test_get_subscriptions_specific_node(self) -> None:
        self.recv(  # language=XML
            """
            <iq type="get"
                from="emacs-user@shakespeare.lit/cool-client"
                to="aim.shakespeare.lit"
                id="0">
              <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <subscriptions node="dev" />
              </pubsub>
            </iq>
            """
        )
        self.send(  # language=XML
            """
            <iq type="result"
                to="emacs-user@shakespeare.lit/cool-client"
                from="aim.shakespeare.lit"
                id="0">
              <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <subscriptions node="dev">
                  <subscription node='dev'
                                jid='emacs-user@shakespeare.lit'
                                subscription='subscribed' />
                </subscriptions>
              </pubsub>
            </iq>
            """
        )

    def test_get_updated_spaces(self) -> None:
        spaces = self.run_coro(_get_updated_spaces(self.user_session()))
        assert len(spaces)
        assert [s.name for s in spaces] == [
            "Sales team",
            "The bosses lounge",
            "The developers corner",
        ]

    def test_fetch_space_metadata_onlyonce(self) -> None:
        req = """
            <iq type='get'
                from='emacs-user@shakespeare.lit/cool-client'
                to='aim.shakespeare.lit'
                id='{id}'>
              <query xmlns='http://jabber.org/protocol/disco#info'
                     node='dev' />
            </iq>
            """
        with unittest.mock.patch(
            "test_spaces.Bookmarks.fetch_space_metadata", return_value=SpaceMetadata()
        ) as fetch_space_metadata:
            self.recv(req.format(id=1))
            self.recv(req.format(id=2))
        fetch_space_metadata.assert_awaited_once()
