borogove
created pr with
borogove/muc-create.1
added borogove/muc-create.2
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
-: ------- > 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
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
lgtm!
cmds
checkout latest patchset:
ssh pr.pico.sh pull borogove:muc-create | git am -3checkout specific patchset revision:
ssh pr.pico.sh pull borogove:muc-create [rev] | git am -3add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh borogove:muc-createadd comment to patch request:
ssh pr.pico.sh comment borogove:muc-create "lgtm!"
Patchset
borogove/muc-create.1
Add tests
Stephen Paul Weber
2026-10-28T20:49:11ZSemantic diff summary
0 added,
0 modified,
0 signature changed,
0 removed
across 0 analyzed files
(3 files skipped: unsupported file type)
+65
-0
test/TestChat.hx
#
| ... | ... | @@ -8,10 +8,12 @@ import borogove.Stanza; | |
| 8 | 8 | import borogove.JID; | |
| 9 | 9 | import borogove.persistence.Dummy; | |
| 10 | 10 | import borogove.CapsRepo; | |
| 11 | + | import borogove.Chat; | |
| 11 | 12 | import borogove.Chat.Channel; | |
| 12 | 13 | import borogove.Chat.AvailableChat; | |
| 13 | 14 | import borogove.Caps.Identity; | |
| 14 | 15 | import borogove.Member; | |
| 16 | + | import borogove.Presence; | |
| 15 | 17 | import borogove.Role; | |
| 16 | 18 | import thenshim.Promise; | |
| 17 | 19 | using Lambda; |
| ... | ... | @@ -651,6 +653,69 @@ class TestChat extends utest.Test { | |
| 651 | 653 | Assert.isFalse(channel.canSend()); | |
| 652 | 654 | } | |
| 653 | 655 | ||
| 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 | + | ||
| 654 | 719 | public function testJoinFailure() { | |
| 655 | 720 | final persistence = new Dummy(); | |
| 656 | 721 | final client = new Client("test@example.com", persistence); |
+316
-0
test/TestClient.hx
#
| ... | ... | @@ -15,6 +16,8 @@ import borogove.Member; | |
| 15 | 16 | import borogove.MemberUpdate; | |
| 16 | 17 | import borogove.Message; | |
| 17 | 18 | import borogove.ModerationAction; | |
| 19 | + | import borogove.MucSettingsCommand; | |
| 20 | + | import borogove.Presence; | |
| 18 | 21 | import borogove.Role; | |
| 19 | 22 | import borogove.Stanza; | |
| 20 | 23 | import borogove.Status; |
| ... | ... | @@ -30,6 +33,24 @@ using Lambda; | |
| 30 | 33 | ||
| 31 | 34 | @:access(borogove) | |
| 32 | 35 | 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 | + | ||
| 33 | 54 | public function testPresenceAppliedBeforeCapsDiscovery() { | |
| 34 | 55 | final client = new Client("test@example.com", new Dummy()); | |
| 35 | 56 | final channel = new borogove.Chat.Channel(client, client.stream, client.persistence, "room@example.com"); |
| ... | ... | @@ -1569,6 +1590,287 @@ class TestClient extends utest.Test { | |
| 1569 | 1590 | }); | |
| 1570 | 1591 | } | |
| 1571 | 1592 | ||
| 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 | + | ||
| 1572 | 1874 | #if js | |
| 1573 | 1875 | public function testPreferOMEMO() { | |
| 1574 | 1876 | final persistence = new Dummy(); |
| ... | ... | @@ -1702,3 +2004,17 @@ class RestoreChatsMockPersistence extends Dummy { | |
| 1702 | 2004 | } | |
| 1703 | 2005 | } | |
| 1704 | 2006 | } | |
| 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 { | |
| 14 | 14 | public function testExecuteSuccess(async: Async) { | |
| 15 | 15 | final persistence = new Dummy(); | |
| 16 | 16 | 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++); | |
| 18 | 19 | ||
| 19 | 20 | client.stream.on("sendStanza", (stanza: Stanza) -> { | |
| 20 | 21 | if (stanza.name == "iq" && stanza.attr.get("type") == "get") { |
| ... | ... | @@ -41,6 +42,7 @@ class TestMucSettingsCommand extends utest.Test { | |
| 41 | 42 | Assert.equals("executing", session.status); | |
| 42 | 43 | Assert.equals(1, session.forms.length); | |
| 43 | 44 | Assert.notNull(session.forms[0]); | |
| 45 | + | Assert.equals(0, completed); | |
| 44 | 46 | ||
| 45 | 47 | client.stream.on("sendStanza", (stanza: Stanza) -> { | |
| 46 | 48 | if (stanza.name == "iq" && stanza.attr.get("type") == "set") { |
| ... | ... | @@ -66,6 +68,70 @@ class TestMucSettingsCommand extends utest.Test { | |
| 66 | 68 | session.execute(null, data, 0).then(nextSession -> { | |
| 67 | 69 | Assert.equals("completed", nextSession.status); | |
| 68 | 70 | 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); | |
| 69 | 135 | async.done(); | |
| 70 | 136 | }, err -> { | |
| 71 | 137 | Assert.fail(err); |
| ... | ... | @@ -78,7 +144,8 @@ class TestMucSettingsCommand extends utest.Test { | |
| 78 | 144 | public function testExecuteGetError(async: Async) { | |
| 79 | 145 | final persistence = new Dummy(); | |
| 80 | 146 | 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++); | |
| 82 | 149 | ||
| 83 | 150 | client.stream.on("sendStanza", (stanza: Stanza) -> { | |
| 84 | 151 | if (stanza.name == "iq" && stanza.attr.get("type") == "get") { |
| ... | ... | @@ -99,6 +166,7 @@ class TestMucSettingsCommand extends utest.Test { | |
| 99 | 166 | command.execute().then(session -> { | |
| 100 | 167 | Assert.equals("error", session.status); | |
| 101 | 168 | Assert.equals(1, session.forms.length); | |
| 169 | + | Assert.equals(0, completed); | |
| 102 | 170 | async.done(); | |
| 103 | 171 | }, err -> { | |
| 104 | 172 | Assert.fail(err); |
| ... | ... | @@ -108,7 +176,8 @@ class TestMucSettingsCommand extends utest.Test { | |
| 108 | 176 | public function testSessionCancel(async: Async) { | |
| 109 | 177 | final persistence = new Dummy(); | |
| 110 | 178 | 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++); | |
| 112 | 181 | ||
| 113 | 182 | final session = new borogove.MucSettingsCommand.MucSettingsCommandSession("executing", null, [], [], command); | |
| 114 | 183 |