aboutsummaryrefslogtreecommitdiffstats
path: root/spec/scansion
diff options
context:
space:
mode:
Diffstat (limited to 'spec/scansion')
-rw-r--r--spec/scansion/basic_message.scs6
-rw-r--r--spec/scansion/blocking.scs8
-rw-r--r--spec/scansion/extdisco.scs71
-rw-r--r--spec/scansion/http_upload.scs83
-rw-r--r--spec/scansion/issue1121.scs75
-rw-r--r--spec/scansion/keep_full_sub_req.scs58
-rw-r--r--spec/scansion/lastactivity.scs45
-rw-r--r--spec/scansion/mam_extended.scs126
-rw-r--r--spec/scansion/mam_prefs_prep.scs2
-rw-r--r--spec/scansion/muc_create_destroy.scs316
-rw-r--r--spec/scansion/muc_members_only_change.scs2
-rw-r--r--spec/scansion/muc_nickname_change.scs127
-rw-r--r--spec/scansion/muc_nickname_robotface.scs46
-rw-r--r--spec/scansion/muc_password.scs2
-rw-r--r--spec/scansion/muc_presence_probe.scs178
-rw-r--r--spec/scansion/muc_register.scs16
-rw-r--r--spec/scansion/muc_show_offline.scs542
-rw-r--r--spec/scansion/muc_subject_issue_667.scs129
-rw-r--r--spec/scansion/pep_nickname.scs12
-rw-r--r--spec/scansion/pep_publish_subscribe.scs6
-rw-r--r--spec/scansion/pep_pubsub_max.scs47
-rw-r--r--spec/scansion/presence_preapproval.scs74
-rw-r--r--spec/scansion/prosody.cfg.lua79
-rw-r--r--spec/scansion/pubsub_advanced.scs8
-rw-r--r--spec/scansion/pubsub_basic.scs4
-rw-r--r--spec/scansion/pubsub_config.scs23
-rw-r--r--spec/scansion/pubsub_max_items.scs210
-rw-r--r--spec/scansion/pubsub_multi_items.scs26
-rw-r--r--spec/scansion/pubsub_preconditions.scs253
-rw-r--r--spec/scansion/pubsub_resend_on_sub.scs152
-rw-r--r--spec/scansion/server_contact_info.scs81
-rw-r--r--spec/scansion/tombstones.scs38
-rw-r--r--spec/scansion/uptime.scs21
-rw-r--r--spec/scansion/version.scs27
34 files changed, 2846 insertions, 47 deletions
diff --git a/spec/scansion/basic_message.scs b/spec/scansion/basic_message.scs
index fb21c465..1258dbf5 100644
--- a/spec/scansion/basic_message.scs
+++ b/spec/scansion/basic_message.scs
@@ -79,7 +79,7 @@ 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>
+ </message>
# Romeo sends another bare-JID message, it should be delivered
# instantly to Juliet's phone
@@ -92,7 +92,7 @@ Romeo sends:
Juliet's phone receives:
<message from="${Romeo's full JID}" type="chat">
<body>Oh, hi!</body>
- </message>
+ </message>
# Juliet's laptop goes online, but with a negative priority
@@ -122,7 +122,7 @@ Romeo sends:
Juliet's phone receives:
<message from="${Romeo's full JID}" type="chat">
<body>How are you?</body>
- </message>
+ </message>
# Romeo sends direct to Juliet's full JID, and she should receive it
diff --git a/spec/scansion/blocking.scs b/spec/scansion/blocking.scs
index 6a9f199e..5bb5a41b 100644
--- a/spec/scansion/blocking.scs
+++ b/spec/scansion/blocking.scs
@@ -145,16 +145,16 @@ Juliet receives:
</message>
# Bye
-Juliet disconnects
-
Juliet sends:
<presence type="unavailable"/>
+Juliet disconnects
+
Romeo receives:
<presence from="${Juliet's full JID}" to="${Romeo's JID}" type="unavailable"/>
-Romeo disconnects
-
Romeo sends:
<presence type="unavailable"/>
+Romeo disconnects
+
diff --git a/spec/scansion/extdisco.scs b/spec/scansion/extdisco.scs
new file mode 100644
index 00000000..f0781dc5
--- /dev/null
+++ b/spec/scansion/extdisco.scs
@@ -0,0 +1,71 @@
+# XEP-0215: External Service Discovery
+
+[Client] Romeo
+ password: password
+ jid: user@localhost/mFquWxSr
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq type='get' xml:lang='sv' id='lx2' to='localhost'>
+ <services xmlns='urn:xmpp:extdisco:2'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' id='lx2' from='localhost'>
+ <services xmlns='urn:xmpp:extdisco:2'>
+ <service host='default.example' transport='udp' port='9876' type='stun'/>
+ <service port='9876' type='turn' restricted='1' password='yHYYBDN7M3mdlug0LTdJbW0GvvQ=' transport='udp' host='default.example' username='1219525744'/>
+ <service port='9876' type='turn' restricted='1' password='1Uc6QfrDhIlbK97rGCUQ/cUICxs=' transport='udp' host='default.example' username='1219525744'/>
+ <service port='2121' type='ftp' restricted='1' password='password' transport='tcp' host='default.example' username='john'/>
+ <service port='21' type='ftp' restricted='1' password='password' transport='tcp' host='ftp.example.com' username='john'/>
+ </services>
+ </iq>
+
+Romeo sends:
+ <iq type='get' xml:lang='sv' id='lx3' to='localhost'>
+ <services xmlns='urn:xmpp:extdisco:2' type='ftp'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' id='lx3' from='localhost'>
+ <services xmlns='urn:xmpp:extdisco:2'>
+ <service port='2121' type='ftp' restricted='1' password='password' transport='tcp' host='default.example' username='john'/>
+ <service port='21' type='ftp' restricted='1' password='password' transport='tcp' host='ftp.example.com' username='john'/>
+ </services>
+ </iq>
+
+Romeo sends:
+ <iq type='get' xml:lang='sv' id='lx4' to='localhost'>
+ <credentials xmlns='urn:xmpp:extdisco:2'>
+ <service host='default.example' type='turn'/>
+ </credentials>
+ </iq>
+
+Romeo receives:
+ <iq type='result' id='lx4' from='localhost'>
+ <credentials xmlns='urn:xmpp:extdisco:2'>
+ <service port='9876' type='turn' restricted='1' password='yHYYBDN7M3mdlug0LTdJbW0GvvQ=' transport='udp' host='default.example' username='1219525744'/>
+ <service port='9876' type='turn' restricted='1' password='1Uc6QfrDhIlbK97rGCUQ/cUICxs=' transport='udp' host='default.example' username='1219525744'/>
+ </credentials>
+ </iq>
+
+Romeo sends:
+ <iq type='get' xml:lang='sv' id='lx5' to='localhost'>
+ <credentials xmlns='urn:xmpp:extdisco:2'>
+ <service host='default.example' />
+ </credentials>
+ </iq>
+
+Romeo receives:
+ <iq type='error' id='lx5' from='localhost'>
+ <error type='modify'>
+ <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </iq>
+
+Romeo disconnects
+
+# recording ended on 2020-07-18T16:47:57Z
diff --git a/spec/scansion/http_upload.scs b/spec/scansion/http_upload.scs
new file mode 100644
index 00000000..b3031bac
--- /dev/null
+++ b/spec/scansion/http_upload.scs
@@ -0,0 +1,83 @@
+# XEP-0363 HTTP Upload with mod_http_file_share
+
+[Client] Romeo
+ password: password
+ jid: filesharingenthusiast@localhost/krxLaE3s
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq to='upload.localhost' type='get' id='932c02fe-4461-4ad4-9c85-54863294b4dc' xml:lang='en'>
+ <request content-type='text/plain' filename='verysmall.dat' xmlns='urn:xmpp:http:upload:0' size='5'/>
+ </iq>
+
+Romeo receives:
+ <iq id='932c02fe-4461-4ad4-9c85-54863294b4dc' from='upload.localhost' type='result'>
+ <slot xmlns='urn:xmpp:http:upload:0'>
+ <get url='{scansion:any}'/>
+ <put url='{scansion:any}'>
+ <header name='Authorization'></header>
+ </put>
+ </slot>
+ </iq>
+
+Romeo sends:
+ <iq to='upload.localhost' type='get' id='46ca64f3-518e-42bd-8e2f-4ab2f6d2224f' xml:lang='en'>
+ <request content-type='text/plain' filename='toolarge.dat' xmlns='urn:xmpp:http:upload:0' size='10000000000'/>
+ </iq>
+
+Romeo receives:
+ <iq id='46ca64f3-518e-42bd-8e2f-4ab2f6d2224f' from='upload.localhost' type='error'>
+ <error type='modify'>
+ <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>File too large</text>
+ <file-too-large xmlns='urn:xmpp:http:upload:0'>
+ <max-file-size>10000000</max-file-size>
+ </file-too-large>
+ </error>
+ </iq>
+
+Romeo sends:
+ <iq to='upload.localhost' type='get' id='497c20dd-dda2-4feb-8199-7086e203de46' xml:lang='en'>
+ <request content-type='text/plain' filename='negative.dat' xmlns='urn:xmpp:http:upload:0' size='-1000'/>
+ </iq>
+
+Romeo receives:
+ <iq id='497c20dd-dda2-4feb-8199-7086e203de46' from='upload.localhost' type='error'>
+ <error type='modify'>
+ <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>File size must be positive integer</text>
+ </error>
+ </iq>
+
+Romeo sends:
+ <iq to='upload.localhost' type='get' id='ac56d83f-a627-4732-8399-60492d1210b6' xml:lang='en'>
+ <request content-type='text/plain' filename='invalid/filename.dat' xmlns='urn:xmpp:http:upload:0' size='1000'/>
+ </iq>
+
+Romeo receives:
+ <iq id='ac56d83f-a627-4732-8399-60492d1210b6' from='upload.localhost' type='error'>
+ <error type='modify'>
+ <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Invalid filename</text>
+ </error>
+ </iq>
+
+Romeo sends:
+ <iq to='upload.localhost' type='get' id='1401d3b5-7973-486f-85b3-3e63d13c7f0e' xml:lang='en'>
+ <request content-type='application/x-executable' filename='evil.exe' xmlns='urn:xmpp:http:upload:0' size='1000'/>
+ </iq>
+
+Romeo receives:
+ <iq id='1401d3b5-7973-486f-85b3-3e63d13c7f0e' from='upload.localhost' type='error'>
+ <error type='modify'>
+ <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>File type not allowed</text>
+ </error>
+ </iq>
+
+Romeo disconnects
+
+# recording ended on 2021-01-27T22:10:46Z
diff --git a/spec/scansion/issue1121.scs b/spec/scansion/issue1121.scs
new file mode 100644
index 00000000..68d728b9
--- /dev/null
+++ b/spec/scansion/issue1121.scs
@@ -0,0 +1,75 @@
+# When removing roster contact, Prosody should send directed "unavailable" presence but sends global unavailable presence
+
+[Client] Romeo
+ jid: romeo@localhost
+ password: password
+
+[Client] Juliet
+ jid: juliet@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends
+ <presence/>
+
+Romeo receives
+ <presence from="${Romeo's full JID}"/>
+
+Juliet connects
+
+Juliet sends
+ <presence/>
+
+Juliet receives
+ <presence from="${Juliet's full JID}"/>
+
+Romeo sends
+ <presence to="juliet@localhost" type="subscribe"/>
+
+Romeo receives
+ <presence from="juliet@localhost" to="romeo@localhost"/>
+
+Juliet receives
+ <presence from="romeo@localhost" to="juliet@localhost" type="subscribe"/>
+
+Juliet sends
+ <presence to="romeo@localhost" type="subscribed"/>
+
+Romeo receives
+ <presence from="${Juliet's full JID}" to="romeo@localhost"/>
+
+Juliet sends
+ <presence to="romeo@localhost" type="subscribe"/>
+
+Juliet receives
+ <presence from="romeo@localhost" to="juliet@localhost"/>
+
+Romeo receives
+ <presence from="juliet@localhost" to="romeo@localhost" type="subscribe"/>
+
+Romeo sends
+ <presence to="juliet@localhost" type="subscribed"/>
+
+Juliet receives
+ <presence from="${Romeo's full JID}" to="juliet@localhost"/>
+
+Romeo receives
+ <presence from="${Juliet's full JID}" to="romeo@localhost"/>
+
+Juliet sends
+ <iq type="set" id="iq1">
+ <query xmlns="jabber:iq:roster">
+ <item jid="romeo@localhost" subscription="remove"/>
+ </query>
+ </iq>
+
+Juliet receives
+ <iq type="result" id="iq1"/>
+
+Romeo receives
+ <presence from="${Juliet's full JID}" to="romeo@localhost" type="unavailable"/>
+
+Romeo disconnects
diff --git a/spec/scansion/keep_full_sub_req.scs b/spec/scansion/keep_full_sub_req.scs
new file mode 100644
index 00000000..41ffec0d
--- /dev/null
+++ b/spec/scansion/keep_full_sub_req.scs
@@ -0,0 +1,58 @@
+# server MUST keep a record of the complete presence stanza comprising the subscription request (#689)
+
+[Client] Alice
+ jid: pars-a@localhost
+ password: password
+
+[Client] Bob
+ jid: pars-b@localhost
+ password: password
+
+[Client] Bob's phone
+ jid: pars-b@localhost/phone
+ password: password
+
+---------
+
+Alice connects
+
+Alice sends:
+ <presence to="${Bob's JID}" type="subscribe">
+ <preauth xmlns="urn:xmpp:pars:0" token="1tMFqYDdKhfe2pwp" />
+ </presence>
+
+Alice disconnects
+
+Bob connects
+
+Bob sends:
+ <presence/>
+
+Bob receives:
+ <presence from="${Bob's full JID}"/>
+
+Bob receives:
+ <presence from="${Alice's JID}" type="subscribe">
+ <preauth xmlns="urn:xmpp:pars:0" token="1tMFqYDdKhfe2pwp" />
+ </presence>
+
+Bob disconnects
+
+# Works if they reconnect too
+
+Bob's phone connects
+
+Bob's phone sends:
+ <presence/>
+
+Bob's phone receives:
+ <presence from="${Bob's phone's full JID}"/>
+
+
+Bob's phone receives:
+ <presence from="${Alice's JID}" type="subscribe">
+ <preauth xmlns="urn:xmpp:pars:0" token="1tMFqYDdKhfe2pwp" />
+ </presence>
+
+Bob's phone disconnects
+
diff --git a/spec/scansion/lastactivity.scs b/spec/scansion/lastactivity.scs
new file mode 100644
index 00000000..44f4e516
--- /dev/null
+++ b/spec/scansion/lastactivity.scs
@@ -0,0 +1,45 @@
+# XEP-0012: Last Activity / mod_lastactivity
+
+[Client] Romeo
+ jid: romeo@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence>
+ <status>Hello</status>
+ </presence>
+
+Romeo receives:
+ <presence from="${Romeo's full JID}">
+ <status>Hello</status>
+ </presence>
+
+Romeo sends:
+ <presence type="unavailable">
+ <status>Goodbye</status>
+ </presence>
+
+Romeo receives:
+ <presence from="${Romeo's full JID}" type="unavailable">
+ <status>Goodbye</status>
+ </presence>
+
+# mod_lastlog saves time + status message from the last unavailable presence
+
+Romeo sends:
+ <iq id='a' type='get'>
+ <query xmlns='jabber:iq:last'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' id='a'>
+ <query xmlns='jabber:iq:last' seconds='0'>Goodbye</query>
+ </iq>
+
+Romeo disconnects
+
+# recording ended on 2020-04-20T14:39:47Z
diff --git a/spec/scansion/mam_extended.scs b/spec/scansion/mam_extended.scs
new file mode 100644
index 00000000..2c6840df
--- /dev/null
+++ b/spec/scansion/mam_extended.scs
@@ -0,0 +1,126 @@
+# MAM 0.7.x Extended features
+
+[Client] Romeo
+ jid: extmamtester@localhost
+ password: password
+
+---------
+
+Romeo connects
+
+# Enable MAM so we can save some messages
+Romeo sends:
+ <iq type="set" id="enablemam">
+ <prefs xmlns="urn:xmpp:mam:2" default="always">
+ <always/>
+ <never/>
+ </prefs>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id="enablemam">
+ <prefs xmlns="urn:xmpp:mam:2" default="always">
+ <always/>
+ <never/>
+ </prefs>
+ </iq>
+
+# Some messages to look for later
+Romeo sends:
+ <message to="someone@localhost" type="chat" id="chat01">
+ <body>Hello</body>
+ </message>
+
+Romeo sends:
+ <message to="someone@localhost" type="chat" id="chat02">
+ <body>U there?</body>
+ </message>
+
+# Metadata
+Romeo sends:
+ <iq type="get" id="mamextmeta">
+ <metadata xmlns="urn:xmpp:mam:2"/>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id="mamextmeta">
+ <metadata xmlns="urn:xmpp:mam:2">
+ <start timestamp="2008-08-22T21:09:04Z" xmlns="urn:xmpp:mam:2" id="{scansion:any}"/>
+ <end timestamp="2008-08-22T21:09:04Z" xmlns="urn:xmpp:mam:2" id="{scansion:any}"/>
+ </metadata>
+ </iq>
+
+Romeo sends:
+ <iq type="set" id="mamquery1">
+ <query xmlns="urn:xmpp:mam:2" queryid="q1"/>
+ </iq>
+
+Romeo receives:
+ <message to="${Romeo's full JID}">
+ <result xmlns="urn:xmpp:mam:2" queryid="q1" id="{scansion:any}">
+ <forwarded xmlns="urn:xmpp:forward:0">
+ <delay stamp="2008-08-22T21:09:04Z" xmlns="urn:xmpp:delay"/>
+ <message to="someone@localhost" xmlns="jabber:client" type="chat" xml:lang="en" id="chat01" from="${Romeo's full JID}">
+ <body>Hello</body>
+ </message>
+ </forwarded>
+ </result>
+ </message>
+
+Romeo receives:
+ <message to="${Romeo's full JID}">
+ <result xmlns="urn:xmpp:mam:2" queryid="q1" id="{scansion:any}">
+ <forwarded xmlns="urn:xmpp:forward:0">
+ <delay stamp="2008-08-22T21:09:04Z" xmlns="urn:xmpp:delay"/>
+ <message to="someone@localhost" xmlns="jabber:client" type="chat" xml:lang="en" id="chat02" from="${Romeo's full JID}">
+ <body>U there?</body>
+ </message>
+ </forwarded>
+ </result>
+ </message>
+
+# FIXME unstable tag order from util.rsm
+Romeo receives:
+ <iq type="result" id="mamquery1" to="${Romeo's full JID}">
+ <fin xmlns="urn:xmpp:mam:2" complete="true" scansion:strict="false">
+ </fin>
+ </iq>
+
+# Get results in reverse order
+Romeo sends:
+ <iq type="set" id="mamquery2">
+ <query xmlns="urn:xmpp:mam:2" queryid="q1">
+ <flip-page/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <message to="${Romeo's full JID}">
+ <result xmlns="urn:xmpp:mam:2" queryid="q1" id="{scansion:any}">
+ <forwarded xmlns="urn:xmpp:forward:0">
+ <delay stamp="2008-08-22T21:09:04Z" xmlns="urn:xmpp:delay"/>
+ <message to="someone@localhost" xmlns="jabber:client" type="chat" xml:lang="en" id="chat02" from="${Romeo's full JID}">
+ <body>U there?</body>
+ </message>
+ </forwarded>
+ </result>
+ </message>
+
+Romeo receives:
+ <message to="${Romeo's full JID}">
+ <result xmlns="urn:xmpp:mam:2" queryid="q1" id="{scansion:any}">
+ <forwarded xmlns="urn:xmpp:forward:0">
+ <delay stamp="2008-08-22T21:09:04Z" xmlns="urn:xmpp:delay"/>
+ <message to="someone@localhost" xmlns="jabber:client" type="chat" xml:lang="en" id="chat01" from="${Romeo's full JID}">
+ <body>Hello</body>
+ </message>
+ </forwarded>
+ </result>
+ </message>
+
+# FIXME unstable tag order from util.rsm
+Romeo receives:
+ <iq type="result" id="mamquery2" to="${Romeo's full JID}">
+ <fin xmlns="urn:xmpp:mam:2" complete="true" scansion:strict="false">
+ </fin>
+ </iq>
diff --git a/spec/scansion/mam_prefs_prep.scs b/spec/scansion/mam_prefs_prep.scs
index 9589ec65..1175a6de 100644
--- a/spec/scansion/mam_prefs_prep.scs
+++ b/spec/scansion/mam_prefs_prep.scs
@@ -1,4 +1,4 @@
-# mod_mam shold apply JIDprep in prefs
+# mod_mam should apply JIDprep in prefs
[Client] Romeo
jid: romeo@localhost
diff --git a/spec/scansion/muc_create_destroy.scs b/spec/scansion/muc_create_destroy.scs
new file mode 100644
index 00000000..789d4c41
--- /dev/null
+++ b/spec/scansion/muc_create_destroy.scs
@@ -0,0 +1,316 @@
+# MUC creation, basic messages and destruction
+
+[Client] Romeo
+ jid: romeo@localhost/mK0dD6Ha
+ password: password
+
+[Client] Juliet
+ jid: juliet@localhost/lVwkim_k
+ password: password
+
+[Client] Admin
+ jid: admin@localhost/DfNgg9VE
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="garden@conference.localhost/romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from="garden@conference.localhost/romeo">
+ <x xmlns="vcard-temp:x:update">
+ <photo/>
+ </x>
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <status code="201"/>
+ <item affiliation="owner" jid="${Romeo's full JID}" role="moderator"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message from="garden@conference.localhost" type="groupchat">
+ <subject/>
+ </message>
+
+Romeo sends:
+ <iq to="garden@conference.localhost" id="lx3" type="set">
+ <query xmlns="http://jabber.org/protocol/muc#owner">
+ <x type="submit" xmlns="jabber:x:data"/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="lx3" type="result" from="garden@conference.localhost"/>
+
+Juliet connects
+
+Romeo sends:
+ <message to="garden@conference.localhost" type="groupchat" id="rm1">
+ <body>Where are thou my Juliet?</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" from="garden@conference.localhost/romeo" id="rm1">
+ <body>Where are thou my Juliet?</body>
+ </message>
+
+Juliet sends:
+ <presence to="garden@conference.localhost/juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="garden@conference.localhost/romeo">
+ <x xmlns="vcard-temp:x:update">
+ <photo/>
+ </x>
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <item affiliation="owner" role="moderator"/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <presence from="garden@conference.localhost/juliet">
+ <x xmlns="vcard-temp:x:update">
+ <photo/>
+ </x>
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <item affiliation="none" jid="${Juliet's full JID}" role="participant"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <message from="garden@conference.localhost/romeo" id="rm1" type="groupchat">
+ <body>Where are thou my Juliet?</body>
+ <delay stamp="{scansion:any}" xmlns="urn:xmpp:delay" from="garden@conference.localhost"/>
+ </message>
+
+Juliet receives:
+ <message from="garden@conference.localhost" type="groupchat">
+ <subject/>
+ </message>
+
+Romeo receives:
+ <presence from="garden@conference.localhost/juliet">
+ <x xmlns="vcard-temp:x:update">
+ <photo/>
+ </x>
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <item affiliation="none" jid="${Juliet's full JID}" role="participant"/>
+ </x>
+ </presence>
+
+Juliet sends:
+ <message to="garden@conference.localhost" type="groupchat" id="jm1">
+ <body>/me jumps out from behind a tree</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="jm1" from="garden@conference.localhost/juliet">
+ <body>/me jumps out from behind a tree</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="jm1" from="garden@conference.localhost/juliet">
+ <body>/me jumps out from behind a tree</body>
+ </message>
+
+Juliet sends:
+ <message to="garden@conference.localhost" type="groupchat" id="jm2">
+ <body>Here I am!</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="jm2" from="garden@conference.localhost/juliet">
+ <body>Here I am!</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="jm2" from="garden@conference.localhost/juliet">
+ <body>Here I am!</body>
+ </message>
+
+Romeo sends:
+ <message to="garden@conference.localhost" type="groupchat" id="rm2">
+ <body>What is this place?</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="rm2" from="garden@conference.localhost/romeo">
+ <body>What is this place?</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="rm2" from="garden@conference.localhost/romeo">
+ <body>What is this place?</body>
+ </message>
+
+Juliet sends:
+ <message to="garden@conference.localhost" type="groupchat" id="jm3">
+ <body>I think we&apos;re in a script!</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="jm3" from="garden@conference.localhost/juliet">
+ <body>I think we&apos;re in a script!</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="jm3" from="garden@conference.localhost/juliet">
+ <body>I think we&apos;re in a script!</body>
+ </message>
+
+Romeo sends:
+ <message to="garden@conference.localhost" type="groupchat" id="rm3">
+ <body>Oh no! Does that mean our love is not real?!</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="rm3" from="garden@conference.localhost/romeo">
+ <body>Oh no! Does that mean our love is not real?!</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="rm3" from="garden@conference.localhost/romeo">
+ <body>Oh no! Does that mean our love is not real?!</body>
+ </message>
+
+Juliet sends:
+ <message to="garden@conference.localhost" type="groupchat" id="jm4">
+ <body>I refuse to accept this! Let&apos;s burn this place to the ground!</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="jm4" from="garden@conference.localhost/juliet">
+ <body>I refuse to accept this! Let&apos;s burn this place to the ground!</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="jm4" from="garden@conference.localhost/juliet">
+ <body>I refuse to accept this! Let&apos;s burn this place to the ground!</body>
+ </message>
+
+Romeo sends:
+ <message to="garden@conference.localhost" type="groupchat" id="rm4">
+ <body>Yes!</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" id="rm4" from="garden@conference.localhost/romeo">
+ <body>Yes!</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" id="rm4" from="garden@conference.localhost/romeo">
+ <body>Yes!</body>
+ </message>
+
+Romeo sends:
+ <iq to="garden@conference.localhost" id="lx4" type="set">
+ <query xmlns="http://jabber.org/protocol/muc#owner">
+ <destroy>
+ <reason>We refuse to live in this fantasy!</reason>
+ </destroy>
+ </query>
+ </iq>
+
+Juliet receives:
+ <presence from="garden@conference.localhost/juliet" type="unavailable">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <destroy>
+ <reason>We refuse to live in this fantasy!</reason>
+ </destroy>
+ <item affiliation="none" jid="${Juliet's full JID}" role="none"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <presence from="garden@conference.localhost/romeo" type="unavailable">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <destroy>
+ <reason>We refuse to live in this fantasy!</reason>
+ </destroy>
+ <item affiliation="owner" jid="${Romeo's full JID}" role="none"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <iq id="lx4" type="result" from="garden@conference.localhost"/>
+
+Juliet disconnects
+
+Romeo sends:
+ <presence to="elsewhere@conference.localhost/romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from="elsewhere@conference.localhost/romeo">
+ <x xmlns="vcard-temp:x:update">
+ <photo/>
+ </x>
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <status code="201"/>
+ <item affiliation="owner" jid="${Romeo's full JID}" role="moderator"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message from="elsewhere@conference.localhost" type="groupchat">
+ <subject/>
+ </message>
+
+Romeo sends:
+ <iq to="elsewhere@conference.localhost" id="lx5" type="set">
+ <query xmlns="http://jabber.org/protocol/muc#owner">
+ <x type="submit" xmlns="jabber:x:data"/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="lx5" type="result" from="elsewhere@conference.localhost"/>
+
+Admin connects
+
+Admin sends:
+ <iq id="destroy" type="set" to="conference.localhost">
+ <command xmlns="http://jabber.org/protocol/commands" node="http://prosody.im/protocol/muc#destroy">
+ <x xmlns="jabber:x:data">
+ <field var="rooms">
+ <value>elsewhere@conference.localhost</value>
+ </field>
+ </x>
+ </command>
+ </iq>
+
+Romeo receives:
+ <presence from="elsewhere@conference.localhost/romeo" type="unavailable">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <destroy/>
+ <item affiliation="owner" jid="${Romeo's full JID}" role="none"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo disconnects
+
+Admin receives:
+ <iq id="destroy" type="result" from="conference.localhost">
+ <command xmlns="http://jabber.org/protocol/commands" node="http://prosody.im/protocol/muc#destroy" status="completed" sessionid="{scansion:any}">
+ <note type="info">The following rooms were destroyed:&#10;elsewhere@conference.localhost</note>
+ </command>
+ </iq>
+
+Admin disconnects
+
+# recording ended on 2019-08-31T13:45:32Z
diff --git a/spec/scansion/muc_members_only_change.scs b/spec/scansion/muc_members_only_change.scs
index dc40b5a0..a708dbfb 100644
--- a/spec/scansion/muc_members_only_change.scs
+++ b/spec/scansion/muc_members_only_change.scs
@@ -94,7 +94,7 @@ Romeo sends:
<item affiliation='none' jid="${Juliet's JID}" />
</query>
</iq>
-
+
# As a non-member, Juliet must now be removed from the room
Romeo receives:
<presence type='unavailable' from='room@conference.localhost/Juliet'>
diff --git a/spec/scansion/muc_nickname_change.scs b/spec/scansion/muc_nickname_change.scs
new file mode 100644
index 00000000..73f81203
--- /dev/null
+++ b/spec/scansion/muc_nickname_change.scs
@@ -0,0 +1,127 @@
+# MUC: Change nickname
+# Make sure a role is not reset, see #1466
+
+[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>
+
+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_moderatedroom'>
+ <value>1</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result"/>
+
+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'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item role='moderator' xmlns='http://jabber.org/protocol/muc#user' affiliation='owner'/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='none' role='visitor'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+
+Juliet 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" role="visitor" jid="${Juliet's full JID}"/>
+ </x>
+ </presence>
+
+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_moderatedroom'>
+ <value>0</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result"/>
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status xmlns='http://jabber.org/protocol/muc#user' code='104'/>
+ </x>
+ </message>
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet2">
+ </presence>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet' type='unavailable'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='303'/>
+ <item nick='Juliet2' jid="${Juliet's full JID}" affiliation='none' role='visitor'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet2'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='none' role='visitor'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
diff --git a/spec/scansion/muc_nickname_robotface.scs b/spec/scansion/muc_nickname_robotface.scs
new file mode 100644
index 00000000..160c13d6
--- /dev/null
+++ b/spec/scansion/muc_nickname_robotface.scs
@@ -0,0 +1,46 @@
+# MUC: Prevent nicknames failing strict resourceprep
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Roboteo
+ jid: bot@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="nobots@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='nobots@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='nobots@conference.localhost'><subject/></message>
+
+Roboteo connects
+
+Roboteo sends:
+ <presence to="nobots@conference.localhost/🤖️">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Roboteo receives:
+ <presence type='error' from='nobots@conference.localhost/🤖'>
+ <error by='nobots@conference.localhost' type='modify'>
+ <jid-malformed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Nickname must pass strict validation</text>
+ </error>
+ </presence>
+
diff --git a/spec/scansion/muc_password.scs b/spec/scansion/muc_password.scs
index 82611183..ca7d4cd2 100644
--- a/spec/scansion/muc_password.scs
+++ b/spec/scansion/muc_password.scs
@@ -58,7 +58,7 @@ Juliet sends:
Juliet receives:
<presence from="room@conference.localhost/Juliet" type="error">
- <error type="auth" code="401">
+ <error type="auth" by="room@conference.localhost">
<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</presence>
diff --git a/spec/scansion/muc_presence_probe.scs b/spec/scansion/muc_presence_probe.scs
new file mode 100644
index 00000000..1fb5d9f5
--- /dev/null
+++ b/spec/scansion/muc_presence_probe.scs
@@ -0,0 +1,178 @@
+# #1535 Let MUCs respond to presence probes
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+[Client] Mercutio
+ jid: user3@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+# Romeo joins the MUC
+
+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>
+
+# Disable presences for non-mods
+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_presencebroadcast'>
+ <value>moderator</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Romeo probes himself
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ </x>
+ </presence>
+
+# Juliet tries to probe Romeo before joining the room
+
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Romeo" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" type="error">
+ <error type="cancel">
+ <not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ </error>
+ </presence>
+
+# Juliet tries to probe Mercutio (who's not in the MUC) before joining the room
+
+Juliet sends:
+ <presence to="room@conference.localhost/Mercutio" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Mercutio" type="error">
+ <error type="cancel">
+ <not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ </error>
+ </presence>
+
+# Juliet joins the room
+
+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" />
+
+# Romeo probes Juliet
+
+Romeo sends:
+ <presence to="room@conference.localhost/Juliet" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='none' role='participant'/>
+ </x>
+ </presence>
+
+
+# Mercutio tries to probe himself in a MUC before joining
+
+Mercutio connects
+
+Mercutio sends:
+ <presence to="room@conference.localhost/Mercutio" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Mercutio receives:
+ <presence from="room@conference.localhost/Mercutio" type="error">
+ <error type="cancel">
+ <not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ </error>
+ </presence>
+
+
+# Romeo makes Mercutio a member and registers his nickname
+
+Romeo sends:
+ <iq id='member1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Mercutio's JID}" nick="Mercutio"/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Mercutio's JID}" affiliation='member' />
+ </x>
+ </message>
+
+Romeo receives:
+ <iq from='room@conference.localhost' id='member1' type='result'/>
+
+
+# Romeo probes Mercutio, even though he's unavailable
+
+Romeo sends:
+ <presence to="room@conference.localhost/Mercutio" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Mercutio' type="unavailable">
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item nick="Mercutio" affiliation='member' role='none' jid="${Mercutio's JID}" />
+ </x>
+ </presence>
diff --git a/spec/scansion/muc_register.scs b/spec/scansion/muc_register.scs
index a7e57177..915a7325 100644
--- a/spec/scansion/muc_register.scs
+++ b/spec/scansion/muc_register.scs
@@ -100,7 +100,9 @@ Juliet receives:
<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'/>
+ <field type='text-single' label='Nickname' var='muc#register_roomnick'>
+ <required/>
+ </field>
</x>
</query>
</iq>
@@ -175,10 +177,9 @@ Rosaline sends:
Rosaline receives:
<presence type='error' from='room@conference.localhost/Juliet'>
- <error type='cancel'>
+ <error type='cancel' by='room@conference.localhost'>
<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
@@ -286,10 +287,9 @@ Rosaline sends:
Rosaline receives:
<presence type='error' from='room@conference.localhost/Juliet'>
- <error type='cancel'>
+ <error type='cancel' by='room@conference.localhost'>
<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
@@ -326,7 +326,7 @@ Romeo receives:
</iq>
# Romeo updates his own registration
-
+
Romeo sends:
<iq id='jw81b36f' to='room@conference.localhost' type='get'>
<query xmlns='jabber:iq:register'/>
@@ -339,7 +339,9 @@ Romeo receives:
<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'/>
+ <field type='text-single' label='Nickname' var='muc#register_roomnick'>
+ <required/>
+ </field>
</x>
</query>
</iq>
diff --git a/spec/scansion/muc_show_offline.scs b/spec/scansion/muc_show_offline.scs
new file mode 100644
index 00000000..c06a892d
--- /dev/null
+++ b/spec/scansion/muc_show_offline.scs
@@ -0,0 +1,542 @@
+# MUC: Room registration and presence broadcast of unavailable members
+
+[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>
+ <field var='muc#roomconfig_presencebroadcast'>
+ <value>none</value>
+ <value>participant</value>
+ <value>moderator</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:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's JID}" affiliation='member' />
+ </x>
+ </message>
+
+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'>
+ <required/>
+ </field>
+ </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' by='room@conference.localhost'>
+ <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </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'/>
+
+# Romeo is notified of Juliet's sad decision
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user' scansion:strict='true'>
+ <item jid="${Juliet's JID}" affiliation='none' />
+ </x>
+ </message>
+
+# 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>
+
+# An out-of-room affiliation change is received for Juliet
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's JID}" affiliation='member' />
+ </x>
+ </message>
+
+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' by='room@conference.localhost'>
+ <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </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'>
+ <required/>
+ </field>
+ </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/Juliet' type='unavailable'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' role='none' nick='Juliet' xmlns='http://jabber.org/protocol/muc#user'/>
+ </x>
+ </presence>
+
+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>
+
+# Romeo sets their their own nickname via admin query (see #1273)
+Romeo sends:
+ <iq to="room@conference.localhost" id="reserve" type="set">
+ <query xmlns="http://jabber.org/protocol/muc#admin">
+ <item nick="Romeo" affiliation="owner" jid="${Romeo's JID}"/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <item xmlns="http://jabber.org/protocol/muc#user" role="moderator" jid="${Romeo's full JID}" affiliation="owner">
+ <actor xmlns="http://jabber.org/protocol/muc#user" nick="Romeo"/>
+ </item>
+ <status xmlns="http://jabber.org/protocol/muc#user" code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <iq from="room@conference.localhost" id="reserve" type="result"/>
+
diff --git a/spec/scansion/muc_subject_issue_667.scs b/spec/scansion/muc_subject_issue_667.scs
new file mode 100644
index 00000000..74980073
--- /dev/null
+++ b/spec/scansion/muc_subject_issue_667.scs
@@ -0,0 +1,129 @@
+# #667 MUC message with subject and body SHALL NOT be interpreted as a subject change
+
+[Client] Romeo
+ password: password
+ jid: romeo@localhost
+
+-----
+
+Romeo connects
+
+# and creates a room
+Romeo sends:
+ <presence to="issue667@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from="issue667@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <status code="201"/>
+ <item affiliation="owner" role="moderator" jid="${Romeo's full JID}"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+# the default (empty) subject
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost">
+ <subject/>
+ </message>
+
+# this should be treated as a normal message
+Romeo sends:
+ <message to="issue667@conference.localhost" type="groupchat">
+ <subject>Greetings</subject>
+ <body>Hello everyone</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>Greetings</subject>
+ <body>Hello everyone</body>
+ </message>
+
+# Resync
+Romeo sends:
+ <presence to="issue667@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+# Presences
+Romeo receives:
+ <presence from="issue667@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <item affiliation="owner" role="moderator" jid="${Romeo's full JID}"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>Greetings</subject>
+ <body>Hello everyone</body>
+ </message>
+
+# the still empty subject
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost">
+ <subject/>
+ </message>
+
+# this is a subject change
+Romeo sends:
+ <message to="issue667@conference.localhost" type="groupchat">
+ <subject>Something to talk about</subject>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>Something to talk about</subject>
+ </message>
+
+# a message without <subject>
+Romeo sends:
+ <message to="issue667@conference.localhost" type="groupchat">
+ <body>Lorem ipsum dolor sit amet</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <body>Lorem ipsum dolor sit amet</body>
+ </message>
+
+# Resync
+Romeo sends:
+ <presence to="issue667@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+# Presences
+Romeo receives:
+ <presence from="issue667@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <item affiliation="owner" role="moderator" jid="${Romeo's full JID}"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+# History
+# These have delay tags but we ignore those for now
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>Greetings</subject>
+ <body>Hello everyone</body>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <body>Lorem ipsum dolor sit amet</body>
+ </message>
+
+# Finally, the topic
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>Something to talk about</subject>
+ </message>
+
+Romeo disconnects
+
diff --git a/spec/scansion/pep_nickname.scs b/spec/scansion/pep_nickname.scs
index f958ec75..aaf53c87 100644
--- a/spec/scansion/pep_nickname.scs
+++ b/spec/scansion/pep_nickname.scs
@@ -1,7 +1,7 @@
# Publishing a nickname in PEP and receiving a notification
[Client] Romeo
- jid: romeo@localhost/nJi7BeTR
+ jid: romeo@localhost
password: password
-----
@@ -20,7 +20,7 @@ Romeo sends:
</iq>
Romeo receives:
- <iq id="4" to="romeo@localhost/nJi7BeTR" type="result">
+ <iq id="4" type="result">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<publish node="http://jabber.org/protocol/nick">
<item id="current"/>
@@ -34,12 +34,12 @@ Romeo sends:
</presence>
Romeo receives:
- <iq id="disco" to="romeo@localhost/nJi7BeTR" from="romeo@localhost" type="get">
+ <iq id="disco" 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">
+ <presence>
<c xmlns="http://jabber.org/protocol/caps" hash="sha-1" node="http://code.matthewwild.co.uk/clix/" ver="jC32N+FhQoLrZ7nNQtZK3aqR0Fk="/>
</presence>
@@ -55,10 +55,10 @@ Romeo sends:
</iq>
Romeo receives:
- <message type="headline" from="romeo@localhost" to="romeo@localhost/nJi7BeTR">
+ <message type="headline" from="romeo@localhost">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<items node="http://jabber.org/protocol/nick">
- <item id="current">
+ <item id="current" publisher="${Romeo's JID}">
<nickname xmlns="http://jabber.org/protocol/nick"/>
</item>
</items>
diff --git a/spec/scansion/pep_publish_subscribe.scs b/spec/scansion/pep_publish_subscribe.scs
index e8080134..6d33ffeb 100644
--- a/spec/scansion/pep_publish_subscribe.scs
+++ b/spec/scansion/pep_publish_subscribe.scs
@@ -182,7 +182,7 @@ Juliet sends:
<iq type='result' id='fixme'/>
Juliet sends:
- <iq type='set' id='7'><pubsub xmlns='http://jabber.org/protocol/pubsub'><publish node='http://jabber.org/protocol/tune'><item id='current'><tune xmlns='http://jabber.org/protocol/tune'><title>Beautiful Cedars</title><artist>The Spinners</artist><source>Not Quite Folk</source><track>4</track></tune></item></publish></pubsub></iq>
+ <iq type='set' id='7'><pubsub xmlns='http://jabber.org/protocol/pubsub'><publish node='http://jabber.org/protocol/tune'><item id='current' publisher="${Juliet's JID}"><tune xmlns='http://jabber.org/protocol/tune'><title>Beautiful Cedars</title><artist>The Spinners</artist><source>Not Quite Folk</source><track>4</track></tune></item></publish></pubsub></iq>
Juliet receives:
<iq type='result' id='7' ><pubsub xmlns='http://jabber.org/protocol/pubsub'><publish node='http://jabber.org/protocol/tune'><item id='current'/></publish></pubsub></iq>
@@ -197,13 +197,13 @@ Juliet sends:
<iq type='result' id='{scansion:any}'/>
Romeo receives:
- <message type='headline' from='pep-test-tqvqu_pv@localhost'><event xmlns='http://jabber.org/protocol/pubsub#event'><items node='http://jabber.org/protocol/tune'><item id='current'><tune xmlns='http://jabber.org/protocol/tune'><title>Beautiful Cedars</title><artist>The Spinners</artist><source>Not Quite Folk</source><track>4</track></tune></item></items></event></message>
+ <message type='headline' from='pep-test-tqvqu_pv@localhost'><event xmlns='http://jabber.org/protocol/pubsub#event'><items node='http://jabber.org/protocol/tune'><item id='current' publisher="${Juliet's JID}"><tune xmlns='http://jabber.org/protocol/tune'><title>Beautiful Cedars</title><artist>The Spinners</artist><source>Not Quite Folk</source><track>4</track></tune></item></items></event></message>
Romeo sends:
<iq type='result' id='disco' to='pep-test-tqvqu_pv@localhost'><query xmlns='http://jabber.org/protocol/disco#info' node='http://code.matthewwild.co.uk/verse/#IfQwbaaDB4LEP5tkGArEaB/3Y+s='><identity type='pc' name='Verse' category='client'/><feature var='http://jabber.org/protocol/tune+notify'/><feature var='http://jabber.org/protocol/disco#info'/><feature var='http://jabber.org/protocol/disco#items'/><feature var='http://jabber.org/protocol/caps'/><feature var='http://jabber.org/protocol/mood+notify'/></query></iq>
Romeo receives:
- <message type='headline' from='pep-test-tqvqu_pv@localhost'><event xmlns='http://jabber.org/protocol/pubsub#event'><items node='http://jabber.org/protocol/tune'><item id='current'><tune xmlns='http://jabber.org/protocol/tune'><title>Beautiful Cedars</title><artist>The Spinners</artist><source>Not Quite Folk</source><track>4</track></tune></item></items></event></message>
+ <message type='headline' from='pep-test-tqvqu_pv@localhost'><event xmlns='http://jabber.org/protocol/pubsub#event'><items node='http://jabber.org/protocol/tune'><item id='current' publisher="${Juliet's JID}"><tune xmlns='http://jabber.org/protocol/tune'><title>Beautiful Cedars</title><artist>The Spinners</artist><source>Not Quite Folk</source><track>4</track></tune></item></items></event></message>
Juliet disconnects
diff --git a/spec/scansion/pep_pubsub_max.scs b/spec/scansion/pep_pubsub_max.scs
new file mode 100644
index 00000000..6961304c
--- /dev/null
+++ b/spec/scansion/pep_pubsub_max.scs
@@ -0,0 +1,47 @@
+# PEP max_items=max
+
+[Client] Romeo
+ jid: pep-test-maxitems@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq type="set" id="pub">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="urn:xmpp:microblog:0">
+ <item>
+ <entry xmlns='http://www.w3.org/2005/Atom'>
+ <title>Hello</title>
+ </entry>
+ </item>
+ </publish>
+ <publish-options>
+ <x xmlns="jabber:x:data" type="submit">
+ <field type="hidden" var="FORM_TYPE">
+ <value>http://jabber.org/protocol/pubsub#publish-options</value>
+ </field>
+ <field var="pubsub#persist_items">
+ <value>true</value>
+ </field>
+ <field var="pubsub#access_model">
+ <value>open</value>
+ </field>
+ <field var="pubsub#max_items">
+ <value>max</value>
+ </field>
+ </x>
+ </publish-options>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id="pub">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="urn:xmpp:microblog:0">
+ <item id="{scansion:any}"/>
+ </publish>
+ </pubsub>
+ </iq>
diff --git a/spec/scansion/presence_preapproval.scs b/spec/scansion/presence_preapproval.scs
new file mode 100644
index 00000000..e34ac7cf
--- /dev/null
+++ b/spec/scansion/presence_preapproval.scs
@@ -0,0 +1,74 @@
+# server supports contact subscription pre-approval (RFC 6121 3.4)
+
+[Client] Alice
+ jid: preappove-a@localhost
+ password: password
+
+[Client] Bob
+ jid: preapprove-b@localhost
+ password: password
+
+---------
+
+Alice connects
+
+Alice sends:
+ <presence/>
+
+Alice receives:
+ <presence/>
+
+Alice sends:
+ <presence to="${Bob's JID}" type="subscribed"/>
+
+Bob connects
+
+Bob sends:
+ <iq type="get" id="roster1">
+ <query xmlns="jabber:iq:roster"/>
+ </iq>
+
+Bob receives:
+ <iq type="result" id="roster1">
+ <query xmlns="jabber:iq:roster" ver="{scansion:any}">
+ </query>
+ </iq>
+
+Bob sends:
+ <presence/>
+
+Bob receives:
+ <presence from="${Bob's full JID}"/>
+
+Bob sends:
+ <presence to="${Alice's JID}" type="subscribe" />
+
+Bob receives:
+ <iq type='set' id='{scansion:any}'>
+ <query ver='1' xmlns='jabber:iq:roster'>
+ <item jid="${Alice's JID}" subscription='none' ask='subscribe' />
+ </query>
+ </iq>
+
+
+
+Bob receives:
+ <presence from="${Alice's JID}" type="subscribed" />
+
+Bob disconnects
+
+Alice sends:
+ <iq type="get" id="roster1">
+ <query xmlns="jabber:iq:roster"/>
+ </iq>
+
+Alice receives:
+ <iq type="result" id="roster1">
+ <query xmlns="jabber:iq:roster" ver="{scansion:any}">
+ <item jid="${Bob's JID}" subscription="from" />
+ </query>
+ </iq>
+
+Alice disconnects
+
+Bob disconnects
diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua
index f95ea31b..6901cc11 100644
--- a/spec/scansion/prosody.cfg.lua
+++ b/spec/scansion/prosody.cfg.lua
@@ -1,23 +1,36 @@
--luacheck: ignore
+-- Mock time functions to simplify tests
+function _G.os.time()
+ return 1219439344;
+end
+package.preload["util.time"] = function ()
+ return {
+ now = function () return 1219439344.1; end;
+ monotonic = function () return 0.1; end;
+ }
+end
+
admins = { "admin@localhost" }
-use_libevent = true
+network_backend = ENV_PROSODY_NETWORK_BACKEND or "epoll"
+network_settings = require"util.json".decode(ENV_PROSODY_NETWORK_SETTINGS or "{}")
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
+ --"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
+ "pep"; -- Enables users to publish their avatar, 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
+ "vcard4"; -- User profiles (stored in PEP)
+ "vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard
-- Nice to have
"version"; -- Replies to server version requests
@@ -26,6 +39,11 @@ modules_enabled = {
"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
+ --"csi_simple"; -- Simple Mobile optimizations
+
+ -- Admin interfaces
+ --"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
+ --"admin_telnet"; -- Opens telnet console interface on localhost port 5582
-- HTTP modules
--"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
@@ -35,19 +53,51 @@ modules_enabled = {
-- Other specific functionality
--"limits"; -- Enable bandwidth limiting for XMPP connections
--"groups"; -- Shared roster support
- --"server_contact_info"; -- Publish contact information for this service
+ "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
+ "lastactivity";
+ "external_services";
+
+ "tombstones";
+ "user_account_management";
-- Useful for testing
--"scansion_record"; -- Records things that happen in scansion test case format
}
-certificate = "certs"
+contact_info = {
+ abuse = { "mailto:abuse@localhost", "xmpp:abuse@localhost" };
+ admin = { "mailto:admin@localhost", "xmpp:admin@localhost" };
+ feedback = { "http://localhost/feedback.html", "mailto:feedback@localhost", "xmpp:feedback@localhost" };
+ sales = { "xmpp:sales@localhost" };
+ security = { "xmpp:security@localhost" };
+ status = { "gopher://status.localhost" };
+ support = { "https://localhost/support.html", "xmpp:support@localhost" };
+}
+
+external_service_host = "default.example"
+external_service_port = 9876
+external_service_secret = "<secret>"
+external_services = {
+ {type = "stun"; transport = "udp"};
+ {type = "turn"; transport = "udp"; secret = true};
+ {type = "turn"; transport = "udp"; secret = "foo"};
+ {type = "ftp"; transport = "tcp"; port = 2121; username = "john"; password = "password"};
+ {type = "ftp"; transport = "tcp"; host = "ftp.example.com"; port = 21; username = "john"; password = "password"};
+}
+
+modules_disabled = {
+ "s2s";
+}
+
+-- TLS is not used during the test, set certificate dir to the config directory
+-- (spec/scansion) to silence an error from the certificate indexer
+certificates = "."
allow_registration = false
@@ -69,15 +119,26 @@ mam_smart_enable = true
-- Logging configuration
-- For advanced logging see https://prosody.im/doc/logging
-log = "*console"
+log = {"*console",debug = ENV_PROSODY_LOGFILE}
-daemonize = true
pidfile = "prosody.pid"
VirtualHost "localhost"
+hide_os_type = true -- absence tested for in version.scs
+
Component "conference.localhost" "muc"
storage = "memory"
+ admins = { "Admin@localhost" }
+ modules_enabled = {
+ "muc_mam";
+ }
+
Component "pubsub.localhost" "pubsub"
storage = "memory"
+ expose_publisher = true
+
+Component "upload.localhost" "http_file_share"
+http_file_share_size_limit = 10000000
+http_file_share_allowed_file_types = { "text/plain", "image/*" }
diff --git a/spec/scansion/pubsub_advanced.scs b/spec/scansion/pubsub_advanced.scs
index c873486e..74ca5309 100644
--- a/spec/scansion/pubsub_advanced.scs
+++ b/spec/scansion/pubsub_advanced.scs
@@ -129,7 +129,7 @@ Juliet receives:
<message type="headline" from="pubsub.localhost">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<items node="princely_musings">
- <item id="current">
+ <item id="current" publisher="${Romeo's JID}">
<entry xmlns="http://www.w3.org/2005/Atom">
<title>Soliloquy</title>
<summary>Lorem ipsum dolor sit amet</summary>
@@ -150,7 +150,11 @@ Juliet sends:
</iq>
Juliet receives:
- <iq type="result" id='unsub1'/>
+ <iq type="result" id='unsub1'>
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ <subscription jid="${Juliet's full JID}" node='princely_musings' subscription='none'/>
+ </pubsub>
+ </iq>
Balthasar sends:
<iq type="set" to="pubsub.localhost" id='del1'>
diff --git a/spec/scansion/pubsub_basic.scs b/spec/scansion/pubsub_basic.scs
index d983ff66..610a6612 100644
--- a/spec/scansion/pubsub_basic.scs
+++ b/spec/scansion/pubsub_basic.scs
@@ -32,7 +32,7 @@ Juliet connects
-- <subscribe node="princely_musings" jid="${Romeo's full JID}"/>
-- </pubsub>
-- </iq>
---
+--
-- Juliet receives:
-- <iq type="error"/>
@@ -67,7 +67,7 @@ Juliet receives:
<message type="headline" from="pubsub.localhost">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<items node="princely_musings">
- <item id="current">
+ <item id="current" publisher="${Romeo's JID}">
<entry xmlns="http://www.w3.org/2005/Atom">
<title>Soliloquy</title>
<summary>Lorem ipsum dolor sit amet</summary>
diff --git a/spec/scansion/pubsub_config.scs b/spec/scansion/pubsub_config.scs
index d979aca5..d06d864e 100644
--- a/spec/scansion/pubsub_config.scs
+++ b/spec/scansion/pubsub_config.scs
@@ -48,7 +48,9 @@ Romeo receives:
<field var="pubsub#description" label="Description" type="text-single"/>
<field var="pubsub#type" label="The type of node data, usually specified by the namespace of the payload (if any)" type="text-single"/>
<field var="pubsub#max_items" label="Max # of items to persist" type="text-single">
- <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="xs:integer"/>
+ <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="pubsub:integer-or-max">
+ <range min="1" max="256"/>
+ </validate>
<value>1</value>
</field>
<field var="pubsub#persist_items" label="Persist items to storage" type="boolean">
@@ -84,6 +86,18 @@ Romeo receives:
</option>
<value>publishers</value>
</field>
+ <field type='list-single' var='pubsub#send_last_published_item'>
+ <option label='never'>
+ <value>never</value>
+ </option>
+ <option label='on_sub'>
+ <value>on_sub</value>
+ </option>
+ <option label='on_sub_and_presence'>
+ <value>on_sub_and_presence</value>
+ </option>
+ <value>on_sub_and_presence</value>
+ </field>
<field var="pubsub#deliver_notifications" label="Whether to deliver event notifications" type="boolean">
<value>1</value>
</field>
@@ -124,7 +138,9 @@ Romeo sends:
<field var="pubsub#description" type="text-single" label="Description"/>
<field var="pubsub#type" type="text-single" label="The type of node data, usually specified by the namespace of the payload (if any)"/>
<field var="pubsub#max_items" type="text-single" label="Max # of items to persist">
- <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="xs:integer"/>
+ <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="pubsub:integer-or-max">
+ <range min="1" max="256"/>
+ </validate>
<value>1</value>
</field>
<field var="pubsub#persist_items" type="boolean" label="Persist items to storage">
@@ -160,6 +176,9 @@ Romeo sends:
</option>
<value>publishers</value>
</field>
+ <field type='list-single' var='pubsub#send_last_published_item'>
+ <value>never</value>
+ </field>
<field var="pubsub#deliver_notifications" type="boolean" label="Whether to deliver event notifications">
<value>1</value>
</field>
diff --git a/spec/scansion/pubsub_max_items.scs b/spec/scansion/pubsub_max_items.scs
new file mode 100644
index 00000000..c5525bd3
--- /dev/null
+++ b/spec/scansion/pubsub_max_items.scs
@@ -0,0 +1,210 @@
+# Pubsub: Requesting the Most Recent Items (#1608)
+
+[Client] Alice
+ jid: admin@localhost
+ password: password
+
+---------
+
+Alice connects
+
+Alice sends:
+ <presence xmlns:stream="http://etherx.jabber.org/streams" id=":7IoqYcT3191rfk_dZGo2"/>
+
+Alice receives:
+ <presence xmlns:stream="http://etherx.jabber.org/streams" from="${Alice's full JID}" id=":7IoqYcT3191rfk_dZGo2"/>
+
+Alice sends:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":m0SM8Hn5JxP9BJJ_X4Mz" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <create node="5549ea47-ea53-4cc1-9e7c-37842fe4bc06"/>
+ </pubsub>
+ </iq>
+
+Alice receives:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":m0SM8Hn5JxP9BJJ_X4Mz"/>
+
+Alice sends:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":gwZgEQmzAHcQz-FZOxi-" type="get">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="5549ea47-ea53-4cc1-9e7c-37842fe4bc06"/>
+ </pubsub>
+ </iq>
+
+Alice receives:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":gwZgEQmzAHcQz-FZOxi-">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="5549ea47-ea53-4cc1-9e7c-37842fe4bc06">
+ <x xmlns="jabber:x:data" type="form">
+ <field var="FORM_TYPE" type="hidden">
+ <value>http://jabber.org/protocol/pubsub#node_config</value>
+ </field>
+ <field var="pubsub#title" label="Title" type="text-single"/>
+ <field var="pubsub#description" label="Description" type="text-single"/>
+ <field var="pubsub#type" label="The type of node data, usually specified by the namespace of the payload (if any)" type="text-single"/>
+ <field var="pubsub#max_items" label="Max # of items to persist" type="text-single">
+ <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="pubsub:integer-or-max">
+ <range min="1" max="256"/>
+ </validate>
+ <value>20</value>
+ </field>
+ <field var="pubsub#persist_items" label="Persist items to storage" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#access_model" label="Specify the subscriber model" type="list-single">
+ <option label="authorize">
+ <value>authorize</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <option label="presence">
+ <value>presence</value>
+ </option>
+ <option label="roster">
+ <value>roster</value>
+ </option>
+ <option label="whitelist">
+ <value>whitelist</value>
+ </option>
+ <value>open</value>
+ </field>
+ <field var="pubsub#publish_model" label="Specify the publisher model" type="list-single">
+ <option label="publishers">
+ <value>publishers</value>
+ </option>
+ <option label="subscribers">
+ <value>subscribers</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <value>publishers</value>
+ </field>
+ <field type='list-single' var='pubsub#send_last_published_item'>
+ <option label='never'>
+ <value>never</value>
+ </option>
+ <option label='on_sub'>
+ <value>on_sub</value>
+ </option>
+ <option label='on_sub_and_presence'>
+ <value>on_sub_and_presence</value>
+ </option>
+ <value>never</value>
+ </field>
+ <field var="pubsub#deliver_notifications" label="Whether to deliver event notifications" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#deliver_payloads" label="Whether to deliver payloads with event notifications" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notification_type" label="Specify the delivery style for notifications" type="list-single">
+ <option label="Messages of type normal">
+ <value>normal</value>
+ </option>
+ <option label="Messages of type headline">
+ <value>headline</value>
+ </option>
+ <value>headline</value>
+ </field>
+ <field var="pubsub#notify_delete" label="Whether to notify subscribers when the node is deleted" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notify_retract" label="Whether to notify subscribers when items are removed from the node" type="boolean">
+ <value>1</value>
+ </field>
+ </x>
+ </configure>
+ </pubsub>
+ </iq>
+
+Alice sends:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":pfWBQ2MNIq8ieul57Qp7" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="5549ea47-ea53-4cc1-9e7c-37842fe4bc06">
+ <item id="20e9eb9e-8acb-436e-a486-40e80400faf1">
+ <foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">foo</foo>
+ </item>
+ </publish>
+ </pubsub>
+ </iq>
+
+Alice receives:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":pfWBQ2MNIq8ieul57Qp7">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="5549ea47-ea53-4cc1-9e7c-37842fe4bc06">
+ <item id="20e9eb9e-8acb-436e-a486-40e80400faf1"/>
+ </publish>
+ </pubsub>
+ </iq>
+
+Alice sends:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":Q5TLT6nsW0HHdkDgrPPe" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="5549ea47-ea53-4cc1-9e7c-37842fe4bc06">
+ <item id="4b94623d-1127-41c0-ac47-e283fd890557">
+ <foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">bar</foo>
+ </item>
+ </publish>
+ </pubsub>
+ </iq>
+
+Alice receives:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":Q5TLT6nsW0HHdkDgrPPe">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="5549ea47-ea53-4cc1-9e7c-37842fe4bc06">
+ <item id="4b94623d-1127-41c0-ac47-e283fd890557"/>
+ </publish>
+ </pubsub>
+ </iq>
+
+Alice sends:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":3nvB2E20p1iuM6lOPaP6" type="get">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <items node="5549ea47-ea53-4cc1-9e7c-37842fe4bc06" max_items="1"/>
+ </pubsub>
+ </iq>
+
+Alice receives:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":3nvB2E20p1iuM6lOPaP6">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <items node="5549ea47-ea53-4cc1-9e7c-37842fe4bc06">
+ <item publisher="${Alice's JID}" xmlns="http://jabber.org/protocol/pubsub" id="4b94623d-1127-41c0-ac47-e283fd890557">
+ <foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">bar</foo>
+ </item>
+ </items>
+ </pubsub>
+ </iq>
+
+Alice sends:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":XQdyK54iyOKiJvUoX9t_" type="get">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <items node="5549ea47-ea53-4cc1-9e7c-37842fe4bc06"/>
+ </pubsub>
+ </iq>
+
+Alice receives:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":XQdyK54iyOKiJvUoX9t_">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <items node="5549ea47-ea53-4cc1-9e7c-37842fe4bc06">
+ <item xmlns="http://jabber.org/protocol/pubsub" publisher="${Alice's JID}" id="20e9eb9e-8acb-436e-a486-40e80400faf1">
+ <foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">foo</foo>
+ </item>
+ <item xmlns="http://jabber.org/protocol/pubsub" publisher="${Alice's JID}" id="4b94623d-1127-41c0-ac47-e283fd890557">
+ <foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">bar</foo>
+ </item>
+ </items>
+ </pubsub>
+ </iq>
+
+Alice sends:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":ySGQOz5tnyWT82idwJZP" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <delete node="5549ea47-ea53-4cc1-9e7c-37842fe4bc06"/>
+ </pubsub>
+ </iq>
+
+Alice receives:
+ <iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":ySGQOz5tnyWT82idwJZP"/>
+
diff --git a/spec/scansion/pubsub_multi_items.scs b/spec/scansion/pubsub_multi_items.scs
index 147aaa8d..e43bc839 100644
--- a/spec/scansion/pubsub_multi_items.scs
+++ b/spec/scansion/pubsub_multi_items.scs
@@ -43,11 +43,13 @@ Alice receives:
<field var="pubsub#description" label="Description" type="text-single"/>
<field var="pubsub#type" label="The type of node data, usually specified by the namespace of the payload (if any)" type="text-single"/>
<field var="pubsub#max_items" label="Max # of items to persist" type="text-single">
- <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="xs:integer"/>
+ <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="pubsub:integer-or-max">
+ <range min="1" max="256"/>
+ </validate>
<value>20</value>
</field>
<field var="pubsub#persist_items" label="Persist items to storage" type="boolean">
- <value>0</value>
+ <value>1</value>
</field>
<field var="pubsub#access_model" label="Specify the subscriber model" type="list-single">
<option label="authorize">
@@ -79,6 +81,18 @@ Alice receives:
</option>
<value>publishers</value>
</field>
+ <field type='list-single' var='pubsub#send_last_published_item'>
+ <option label='never'>
+ <value>never</value>
+ </option>
+ <option label='on_sub'>
+ <value>on_sub</value>
+ </option>
+ <option label='on_sub_and_presence'>
+ <value>on_sub_and_presence</value>
+ </option>
+ <value>never</value>
+ </field>
<field var="pubsub#deliver_notifications" label="Whether to deliver event notifications" type="boolean">
<value>1</value>
</field>
@@ -159,10 +173,10 @@ Alice receives:
<iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":3nvB2E20p1iuM6lOPaP6">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<items node="e96caf12-264f-4e5a-988e-00ae191771b6">
- <item xmlns="http://jabber.org/protocol/pubsub" id="20e9eb9e-8acb-436e-a486-40e80400faf1">
+ <item publisher="${Alice's JID}" xmlns="http://jabber.org/protocol/pubsub" id="20e9eb9e-8acb-436e-a486-40e80400faf1">
<foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">foo</foo>
</item>
- <item xmlns="http://jabber.org/protocol/pubsub" id="4b94623d-1127-41c0-ac47-e283fd890557">
+ <item publisher="${Alice's JID}" xmlns="http://jabber.org/protocol/pubsub" id="4b94623d-1127-41c0-ac47-e283fd890557">
<foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">bar</foo>
</item>
</items>
@@ -180,10 +194,10 @@ Alice receives:
<iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":XQdyK54iyOKiJvUoX9t_">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<items node="e96caf12-264f-4e5a-988e-00ae191771b6">
- <item xmlns="http://jabber.org/protocol/pubsub" id="20e9eb9e-8acb-436e-a486-40e80400faf1">
+ <item xmlns="http://jabber.org/protocol/pubsub" publisher="${Alice's JID}" id="20e9eb9e-8acb-436e-a486-40e80400faf1">
<foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">foo</foo>
</item>
- <item xmlns="http://jabber.org/protocol/pubsub" id="4b94623d-1127-41c0-ac47-e283fd890557">
+ <item xmlns="http://jabber.org/protocol/pubsub" publisher="${Alice's JID}" id="4b94623d-1127-41c0-ac47-e283fd890557">
<foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">bar</foo>
</item>
</items>
diff --git a/spec/scansion/pubsub_preconditions.scs b/spec/scansion/pubsub_preconditions.scs
new file mode 100644
index 00000000..5c0c2569
--- /dev/null
+++ b/spec/scansion/pubsub_preconditions.scs
@@ -0,0 +1,253 @@
+# Pubsub preconditions are enforced
+
+[Client] Romeo
+ password: password
+ jid: jqpcrbq2@localhost
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq id="67eb1f47-1e69-4cb3-91e2-4d5943e72d4c" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="http://jabber.org/protocol/tune">
+ <item id="current">
+ <tune xmlns="http://jabber.org/protocol/tune"/>
+ </item>
+ </publish>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq id="67eb1f47-1e69-4cb3-91e2-4d5943e72d4c" type="result">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="http://jabber.org/protocol/tune">
+ <item id="current"/>
+ </publish>
+ </pubsub>
+ </iq>
+
+Romeo sends:
+ <iq id="52d74a36-afb0-4028-87ed-b25b988b049e" type="get">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="http://jabber.org/protocol/tune"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq id="52d74a36-afb0-4028-87ed-b25b988b049e" type="result">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="http://jabber.org/protocol/tune">
+ <x xmlns="jabber:x:data" type="form">
+ <field var="FORM_TYPE" type="hidden">
+ <value>http://jabber.org/protocol/pubsub#node_config</value>
+ </field>
+ <field var="pubsub#title" label="Title" type="text-single"/>
+ <field var="pubsub#description" label="Description" type="text-single"/>
+ <field var="pubsub#type" label="The type of node data, usually specified by the namespace of the payload (if any)" type="text-single"/>
+ <field var="pubsub#max_items" label="Max # of items to persist" type="text-single">
+ <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="pubsub:integer-or-max">
+ <range min="1" max="256"/>
+ </validate>
+ <value>1</value>
+ </field>
+ <field var="pubsub#persist_items" label="Persist items to storage" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#access_model" label="Specify the subscriber model" type="list-single">
+ <option label="authorize">
+ <value>authorize</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <option label="presence">
+ <value>presence</value>
+ </option>
+ <option label="roster">
+ <value>roster</value>
+ </option>
+ <option label="whitelist">
+ <value>whitelist</value>
+ </option>
+ <value>presence</value>
+ </field>
+ <field var="pubsub#publish_model" label="Specify the publisher model" type="list-single">
+ <option label="publishers">
+ <value>publishers</value>
+ </option>
+ <option label="subscribers">
+ <value>subscribers</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <value>publishers</value>
+ </field>
+ <field type='list-single' var='pubsub#send_last_published_item'>
+ <option label='never'>
+ <value>never</value>
+ </option>
+ <option label='on_sub'>
+ <value>on_sub</value>
+ </option>
+ <option label='on_sub_and_presence'>
+ <value>on_sub_and_presence</value>
+ </option>
+ <value>on_sub_and_presence</value>
+ </field>
+ <field var="pubsub#deliver_notifications" label="Whether to deliver event notifications" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#deliver_payloads" label="Whether to deliver payloads with event notifications" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notification_type" label="Specify the delivery style for notifications" type="list-single">
+ <option label="Messages of type normal">
+ <value>normal</value>
+ </option>
+ <option label="Messages of type headline">
+ <value>headline</value>
+ </option>
+ <value>headline</value>
+ </field>
+ <field var="pubsub#notify_delete" label="Whether to notify subscribers when the node is deleted" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notify_retract" label="Whether to notify subscribers when items are removed from the node" type="boolean">
+ <value>1</value>
+ </field>
+ </x>
+ </configure>
+ </pubsub>
+ </iq>
+
+Romeo sends:
+ <iq id="a73aac09-74be-4ee2-97e5-571bbdbcd956" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="http://jabber.org/protocol/tune">
+ <x xmlns="jabber:x:data" type="submit">
+ <field var="FORM_TYPE" type="hidden">
+ <value>http://jabber.org/protocol/pubsub#node_config</value>
+ </field>
+ <field var="pubsub#title" type="text-single" label="Title">
+ <value>Nice tunes</value>
+ </field>
+ <field var="pubsub#description" type="text-single" label="Description"/>
+ <field var="pubsub#type" type="text-single" label="The type of node data, usually specified by the namespace of the payload (if any)"/>
+ <field var="pubsub#max_items" type="text-single" label="Max # of items to persist">
+ <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="pubsub:integer-or-max">
+ <range min="1" max="256"/>
+ </validate>
+ <value>1</value>
+ </field>
+ <field var="pubsub#persist_items" type="boolean" label="Persist items to storage">
+ <value>1</value>
+ </field>
+ <field var="pubsub#access_model" type="list-single" label="Specify the subscriber model">
+ <option label="authorize">
+ <value>authorize</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <option label="presence">
+ <value>presence</value>
+ </option>
+ <option label="roster">
+ <value>roster</value>
+ </option>
+ <option label="whitelist">
+ <value>whitelist</value>
+ </option>
+ <value>presence</value>
+ </field>
+ <field var="pubsub#publish_model" type="list-single" label="Specify the publisher model">
+ <option label="publishers">
+ <value>publishers</value>
+ </option>
+ <option label="subscribers">
+ <value>subscribers</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <value>publishers</value>
+ </field>
+ <field type='list-single' var='pubsub#send_last_published_item'>
+ <value>never</value>
+ </field>
+ <field var="pubsub#deliver_notifications" type="boolean" label="Whether to deliver event notifications">
+ <value>1</value>
+ </field>
+ <field var="pubsub#deliver_payloads" type="boolean" label="Whether to deliver payloads with event notifications">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notification_type" type="list-single" label="Specify the delivery style for notifications">
+ <option label="Messages of type normal">
+ <value>normal</value>
+ </option>
+ <option label="Messages of type headline">
+ <value>headline</value>
+ </option>
+ <value>headline</value>
+ </field>
+ <field var="pubsub#notify_delete" type="boolean" label="Whether to notify subscribers when the node is deleted">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notify_retract" type="boolean" label="Whether to notify subscribers when items are removed from the node">
+ <value>1</value>
+ </field>
+ </x>
+ </configure>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq id="a73aac09-74be-4ee2-97e5-571bbdbcd956" type="result"/>
+
+Romeo sends:
+ <iq id="ab0e92d2-c06b-4987-9d45-f9f9e7721709" type="get">
+ <query xmlns="http://jabber.org/protocol/disco#items"/>
+ </iq>
+
+Romeo receives:
+ <iq id="ab0e92d2-c06b-4987-9d45-f9f9e7721709" type="result">
+ <query xmlns="http://jabber.org/protocol/disco#items">
+ <item name="Nice tunes" node="http://jabber.org/protocol/tune" jid="${Romeo's JID}"/>
+ </query>
+ </iq>
+
+Romeo sends:
+ <iq id="67eb1f47-1e69-4cb3-91e2-4d5943e72d4c" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="http://jabber.org/protocol/tune">
+ <item id="current">
+ <tune xmlns="http://jabber.org/protocol/tune"/>
+ </item>
+ </publish>
+ <publish-options>
+ <x xmlns="jabber:x:data">
+ <field var="FORM_TYPE" type="hidden">
+ <value>http://jabber.org/protocol/pubsub#publish-options</value>
+ </field>
+ <field var="pubsub#access_model">
+ <value>whitelist</value>
+ </field>
+ </x>
+ </publish-options>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type='error' id='67eb1f47-1e69-4cb3-91e2-4d5943e72d4c'>
+ <error type='cancel'>
+ <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Field does not match: access_model</text>
+ <precondition-not-met xmlns='http://jabber.org/protocol/pubsub#errors'/>
+ </error>
+ </iq>
+
+Romeo disconnects
+
diff --git a/spec/scansion/pubsub_resend_on_sub.scs b/spec/scansion/pubsub_resend_on_sub.scs
new file mode 100644
index 00000000..cfce8934
--- /dev/null
+++ b/spec/scansion/pubsub_resend_on_sub.scs
@@ -0,0 +1,152 @@
+# Pubsub: Send last item on subscribe #1436
+
+[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'/>
+
+Romeo sends:
+ <iq to="pubsub.localhost" id="config-never" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="princely_musings">
+ <x xmlns="jabber:x:data" type="submit">
+ <field var="FORM_TYPE" type="hidden">
+ <value>http://jabber.org/protocol/pubsub#node_config</value>
+ </field>
+ <field type='list-single' var='pubsub#send_last_published_item'>
+ <value>never</value>
+ </field>
+ </x>
+ </configure>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq from="pubsub.localhost" id="config-never" type="result"/>
+
+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 connects
+
+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'/>
+
+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'/>
+
+Romeo sends:
+ <iq to="pubsub.localhost" id="config-on_sub" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="princely_musings">
+ <x xmlns="jabber:x:data" type="submit">
+ <field var="FORM_TYPE" type="hidden">
+ <value>http://jabber.org/protocol/pubsub#node_config</value>
+ </field>
+ <field type='list-single' var='pubsub#send_last_published_item'>
+ <value>on_sub</value>
+ </field>
+ </x>
+ </configure>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq from="pubsub.localhost" id="config-on_sub" type="result"/>
+
+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'/>
+
+Juliet receives:
+ <message type="headline" from="pubsub.localhost">
+ <event xmlns="http://jabber.org/protocol/pubsub#event">
+ <items node="princely_musings">
+ <item id="current" publisher="${Romeo's JID}">
+ <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='unsub2'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <unsubscribe node="princely_musings" jid="${Juliet's full JID}"/>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <iq type="result" id='unsub2'/>
+
+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/server_contact_info.scs b/spec/scansion/server_contact_info.scs
new file mode 100644
index 00000000..f33d0957
--- /dev/null
+++ b/spec/scansion/server_contact_info.scs
@@ -0,0 +1,81 @@
+# XEP-0157: Contact Addresses for XMPP Services
+# mod_server_contact_info
+
+[Client] Romeo
+ jid: romeo@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq type='get' id='lx2' to='localhost'>
+ <query xmlns='http://jabber.org/protocol/disco#info'/>
+ </iq>
+
+# Ignore other disco#info features, identities etc
+
+Romeo receives:
+ <iq from='localhost' id='lx2' type='result'>
+ <query xmlns='http://jabber.org/protocol/disco#info' scansion:strict='false'>
+ <x xmlns='jabber:x:data' type='result'>
+ <field type='hidden' var='FORM_TYPE'>
+ <value>http://jabber.org/network/serverinfo</value>
+ </field>
+ <field type='list-multi' var='abuse-addresses'>
+ <value>mailto:abuse@localhost</value>
+ <value>xmpp:abuse@localhost</value>
+ </field>
+ <field type='list-multi' var='admin-addresses'>
+ <value>mailto:admin@localhost</value>
+ <value>xmpp:admin@localhost</value>
+ </field>
+ <field type='list-multi' var='feedback-addresses'>
+ <value>http://localhost/feedback.html</value>
+ <value>mailto:feedback@localhost</value>
+ <value>xmpp:feedback@localhost</value>
+ </field>
+ <field type='list-multi' var='sales-addresses'>
+ <value>xmpp:sales@localhost</value>
+ </field>
+ <field type='list-multi' var='security-addresses'>
+ <value>xmpp:security@localhost</value>
+ </field>
+ <field type='list-multi' var='status-addresses'>
+ <value>gopher://status.localhost</value>
+ </field>
+ <field type='list-multi' var='support-addresses'>
+ <value>https://localhost/support.html</value>
+ <value>xmpp:support@localhost</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+
+Romeo sends:
+ <iq type='get' id='lx2' to='conference.localhost'>
+ <query xmlns='http://jabber.org/protocol/disco#info'/>
+ </iq>
+
+ <iq from='localhost' id='lx2' type='result'>
+ <query xmlns='http://jabber.org/protocol/disco#info' scansion:strict='false'>
+ <x xmlns='jabber:x:data' type='result'>
+ <field type='hidden' var='FORM_TYPE'>
+ <value>http://jabber.org/network/serverinfo</value>
+ </field>
+ <field type='list-multi' var='abuse-addresses'/>
+ <field type='list-multi' var='admin-addresses'>
+ <value>xmpp:admin@localhost</value>
+ </field>
+ <field type='list-multi' var='feedback-addresses'/>
+ <field type='list-multi' var='sales-addresses'/>
+ <field type='list-multi' var='security-addresses'/>
+ <field type='list-multi' var='status-addresses'/>
+ <field type='list-multi' var='support-addresses'/>
+ </x>
+ </query>
+ </iq>
+
+Romeo disconnects
diff --git a/spec/scansion/tombstones.scs b/spec/scansion/tombstones.scs
new file mode 100644
index 00000000..dd07110e
--- /dev/null
+++ b/spec/scansion/tombstones.scs
@@ -0,0 +1,38 @@
+# Tombstones
+
+[Client] Romeo
+ jid: romeo@localhost
+ password: password
+
+[Client] Juliet
+ jid: juliet-tombstones@localhost
+ password: password
+
+---------
+
+Romeo connects
+
+Juliet connects
+
+Juliet sends:
+ <iq type="set" id="bye">
+ <query xmlns="jabber:iq:register">
+ <remove/>
+ </query>
+ </iq>
+
+# Scansion gets disconnected right after this with a stream error makes
+# scansion itself abort, so we preemptively disconnect to avoid that
+# Juliet receives:
+# <iq type="result" id="bye"/>
+
+Juliet disconnects
+
+Romeo sends:
+ <presence type="probe" to="${Juliet's JID}"/>
+
+Romeo receives:
+ <presence type="error" from="${Juliet's JID}"/>
+
+Romeo receives:
+ <presence type="unsubscribed" from="${Juliet's JID}"/>
diff --git a/spec/scansion/uptime.scs b/spec/scansion/uptime.scs
new file mode 100644
index 00000000..188b9eb5
--- /dev/null
+++ b/spec/scansion/uptime.scs
@@ -0,0 +1,21 @@
+# XEP-0012: Last Activity / mod_uptime
+
+[Client] Romeo
+ jid: romeo@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq id='a' type='get' to='localhost'>
+ <query xmlns='jabber:iq:last'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' id='a' from='localhost'>
+ <query xmlns='jabber:iq:last' seconds='0'/>
+ </iq>
+
+Romeo disconnects
diff --git a/spec/scansion/version.scs b/spec/scansion/version.scs
new file mode 100644
index 00000000..6c841dd9
--- /dev/null
+++ b/spec/scansion/version.scs
@@ -0,0 +1,27 @@
+# XEP-0092: Software Version / mod_version
+
+[Client] Romeo
+ password: password
+ jid: romeo@localhost/dfaZpuxV
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq id='lx2' to='localhost' type='get'>
+ <query xmlns='jabber:iq:version'/>
+ </iq>
+
+# Version string would vary so we can't do an exact match atm
+# Inclusion of <os/> is disabled in the config, it should be absent
+Romeo receives:
+ <iq id='lx2' from='localhost' type='result'>
+ <query xmlns='jabber:iq:version' scansion:strict='true'>
+ <name>Prosody</name>
+ <version scansion:strict='false'/>
+ </query>
+ </iq>
+
+
+Romeo disconnects