aboutsummaryrefslogtreecommitdiffstats
path: root/spec
diff options
context:
space:
mode:
authorMatthew Wild <mwild1@gmail.com>2018-09-06 16:44:48 +0100
committerMatthew Wild <mwild1@gmail.com>2018-09-06 16:44:48 +0100
commitd78bd730e5654ba99f1fbdda5ef07b8b271efe4b (patch)
tree12dd5fa0c79626ad740d37941b15a62428ced0ee /spec
parent5d5fbb462cde24b672ffbef5318102892bc2776e (diff)
downloadprosody-d78bd730e5654ba99f1fbdda5ef07b8b271efe4b.tar.gz
prosody-d78bd730e5654ba99f1fbdda5ef07b8b271efe4b.zip
spec/scansion: Add scansion scripts used for testing
All past, except issue978-multi which fails randomly based on ordering of elements - this is a scansion issue to be resolved.
Diffstat (limited to 'spec')
-rw-r--r--spec/scansion/basic.scs16
-rw-r--r--spec/scansion/basic_message.scs174
-rw-r--r--spec/scansion/basic_roster.scs39
-rw-r--r--spec/scansion/issue505.scs77
-rw-r--r--spec/scansion/issue978-multi.scs109
-rw-r--r--spec/scansion/issue978.scs84
-rw-r--r--spec/scansion/muc_mediated_invite.scs74
-rw-r--r--spec/scansion/muc_password-trunk.scs141
-rw-r--r--spec/scansion/muc_password.scs149
-rw-r--r--spec/scansion/muc_register.scs286
-rw-r--r--spec/scansion/pubsub_advanced.scs162
-rw-r--r--spec/scansion/pubsub_basic.scs102
-rw-r--r--spec/scansion/pubsub_createdelete.scs61
13 files changed, 1474 insertions, 0 deletions
diff --git a/spec/scansion/basic.scs b/spec/scansion/basic.scs
new file mode 100644
index 00000000..c88672c1
--- /dev/null
+++ b/spec/scansion/basic.scs
@@ -0,0 +1,16 @@
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+---------
+
+Romeo connects
+
+Romeo sends:
+ <presence/>
+
+Romeo receives:
+ <presence/>
+
+Romeo disconnects
+
diff --git a/spec/scansion/basic_message.scs b/spec/scansion/basic_message.scs
new file mode 100644
index 00000000..f01d8925
--- /dev/null
+++ b/spec/scansion/basic_message.scs
@@ -0,0 +1,174 @@
+# A script testing basic message routing and delivery
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: juliet@localhost
+ password: password
+
+[Client] Juliet's phone
+ jid: juliet@localhost
+ password: password
+ resource: mobile
+
+---------
+
+# Act 1, scene 1
+# The clients connect
+
+Romeo connects
+
+Juliet connects
+
+Juliet's phone connects
+
+# Romeo publishes his presence. Juliet has not, and so does not receive presence.
+
+Romeo sends:
+ <presence/>
+
+Romeo receives:
+ <presence from="${Romeo's full JID}" />
+
+# Romeo sends a message to Juliet's full JID
+
+Romeo sends:
+ <message to="${Juliet's full JID}" type="chat">
+ <body>Hello Juliet!</body>
+ </message>
+
+Juliet receives:
+ <message to="${Juliet's full JID}" from="${Romeo's full JID}" type="chat">
+ <body>Hello Juliet!</body>
+ </message>
+
+# Romeo sends a message to Juliet's phone
+
+Romeo sends:
+ <message to="${Juliet's phone's full JID}" type="chat">
+ <body>Hello Juliet, on your phone.</body>
+ </message>
+
+Juliet's phone receives:
+ <message to="${Juliet's phone's full JID}" from="${Romeo's full JID}" type="chat">
+ <body>Hello Juliet, on your phone.</body>
+ </message>
+
+# Scene 2
+# This requires the server to support offline messages (which is optional).
+
+# Romeo sends a message to Juliet's bare JID. This is not immediately delivered, as she
+# has not published presence on either of her resources.
+
+Romeo sends:
+ <message to="juliet@localhost" type="chat">
+ <body>Hello Juliet, are you there?</body>
+ </message>
+
+# Juliet sends presence on her phone, and should receive the message there
+
+Juliet's phone sends:
+ <presence/>
+
+Juliet's phone receives:
+ <presence/>
+
+Juliet's phone receives:
+ <message from="${Romeo's full JID}" type="chat">
+ <body>Hello Juliet, are you there?</body>
+ <delay xmlns='urn:xmpp:delay' from='localhost' />
+ </message>
+
+# Romeo sends another bare-JID message, it should be delivered
+# instantly to Juliet's phone
+
+Romeo sends:
+ <message to="juliet@localhost" type="chat">
+ <body>Oh, hi!</body>
+ </message>
+
+Juliet's phone receives:
+ <message from="${Romeo's full JID}" type="chat">
+ <body>Oh, hi!</body>
+ </message>
+
+# Juliet's laptop goes online, but with a negative priority
+
+Juliet sends:
+ <presence>
+ <priority>-1</priority>
+ </presence>
+
+Juliet receives:
+ <presence from="${Juliet's full JID}">
+ <priority>-1</priority>
+ </presence>
+
+Juliet's phone receives:
+ <presence from="${Juliet's full JID}">
+ <priority>-1</priority>
+ </presence>
+
+# Again, Romeo sends a message to her bare JID, but it should
+# only get delivered to her phone:
+
+Romeo sends:
+ <message to="juliet@localhost" type="chat">
+ <body>How are you?</body>
+ </message>
+
+Juliet's phone receives:
+ <message from="${Romeo's full JID}" type="chat">
+ <body>How are you?</body>
+ </message>
+
+# Romeo sends direct to Juliet's full JID, and she should receive it
+
+Romeo sends:
+ <message to="${Juliet's full JID}" type="chat">
+ <body>Are you hiding?</body>
+ </message>
+
+Juliet receives:
+ <message from="${Romeo's full JID}" type="chat">
+ <body>Are you hiding?</body>
+ </message>
+
+# Juliet publishes non-negative presence
+
+Juliet sends:
+ <presence/>
+
+Juliet receives:
+ <presence from="${Juliet's full JID}"/>
+
+Juliet's phone receives:
+ <presence from="${Juliet's full JID}"/>
+
+# And now Romeo's bare JID messages get delivered to both resources
+# (server behaviour may vary here)
+
+Romeo sends:
+ <message to="juliet@localhost" type="chat">
+ <body>There!</body>
+ </message>
+
+Juliet receives:
+ <message from="${Romeo's full JID}" type="chat">
+ <body>There!</body>
+ </message>
+
+Juliet's phone receives:
+ <message from="${Romeo's full JID}" type="chat">
+ <body>There!</body>
+ </message>
+
+# The End
+
+Romeo disconnects
+
+Juliet disconnects
+
+Juliet's phone disconnects
diff --git a/spec/scansion/basic_roster.scs b/spec/scansion/basic_roster.scs
new file mode 100644
index 00000000..2655639a
--- /dev/null
+++ b/spec/scansion/basic_roster.scs
@@ -0,0 +1,39 @@
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: juliet@localhost
+ password: password
+
+---------
+
+Romeo connects
+
+Juliet connects
+
+Romeo sends:
+ <presence/>
+
+Romeo receives:
+ <presence from="${Romeo's full JID}" />
+
+Romeo sends:
+ <iq type="get" id="roster1">
+ <query xmlns='jabber:iq:roster'/>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id="roster1">
+ <query xmlns="jabber:iq:roster"/>
+ </iq>
+
+Romeo disconnects
+
+#Juliet receives:
+# <presence from="${Romeo's full JID}" />
+#
+#Juliet receives:
+# <presence from="${Romeo's full JID}" type="unavailable" />
+#
+#Juliet disconnects
diff --git a/spec/scansion/issue505.scs b/spec/scansion/issue505.scs
new file mode 100644
index 00000000..6d4d27b7
--- /dev/null
+++ b/spec/scansion/issue505.scs
@@ -0,0 +1,77 @@
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Juliet connects, and joins the room
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet sends:
+ <presence type='unavailable' to='room@conference.localhost'>
+ <status>Farewell</status>
+ </presence>
+
+Romeo receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet'>
+ <status>Farewell</status>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='none' role='none'/>
+ </x>
+ </presence>
diff --git a/spec/scansion/issue978-multi.scs b/spec/scansion/issue978-multi.scs
new file mode 100644
index 00000000..340f2b27
--- /dev/null
+++ b/spec/scansion/issue978-multi.scs
@@ -0,0 +1,109 @@
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+[Client] Juliet's phone
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Juliet connects, and joins the room
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Juliet's phone connects, and joins the room
+Juliet's phone connects
+
+Juliet's phone sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet's phone receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet's phone receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet's phone receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='none' jid="${Juliet's phone's full JID}" role='participant'/>
+ <item affiliation='none' jid="${Juliet's full JID}" role='participant'/>
+ </x>
+ </presence>
+
+# Juliet leaves with an error
+Juliet sends:
+ <presence type='error' to='room@conference.localhost'>
+ <error type='cancel'>
+ <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Test error</text>
+ </error>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's phone's full JID}" affiliation='none' role='participant'/>
+ </x>
+ </presence>
diff --git a/spec/scansion/issue978.scs b/spec/scansion/issue978.scs
new file mode 100644
index 00000000..d47473be
--- /dev/null
+++ b/spec/scansion/issue978.scs
@@ -0,0 +1,84 @@
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_whois'>
+ <value>anyone</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Juliet connects, and joins the room
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet sends:
+ <presence type='error' to='room@conference.localhost'>
+ <error type='cancel'>
+ <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Test error</text>
+ </error>
+ </presence>
+
+Romeo receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet'>
+ <status>Kicked: service unavailable: Test error</status>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='333'/>
+ <item jid="${Juliet's full JID}" affiliation='none' role='none'/>
+ </x>
+ </presence>
diff --git a/spec/scansion/muc_mediated_invite.scs b/spec/scansion/muc_mediated_invite.scs
new file mode 100644
index 00000000..7662fc7b
--- /dev/null
+++ b/spec/scansion/muc_mediated_invite.scs
@@ -0,0 +1,74 @@
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Juliet connects
+Juliet connects
+
+Juliet sends:
+ <presence/>
+
+Juliet receives:
+ <presence/>
+
+
+# Romeo invites Juliet to join the room
+
+Romeo sends:
+ <message to="room@conference.localhost" id="invite1">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <invite to="${Juliet's JID}" />
+ </x>
+ </message>
+
+Juliet receives:
+ <message from="room@conference.localhost" id="invite1">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <invite from="room@conference.localhost/Romeo">
+ <reason/>
+ </invite>
+ </x>
+ <body>room@conference.localhost/Romeo invited you to the room room@conference.localhost</body>
+ <x xmlns="jabber:x:conference" jid="room@conference.localhost"/>
+ </message>
diff --git a/spec/scansion/muc_password-trunk.scs b/spec/scansion/muc_password-trunk.scs
new file mode 100644
index 00000000..0a343b91
--- /dev/null
+++ b/spec/scansion/muc_password-trunk.scs
@@ -0,0 +1,141 @@
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_roomsecret'>
+ <value>cauldronburn</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Juliet connects, and tries to join the room (password-protected)
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" type="error">
+ <error type="auth">
+ <not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </presence>
+
+# Retry with the correct password
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc">
+ <password>cauldronburn</password>
+ </x>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Ok, now Juliet leaves, and Romeo unsets the password
+
+Juliet sends:
+ <presence type="unavailable" to="room@conference.localhost"/>
+
+Romeo receives:
+ <presence type="unavailable" from="room@conference.localhost/Juliet"/>
+
+Juliet receives:
+ <presence type="unavailable" from="room@conference.localhost/Juliet"/>
+
+# Remove room password
+Romeo sends:
+ <iq id='config2' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_roomsecret'>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+# Config change success
+Romeo receives:
+ <iq id="config2" from="room@conference.localhost" type="result">
+ </iq>
+
+# Notification of room configuration update
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='104'/>
+ </x>
+ </message>
+
+# Juliet tries to join (should succeed)
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+# Notification of Romeo's presence in the room
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Room topic
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
diff --git a/spec/scansion/muc_password.scs b/spec/scansion/muc_password.scs
new file mode 100644
index 00000000..813265a5
--- /dev/null
+++ b/spec/scansion/muc_password.scs
@@ -0,0 +1,149 @@
+# MUC password test
+# Ensures that setting and unsetting a MUC password works, and that the password is
+# required to join a password-protected room.
+
+## prosody-0.9 result: fails because of missing 201 status code (related to issue #328)
+## prosody-0.10 result: fails because of missing 201 status code (related to issue #328)
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_roomsecret'>
+ <value>cauldronburn</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Juliet connects, and tries to join the room (password-protected)
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" type="error">
+ <error type="auth">
+ <not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </presence>
+
+# Retry with the correct password
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc">
+ <password>cauldronburn</password>
+ </x>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Ok, now Juliet leaves, and Romeo unsets the password
+
+Juliet sends:
+ <presence type="unavailable" to="room@conference.localhost"/>
+
+Romeo receives:
+ <presence type="unavailable" from="room@conference.localhost/Juliet"/>
+
+Juliet receives:
+ <presence type="unavailable" from="room@conference.localhost/Juliet"/>
+
+# Remove room password
+Romeo sends:
+ <iq id='config2' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_roomsecret'>
+ <value></value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+# Config change success
+Romeo receives:
+ <iq id="config2" from="room@conference.localhost" type="result">
+ </iq>
+
+# Notification of room configuration update
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='104'/>
+ </x>
+ </message>
+
+# Juliet tries to join (should succeed)
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+# Notification of Romeo's presence in the room
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Room topic
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
diff --git a/spec/scansion/muc_register.scs b/spec/scansion/muc_register.scs
new file mode 100644
index 00000000..3bbf251b
--- /dev/null
+++ b/spec/scansion/muc_register.scs
@@ -0,0 +1,286 @@
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+[Client] Rosaline
+ jid: user3@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+Romeo sends:
+ <iq id='member1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Juliet's JID}" />
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq from='room@conference.localhost' id='member1' type='result'/>
+
+# Juliet connects, and joins the room
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Juliet retrieves the registration form
+
+Juliet sends:
+ <iq id='jw81b36f' to='room@conference.localhost' type='get'>
+ <query xmlns='jabber:iq:register'/>
+ </iq>
+
+Juliet receives:
+ <iq type='result' from='room@conference.localhost' id='jw81b36f'>
+ <query xmlns='jabber:iq:register'>
+ <x type='form' xmlns='jabber:x:data'>
+ <field type='hidden' var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#register</value>
+ </field>
+ <field type='text-single' label='Nickname' var='muc#register_roomnick'/>
+ </x>
+ </query>
+ </iq>
+
+Juliet sends:
+ <iq id='nv71va54' to='room@conference.localhost' type='set'>
+ <query xmlns='jabber:iq:register'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#register</value>
+ </field>
+ <field var='muc#register_roomnick'>
+ <value>Juliet</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' jid="${Juliet's full JID}" role='participant'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <iq type='result' from='room@conference.localhost' id='nv71va54'/>
+
+# Juliet discovers her reserved nick
+
+Juliet sends:
+ <iq id='getnick1' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/>
+ </iq>
+
+Juliet receives:
+ <iq type='result' from='room@conference.localhost' id='getnick1'>
+ <query xmlns='http://jabber.org/protocol/disco#info'>
+ <identity category='conference' name='Juliet' type='text'/>
+ </query>
+ </iq>
+
+# Juliet leaves the room:
+
+Juliet sends:
+ <presence type="unavailable" to="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='member' role='none'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='member' role='participant'/>
+ </x>
+ </presence>
+
+# Rosaline connect and tries to join the room as Juliet
+
+Rosaline connects
+
+Rosaline sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Rosaline receives:
+ <presence type='error' from='room@conference.localhost/Juliet'>
+ <error type='cancel'>
+ <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ <x xmlns='http://jabber.org/protocol/muc'/>
+ </presence>
+
+# In a heated moment, Juliet unregisters from the room
+
+Juliet sends:
+ <iq type='set' to='room@conference.localhost' id='unreg1'>
+ <query xmlns='jabber:iq:register'>
+ <remove/>
+ </query>
+ </iq>
+
+Juliet receives:
+ <iq type='result' from='room@conference.localhost' id='unreg1'/>
+
+# Rosaline attempts once more to sneak into the room, disguised as Juliet
+
+Rosaline sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Rosaline receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='owner' role='moderator'/>
+ </x>
+ </presence>
+
+Rosaline receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='none' jid="${Rosaline's full JID}" role='participant'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='none' jid="${Rosaline's full JID}" role='participant'/>
+ </x>
+ </presence>
+
+# On discovering the ruse, Romeo restores Juliet's nick and status within the room
+
+Romeo sends:
+ <iq id='member1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Juliet's JID}" nick='Juliet' />
+ </query>
+ </iq>
+
+# Rosaline is evicted from the room
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet' type='unavailable'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='307'/>
+ <item affiliation='none' role='none' jid="${Rosaline's full JID}">
+ <reason>This nickname is reserved</reason>
+ </item>
+ </x>
+ </presence>
+
+Romeo receives:
+ <iq type='result' id='member1' from='room@conference.localhost' />
+
+Rosaline receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='307'/>
+ <item affiliation='none' jid="${Rosaline's full JID}" role='none'>
+ <reason>This nickname is reserved</reason>
+ </item>
+ <status code='110'/>
+ </x>
+ </presence>
+
+# Rosaline, frustrated, attempts to get back into the room...
+
+Rosaline sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+# ...but once again, is denied
+
+Rosaline receives:
+ <presence type='error' from='room@conference.localhost/Juliet'>
+ <error type='cancel'>
+ <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ <x xmlns='http://jabber.org/protocol/muc'/>
+ </presence>
+
+# Juliet, however, quietly joins the room with success
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
diff --git a/spec/scansion/pubsub_advanced.scs b/spec/scansion/pubsub_advanced.scs
new file mode 100644
index 00000000..b7b96a22
--- /dev/null
+++ b/spec/scansion/pubsub_advanced.scs
@@ -0,0 +1,162 @@
+[Client] Balthasar
+ jid: admin@localhost
+ password: password
+
+[Client] Romeo
+ jid: romeo@localhost
+ password: password
+
+[Client] Juliet
+ jid: juliet@localhost
+ password: password
+
+---------
+
+Romeo connects
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='create1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <create node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="error" id='create1'>
+ <error type="auth">
+ <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </iq>
+
+Balthasar connects
+
+Balthasar sends:
+ <iq type='set' to='pubsub.localhost' id='create2'>
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ <create node='princely_musings'/>
+ </pubsub>
+ </iq>
+
+Balthasar receives:
+ <iq type="result" id='create2'/>
+
+Balthasar sends:
+ <iq type="set" to="pubsub.localhost" id='create3'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <create node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Balthasar receives:
+ <iq type="error" id='create3'>
+ <error type="cancel">
+ <conflict xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ </error>
+ </iq>
+
+Juliet connects
+
+Juliet sends:
+ <iq type="set" to="pubsub.localhost" id='sub1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <subscribe node="princely_musings" jid="${Romeo's full JID}"/>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <iq type="error" id='sub1'/>
+
+Juliet sends:
+ <iq type="set" to="pubsub.localhost" id='sub2'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <subscribe node="princely_musings" jid="${Juliet's full JID}"/>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <iq type="result" id='sub2'>
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ <subscription jid="${Juliet's full JID}" node='princely_musings' subscription='subscribed'/>
+ </pubsub>
+ </iq>
+
+Balthasar sends:
+ <iq type="get" id='aff1' to='pubsub.localhost'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <affiliations node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Balthasar receives:
+ <iq type="result" id='aff1' from='pubsub.localhost'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <affiliations node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Balthasar sends:
+ <iq type="set" id='aff2' to='pubsub.localhost'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <affiliations node="princely_musings">
+ <affiliation jid="${Romeo's JID}" affiliation="publisher"/>
+ </affiliations>
+ </pubsub>
+ </iq>
+
+Balthasar receives:
+ <iq type="result" id='aff2' from='pubsub.localhost'/>
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='pub1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="princely_musings">
+ <item id="current">
+ <entry xmlns="http://www.w3.org/2005/Atom">
+ <title>Soliloquy</title>
+ <summary>Lorem ipsum dolor sit amet</summary>
+ </entry>
+ </item>
+ </publish>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <message type="headline" from="pubsub.localhost">
+ <event xmlns="http://jabber.org/protocol/pubsub#event">
+ <items node="princely_musings">
+ <item id="current">
+ <entry xmlns="http://www.w3.org/2005/Atom">
+ <title>Soliloquy</title>
+ <summary>Lorem ipsum dolor sit amet</summary>
+ </entry>
+ </item>
+ </items>
+ </event>
+ </message>
+
+Romeo receives:
+ <iq type="result" id='pub1'/>
+
+Juliet sends:
+ <iq type="set" to="pubsub.localhost" id='unsub1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <unsubscribe node="princely_musings" jid="${Juliet's full JID}"/>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <iq type="result" id='unsub1'/>
+
+Balthasar sends:
+ <iq type="set" to="pubsub.localhost" id='del1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <delete node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Balthasar receives:
+ <iq type="result" from='pubsub.localhost' id='del1'/>
+
+Romeo disconnects
+
+// vim: syntax=xml:
diff --git a/spec/scansion/pubsub_basic.scs b/spec/scansion/pubsub_basic.scs
new file mode 100644
index 00000000..ec21afdc
--- /dev/null
+++ b/spec/scansion/pubsub_basic.scs
@@ -0,0 +1,102 @@
+[Client] Romeo
+ jid: admin@localhost
+ password: password
+
+// admin@localhost is assumed to have node creation privileges
+
+[Client] Juliet
+ jid: juliet@localhost
+ password: password
+
+---------
+
+Romeo connects
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='create1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <create node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id='create1'/>
+
+Juliet connects
+
+-- Juliet sends:
+-- <iq type="set" to="pubsub.localhost">
+-- <pubsub xmlns="http://jabber.org/protocol/pubsub">
+-- <subscribe node="princely_musings" jid="${Romeo's full JID}"/>
+-- </pubsub>
+-- </iq>
+--
+-- Juliet receives:
+-- <iq type="error"/>
+
+Juliet sends:
+ <iq type="set" to="pubsub.localhost" id='sub1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <subscribe node="princely_musings" jid="${Juliet's full JID}"/>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <iq type="result" id='sub1'/>
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='pub1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="princely_musings">
+ <item id="current">
+ <entry xmlns="http://www.w3.org/2005/Atom">
+ <title>Soliloquy</title>
+ <summary>Lorem ipsum dolor sit amet</summary>
+ </entry>
+ </item>
+ </publish>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id='pub1'/>
+
+Juliet receives:
+ <message type="headline" from="pubsub.localhost">
+ <event xmlns="http://jabber.org/protocol/pubsub#event">
+ <items node="princely_musings">
+ <item id="current">
+ <entry xmlns="http://www.w3.org/2005/Atom">
+ <title>Soliloquy</title>
+ <summary>Lorem ipsum dolor sit amet</summary>
+ </entry>
+ </item>
+ </items>
+ </event>
+ </message>
+
+Juliet sends:
+ <iq type="set" to="pubsub.localhost" id='unsub1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <unsubscribe node="princely_musings" jid="${Juliet's full JID}"/>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <iq type="result" id='unsub1'/>
+
+Juliet disconnects
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='del1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <delete node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id='del1'/>
+
+Romeo disconnects
+
+// vim: syntax=xml:
diff --git a/spec/scansion/pubsub_createdelete.scs b/spec/scansion/pubsub_createdelete.scs
new file mode 100644
index 00000000..f6fd1831
--- /dev/null
+++ b/spec/scansion/pubsub_createdelete.scs
@@ -0,0 +1,61 @@
+[Client] Romeo
+ jid: admin@localhost
+ password: password
+
+// admin@localhost is assumed to have node creation privileges
+
+---------
+
+Romeo connects
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='create1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <create node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id='create1'/>
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='create2'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <create node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="error" id='create2'>
+ <error type="cancel">
+ <conflict xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ </error>
+ </iq>
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='delete1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <delete node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id='delete1'/>
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='delete2'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <delete node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="error" id='delete2'>
+ <error type="cancel">
+ <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ </error>
+ </iq>
+
+Romeo disconnects
+
+// vim: syntax=xml: