Просмотр исходного кода

Implement setting a pubsub node's config

JC Brand 6 месяцев назад
Родитель
Сommit
6c36116d78

+ 14 - 5
src/headless/plugins/pubsub/api.js

@@ -63,35 +63,44 @@ export default {
              * @param {string} jid The JID of the pubsub service where the node resides
              * @param {string} node The node to configure
              * @param {PubSubConfigOptions} config The configuration options
-             * @returns {Promise<void|Element>}
+             * @returns {Promise<import('./types').PubSubConfigOptions>}
              */
             async set(jid, node, config) {
+                if (!node) throw new Error('api.pubsub.config.set: Node value required');
+
                 const bare_jid = _converse.session.get('bare_jid');
                 const entity_jid = jid || bare_jid;
+                const new_config = {
+                    ...(await api.pubsub.config.get(entity_jid, node)),
+                    ...config
+                };
 
                 const stanza = stx`
                     <iq xmlns="jabber:client"
                         from="${bare_jid}"
                         type="set"
                         to="${entity_jid}">
-                    <pubsub xmlns="${Strophe.NS.PUBSUB}">
+                    <pubsub xmlns="${Strophe.NS.PUBSUB}#owner">
                         <configure node="${node}">
                             <x xmlns="${Strophe.NS.XFORM}" type="submit">
                                 <field var="FORM_TYPE" type="hidden">
                                     <value>${Strophe.NS.PUBSUB}#nodeconfig</value>
                                 </field>
-                                ${Object.entries(config).map(([k, v]) => stx`<field var="${k}"><value>${v}</value></field>`)}
+                                ${Object.entries(new_config).map(([k, v]) => stx`<field var="${k}"><value>${v}</value></field>`)}
                             </x>
                         </configure>
                     </pubsub>
                     </iq>`;
 
                 try {
-                    const response = await api.sendIQ(stanza);
-                    return response;
+                    await api.sendIQ(stanza);
                 } catch (error) {
+                    if (u.isErrorStanza(error)) {
+                        throw parseErrorStanza(error);
+                    }
                     throw error;
                 }
+                return new_config;
             },
         },
 

+ 247 - 21
src/headless/plugins/pubsub/tests/config.js

@@ -2,6 +2,7 @@
 const { Strophe, sizzle, stx, u, errors } = converse.env;
 
 describe('The pubsub API', function () {
+    beforeAll(() => jasmine.addMatchers({ toEqualStanza: jasmine.toEqualStanza }));
 
     describe('fetching a nodes config settings', function () {
         it(
@@ -163,9 +164,8 @@ describe('The pubsub API', function () {
         );
 
         it(
-            "handles error cases",
+            'handles error cases',
             mock.initConverse([], {}, async function (_converse) {
-
                 await mock.waitForRoster(_converse, 'current', 0);
                 const { api } = _converse;
                 const sent_stanzas = api.connection.get().sent_stanzas;
@@ -187,15 +187,17 @@ describe('The pubsub API', function () {
                         <feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
                         <unsupported xmlns='http://jabber.org/protocol/pubsub#errors' feature='config-node'/>
                     </error>
-                </iq>`
+                </iq>`;
 
                 let first_error_thrown = false;
-                promise.catch((e) => {
-                    expect(e instanceof errors.NotImplementedError).toBe(true);
-                    first_error_thrown = true;
-                }).finally(() => {
-                    expect(first_error_thrown).toBe(true);
-                });
+                promise
+                    .catch((e) => {
+                        expect(e instanceof errors.NotImplementedError).toBe(true);
+                        first_error_thrown = true;
+                    })
+                    .finally(() => {
+                        expect(first_error_thrown).toBe(true);
+                    });
                 _converse.api.connection.get()._dataRecv(mock.createRequest(response));
 
                 promise = api.pubsub.config.get(pubsub_jid, node);
@@ -211,12 +213,14 @@ describe('The pubsub API', function () {
                 </iq>`;
 
                 let second_error_thrown = false;
-                promise.catch((e) => {
-                    expect(e instanceof errors.ForbiddenError).toBe(true);
-                    second_error_thrown = true;
-                }).finally(() => {
-                    expect(second_error_thrown).toBe(true);
-                });
+                promise
+                    .catch((e) => {
+                        expect(e instanceof errors.ForbiddenError).toBe(true);
+                        second_error_thrown = true;
+                    })
+                    .finally(() => {
+                        expect(second_error_thrown).toBe(true);
+                    });
                 _converse.api.connection.get()._dataRecv(mock.createRequest(response));
 
                 promise = api.pubsub.config.get(pubsub_jid, node);
@@ -232,13 +236,235 @@ describe('The pubsub API', function () {
                 </iq>`;
 
                 let third_error_thrown = false;
-                promise.catch((e) => {
-                    expect(e instanceof errors.ItemNotFoundError).toBe(true);
-                    third_error_thrown = true;
-                }).finally(() => {
-                    expect(third_error_thrown).toBe(true);
+                promise
+                    .catch((e) => {
+                        expect(e instanceof errors.ItemNotFoundError).toBe(true);
+                        third_error_thrown = true;
+                    })
+                    .finally(() => {
+                        expect(third_error_thrown).toBe(true);
+                    });
+                _converse.api.connection.get()._dataRecv(mock.createRequest(response));
+            })
+        );
+    });
+
+    describe('setting a nodes config settings', function () {
+        it(
+            'first fetches the config, and then changes the specified values',
+            mock.initConverse([], {}, async function (_converse) {
+                await mock.waitForRoster(_converse, 'current', 0);
+                const { api } = _converse;
+                const sent_stanzas = api.connection.get().sent_stanzas;
+                const own_jid = _converse.session.get('jid');
+
+                const node = 'princely_musings';
+                const pubsub_jid = 'pubsub.shakespeare.lit';
+                const promise = api.pubsub.config.set(pubsub_jid, node, { access_model: 'whitelist' });
+
+                let sent_stanza = await u.waitUntil(() =>
+                    sent_stanzas.filter((iq) => sizzle('pubsub configure', iq)).pop()
+                );
+                _converse.api.connection.get()._dataRecv(
+                    mock.createRequest(stx`
+                    <iq type='result'
+                        xmlns="jabber:client"
+                        from='${pubsub_jid}'
+                        to='${own_jid}'
+                        id="${sent_stanza.getAttribute('id')}">
+                    <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
+                        <configure node='${node}'>
+                        <x xmlns='jabber:x:data' type='form'>
+                            <field var='FORM_TYPE' type='hidden'>
+                            <value>http://jabber.org/protocol/pubsub#node_config</value>
+                            </field>
+                            <field var='pubsub#title' type='text-single'
+                                label='A friendly name for the node'/>
+                            <field var='pubsub#deliver_notifications' type='boolean'
+                                label='Whether to deliver event notifications'>
+                            <value>true</value>
+                            </field>
+                            <field var='pubsub#deliver_payloads' type='boolean'
+                                label='Whether to deliver payloads with event notifications'>
+                            <value>true</value>
+                            </field>
+                            <field var='pubsub#notify_config' type='boolean'
+                                label='Notify subscribers when the node configuration changes'>
+                            <value>0</value>
+                            </field>
+                            <field var='pubsub#notify_delete' type='boolean'
+                                label='Notify subscribers when the node is deleted'>
+                            <value>false</value>
+                            </field>
+                            <field var='pubsub#notify_retract' type='boolean'
+                                label='Notify subscribers when items are removed from the node'>
+                            <value>false</value>
+                            </field>
+                            <field var='pubsub#notify_sub' type='boolean'
+                                label='Notify owners about new subscribers and unsubscribes'>
+                            <value>0</value>
+                            </field>
+                            <field var='pubsub#persist_items' type='boolean'
+                                label='Persist items to storage'>
+                            <value>1</value>
+                            </field>
+                            <field var='pubsub#max_items' type='text-single'
+                                label='Max # of items to persist. \`max\` for no specific limit other than a server imposed maximum.'>
+                            <value>10</value>
+                            </field>
+                            <field var='pubsub#item_expire' type='text-single'
+                                label='Time after which to automatically purge items. \`max\` for no specific limit other than a server imposed maximum.'>
+                            <value>604800</value>
+                            </field>
+                            <field var='pubsub#subscribe' type='boolean'
+                                label='Whether to allow subscriptions'>
+                            <value>1</value>
+                            </field>
+                            <field var='pubsub#publish_model' type='list-single'
+                                label='Specify the publisher model'>
+                            <option><value>publishers</value></option>
+                            <option><value>subscribers</value></option>
+                            <option><value>open</value></option>
+                            <value>publishers</value>
+                            </field>
+                            <field var='pubsub#purge_offline' type='boolean'
+                                label='Purge all items when the relevant publisher goes offline?'>
+                            <value>0</value>
+                            </field>
+                        </x>
+                        </configure>
+                    </pubsub>
+                </iq>`)
+                );
+
+                sent_stanza = await u.waitUntil(() =>
+                    sent_stanzas
+                        .filter((iq) => iq.getAttribute('type') === 'set' && sizzle('pubsub configure', iq))
+                        .pop()
+                );
+                expect(sent_stanza).toEqualStanza(stx`<iq xmlns="jabber:client"
+                    from="${_converse.bare_jid}"
+                    to="${pubsub_jid}"
+                    type="set"
+                    id="${sent_stanza.getAttribute('id')}">
+                        <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+                            <configure node="princely_musings">
+                            <x xmlns="jabber:x:data" type="submit">
+                                <field var="FORM_TYPE" type="hidden"><value>http://jabber.org/protocol/pubsub#nodeconfig</value></field>
+                                <field var="title"><value/></field>
+                                <field var="deliver_notifications"><value>true</value></field>
+                                <field var="deliver_payloads"><value>true</value></field>
+                                <field var="notify_config"><value>false</value></field>
+                                <field var="notify_delete"><value>false</value></field>
+                                <field var="notify_retract"><value>false</value></field>
+                                <field var="notify_sub"><value>false</value></field>
+                                <field var="persist_items"><value>true</value></field>
+                                <field var="max_items"><value>10</value></field>
+                                <field var="item_expire"><value>604800</value></field>
+                                <field var="subscribe"><value>true</value></field>
+                                <field var="publish_model"><value/></field>
+                                <field var="purge_offline"><value>false</value></field>
+                                <field var="access_model"><value>whitelist</value></field>
+                            </x>
+                            </configure>
+                        </pubsub>
+                    </iq>`);
+
+                _converse.api.connection.get()._dataRecv(
+                    mock.createRequest(stx`
+                    <iq type='result'
+                        xmlns="jabber:client"
+                        from='${pubsub_jid}'
+                        to='${own_jid}'
+                        id="${sent_stanza.getAttribute('id')}"></iq>`)
+                );
+
+                const result = await promise;
+                expect(result).toEqual({
+                    access_model: 'whitelist',
+                    deliver_notifications: true,
+                    deliver_payloads: true,
+                    item_expire: '604800',
+                    max_items: '10',
+                    notify_config: false,
+                    notify_delete: false,
+                    notify_retract: false,
+                    notify_sub: false,
+                    persist_items: true,
+                    publish_model: null,
+                    purge_offline: false,
+                    subscribe: true,
+                    title: null,
                 });
+            })
+        );
+
+        it(
+            'handles error cases',
+            mock.initConverse([], {}, async function (_converse) {
+                await mock.waitForRoster(_converse, 'current', 0);
+                const { api } = _converse;
+                const sent_stanzas = api.connection.get().sent_stanzas;
+                const own_jid = _converse.session.get('jid');
+
+                const node = 'princely_musings';
+                const pubsub_jid = 'pubsub.shakespeare.lit';
+
+                const promise = api.pubsub.config.set(pubsub_jid, node, { access_model: 'whitelist' });
+                let sent_stanza = await u.waitUntil(() =>
+                    sent_stanzas.filter((iq) => sizzle('pubsub configure', iq)).pop()
+                );
+                _converse.api.connection.get()._dataRecv(
+                    mock.createRequest(stx`
+                    <iq type='result'
+                        xmlns="jabber:client"
+                        from='${pubsub_jid}'
+                        to='${own_jid}'
+                        id="${sent_stanza.getAttribute('id')}">
+                    <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
+                        <configure node='${node}'>
+                        <x xmlns='jabber:x:data' type='form'>
+                            <field var='FORM_TYPE' type='hidden'>
+                            <value>http://jabber.org/protocol/pubsub#node_config</value>
+                            </field>
+                            <field var='pubsub#title' type='text-single'
+                                label='A friendly name for the node'/>
+                            <field var='pubsub#deliver_notifications' type='boolean'
+                                label='Whether to deliver event notifications'>
+                            <value>true</value>
+                            </field>
+                        </x>
+                        </configure>
+                    </pubsub>
+                </iq>`)
+                );
+
+                sent_stanza = await u.waitUntil(() =>
+                    sent_stanzas
+                        .filter((iq) => iq.getAttribute('type') === 'set' && sizzle('pubsub configure', iq))
+                        .pop()
+                );
+
+                const response = stx`
+                    <iq type='error'
+                            xmlns="jabber:client"
+                            from='${pubsub_jid}'
+                            to='${own_jid}'
+                            id="${sent_stanza.getAttribute('id')}">
+                        <error type='modify'><not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>
+                    </iq>`;
+
+                let first_error_thrown = false;
+                promise
+                    .catch((e) => {
+                        expect(e instanceof errors.NotAcceptableError).toBe(true);
+                        first_error_thrown = true;
+                    })
+                    .finally(() => {
+                        expect(first_error_thrown).toBe(true);
+                    });
                 _converse.api.connection.get()._dataRecv(mock.createRequest(response));
-        }));
+            })
+        );
     });
 });

+ 1 - 0
src/headless/shared/errors.js

@@ -42,3 +42,4 @@ export class ForbiddenError extends StanzaError {}
 export class BadRequestError extends StanzaError {}
 export class NotAllowedError extends StanzaError {}
 export class ItemNotFoundError extends StanzaError {}
+export class NotAcceptableError extends StanzaError {}

+ 2 - 0
src/headless/shared/parsers.js

@@ -38,6 +38,8 @@ export function parseErrorStanza(stanza) {
         return new errors.NotAllowedError(stanza);
     } else if (nodeName === 'item-not-found') {
         return new errors.ItemNotFoundError(stanza);
+    } else if (nodeName === 'not-acceptable') {
+        return new errors.NotAcceptableError(stanza);
     }
     return new errors.StanzaError(stanza);
 }

+ 2 - 2
src/headless/types/plugins/pubsub/api.d.ts

@@ -15,9 +15,9 @@ declare namespace _default {
              * @param {string} jid The JID of the pubsub service where the node resides
              * @param {string} node The node to configure
              * @param {PubSubConfigOptions} config The configuration options
-             * @returns {Promise<void|Element>}
+             * @returns {Promise<import('./types').PubSubConfigOptions>}
              */
-            function set(jid: string, node: string, config: import("./types").PubSubConfigOptions): Promise<void | Element>;
+            function set(jid: string, node: string, config: import("./types").PubSubConfigOptions): Promise<import("./types").PubSubConfigOptions>;
         }
         /**
          * Publshes an item to a PubSub node

+ 2 - 0
src/headless/types/shared/errors.d.ts

@@ -31,4 +31,6 @@ export class NotAllowedError extends StanzaError {
 }
 export class ItemNotFoundError extends StanzaError {
 }
+export class NotAcceptableError extends StanzaError {
+}
 //# sourceMappingURL=errors.d.ts.map