aboutsummaryrefslogtreecommitdiffstats
path: root/spec/scansion
diff options
context:
space:
mode:
Diffstat (limited to 'spec/scansion')
-rw-r--r--spec/scansion/basic.scs18
-rw-r--r--spec/scansion/basic_message.scs174
-rw-r--r--spec/scansion/basic_roster.scs73
-rw-r--r--spec/scansion/issue505.scs79
-rw-r--r--spec/scansion/issue978-multi.scs111
-rw-r--r--spec/scansion/issue978.scs85
-rw-r--r--spec/scansion/muc_mediated_invite.scs76
-rw-r--r--spec/scansion/muc_password.scs143
-rw-r--r--spec/scansion/muc_register.scs481
-rw-r--r--spec/scansion/pep_nickname.scs72
-rw-r--r--spec/scansion/prosody.cfg.lua79
-rw-r--r--spec/scansion/pubsub_advanced.scs167
-rw-r--r--spec/scansion/pubsub_basic.scs104
-rw-r--r--spec/scansion/pubsub_createdelete.scs63
14 files changed, 1725 insertions, 0 deletions
diff --git a/spec/scansion/basic.scs b/spec/scansion/basic.scs
new file mode 100644
index 00000000..43c9831a
--- /dev/null
+++ b/spec/scansion/basic.scs
@@ -0,0 +1,18 @@
+# Basic login and initial presence
+
+[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..fb21c465
--- /dev/null
+++ b/spec/scansion/basic_message.scs
@@ -0,0 +1,174 @@
+# 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' stamp='{scansion:any}' />
+ </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..2e292083
--- /dev/null
+++ b/spec/scansion/basic_roster.scs
@@ -0,0 +1,73 @@
+# Basic roster test
+
+[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 ver='{scansion:any}' xmlns="jabber:iq:roster"/>
+ </iq>
+
+# Add nurse to roster
+
+Romeo sends:
+ <iq type="set" id="roster2">
+ <query xmlns="jabber:iq:roster">
+ <item jid='nurse@localhost'/>
+ </query>
+ </iq>
+
+# Receive the roster add result
+
+Romeo receives:
+ <iq type="result" id="roster2"/>
+
+# Receive the roster push
+
+Romeo receives:
+ <iq type="set" id="{scansion:any}">
+ <query xmlns='jabber:iq:roster' ver='{scansion:any}'>
+ <item jid='nurse@localhost' subscription='none'/>
+ </query>
+ </iq>
+
+Romeo sends:
+ <iq type="result" id="fixme"/>
+
+# Fetch the roster, it should include nurse now
+
+Romeo sends:
+ <iq type="get" id="roster3">
+ <query xmlns='jabber:iq:roster'/>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id="roster3">
+ <query xmlns='jabber:iq:roster' ver="{scansion:any}">
+ <item subscription='none' jid='nurse@localhost'/>
+ </query>
+ </iq>
+
+Romeo disconnects
diff --git a/spec/scansion/issue505.scs b/spec/scansion/issue505.scs
new file mode 100644
index 00000000..24fbeb72
--- /dev/null
+++ b/spec/scansion/issue505.scs
@@ -0,0 +1,79 @@
+# Issue 505: mod_muc doesn’t forward part statuses
+
+[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..d8f99228
--- /dev/null
+++ b/spec/scansion/issue978-multi.scs
@@ -0,0 +1,111 @@
+# Issue 978: MUC does not carry error into occupant leave status (multiple clients)
+
+[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' scansion:strict='false'>
+ <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' scansion:strict='false'>
+ <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..59db8335
--- /dev/null
+++ b/spec/scansion/issue978.scs
@@ -0,0 +1,85 @@
+# Issue 978: MUC does not carry error into occupant leave status (single client)
+[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..340aefc7
--- /dev/null
+++ b/spec/scansion/muc_mediated_invite.scs
@@ -0,0 +1,76 @@
+# MUC: Mediated invites
+
+[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.scs b/spec/scansion/muc_password.scs
new file mode 100644
index 00000000..82611183
--- /dev/null
+++ b/spec/scansion/muc_password.scs
@@ -0,0 +1,143 @@
+# MUC: Password-protected rooms
+
+[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" code="401">
+ <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_register.scs b/spec/scansion/muc_register.scs
new file mode 100644
index 00000000..1cd8e36e
--- /dev/null
+++ b/spec/scansion/muc_register.scs
@@ -0,0 +1,481 @@
+# MUC: Room registration and reserved nicknames
+
+[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' node='x-roomuser-item'>
+ <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" />
+
+# Romeo checks whether he has reserved his own nick yet
+
+Romeo sends:
+ <iq id='getnick1' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/>
+ </iq>
+
+# But no nick is returned, as he hasn't registered yet!
+
+Romeo receives:
+ <iq type='result' from='room@conference.localhost' id='getnick1'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item' scansion:strict='true' />
+ </iq>
+
+# Romeo updates his own registration
+
+Romeo sends:
+ <iq id='jw81b36f' to='room@conference.localhost' type='get'>
+ <query xmlns='jabber:iq:register'/>
+ </iq>
+
+Romeo 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>
+
+Romeo 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>Romeo</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='owner' jid="${Romeo's full JID}" role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <iq type='result' from='room@conference.localhost' id='nv71va54'/>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item role='moderator' xmlns='http://jabber.org/protocol/muc#user' affiliation='owner'/>
+ </x>
+ </presence>
+
+# Romeo discovers his reserved nick
+
+Romeo sends:
+ <iq id='getnick1' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' from='room@conference.localhost' id='getnick1'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'>
+ <identity category='conference' name='Romeo' type='text'/>
+ </query>
+ </iq>
+
+# To check the status of the room is as expected, Romeo requests the member list
+
+Romeo sends:
+ <iq id='member3' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member'/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq from='room@conference.localhost' type='result' id='member3'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item jid="${Juliet's JID}" affiliation='member' nick='Juliet'/>
+ </query>
+ </iq>
+
+Juliet sends:
+ <presence type="unavailable" to="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet' type='unavailable' />
+
+Romeo receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet' />
+
+# Rosaline joins as herself
+
+Rosaline sends:
+ <presence to="room@conference.localhost/Rosaline">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Rosaline receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Rosaline receives:
+ <presence from="room@conference.localhost/Rosaline" />
+
+Rosaline receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Rosaline'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Rosaline's full JID}" affiliation='none' role='participant'/>
+ </x>
+ </presence>
+
+# Rosaline tries to register her own nickname, but unaffiliated
+# registration is disabled by default
+
+Rosaline sends:
+ <iq id='reg990' to='room@conference.localhost' type='get'>
+ <query xmlns='jabber:iq:register'/>
+ </iq>
+
+Rosaline receives:
+ <iq type='error' from='room@conference.localhost' id='reg990'>
+ <error type='auth'>
+ <registration-required xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </iq>
+
+Rosaline sends:
+ <iq id='reg991' 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>Romeo</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Rosaline receives:
+ <iq id='reg991' type='error'>
+ <error type='auth'>
+ <registration-required xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </iq>
+
+# Romeo reserves her nickname for her
+
+Romeo sends:
+ <iq id='member2' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Rosaline's JID}" nick='Rosaline' />
+ </query>
+ </iq>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Rosaline'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' role='participant' jid="${Rosaline's full JID}">
+ <actor jid="${Romeo's full JID}" nick='Romeo'/>
+ </item>
+ </x>
+ </presence>
+
+Romeo receives:
+ <iq type='result' id='member2' from='room@conference.localhost' />
+
+Rosaline receives:
+ <presence from='room@conference.localhost/Rosaline'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' role='participant' jid="${Rosaline's full JID}">
+ <actor nick='Romeo' />
+ </item>
+ <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
+ </x>
+ </presence>
diff --git a/spec/scansion/pep_nickname.scs b/spec/scansion/pep_nickname.scs
new file mode 100644
index 00000000..f958ec75
--- /dev/null
+++ b/spec/scansion/pep_nickname.scs
@@ -0,0 +1,72 @@
+# Publishing a nickname in PEP and receiving a notification
+
+[Client] Romeo
+ jid: romeo@localhost/nJi7BeTR
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq id="4" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="http://jabber.org/protocol/nick">
+ <item id="current">
+ <nickname xmlns="http://jabber.org/protocol/nick"/>
+ </item>
+ </publish>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq id="4" to="romeo@localhost/nJi7BeTR" type="result">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="http://jabber.org/protocol/nick">
+ <item id="current"/>
+ </publish>
+ </pubsub>
+ </iq>
+
+Romeo sends:
+ <presence>
+ <c xmlns="http://jabber.org/protocol/caps" hash="sha-1" node="http://code.matthewwild.co.uk/clix/" ver="jC32N+FhQoLrZ7nNQtZK3aqR0Fk="/>
+ </presence>
+
+Romeo receives:
+ <iq id="disco" to="romeo@localhost/nJi7BeTR" from="romeo@localhost" type="get">
+ <query xmlns="http://jabber.org/protocol/disco#info" node="http://code.matthewwild.co.uk/clix/#jC32N+FhQoLrZ7nNQtZK3aqR0Fk="/>
+ </iq>
+
+Romeo receives:
+ <presence from="romeo@localhost/nJi7BeTR">
+ <c xmlns="http://jabber.org/protocol/caps" hash="sha-1" node="http://code.matthewwild.co.uk/clix/" ver="jC32N+FhQoLrZ7nNQtZK3aqR0Fk="/>
+ </presence>
+
+Romeo sends:
+ <iq id="disco" type="result" to="romeo@localhost">
+ <query xmlns="http://jabber.org/protocol/disco#info" node="http://code.matthewwild.co.uk/clix/#jC32N+FhQoLrZ7nNQtZK3aqR0Fk=">
+ <identity type="console" name="clix" category="client"/>
+ <feature var="http://jabber.org/protocol/disco#items"/>
+ <feature var="http://jabber.org/protocol/disco#info"/>
+ <feature var="http://jabber.org/protocol/caps"/>
+ <feature var="http://jabber.org/protocol/nick+notify"/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <message type="headline" from="romeo@localhost" to="romeo@localhost/nJi7BeTR">
+ <event xmlns="http://jabber.org/protocol/pubsub#event">
+ <items node="http://jabber.org/protocol/nick">
+ <item id="current">
+ <nickname xmlns="http://jabber.org/protocol/nick"/>
+ </item>
+ </items>
+ </event>
+ </message>
+
+Romeo sends:
+ <presence type="unavailable"/>
+
+Romeo disconnects
+
diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua
new file mode 100644
index 00000000..b30d5218
--- /dev/null
+++ b/spec/scansion/prosody.cfg.lua
@@ -0,0 +1,79 @@
+--luacheck: ignore
+
+admins = { "admin@localhost" }
+
+use_libevent = true
+
+modules_enabled = {
+ -- Generally required
+ "roster"; -- Allow users to have a roster. Recommended ;)
+ "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
+ "tls"; -- Add support for secure TLS on c2s/s2s connections
+ "dialback"; -- s2s dialback support
+ "disco"; -- Service discovery
+
+ -- Not essential, but recommended
+ "carbons"; -- Keep multiple clients in sync
+ "pep"; -- Enables users to publish their mood, activity, playing music and more
+ "private"; -- Private XML storage (for room bookmarks, etc.)
+ "blocklist"; -- Allow users to block communications with other users
+ "vcard"; -- Allow users to set vCards
+
+ -- Nice to have
+ "version"; -- Replies to server version requests
+ "uptime"; -- Report how long server has been running
+ "time"; -- Let others know the time here on this server
+ "ping"; -- Replies to XMPP pings with pongs
+ "register"; -- Allow users to register on this server using a client and change passwords
+ --"mam"; -- Store messages in an archive and allow users to access it
+
+ -- HTTP modules
+ --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
+ --"websocket"; -- XMPP over WebSockets
+ --"http_files"; -- Serve static files from a directory over HTTP
+
+ -- Other specific functionality
+ --"limits"; -- Enable bandwidth limiting for XMPP connections
+ --"groups"; -- Shared roster support
+ --"server_contact_info"; -- Publish contact information for this service
+ --"announce"; -- Send announcement to all online users
+ --"welcome"; -- Welcome users who register accounts
+ --"watchregistrations"; -- Alert admins of registrations
+ --"motd"; -- Send a message to users when they log in
+ --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
+ --"proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use
+}
+
+certificate = "certs"
+
+allow_registration = false
+
+c2s_require_encryption = false
+allow_unencrypted_plain_auth = true
+
+authentication = "insecure"
+insecure_open_authentication = "Yes please, I know what I'm doing!"
+
+storage = "memory"
+
+
+-- For the "sql" backend, you can uncomment *one* of the below to configure:
+--sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename.
+--sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
+--sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
+
+
+-- Logging configuration
+-- For advanced logging see https://prosody.im/doc/logging
+log = "*console"
+
+daemonize = true
+pidfile = "prosody.pid"
+
+VirtualHost "localhost"
+
+Component "conference.localhost" "muc"
+ storage = "memory"
+
+Component "pubsub.localhost" "pubsub"
+ storage = "memory"
diff --git a/spec/scansion/pubsub_advanced.scs b/spec/scansion/pubsub_advanced.scs
new file mode 100644
index 00000000..c873486e
--- /dev/null
+++ b/spec/scansion/pubsub_advanced.scs
@@ -0,0 +1,167 @@
+# Pubsub: Node creation, publish, subscribe, affiliations and delete
+
+[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">
+ <affiliation affiliation='owner' jid='admin@localhost' xmlns='http://jabber.org/protocol/pubsub#owner'/>
+ </affiliations>
+ </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 affiliation='owner' jid='admin@localhost' xmlns='http://jabber.org/protocol/pubsub#owner'/>
+ <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..d983ff66
--- /dev/null
+++ b/spec/scansion/pubsub_basic.scs
@@ -0,0 +1,104 @@
+# Pubsub: Basic support
+
+[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..a44695e7
--- /dev/null
+++ b/spec/scansion/pubsub_createdelete.scs
@@ -0,0 +1,63 @@
+# Pubsub: Create and delete
+
+[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: