borogove

created pr with borogove/muc-create.1 on 2026-10-28T20:49:38Z · by 9e834e0d
added borogove/muc-create.2 on 2026-10-30T12:26:53Z · by 3c31bc8c
1: 7eacfb3 < -: ------- Initial channel creation
2: 4b5a982 ! 1: d85d8d1 review
3: 8c9556b < -: ------- Support intant room creation
4: 941aad7 < -: ------- Add tests
added borogove/muc-create.3 on 2026-11-03T03:40:00Z · by 9e834e0d
-: ------- > 1: 0aa766b Initial channel creation
1: d85d8d1 ! 2: a7f33d5 After config form is submitted, creation is complete
-: ------- > 3: e6042b0 Support intant room creation
-: ------- > 4: 366bac7 Add tests
-: ------- > 5: fece298 review
-: ------- > 6: 322b2bd Review replies
added borogove/muc-create.4 on 2026-11-03T03:42:40Z · by 9e834e0d
1: 0aa766b = 1: 0aa766b Initial channel creation
2: a7f33d5 = 2: a7f33d5 After config form is submitted, creation is complete
3: e6042b0 = 3: e6042b0 Support intant room creation
4: 366bac7 ! 4: 0da1c7e Add tests
5: fece298 = 5: 86bad8e review
6: 322b2bd = 6: 997cf5a Review replies
commented on 2026-11-09T14:24:06Z · by 3c31bc8c
lgtm!
cmds
checkout latest patchset:
ssh pr.pico.sh pull borogove:muc-create | git am -3
checkout specific patchset revision:
ssh pr.pico.sh pull borogove:muc-create [rev] | git am -3
add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh borogove:muc-create
add comment to patch request:
ssh pr.pico.sh comment borogove:muc-create "lgtm!"
+65 -0 test/TestChat.hx #
......@@ -8,10 +8,12 @@ import borogove.Stanza;
88 import borogove.JID;
99 import borogove.persistence.Dummy;
1010 import borogove.CapsRepo;
11+import borogove.Chat;
1112 import borogove.Chat.Channel;
1213 import borogove.Chat.AvailableChat;
1314 import borogove.Caps.Identity;
1415 import borogove.Member;
16+import borogove.Presence;
1517 import borogove.Role;
1618 import thenshim.Promise;
1719 using Lambda;
......@@ -651,6 +653,69 @@ class TestChat extends utest.Test {
651653 Assert.isFalse(channel.canSend());
652654 }
653655
656+ public function testCreationRequires201(async: Async) {
657+ final persistence = new Dummy();
658+ final client = new Client("test@example.com", persistence);
659+ final channel = new Channel(client, client.stream, persistence, "room@example.com", Creating);
660+ var sentUnavailable = false;
661+
662+ client.stream.on("sendStanza", (stanza: Stanza) -> {
663+ if (stanza.name == "presence" && stanza.attr.get("type") == "unavailable") sentUnavailable = true;
664+ return EventUnhandled;
665+ });
666+
667+ Assert.isFalse(channel.canSend());
668+
669+ channel.join(false, false).then(_ -> {
670+ final presence: Presence = cast Stanza.parse('<presence xmlns="jabber:client" from="room@example.com/test" to="test@example.com"><x xmlns="http://jabber.org/protocol/muc#user"><item affiliation="member" role="participant"/><status code="110"/></x></presence>');
671+ channel.setPresence("test", presence, false);
672+
673+ Assert.equals("conflict", channel.joinFailed?.getError()?.condition);
674+ Assert.equals(Closed, channel.uiState);
675+ Assert.isTrue(sentUnavailable);
676+ async.done();
677+ return null;
678+ }, err -> {
679+ Assert.fail(err);
680+ return null;
681+ });
682+ }
683+
684+ public function testCreationErrorPresenceClosesChannel(async: Async) {
685+ final persistence = new Dummy();
686+ final client = new Client("test@example.com", persistence);
687+ final channel = new Channel(client, client.stream, persistence, "room@example.com", Creating);
688+ client.chats.push(channel);
689+ client.chatsIndex[channel.chatId] = channel;
690+
691+ channel.join(false, false).then(_ -> {
692+ client.stream.onStanza(Stanza.parse('<presence xmlns="jabber:client" from="room@example.com/test" to="test@example.com" type="error"><error type="auth"><forbidden xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></presence>'));
693+
694+ Assert.equals("forbidden", channel.joinFailed?.getError()?.condition);
695+ Assert.equals(Closed, channel.uiState);
696+ async.done();
697+ return null;
698+ }, err -> {
699+ Assert.fail(err);
700+ return null;
701+ });
702+ }
703+
704+ public function testSelfPingNoopWhileCreating() {
705+ final persistence = new Dummy();
706+ final client = new Client("test@example.com", persistence);
707+ final channel = new Channel(client, client.stream, persistence, "room@example.com", Creating);
708+ var sentPing = false;
709+
710+ client.stream.on("sendStanza", (stanza: Stanza) -> {
711+ if (stanza.name == "iq" && stanza.getChild("ping", "urn:xmpp:ping") != null) sentPing = true;
712+ return EventUnhandled;
713+ });
714+
715+ channel.selfPing(true);
716+ Assert.isFalse(sentPing);
717+ }
718+
654719 public function testJoinFailure() {
655720 final persistence = new Dummy();
656721 final client = new Client("test@example.com", persistence);
+316 -0 test/TestClient.hx #
......@@ -5,6 +5,7 @@ import utest.Assert;
55 import utest.Async;
66
77 import borogove.Chat;
8+import borogove.Chat.Channel;
89 import borogove.ChatMessage;
910 import borogove.ChatMessageBuilder;
1011 import borogove.Client;
......@@ -15,6 +16,8 @@ import borogove.Member;
1516 import borogove.MemberUpdate;
1617 import borogove.Message;
1718 import borogove.ModerationAction;
19+import borogove.MucSettingsCommand;
20+import borogove.Presence;
1821 import borogove.Role;
1922 import borogove.Stanza;
2023 import borogove.Status;
......@@ -30,6 +33,24 @@ using Lambda;
3033
3134 @:access(borogove)
3235 class TestClient extends utest.Test {
36+ private function errorStanza(condition: String, type: String = "cancel"): Stanza {
37+ return new Stanza("presence", { type: "error" })
38+ .tag("error", { type: type })
39+ .tag(condition, { xmlns: "urn:ietf:params:xml:ns:xmpp-stanzas" }).up()
40+ .up();
41+ }
42+
43+ private function discardChannel(client: Client, channel: Channel): Void {
44+ channel.setJoinFailed(errorStanza("internal-server-error"));
45+ client.chats.remove(channel);
46+ client.chatsIndex.remove(channel.chatId);
47+ }
48+
49+ private function submittedField(stanza: Stanza, name: String): Null<String> {
50+ final x = stanza.getChild("query", "http://jabber.org/protocol/muc#owner")?.getChild("x", "jabber:x:data");
51+ return x?.allTags("field").find(field -> field.attr.get("var") == name)?.getChildText("value");
52+ }
53+
3354 public function testPresenceAppliedBeforeCapsDiscovery() {
3455 final client = new Client("test@example.com", new Dummy());
3556 final channel = new borogove.Chat.Channel(client, client.stream, client.persistence, "room@example.com");
......@@ -1569,6 +1590,287 @@ class TestClient extends utest.Test {
15691590 });
15701591 }
15711592
1593+ public function testCreateChannelDerivesLocalpartFromName(async: Async) {
1594+ final client = new Client("test@example.com", new Dummy());
1595+
1596+ client.createChannel(" Design Team & Chat! ", " ", "groups.example.com").then(channel -> {
1597+ Assert.equals("design-team-chat@groups.example.com", channel.chatId);
1598+ discardChannel(client, channel);
1599+ async.done();
1600+ return null;
1601+ }, err -> {
1602+ Assert.fail(err);
1603+ return null;
1604+ });
1605+ }
1606+
1607+ public function testCreateChannelFallsBackToUuid(async: Async) {
1608+ final client = new Client("test@example.com", new Dummy());
1609+
1610+ client.createChannel(null, " ", "groups.example.com").then(channel -> {
1611+ final parsed = JID.parse(channel.chatId);
1612+ Assert.notNull(parsed.node);
1613+ Assert.isFalse(parsed.node == "");
1614+ Assert.equals("groups.example.com", parsed.domain);
1615+ discardChannel(client, channel);
1616+ async.done();
1617+ return null;
1618+ }, err -> {
1619+ Assert.fail(err);
1620+ return null;
1621+ });
1622+ }
1623+
1624+ public function testCreateChannelUsesPersistedService(async: Async) {
1625+ final persistence = new ChannelCreationMockPersistence();
1626+ persistence.services = [{
1627+ serviceId: "groups.example.com",
1628+ name: "Groups",
1629+ node: null,
1630+ caps: new borogove.Caps("", [], ["http://jabber.org/protocol/muc"], []),
1631+ }];
1632+ final client = new Client("test@example.com", persistence);
1633+
1634+ client.createChannel(null, "design-team").then(channel -> {
1635+ Assert.equals("design-team@groups.example.com", channel.chatId);
1636+ discardChannel(client, channel);
1637+ async.done();
1638+ return null;
1639+ }, err -> {
1640+ Assert.fail(err);
1641+ return null;
1642+ });
1643+ }
1644+
1645+ public function testCreateChannelWithoutServiceRejects(async: Async) {
1646+ final client = new Client("test@example.com", new Dummy());
1647+
1648+ client.createChannel(null, null).then(_ -> {
1649+ Assert.fail("Expected createChannel to reject");
1650+ return null;
1651+ }, err -> {
1652+ Assert.notNull(err);
1653+ async.done();
1654+ return null;
1655+ });
1656+ }
1657+
1658+ public function testCreateChannelRejectsExistingChat(async: Async) {
1659+ final client = new Client("test@example.com", new Dummy());
1660+
1661+ client.createChannel(null, "design-team", "groups.example.com").then(channel -> {
1662+ client.createChannel(null, "design-team", "groups.example.com").then(_ -> {
1663+ Assert.fail("Expected second createChannel to reject");
1664+ return null;
1665+ }, err -> {
1666+ Assert.notNull(err);
1667+ discardChannel(client, channel);
1668+ async.done();
1669+ return null;
1670+ });
1671+ return null;
1672+ }, err -> {
1673+ Assert.fail(err);
1674+ return null;
1675+ });
1676+ }
1677+
1678+ public function testCreateChannelInstantConfiguresAutomatically(async: Async) {
1679+ final persistence = new ChannelCreationMockPersistence();
1680+ final client = new Client("test@example.com", persistence);
1681+
1682+ var created: Null<Channel> = null;
1683+ var sentPresence: Null<Stanza> = null;
1684+ var submitted: Null<Stanza> = null;
1685+ var ownerGets = 0;
1686+ var ownerSets = 0;
1687+ var done = false;
1688+
1689+ client.on("chats/update", (chats: Array<Chat>) -> {
1690+ final channel = created;
1691+ if (done || channel == null || chats.indexOf(channel) < 0 || channel.uiState != Open) return EventHandled;
1692+
1693+ done = true;
1694+ Assert.equals(1, ownerGets);
1695+ Assert.equals(1, ownerSets);
1696+ Assert.notNull(submitted);
1697+ Assert.equals("Design Team", submittedField(submitted, "muc#roomconfig_roomname"));
1698+ Assert.equals("0", submittedField(submitted, "muc#roomconfig_membersonly"));
1699+ Assert.equals("1", submittedField(submitted, "muc#roomconfig_persistentroom"));
1700+ Assert.equals("anyone", submittedField(submitted, "muc#roomconfig_whois"));
1701+ Assert.equals("1", submittedField(submitted, "muc#roomconfig_allowinvites"));
1702+ Assert.equals(Open, channel.uiState);
1703+ Assert.isTrue(channel.canSend());
1704+ Assert.isTrue(persistence.storedChats.indexOf(channel) >= 0);
1705+ async.done();
1706+ return EventHandled;
1707+ });
1708+
1709+ client.stream.on("sendStanza", (stanza: Stanza) -> {
1710+ if (stanza.name == "presence") {
1711+ sentPresence = stanza;
1712+ return EventHandled;
1713+ }
1714+
1715+ if (stanza.name == "iq" && stanza.attr.get("type") == "get") {
1716+ final query = stanza.getChild("query", "http://jabber.org/protocol/muc#owner");
1717+ if (query != null) {
1718+ ownerGets++;
1719+ final responseStanza = new Stanza("iq", { xmlns: "jabber:client", type: "result", id: stanza.attr.get("id"), from: "design-team@groups.example.com", to: "test@example.com" })
1720+ .tag("query", { xmlns: "http://jabber.org/protocol/muc#owner" })
1721+ .tag("x", { xmlns: "jabber:x:data", type: "form" })
1722+ .tag("field", { "var": "muc#roomconfig_roomname" })
1723+ .textTag("value", "Server Default")
1724+ .up()
1725+ .tag("field", { "var": "muc#roomconfig_membersonly", type: "boolean" })
1726+ .textTag("value", "1")
1727+ .up()
1728+ .up()
1729+ .up();
1730+
1731+ client.stream.onStanza(responseStanza);
1732+ return EventHandled;
1733+ }
1734+ } else if (stanza.name == "iq" && stanza.attr.get("type") == "set") {
1735+ final query = stanza.getChild("query", "http://jabber.org/protocol/muc#owner");
1736+ if (query != null) {
1737+ ownerSets++;
1738+ submitted = stanza;
1739+ final responseStanza = new Stanza("iq", { xmlns: "jabber:client", type: "result", id: stanza.attr.get("id"), from: "design-team@groups.example.com", to: "test@example.com" });
1740+ client.stream.onStanza(responseStanza);
1741+ return EventHandled;
1742+ }
1743+ }
1744+
1745+ return EventUnhandled;
1746+ });
1747+
1748+ client.createChannelInstant("Design Team", false, "design-team", "groups.example.com").then(channel -> {
1749+ created = channel;
1750+
1751+ Assert.equals("design-team@groups.example.com", channel.chatId);
1752+ Assert.equals("Design Team", channel.getDisplayName());
1753+ Assert.equals(Creating, channel.uiState);
1754+ Assert.isFalse(channel.canSend());
1755+ Assert.equals(channel, client.getChat(channel.chatId));
1756+ Assert.isTrue(client.getChats().indexOf(channel) >= 0);
1757+ Assert.isFalse(persistence.storedChats.indexOf(channel) >= 0);
1758+ Assert.notNull(channel.creationFormSubmission);
1759+ Assert.equals("Design Team", channel.creationFormSubmission.get("muc#roomconfig_roomname")[0]);
1760+ Assert.equals("0", channel.creationFormSubmission.get("muc#roomconfig_membersonly")[0]);
1761+ Assert.notNull(sentPresence);
1762+ Assert.equals("design-team@groups.example.com/test", sentPresence.attr.get("to"));
1763+ Assert.notNull(sentPresence.getChild("x", "http://jabber.org/protocol/muc"));
1764+
1765+ final presence: Presence = cast Stanza.parse('<presence xmlns="jabber:client" from="design-team@groups.example.com/test" to="test@example.com"><x xmlns="http://jabber.org/protocol/muc#user"><item affiliation="owner" role="moderator"/><status code="110"/><status code="201"/></x></presence>');
1766+ client.stream.onStanza(presence);
1767+ return null;
1768+ }, err -> {
1769+ Assert.fail(err);
1770+ return null;
1771+ });
1772+ }
1773+
1774+ public function testCreateChannelDefersConfig(async: Async) {
1775+ final persistence = new ChannelCreationMockPersistence();
1776+ final client = new Client("test@example.com", persistence);
1777+
1778+ var submitted: Null<Stanza> = null;
1779+ var ownerGets = 0;
1780+ var ownerSets = 0;
1781+
1782+ client.stream.on("sendStanza", (stanza: Stanza) -> {
1783+ if (stanza.name == "iq" && stanza.attr.get("type") == "get") {
1784+ final query = stanza.getChild("query", "http://jabber.org/protocol/muc#owner");
1785+ if (query != null) {
1786+ ownerGets++;
1787+ final responseStanza = new Stanza("iq", { xmlns: "jabber:client", type: "result", id: stanza.attr.get("id"), from: "design-team@groups.example.com", to: "test@example.com" })
1788+ .tag("query", { xmlns: "http://jabber.org/protocol/muc#owner" })
1789+ .tag("x", { xmlns: "jabber:x:data", type: "form" })
1790+ .tag("field", { "var": "muc#roomconfig_roomname" })
1791+ .textTag("value", "Server Default")
1792+ .up()
1793+ .up()
1794+ .up();
1795+
1796+ client.stream.onStanza(responseStanza);
1797+ return EventHandled;
1798+ }
1799+ } else if (stanza.name == "iq" && stanza.attr.get("type") == "set") {
1800+ final query = stanza.getChild("query", "http://jabber.org/protocol/muc#owner");
1801+ if (query != null) {
1802+ ownerSets++;
1803+ submitted = stanza;
1804+ final responseStanza = new Stanza("iq", { xmlns: "jabber:client", type: "result", id: stanza.attr.get("id"), from: "design-team@groups.example.com", to: "test@example.com" });
1805+ client.stream.onStanza(responseStanza);
1806+ return EventHandled;
1807+ }
1808+ }
1809+ return EventUnhandled;
1810+ });
1811+
1812+ client.createChannel("Design Team", "design-team", "groups.example.com").then(channel -> {
1813+ Assert.equals("design-team@groups.example.com", channel.chatId);
1814+ Assert.equals(Creating, channel.uiState);
1815+ Assert.isFalse(channel.canSend());
1816+ Assert.isNull(channel.creationFormSubmission);
1817+ Assert.notNull(channel.creationFormPrefill);
1818+ Assert.equals("Design Team", channel.creationFormPrefill.get("muc#roomconfig_roomname")[0]);
1819+
1820+ final presence: Presence = cast Stanza.parse('<presence xmlns="jabber:client" from="design-team@groups.example.com/test" to="test@example.com"><x xmlns="http://jabber.org/protocol/muc#user"><item affiliation="owner" role="moderator"/><status code="110"/><status code="201"/></x></presence>');
1821+ client.stream.onStanza(presence);
1822+
1823+ Assert.isTrue(channel.isJoined);
1824+ Assert.equals(Creating, channel.uiState);
1825+ Assert.isFalse(channel.canSend());
1826+ Assert.equals(0, ownerGets);
1827+
1828+ channel.commands().then(cmds -> {
1829+ final settings = cmds.filter(cmd -> Std.isOfType(cmd, MucSettingsCommand))[0];
1830+ settings.execute().then(session -> {
1831+ Assert.equals("executing", session.status);
1832+ final nameItem = session.forms[0].items().find(item -> item.field?.name == "muc#roomconfig_roomname");
1833+ Assert.notNull(nameItem);
1834+ Assert.equals("Design Team", nameItem.field.value[0]);
1835+
1836+ #if js
1837+ final data = {};
1838+ #else
1839+ final data = new borogove.Form.FormSubmitBuilder();
1840+ #end
1841+
1842+ session.execute(null, data, 0).then(nextSession -> {
1843+ Assert.equals("completed", nextSession.status);
1844+ Assert.equals(1, ownerGets);
1845+ Assert.equals(1, ownerSets);
1846+ Assert.notNull(submitted);
1847+ Assert.equals("Design Team", submittedField(submitted, "muc#roomconfig_roomname"));
1848+ Assert.equals(Open, channel.uiState);
1849+ Assert.isTrue(channel.canSend());
1850+ Assert.isTrue(persistence.storedChats.indexOf(channel) >= 0);
1851+ async.done();
1852+ return null;
1853+ }, err -> {
1854+ Assert.fail(err);
1855+ return null;
1856+ });
1857+ return null;
1858+ }, err -> {
1859+ Assert.fail(err);
1860+ return null;
1861+ });
1862+ return null;
1863+ }, err -> {
1864+ Assert.fail(err);
1865+ return null;
1866+ });
1867+ return null;
1868+ }, err -> {
1869+ Assert.fail(err);
1870+ return null;
1871+ });
1872+ }
1873+
15721874 #if js
15731875 public function testPreferOMEMO() {
15741876 final persistence = new Dummy();
......@@ -1702,3 +2004,17 @@ class RestoreChatsMockPersistence extends Dummy {
17022004 }
17032005 }
17042006 }
2007+
2008+@:access(borogove)
2009+class ChannelCreationMockPersistence extends Dummy {
2010+ public var storedChats: Array<Chat> = [];
2011+ public var services: Array<{serviceId: String, name: Null<String>, node: Null<String>, caps: borogove.Caps}> = [];
2012+
2013+ override public function storeChats(accountId: String, chats: Array<Chat>) {
2014+ storedChats = storedChats.concat(chats);
2015+ }
2016+
2017+ override public function findServicesWithFeature(accountId: String, feature: String): Promise<Array<{serviceId: String, name: Null<String>, node: Null<String>, caps: borogove.Caps}>> {
2018+ return Promise.resolve(services);
2019+ }
2020+}
+73 -3 test/TestMucSettingsCommand.hx #
......@@ -14,7 +14,8 @@ class TestMucSettingsCommand extends utest.Test {
1414 public function testExecuteSuccess(async: Async) {
1515 final persistence = new Dummy();
1616 final client = new Client("test@example.com", persistence);
17- final command = new MucSettingsCommand(client, JID.parse("channel@example.com"));
17+ var completed = 0;
18+ final command = new MucSettingsCommand(client, JID.parse("channel@example.com"), () -> completed++);
1819
1920 client.stream.on("sendStanza", (stanza: Stanza) -> {
2021 if (stanza.name == "iq" && stanza.attr.get("type") == "get") {
......@@ -41,6 +42,7 @@ class TestMucSettingsCommand extends utest.Test {
4142 Assert.equals("executing", session.status);
4243 Assert.equals(1, session.forms.length);
4344 Assert.notNull(session.forms[0]);
45+ Assert.equals(0, completed);
4446
4547 client.stream.on("sendStanza", (stanza: Stanza) -> {
4648 if (stanza.name == "iq" && stanza.attr.get("type") == "set") {
......@@ -66,6 +68,70 @@ class TestMucSettingsCommand extends utest.Test {
6668 session.execute(null, data, 0).then(nextSession -> {
6769 Assert.equals("completed", nextSession.status);
6870 Assert.equals(0, nextSession.forms.length);
71+ Assert.equals(1, completed);
72+ async.done();
73+ }, err -> {
74+ Assert.fail(err);
75+ });
76+ }, err -> {
77+ Assert.fail(err);
78+ });
79+ }
80+
81+ public function testSubmitErrorDoesNotComplete(async: Async) {
82+ final persistence = new Dummy();
83+ final client = new Client("test@example.com", persistence);
84+ var completed = 0;
85+ final command = new MucSettingsCommand(client, JID.parse("channel@example.com"), () -> completed++);
86+
87+ client.stream.on("sendStanza", (stanza: Stanza) -> {
88+ if (stanza.name == "iq" && stanza.attr.get("type") == "get") {
89+ final query = stanza.getChild("query", "http://jabber.org/protocol/muc#owner");
90+ if (query != null) {
91+ final responseStanza = new Stanza("iq", { xmlns: "jabber:client", type: "result", id: stanza.attr.get("id"), from: "channel@example.com", to: "test@example.com" })
92+ .tag("query", { xmlns: "http://jabber.org/protocol/muc#owner" })
93+ .tag("x", { xmlns: "jabber:x:data", type: "form" })
94+ .tag("field", { "var": "muc#roomconfig_roomname" })
95+ .textTag("value", "Test Room")
96+ .up()
97+ .up()
98+ .up();
99+
100+ client.stream.onStanza(responseStanza);
101+ return EventHandled;
102+ }
103+ }
104+ return EventUnhandled;
105+ });
106+
107+ command.execute().then(session -> {
108+ Assert.equals("executing", session.status);
109+
110+ client.stream.on("sendStanza", (stanza: Stanza) -> {
111+ if (stanza.name == "iq" && stanza.attr.get("type") == "set") {
112+ final query = stanza.getChild("query", "http://jabber.org/protocol/muc#owner");
113+ if (query != null) {
114+ final responseStanza = new Stanza("iq", { xmlns: "jabber:client", type: "error", id: stanza.attr.get("id"), from: "channel@example.com", to: "test@example.com" })
115+ .tag("error", { type: "cancel" })
116+ .tag("conflict", { xmlns: "urn:ietf:params:xml:ns:xmpp-stanzas" }).up()
117+ .up();
118+ client.stream.onStanza(responseStanza);
119+ return EventHandled;
120+ }
121+ }
122+ return EventUnhandled;
123+ });
124+
125+ #if js
126+ final data = {};
127+ #else
128+ final data = new borogove.Form.FormSubmitBuilder();
129+ #end
130+
131+ session.execute(null, data, 0).then(nextSession -> {
132+ Assert.equals("error", nextSession.status);
133+ Assert.equals(1, nextSession.forms.length);
134+ Assert.equals(0, completed);
69135 async.done();
70136 }, err -> {
71137 Assert.fail(err);
......@@ -78,7 +144,8 @@ class TestMucSettingsCommand extends utest.Test {
78144 public function testExecuteGetError(async: Async) {
79145 final persistence = new Dummy();
80146 final client = new Client("test@example.com", persistence);
81- final command = new MucSettingsCommand(client, JID.parse("channel@example.com"));
147+ var completed = 0;
148+ final command = new MucSettingsCommand(client, JID.parse("channel@example.com"), () -> completed++);
82149
83150 client.stream.on("sendStanza", (stanza: Stanza) -> {
84151 if (stanza.name == "iq" && stanza.attr.get("type") == "get") {
......@@ -99,6 +166,7 @@ class TestMucSettingsCommand extends utest.Test {
99166 command.execute().then(session -> {
100167 Assert.equals("error", session.status);
101168 Assert.equals(1, session.forms.length);
169+ Assert.equals(0, completed);
102170 async.done();
103171 }, err -> {
104172 Assert.fail(err);
......@@ -108,7 +176,8 @@ class TestMucSettingsCommand extends utest.Test {
108176 public function testSessionCancel(async: Async) {
109177 final persistence = new Dummy();
110178 final client = new Client("test@example.com", persistence);
111- final command = new MucSettingsCommand(client, JID.parse("channel@example.com"));
179+ var completed = 0;
180+ final command = new MucSettingsCommand(client, JID.parse("channel@example.com"), () -> completed++);
112181
113182 final session = new borogove.MucSettingsCommand.MucSettingsCommandSession("executing", null, [], [], command);
114183
......@@ -119,6 +188,7 @@ class TestMucSettingsCommand extends utest.Test {
119188
120189 session.execute("cancel", null, 0).then(nextSession -> {
121190 Assert.equals("canceled", nextSession.status);
191+ Assert.equals(0, completed);
122192 async.done();
123193 }, err -> {
124194 Assert.fail(err);
Back to top