mirror of https://github.com/FluuxIO/go-xmpp
PubSub protocol support (#142)
* PubSub protocol support Added support for : - XEP-0050 (Command)) - XEP-0060 (PubSub) - XEP-0004 (Forms) Fixed the NewClient function by adding parsing of the domain from the JID if no domain is provided in transport config. Updated xmpp_jukebox example * Delete useless pubsub errors * README.md update Fixed import in echo example * Typo * Fixed raw send on client example * Fixed jukebox example and added a README.mdpull/144/head
parent
6e2ba9ca57
commit
947fcf0432
@ -0,0 +1,37 @@
|
||||
# Jukebox example
|
||||
|
||||
## Requirements
|
||||
- You need mpg123 installed on your computer because the example runs it as a command :
|
||||
[Official MPG123 website](https://mpg123.de/)
|
||||
Most linux distributions have a package for it.
|
||||
- You need a soundcloud ID to play a music from the website through mpg123. You currently cannot play music files with this example.
|
||||
Your user ID is available in your account settings on the [soundcloud website](https://soundcloud.com/)
|
||||
**One is provided for convenience.**
|
||||
- You need a running jabber server. You can run your local instance of [ejabberd](https://www.ejabberd.im/) for example.
|
||||
- You need a registered user on the running jabber server.
|
||||
|
||||
## Run
|
||||
You can edit the soundcloud ID in the example file with your own, or use the provided one :
|
||||
```go
|
||||
const scClientID = "dde6a0075614ac4f3bea423863076b22"
|
||||
```
|
||||
|
||||
To run the example, build it with (while in the example directory) :
|
||||
```
|
||||
go build xmpp_jukebox.go
|
||||
```
|
||||
|
||||
then run it with (update the command arguments accordingly):
|
||||
```
|
||||
./xmpp_jukebox -jid=MY_USERE@MY_DOMAIN/jukebox -password=MY_PASSWORD -address=MY_SERVER:MY_SERVER_PORT
|
||||
```
|
||||
Make sure to have a resource, for instance "/jukebox", on your jid.
|
||||
|
||||
Then you can send the following stanza to "MY_USERE@MY_DOMAIN/jukebox" (with the resource) to play a song (update the soundcloud URL accordingly) :
|
||||
```xml
|
||||
<iq id="1" to="MY_USERE@MY_DOMAIN/jukebox" type="set">
|
||||
<set xml:lang="en" xmlns="urn:xmpp:iot:control">
|
||||
<string name="url" value="https://soundcloud.com/UPDATE/ME"/>
|
||||
</set>
|
||||
</iq>
|
||||
```
|
@ -0,0 +1,136 @@
|
||||
package stanza
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// Implements the XEP-0050 extension
|
||||
|
||||
const (
|
||||
CommandActionCancel = "cancel"
|
||||
CommandActionComplete = "complete"
|
||||
CommandActionExecute = "execute"
|
||||
CommandActionNext = "next"
|
||||
CommandActionPrevious = "prev"
|
||||
|
||||
CommandStatusCancelled = "canceled"
|
||||
CommandStatusCompleted = "completed"
|
||||
CommandStatusExecuting = "executing"
|
||||
|
||||
CommandNoteTypeErr = "error"
|
||||
CommandNoteTypeInfo = "info"
|
||||
CommandNoteTypeWarn = "warn"
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
XMLName xml.Name `xml:"http://jabber.org/protocol/commands command"`
|
||||
|
||||
CommandElement CommandElement `xml:",any"`
|
||||
|
||||
BadAction *struct{} `xml:"bad-action,omitempty"`
|
||||
BadLocale *struct{} `xml:"bad-locale,omitempty"`
|
||||
BadPayload *struct{} `xml:"bad-payload,omitempty"`
|
||||
BadSessionId *struct{} `xml:"bad-sessionid,omitempty"`
|
||||
MalformedAction *struct{} `xml:"malformed-action,omitempty"`
|
||||
SessionExpired *struct{} `xml:"session-expired,omitempty"`
|
||||
|
||||
// Attributes
|
||||
Action string `xml:"action,attr,omitempty"`
|
||||
Node string `xml:"node,attr"`
|
||||
SessionId string `xml:"sessionid,attr,omitempty"`
|
||||
Status string `xml:"status,attr,omitempty"`
|
||||
Lang string `xml:"lang,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Command) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
}
|
||||
|
||||
type CommandElement interface {
|
||||
Ref() string
|
||||
}
|
||||
|
||||
type Actions struct {
|
||||
Prev *struct{} `xml:"prev,omitempty"`
|
||||
Next *struct{} `xml:"next,omitempty"`
|
||||
Complete *struct{} `xml:"complete,omitempty"`
|
||||
|
||||
Execute string `xml:"execute,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (a *Actions) Ref() string {
|
||||
return "actions"
|
||||
}
|
||||
|
||||
type Note struct {
|
||||
Text string `xml:",cdata"`
|
||||
Type string `xml:"type,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (n *Note) Ref() string {
|
||||
return "note"
|
||||
}
|
||||
|
||||
func (n *Node) Ref() string {
|
||||
return "node"
|
||||
}
|
||||
|
||||
func (c *Command) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
c.XMLName = start.Name
|
||||
|
||||
// Extract packet attributes
|
||||
for _, attr := range start.Attr {
|
||||
if attr.Name.Local == "action" {
|
||||
c.Action = attr.Value
|
||||
}
|
||||
if attr.Name.Local == "node" {
|
||||
c.Node = attr.Value
|
||||
}
|
||||
if attr.Name.Local == "sessionid" {
|
||||
c.SessionId = attr.Value
|
||||
}
|
||||
if attr.Name.Local == "status" {
|
||||
c.Status = attr.Value
|
||||
}
|
||||
if attr.Name.Local == "lang" {
|
||||
c.Lang = attr.Value
|
||||
}
|
||||
}
|
||||
|
||||
// decode inner elements
|
||||
for {
|
||||
t, err := d.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch tt := t.(type) {
|
||||
|
||||
case xml.StartElement:
|
||||
// Decode sub-elements
|
||||
var err error
|
||||
switch tt.Name.Local {
|
||||
|
||||
case "affiliations":
|
||||
a := Actions{}
|
||||
d.DecodeElement(&a, &tt)
|
||||
c.CommandElement = &a
|
||||
case "configure":
|
||||
nt := Note{}
|
||||
d.DecodeElement(&nt, &tt)
|
||||
c.CommandElement = &nt
|
||||
default:
|
||||
n := Node{}
|
||||
e := d.DecodeElement(&n, &tt)
|
||||
_ = e
|
||||
c.CommandElement = &n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case xml.EndElement:
|
||||
if tt == start.End() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package stanza_test
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMarshalCommands(t *testing.T) {
|
||||
input := "<command xmlns=\"http://jabber.org/protocol/commands\" node=\"list\" sessionid=\"list:20020923T213616Z-700\" status=\"completed\"><x " +
|
||||
"xmlns=\"jabber:x:data\" type=\"result\"><title xmlns=\"jabber:x:data\">Available Servi" +
|
||||
"ces</title><reported xmlns=\"jabber:x:data\"><field xmlns=\"jabber:x:data\" label=\"S" +
|
||||
"ervice\" var=\"service\"></field><field xmlns=\"jabber:x:data\" label=\"Single-User mo" +
|
||||
"de\" var=\"runlevel-1\"></field><field xmlns=\"jabber:x:data\" label=\"Non-Networked M" +
|
||||
"ulti-User mode\" var=\"runlevel-2\"></field><field xmlns=\"jabber:x:data\" label=\"Ful" +
|
||||
"l Multi-User mode\" var=\"runlevel-3\"></field><field xmlns=\"jabber:x:data\" label=\"" +
|
||||
"X-Window mode\" var=\"runlevel-5\"></field></reported><item xmlns=\"jabber:x:data\"><" +
|
||||
"field xmlns=\"jabber:x:data\" var=\"service\"><value xmlns=\"jabber:x:data\">httpd</va" +
|
||||
"lue></field><field xmlns=\"jabber:x:data\" var=\"runlevel-1\"><value xmlns=\"jabber:x" +
|
||||
":data\">off</value></field><field xmlns=\"jabber:x:data\" var=\"runlevel-2\"><value x" +
|
||||
"mlns=\"jabber:x:data\">off</value></field><field xmlns=\"jabber:x:data\" var=\"runlev" +
|
||||
"el-3\"><value xmlns=\"jabber:x:data\">on</value></field><field xmlns=\"jabber:x:data" +
|
||||
"\" var=\"runlevel-5\"><value xmlns=\"jabber:x:data\">on</value></field></item><item x" +
|
||||
"mlns=\"jabber:x:data\"><field xmlns=\"jabber:x:data\" var=\"service\"><value xmlns=\"ja" +
|
||||
"bber:x:data\">postgresql</value></field><field xmlns=\"jabber:x:data\" var=\"runleve" +
|
||||
"l-1\"><value xmlns=\"jabber:x:data\">off</value></field><field xmlns=\"jabber:x:data" +
|
||||
"\" var=\"runlevel-2\"><value xmlns=\"jabber:x:data\">off</value></field><field xmlns=" +
|
||||
"\"jabber:x:data\" var=\"runlevel-3\"><value xmlns=\"jabber:x:data\">on</value></field>" +
|
||||
"<field xmlns=\"jabber:x:data\" var=\"runlevel-5\"><value xmlns=\"jabber:x:data\">on</v" +
|
||||
"alue></field></item><item xmlns=\"jabber:x:data\"><field xmlns=\"jabber:x:data\" var" +
|
||||
"=\"service\"><value xmlns=\"jabber:x:data\">jabberd</value></field><field xmlns=\"jab" +
|
||||
"ber:x:data\" var=\"runlevel-1\"><value xmlns=\"jabber:x:data\">off</value></field><fi" +
|
||||
"eld xmlns=\"jabber:x:data\" var=\"runlevel-2\"><value xmlns=\"jabber:x:data\">off</val" +
|
||||
"ue></field><field xmlns=\"jabber:x:data\" var=\"runlevel-3\"><value xmlns=\"jabber:x:" +
|
||||
"data\">on</value></field><field xmlns=\"jabber:x:data\" var=\"runlevel-5\"><value xml" +
|
||||
"ns=\"jabber:x:data\">on</value></field></item></x></command>"
|
||||
|
||||
var c stanza.Command
|
||||
err := xml.Unmarshal([]byte(input), &c)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal initial input")
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(c)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal unmarshalled input")
|
||||
}
|
||||
|
||||
if err := compareMarshal(input, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package stanza
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
type FormType string
|
||||
|
||||
const (
|
||||
FormTypeCancel = "cancel"
|
||||
FormTypeForm = "form"
|
||||
FormTypeResult = "result"
|
||||
FormTypeSubmit = "submit"
|
||||
)
|
||||
|
||||
// See XEP-0004 and XEP-0068
|
||||
// Pointer semantics
|
||||
type Form struct {
|
||||
XMLName xml.Name `xml:"jabber:x:data x"`
|
||||
Instructions []string `xml:"instructions"`
|
||||
Title string `xml:"title,omitempty"`
|
||||
Fields []Field `xml:"field,omitempty"`
|
||||
Reported *FormItem `xml:"reported"`
|
||||
Items []FormItem
|
||||
Type string `xml:"type,attr"`
|
||||
}
|
||||
|
||||
type FormItem struct {
|
||||
Fields []Field
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
XMLName xml.Name `xml:"field"`
|
||||
Description string `xml:"desc,omitempty"`
|
||||
Required *string `xml:"required"`
|
||||
ValuesList []string `xml:"value"`
|
||||
Options []Option `xml:"option,omitempty"`
|
||||
Var string `xml:"var,attr,omitempty"`
|
||||
Type string `xml:"type,attr,omitempty"`
|
||||
Label string `xml:"label,attr,omitempty"`
|
||||
}
|
||||
|
||||
func NewForm(fields []Field, formType string) *Form {
|
||||
return &Form{
|
||||
Type: formType,
|
||||
Fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
type FieldType string
|
||||
|
||||
const (
|
||||
FieldTypeBool = "boolean"
|
||||
FieldTypeFixed = "fixed"
|
||||
FieldTypeHidden = "hidden"
|
||||
FieldTypeJidMulti = "jid-multi"
|
||||
FieldTypeJidSingle = "jid-single"
|
||||
FieldTypeListMulti = "list-multi"
|
||||
FieldTypeListSingle = "list-single"
|
||||
FieldTypeTextMulti = "text-multi"
|
||||
FieldTypeTextPrivate = "text-private"
|
||||
FieldTypeTextSingle = "text-Single"
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
XMLName xml.Name `xml:"option"`
|
||||
Label string `xml:"label,attr,omitempty"`
|
||||
ValuesList []string `xml:"value"`
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package stanza
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
formSubmit = "<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 var=\"pubsub#title\">" +
|
||||
"<value>Princely Musings (Atom)</value>" +
|
||||
"</field>" +
|
||||
"<field var=\"pubsub#deliver_notifications\">" +
|
||||
"<value>1</value>" +
|
||||
"</field>" +
|
||||
"<field var=\"pubsub#access_model\">" +
|
||||
"<value>roster</value>" +
|
||||
"</field>" +
|
||||
"<field var=\"pubsub#roster_groups_allowed\">" +
|
||||
"<value>friends</value>" +
|
||||
"<value>servants</value>" +
|
||||
"<value>courtiers</value>" +
|
||||
"</field>" +
|
||||
"<field var=\"pubsub#type\">" +
|
||||
"<value>http://www.w3.org/2005/Atom</value>" +
|
||||
"</field>" +
|
||||
"<field var=\"pubsub#notification_type\" type=\"list-single\"" +
|
||||
"label=\"Specify the delivery style for event notifications\">" +
|
||||
"<value>headline</value>" +
|
||||
"<option>" +
|
||||
"<value>normal</value>" +
|
||||
"</option>" +
|
||||
"<option>" +
|
||||
"<value>headline</value>" +
|
||||
"</option>" +
|
||||
"</field>" +
|
||||
"</x>" +
|
||||
"</configure>" +
|
||||
"</pubsub>"
|
||||
|
||||
clientJid = "hamlet@denmark.lit/elsinore"
|
||||
serviceJid = "pubsub.shakespeare.lit"
|
||||
iqId = "config1"
|
||||
serviceNode = "princely_musings"
|
||||
)
|
||||
|
||||
func TestMarshalFormSubmit(t *testing.T) {
|
||||
formIQ := NewIQ(Attrs{From: clientJid, To: serviceJid, Id: iqId, Type: IQTypeSet})
|
||||
formIQ.Payload = &PubSubOwner{
|
||||
OwnerUseCase: &ConfigureOwner{
|
||||
Node: serviceNode,
|
||||
Form: &Form{
|
||||
Type: FormTypeSubmit,
|
||||
Fields: []Field{
|
||||
{Var: "FORM_TYPE", Type: FieldTypeHidden, ValuesList: []string{"http://jabber.org/protocol/pubsub#node_config"}},
|
||||
{Var: "pubsub#title", ValuesList: []string{"Princely Musings (Atom)"}},
|
||||
{Var: "pubsub#deliver_notifications", ValuesList: []string{"1"}},
|
||||
{Var: "pubsub#access_model", ValuesList: []string{"roster"}},
|
||||
{Var: "pubsub#roster_groups_allowed", ValuesList: []string{"friends", "servants", "courtiers"}},
|
||||
{Var: "pubsub#type", ValuesList: []string{"http://www.w3.org/2005/Atom"}},
|
||||
{
|
||||
Var: "pubsub#notification_type",
|
||||
Type: "list-single",
|
||||
Label: "Specify the delivery style for event notifications",
|
||||
ValuesList: []string{"headline"},
|
||||
Options: []Option{
|
||||
{ValuesList: []string{"normal"}},
|
||||
{ValuesList: []string{"headline"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
b, err := xml.Marshal(formIQ.Payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not marshal formIQ : %v", err)
|
||||
}
|
||||
|
||||
if strings.ReplaceAll(string(b), " ", "") != strings.ReplaceAll(formSubmit, " ", "") {
|
||||
t.Fatalf("Expected formIQ and marshalled one are different.\nExepected : %s\nMarshalled : %s", formSubmit, string(b))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUnmarshalFormSubmit(t *testing.T) {
|
||||
var f PubSubOwner
|
||||
mErr := xml.Unmarshal([]byte(formSubmit), &f)
|
||||
if mErr != nil {
|
||||
t.Fatalf("failed to unmarshal formSubmit ! %s", mErr)
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(&f)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal formSubmit")
|
||||
}
|
||||
|
||||
if strings.ReplaceAll(string(data), " ", "") != strings.ReplaceAll(formSubmit, " ", "") {
|
||||
t.Fatalf("failed unmarshal/marshal for formSubmit : %s\n%s", string(data), formSubmit)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package xmpp
|
||||
package stanza
|
||||
|
||||
import (
|
||||
"testing"
|
@ -0,0 +1,214 @@
|
||||
package stanza
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// Implementation of the http://jabber.org/protocol/pubsub#event namespace
|
||||
type PubSubEvent struct {
|
||||
XMLName xml.Name `xml:"http://jabber.org/protocol/pubsub#event event"`
|
||||
MsgExtension
|
||||
EventElement EventElement
|
||||
//List ItemsEvent
|
||||
}
|
||||
|
||||
func init() {
|
||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: "http://jabber.org/protocol/pubsub#event", Local: "event"}, PubSubEvent{})
|
||||
}
|
||||
|
||||
type EventElement interface {
|
||||
Name() string
|
||||
}
|
||||
|
||||
// *********************
|
||||
// Collection
|
||||
// *********************
|
||||
|
||||
const PubSubCollectionEventName = "Collection"
|
||||
|
||||
type CollectionEvent struct {
|
||||
AssocDisassoc AssocDisassoc
|
||||
Node string `xml:"node,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (c CollectionEvent) Name() string {
|
||||
return PubSubCollectionEventName
|
||||
}
|
||||
|
||||
// *********************
|
||||
// Associate/Disassociate
|
||||
// *********************
|
||||
type AssocDisassoc interface {
|
||||
GetAssocDisassoc() string
|
||||
}
|
||||
|
||||
// *********************
|
||||
// Associate
|
||||
// *********************
|
||||
const Assoc = "Associate"
|
||||
|
||||
type AssociateEvent struct {
|
||||
XMLName xml.Name `xml:"associate"`
|
||||
Node string `xml:"node,attr"`
|
||||
}
|
||||
|
||||
func (a *AssociateEvent) GetAssocDisassoc() string {
|
||||
return Assoc
|
||||
}
|
||||
|
||||
// *********************
|
||||
// Disassociate
|
||||
// *********************
|
||||
const Disassoc = "Disassociate"
|
||||
|
||||
type DisassociateEvent struct {
|
||||
XMLName xml.Name `xml:"disassociate"`
|
||||
Node string `xml:"node,attr"`
|
||||
}
|
||||
|
||||
func (e *DisassociateEvent) GetAssocDisassoc() string {
|
||||
return Disassoc
|
||||
}
|
||||
|
||||
// *********************
|
||||
// Configuration
|
||||
// *********************
|
||||
|
||||
const PubSubConfigEventName = "Configuration"
|
||||
|
||||
type ConfigurationEvent struct {
|
||||
Node string `xml:"node,attr,omitempty"`
|
||||
Form *Form
|
||||
}
|
||||
|
||||
func (c ConfigurationEvent) Name() string {
|
||||
return PubSubConfigEventName
|
||||
}
|
||||
|
||||
// *********************
|
||||
// Delete
|
||||
// *********************
|
||||
const PubSubDeleteEventName = "Delete"
|
||||
|
||||
type DeleteEvent struct {
|
||||
Node string `xml:"node,attr"`
|
||||
Redirect *RedirectEvent `xml:"redirect"`
|
||||
}
|
||||
|
||||
func (c DeleteEvent) Name() string {
|
||||
return PubSubConfigEventName
|
||||
}
|
||||
|
||||
// *********************
|
||||
// Redirect
|
||||
// *********************
|
||||
type RedirectEvent struct {
|
||||
URI string `xml:"uri,attr"`
|
||||
}
|
||||
|
||||
// *********************
|
||||
// List
|
||||
// *********************
|
||||
|
||||
const PubSubItemsEventName = "List"
|
||||
|
||||
type ItemsEvent struct {
|
||||
XMLName xml.Name `xml:"items"`
|
||||
Items []ItemEvent `xml:"item,omitempty"`
|
||||
Node string `xml:"node,attr"`
|
||||
Retract *RetractEvent `xml:"retract"`
|
||||
}
|
||||
|
||||
type ItemEvent struct {
|
||||
XMLName xml.Name `xml:"item"`
|
||||
Id string `xml:"id,attr,omitempty"`
|
||||
Publisher string `xml:"publisher,attr,omitempty"`
|
||||
Any *Node `xml:",any"`
|
||||
}
|
||||
|
||||
func (i ItemsEvent) Name() string {
|
||||
return PubSubItemsEventName
|
||||
}
|
||||
|
||||
// *********************
|
||||
// List
|
||||
// *********************
|
||||
|
||||
type RetractEvent struct {
|
||||
XMLName xml.Name `xml:"retract"`
|
||||
ID string `xml:"node,attr"`
|
||||
}
|
||||
|
||||
// *********************
|
||||
// Purge
|
||||
// *********************
|
||||
const PubSubPurgeEventName = "Purge"
|
||||
|
||||
type PurgeEvent struct {
|
||||
XMLName xml.Name `xml:"purge"`
|
||||
Node string `xml:"node,attr"`
|
||||
}
|
||||
|
||||
func (p PurgeEvent) Name() string {
|
||||
return PubSubPurgeEventName
|
||||
}
|
||||
|
||||
// *********************
|
||||
// Subscription
|
||||
// *********************
|
||||
const PubSubSubscriptionEventName = "Subscription"
|
||||
|
||||
type SubscriptionEvent struct {
|
||||
SubStatus string `xml:"subscription,attr,omitempty"`
|
||||
Expiry string `xml:"expiry,attr,omitempty"`
|
||||
SubInfo `xml:",omitempty"`
|
||||
}
|
||||
|
||||
func (s SubscriptionEvent) Name() string {
|
||||
return PubSubSubscriptionEventName
|
||||
}
|
||||
|
||||
func (pse *PubSubEvent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
pse.XMLName = start.Name
|
||||
// decode inner elements
|
||||
for {
|
||||
t, err := d.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ee EventElement
|
||||
switch tt := t.(type) {
|
||||
case xml.StartElement:
|
||||
switch tt.Name.Local {
|
||||
case "collection":
|
||||
ee = &CollectionEvent{}
|
||||
case "configuration":
|
||||
ee = &ConfigurationEvent{}
|
||||
case "delete":
|
||||
ee = &DeleteEvent{}
|
||||
case "items":
|
||||
ee = &ItemsEvent{}
|
||||
case "purge":
|
||||
ee = &PurgeEvent{}
|
||||
case "subscription":
|
||||
ee = &SubscriptionEvent{}
|
||||
default:
|
||||
ee = nil
|
||||
}
|
||||
// known child element found, decode it
|
||||
if ee != nil {
|
||||
err = d.DecodeElement(ee, &tt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pse.EventElement = ee
|
||||
}
|
||||
case xml.EndElement:
|
||||
if tt == start.End() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
package stanza_test
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeMsgEvent(t *testing.T) {
|
||||
str := `<message from='pubsub.shakespeare.lit' to='francisco@denmark.lit' id='foo'>
|
||||
<event xmlns='http://jabber.org/protocol/pubsub#event'>
|
||||
<items node='princely_musings'>
|
||||
<item id='ae890ac52d0df67ed7cfdf51b644e901'>
|
||||
<entry xmlns='http://www.w3.org/2005/Atom'>
|
||||
<title>Soliloquy</title>
|
||||
<summary>
|
||||
To be, or not to be: that is the question:
|
||||
Whether 'tis nobler in the mind to suffer
|
||||
The slings and arrows of outrageous fortune,
|
||||
Or to take arms against a sea of troubles,
|
||||
And by opposing end them?
|
||||
</summary>
|
||||
<link rel='alternate' type='text/html'
|
||||
href='http://denmark.lit/2003/12/13/atom03'/>
|
||||
<id>tag:denmark.lit,2003:entry-32397</id>
|
||||
<published>2003-12-13T18:30:02Z</published>
|
||||
<updated>2003-12-13T18:30:02Z</updated>
|
||||
</entry>
|
||||
</item>
|
||||
</items>
|
||||
</event>
|
||||
</message>
|
||||
`
|
||||
parsedMessage := stanza.Message{}
|
||||
if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil {
|
||||
t.Errorf("message receipt unmarshall error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if parsedMessage.Body != "" {
|
||||
t.Errorf("Unexpected body: '%s'", parsedMessage.Body)
|
||||
}
|
||||
|
||||
if len(parsedMessage.Extensions) < 1 {
|
||||
t.Errorf("no extension found on parsed message")
|
||||
return
|
||||
}
|
||||
|
||||
switch ext := parsedMessage.Extensions[0].(type) {
|
||||
case *stanza.PubSubEvent:
|
||||
if ext.XMLName.Local != "event" {
|
||||
t.Fatalf("unexpected extension: %s:%s", ext.XMLName.Space, ext.XMLName.Local)
|
||||
}
|
||||
tmp, ok := parsedMessage.Extensions[0].(*stanza.PubSubEvent)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected extension element: %s:%s", ext.XMLName.Space, ext.XMLName.Local)
|
||||
}
|
||||
ie, ok := tmp.EventElement.(*stanza.ItemsEvent)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected extension element: %s:%s", ext.XMLName.Space, ext.XMLName.Local)
|
||||
}
|
||||
if ie.Items[0].Any.Nodes[0].Content != "Soliloquy" {
|
||||
t.Fatalf("could not read title ! Read this : %s", ie.Items[0].Any.Nodes[0].Content)
|
||||
}
|
||||
|
||||
if len(ie.Items[0].Any.Nodes) != 6 {
|
||||
t.Fatalf("some nodes were not correctly parsed")
|
||||
}
|
||||
default:
|
||||
t.Fatalf("could not find pubsub event extension")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEncodeEvent(t *testing.T) {
|
||||
expected := "<message><event xmlns=\"http://jabber.org/protocol/pubsub#event\">" +
|
||||
"<items node=\"princely_musings\"><item id=\"ae890ac52d0df67ed7cfdf51b644e901\">" +
|
||||
"<entry xmlns=\"http://www.w3.org/2005/Atom\"><title>My pub item title</title>" +
|
||||
"<summary>My pub item content summary</summary><link rel=\"alternate\" " +
|
||||
"type=\"text/html\" href=\"http://denmark.lit/2003/12/13/atom03\">" +
|
||||
"</link><id>My pub item content ID</id><published>2003-12-13T18:30:02Z</published>" +
|
||||
"<updated>2003-12-13T18:30:02Z</updated></entry></item></items></event></message>"
|
||||
message := stanza.Message{
|
||||
Extensions: []stanza.MsgExtension{
|
||||
stanza.PubSubEvent{
|
||||
EventElement: stanza.ItemsEvent{
|
||||
Items: []stanza.ItemEvent{
|
||||
{
|
||||
Id: "ae890ac52d0df67ed7cfdf51b644e901",
|
||||
Any: &stanza.Node{
|
||||
XMLName: xml.Name{
|
||||
Space: "http://www.w3.org/2005/Atom",
|
||||
Local: "entry",
|
||||
},
|
||||
Attrs: nil,
|
||||
Content: "",
|
||||
Nodes: []stanza.Node{
|
||||
{
|
||||
XMLName: xml.Name{Space: "", Local: "title"},
|
||||
Attrs: nil,
|
||||
Content: "My pub item title",
|
||||
Nodes: nil,
|
||||
},
|
||||
{
|
||||
XMLName: xml.Name{Space: "", Local: "summary"},
|
||||
Attrs: nil,
|
||||
Content: "My pub item content summary",
|
||||
Nodes: nil,
|
||||
},
|
||||
{
|
||||
XMLName: xml.Name{Space: "", Local: "link"},
|
||||
Attrs: []xml.Attr{
|
||||
{
|
||||
Name: xml.Name{Space: "", Local: "rel"},
|
||||
Value: "alternate",
|
||||
},
|
||||
{
|
||||
Name: xml.Name{Space: "", Local: "type"},
|
||||
Value: "text/html",
|
||||
},
|
||||
{
|
||||
Name: xml.Name{Space: "", Local: "href"},
|
||||
Value: "http://denmark.lit/2003/12/13/atom03",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
XMLName: xml.Name{Space: "", Local: "id"},
|
||||
Attrs: nil,
|
||||
Content: "My pub item content ID",
|
||||
Nodes: nil,
|
||||
},
|
||||
{
|
||||
XMLName: xml.Name{Space: "", Local: "published"},
|
||||
Attrs: nil,
|
||||
Content: "2003-12-13T18:30:02Z",
|
||||
Nodes: nil,
|
||||
},
|
||||
{
|
||||
XMLName: xml.Name{Space: "", Local: "updated"},
|
||||
Attrs: nil,
|
||||
Content: "2003-12-13T18:30:02Z",
|
||||
Nodes: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Node: "princely_musings",
|
||||
Retract: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data, _ := xml.Marshal(message)
|
||||
if strings.TrimSpace(string(data)) != strings.TrimSpace(expected) {
|
||||
t.Errorf("event was not encoded properly : \nexpected:%s \ngot: %s", expected, string(data))
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,377 @@
|
||||
package stanza
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PubSubOwner struct {
|
||||
XMLName xml.Name `xml:"http://jabber.org/protocol/pubsub#owner pubsub"`
|
||||
OwnerUseCase OwnerUseCase
|
||||
}
|
||||
|
||||
func (pso *PubSubOwner) Namespace() string {
|
||||
return pso.XMLName.Space
|
||||
}
|
||||
|
||||
type OwnerUseCase interface {
|
||||
UseCase() string
|
||||
}
|
||||
|
||||
type AffiliationsOwner struct {
|
||||
XMLName xml.Name `xml:"affiliations"`
|
||||
Affiliations []AffiliationOwner `xml:"affiliation,omitempty"`
|
||||
Node string `xml:"node,attr"`
|
||||
}
|
||||
|
||||
func (AffiliationsOwner) UseCase() string {
|
||||
return "affiliations"
|
||||
}
|
||||
|
||||
type AffiliationOwner struct {
|
||||
XMLName xml.Name `xml:"affiliation"`
|
||||
AffiliationStatus string `xml:"affiliation,attr"`
|
||||
Jid string `xml:"jid,attr"`
|
||||
}
|
||||
|
||||
const (
|
||||
AffiliationStatusMember = "member"
|
||||
AffiliationStatusNone = "none"
|
||||
AffiliationStatusOutcast = "outcast"
|
||||
AffiliationStatusOwner = "owner"
|
||||
AffiliationStatusPublisher = "publisher"
|
||||
AffiliationStatusPublishOnly = "publish-only"
|
||||
)
|
||||
|
||||
type ConfigureOwner struct {
|
||||
XMLName xml.Name `xml:"configure"`
|
||||
Node string `xml:"node,attr,omitempty"`
|
||||
Form *Form `xml:"x,omitempty"`
|
||||
}
|
||||
|
||||
func (*ConfigureOwner) UseCase() string {
|
||||
return "configure"
|
||||
}
|
||||
|
||||
type DefaultOwner struct {
|
||||
XMLName xml.Name `xml:"default"`
|
||||
Form *Form `xml:"x,omitempty"`
|
||||
}
|
||||
|
||||
func (*DefaultOwner) UseCase() string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
type DeleteOwner struct {
|
||||
XMLName xml.Name `xml:"delete"`
|
||||
RedirectOwner *RedirectOwner `xml:"redirect,omitempty"`
|
||||
Node string `xml:"node,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (*DeleteOwner) UseCase() string {
|
||||
return "delete"
|
||||
}
|
||||
|
||||
type RedirectOwner struct {
|
||||
XMLName xml.Name `xml:"redirect"`
|
||||
URI string `xml:"uri,attr"`
|
||||
}
|
||||
|
||||
type PurgeOwner struct {
|
||||
XMLName xml.Name `xml:"purge"`
|
||||
Node string `xml:"node,attr"`
|
||||
}
|
||||
|
||||
func (*PurgeOwner) UseCase() string {
|
||||
return "purge"
|
||||
}
|
||||
|
||||
type SubscriptionsOwner struct {
|
||||
XMLName xml.Name `xml:"subscriptions"`
|
||||
Subscriptions []SubscriptionOwner `xml:"subscription"`
|
||||
Node string `xml:"node,attr"`
|
||||
}
|
||||
|
||||
func (*SubscriptionsOwner) UseCase() string {
|
||||
return "subscriptions"
|
||||
}
|
||||
|
||||
type SubscriptionOwner struct {
|
||||
SubscriptionStatus string `xml:"subscription"`
|
||||
Jid string `xml:"jid,attr"`
|
||||
}
|
||||
|
||||
const (
|
||||
SubscriptionStatusNone = "none"
|
||||
SubscriptionStatusPending = "pending"
|
||||
SubscriptionStatusSubscribed = "subscribed"
|
||||
SubscriptionStatusUnconfigured = "unconfigured"
|
||||
)
|
||||
|
||||
// NewConfigureNode creates a request to configure a node on the given service.
|
||||
// A form will be returned by the service, to which the user must respond using for instance the NewFormSubmission function.
|
||||
// See 8.2 Configure a Node
|
||||
func NewConfigureNode(serviceId, nodeName string) (IQ, error) {
|
||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||
iq.Payload = &PubSubOwner{
|
||||
OwnerUseCase: &ConfigureOwner{Node: nodeName},
|
||||
}
|
||||
return iq, nil
|
||||
}
|
||||
|
||||
// NewDelNode creates a request to delete node "nodeID" from the "serviceId" service
|
||||
// See 8.4 Delete a Node
|
||||
func NewDelNode(serviceId, nodeID string) (IQ, error) {
|
||||
if strings.TrimSpace(nodeID) == "" {
|
||||
return IQ{}, errors.New("cannot delete a node without a target node ID")
|
||||
}
|
||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||
iq.Payload = &PubSubOwner{
|
||||
OwnerUseCase: &DeleteOwner{Node: nodeID},
|
||||
}
|
||||
return iq, nil
|
||||
}
|
||||
|
||||
// NewPurgeAllItems creates a new purge request for the "nodeId" node, at "serviceId" service
|
||||
// See 8.5 Purge All Node Items
|
||||
func NewPurgeAllItems(serviceId, nodeId string) (IQ, error) {
|
||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||
iq.Payload = &PubSubOwner{
|
||||
OwnerUseCase: &PurgeOwner{Node: nodeId},
|
||||
}
|
||||
return iq, nil
|
||||
}
|
||||
|
||||
// NewRequestDefaultConfig build a request to ask the service for the default config of its nodes
|
||||
// See 8.3 Request Default Node Configuration Options
|
||||
func NewRequestDefaultConfig(serviceId string) (IQ, error) {
|
||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||
iq.Payload = &PubSubOwner{
|
||||
OwnerUseCase: &DefaultOwner{},
|
||||
}
|
||||
return iq, nil
|
||||
}
|
||||
|
||||
// NewApproveSubRequest creates a new sub approval response to a request from the service to the owner of the node
|
||||
// In order to approve the request, the owner shall submit the form and set the "pubsub#allow" field to a value of "1" or "true"
|
||||
// For tracking purposes the message MUST reflect the 'id' attribute originally provided in the request.
|
||||
// See 8.6 Manage Subscription Requests
|
||||
func NewApproveSubRequest(serviceId, reqID string, apprForm *Form) (Message, error) {
|
||||
if serviceId == "" {
|
||||
return Message{}, errors.New("need a target service serviceId send approval serviceId")
|
||||
}
|
||||
if reqID == "" {
|
||||
return Message{}, errors.New("the request ID is empty but must be used for the approval")
|
||||
}
|
||||
if apprForm == nil {
|
||||
return Message{}, errors.New("approval form is nil")
|
||||
}
|
||||
apprMess := NewMessage(Attrs{To: serviceId})
|
||||
apprMess.Extensions = []MsgExtension{apprForm}
|
||||
apprMess.Id = reqID
|
||||
|
||||
return apprMess, nil
|
||||
}
|
||||
|
||||
// NewGetPendingSubRequests creates a new request for all pending subscriptions to all their nodes at a service
|
||||
// This feature MUST be implemented using the Ad-Hoc Commands (XEP-0050) protocol
|
||||
// 8.7 Process Pending Subscription Requests
|
||||
func NewGetPendingSubRequests(serviceId string) (IQ, error) {
|
||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||
iq.Payload = &Command{
|
||||
// the command name ('node' attribute of the command element) MUST have a value of "http://jabber.org/protocol/pubsub#get-pending"
|
||||
Node: "http://jabber.org/protocol/pubsub#get-pending",
|
||||
Action: CommandActionExecute,
|
||||
}
|
||||
return iq, nil
|
||||
}
|
||||
|
||||
// NewGetPendingSubRequests creates a new request for all pending subscriptions to be approved on a given node
|
||||
// Upon receiving the data form for managing subscription requests, the owner then MAY request pending subscription
|
||||
// approval requests for a given node.
|
||||
// See 8.7.4 Per-Node Request
|
||||
func NewApprovePendingSubRequest(serviceId, sessionId, nodeId string) (IQ, error) {
|
||||
if sessionId == "" {
|
||||
return IQ{}, errors.New("the sessionId must be maintained for the command")
|
||||
}
|
||||
|
||||
form := &Form{
|
||||
Type: FormTypeSubmit,
|
||||
Fields: []Field{{Var: "pubsub#node", ValuesList: []string{nodeId}}},
|
||||
}
|
||||
data, err := xml.Marshal(form)
|
||||
if err != nil {
|
||||
return IQ{}, err
|
||||
}
|
||||
var n Node
|
||||
xml.Unmarshal(data, &n)
|
||||
|
||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||
iq.Payload = &Command{
|
||||
// the command name ('node' attribute of the command element) MUST have a value of "http://jabber.org/protocol/pubsub#get-pending"
|
||||
Node: "http://jabber.org/protocol/pubsub#get-pending",
|
||||
Action: CommandActionExecute,
|
||||
SessionId: sessionId,
|
||||
CommandElement: &n,
|
||||
}
|
||||
return iq, nil
|
||||
}
|
||||
|
||||
// NewSubListRequest creates a request to list subscriptions of the client, for all nodes at the service.
|
||||
// It's a Get type IQ
|
||||
// 8.8.1 Retrieve Subscriptions
|
||||
func NewSubListRqPl(serviceId, nodeID string) (IQ, error) {
|
||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||
iq.Payload = &PubSubOwner{
|
||||
OwnerUseCase: &SubscriptionsOwner{Node: nodeID},
|
||||
}
|
||||
return iq, nil
|
||||
}
|
||||
|
||||
func NewSubsForEntitiesRequest(serviceId, nodeID string, subs []SubscriptionOwner) (IQ, error) {
|
||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||
iq.Payload = &PubSubOwner{
|
||||
OwnerUseCase: &SubscriptionsOwner{Node: nodeID, Subscriptions: subs},
|
||||
}
|
||||
return iq, nil
|
||||
}
|
||||
|
||||
// NewModifAffiliationRequest creates a request to either modify one or more affiliations, or delete one or more affiliations
|
||||
// 8.9.2 Modify Affiliation & 8.9.2.4 Multiple Simultaneous Modifications & 8.9.3 Delete an Entity (just set the status to "none")
|
||||
func NewModifAffiliationRequest(serviceId, nodeID string, newAffils []AffiliationOwner) (IQ, error) {
|
||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||
iq.Payload = &PubSubOwner{
|
||||
OwnerUseCase: &AffiliationsOwner{
|
||||
Node: nodeID,
|
||||
Affiliations: newAffils,
|
||||
},
|
||||
}
|
||||
return iq, nil
|
||||
}
|
||||
|
||||
// NewAffiliationListRequest creates a request to list all affiliated entities
|
||||
// See 8.9.1 Retrieve List List
|
||||
func NewAffiliationListRequest(serviceId, nodeID string) (IQ, error) {
|
||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||
iq.Payload = &PubSubOwner{
|
||||
OwnerUseCase: &AffiliationsOwner{
|
||||
Node: nodeID,
|
||||
},
|
||||
}
|
||||
return iq, nil
|
||||
}
|
||||
|
||||
// GetFormFields gets the fields from a form in a IQ stanza of type result, as a map.
|
||||
// Key is the "var" attribute of the field, and field is the value.
|
||||
// The user can then select and modify the fields they want to alter, and submit a new form to the service using the
|
||||
// NewFormSubmission function to build the IQ.
|
||||
// TODO : remove restriction on IQ type ?
|
||||
func (iq *IQ) GetFormFields() (map[string]Field, error) {
|
||||
if iq.Type != IQTypeResult {
|
||||
return nil, errors.New("this IQ is not a result type IQ. Cannot extract the form from it")
|
||||
}
|
||||
switch payload := iq.Payload.(type) {
|
||||
// We support IOT Control IQ
|
||||
case *PubSubGeneric:
|
||||
fieldMap := make(map[string]Field)
|
||||
for _, elt := range payload.Configure.Form.Fields {
|
||||
fieldMap[elt.Var] = elt
|
||||
}
|
||||
return fieldMap, nil
|
||||
case *PubSubOwner:
|
||||
fieldMap := make(map[string]Field)
|
||||
co, ok := payload.OwnerUseCase.(*ConfigureOwner)
|
||||
if !ok {
|
||||
return nil, errors.New("this IQ does not contain a PubSub payload with a configure tag for the owner namespace")
|
||||
}
|
||||
for _, elt := range co.Form.Fields {
|
||||
fieldMap[elt.Var] = elt
|
||||
}
|
||||
return fieldMap, nil
|
||||
default:
|
||||
if iq.Any != nil {
|
||||
fieldMap := make(map[string]Field)
|
||||
if iq.Any.XMLName.Local != "command" {
|
||||
return nil, errors.New("this IQ does not contain a form")
|
||||
}
|
||||
|
||||
for _, nde := range iq.Any.Nodes {
|
||||
if nde.XMLName.Local == "x" {
|
||||
for _, n := range nde.Nodes {
|
||||
if n.XMLName.Local == "field" {
|
||||
f := Field{}
|
||||
data, err := xml.Marshal(n)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
err = xml.Unmarshal(data, &f)
|
||||
if err == nil {
|
||||
fieldMap[f.Var] = f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fieldMap, nil
|
||||
}
|
||||
return nil, errors.New("this IQ does not contain a form")
|
||||
}
|
||||
}
|
||||
|
||||
func (pso *PubSubOwner) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
pso.XMLName = start.Name
|
||||
// decode inner elements
|
||||
for {
|
||||
t, err := d.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch tt := t.(type) {
|
||||
|
||||
case xml.StartElement:
|
||||
// Decode sub-elements
|
||||
var err error
|
||||
switch tt.Name.Local {
|
||||
|
||||
case "affiliations":
|
||||
aff := AffiliationsOwner{}
|
||||
d.DecodeElement(&aff, &tt)
|
||||
pso.OwnerUseCase = &aff
|
||||
case "configure":
|
||||
co := ConfigureOwner{}
|
||||
d.DecodeElement(&co, &tt)
|
||||
pso.OwnerUseCase = &co
|
||||
case "default":
|
||||
def := DefaultOwner{}
|
||||
d.DecodeElement(&def, &tt)
|
||||
pso.OwnerUseCase = &def
|
||||
case "delete":
|
||||
del := DeleteOwner{}
|
||||
d.DecodeElement(&del, &tt)
|
||||
pso.OwnerUseCase = &del
|
||||
case "purge":
|
||||
pu := PurgeOwner{}
|
||||
d.DecodeElement(&pu, &tt)
|
||||
pso.OwnerUseCase = &pu
|
||||
case "subscriptions":
|
||||
subs := SubscriptionsOwner{}
|
||||
d.DecodeElement(&subs, &tt)
|
||||
pso.OwnerUseCase = &subs
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case xml.EndElement:
|
||||
if tt == start.End() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: "http://jabber.org/protocol/pubsub#owner", Local: "pubsub"}, PubSubOwner{})
|
||||
}
|
@ -0,0 +1,833 @@
|
||||
package stanza_test
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ******************************
|
||||
// * 8.2 Configure a Node
|
||||
// ******************************
|
||||
func TestNewConfigureNode(t *testing.T) {
|
||||
expectedReq := "<iq type=\"get\" id=\"config1\" to=\"pubsub.shakespeare.lit\" > " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> <configure node=\"princely_musings\"></configure> " +
|
||||
"</pubsub> </iq>"
|
||||
|
||||
subR, err := stanza.NewConfigureNode("pubsub.shakespeare.lit", "princely_musings")
|
||||
subR.Id = "config1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
|
||||
if pubsub.OwnerUseCase == nil {
|
||||
t.Fatalf("owner use case is nil")
|
||||
}
|
||||
|
||||
ownrUsecase, ok := pubsub.OwnerUseCase.(*stanza.ConfigureOwner)
|
||||
if !ok {
|
||||
t.Fatalf("owner use case is not a configure tag")
|
||||
}
|
||||
|
||||
if ownrUsecase.Node == "" {
|
||||
t.Fatalf("could not parse node from config tag")
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewConfigureNodeResp(t *testing.T) {
|
||||
response := `
|
||||
<iq from="pubsub.shakespeare.lit" id="config1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<configure node="princely_musings">
|
||||
<x type="form" xmlns="jabber:x:data">
|
||||
<field type="hidden" var="FORM_TYPE">
|
||||
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||
</field>
|
||||
<field label="Purge all items when the relevant publisher goes offline?" type="boolean" var="pubsub#purge_offline">
|
||||
<value>0</value>
|
||||
</field>
|
||||
<field label="Max Payload size in bytes" type="text-single" var="pubsub#max_payload_size">
|
||||
<value>1028</value>
|
||||
</field>
|
||||
<field label="When to send the last published item" type="list-single" var="pubsub#send_last_published_item">
|
||||
<option label="Never">
|
||||
<value>never</value>
|
||||
</option>
|
||||
<option label="When a new subscription is processed">
|
||||
<value>on_sub</value>
|
||||
</option>
|
||||
<option label="When a new subscription is processed and whenever a subscriber comes online">
|
||||
<value>on_sub_and_presence</value>
|
||||
</option>
|
||||
<value>never</value>
|
||||
</field>
|
||||
<field label="Deliver event notifications only to available users" type="boolean" var="pubsub#presence_based_delivery">
|
||||
<value>0</value>
|
||||
</field>
|
||||
<field label="Specify the delivery style for event notifications" type="list-single" var="pubsub#notification_type">
|
||||
<option>
|
||||
<value>normal</value>
|
||||
</option>
|
||||
<option>
|
||||
<value>headline</value>
|
||||
</option>
|
||||
<value>headline</value>
|
||||
</field>
|
||||
<field label="Specify the type of payload data to be provided at this node" type="text-single" var="pubsub#type">
|
||||
<value>http://www.w3.org/2005/Atom</value>
|
||||
</field>
|
||||
<field label="Payload XSLT" type="text-single" var="pubsub#dataform_xslt"/>
|
||||
</x>
|
||||
</configure>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
|
||||
pubsub, err := getPubSubOwnerPayload(response)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
if pubsub.OwnerUseCase == nil {
|
||||
t.Fatalf("owner use case is nil")
|
||||
}
|
||||
|
||||
ownrUsecase, ok := pubsub.OwnerUseCase.(*stanza.ConfigureOwner)
|
||||
if !ok {
|
||||
t.Fatalf("owner use case is not a configure tag")
|
||||
}
|
||||
|
||||
if ownrUsecase.Form == nil {
|
||||
t.Fatalf("form is nil in the parsed config tag")
|
||||
}
|
||||
|
||||
if len(ownrUsecase.Form.Fields) != 8 {
|
||||
t.Fatalf("one or more fields in the response form could not be parsed correctly")
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************
|
||||
// * 8.3 Request Default Node Configuration Options
|
||||
// *************************************************
|
||||
|
||||
func TestNewRequestDefaultConfig(t *testing.T) {
|
||||
expectedReq := "<iq type=\"get\" id=\"def1\" to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> <default></default> </pubsub> </iq>"
|
||||
|
||||
subR, err := stanza.NewRequestDefaultConfig("pubsub.shakespeare.lit")
|
||||
subR.Id = "def1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
|
||||
if pubsub.OwnerUseCase == nil {
|
||||
t.Fatalf("owner use case is nil")
|
||||
}
|
||||
|
||||
_, ok = pubsub.OwnerUseCase.(*stanza.DefaultOwner)
|
||||
if !ok {
|
||||
t.Fatalf("owner use case is not a default tag")
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRequestDefaultConfigResp(t *testing.T) {
|
||||
response := `
|
||||
<iq from="pubsub.shakespeare.lit" id="config1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<configure node="princely_musings">
|
||||
<x type="form" xmlns="jabber:x:data">
|
||||
<field type="hidden" var="FORM_TYPE">
|
||||
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||
</field>
|
||||
<field label="Purge all items when the relevant publisher goes offline?" type="boolean" var="pubsub#purge_offline">
|
||||
<value>0</value>
|
||||
</field>
|
||||
<field label="Max Payload size in bytes" type="text-single" var="pubsub#max_payload_size">
|
||||
<value>1028</value>
|
||||
</field>
|
||||
<field label="When to send the last published item" type="list-single" var="pubsub#send_last_published_item">
|
||||
<option label="Never">
|
||||
<value>never</value>
|
||||
</option>
|
||||
<option label="When a new subscription is processed">
|
||||
<value>on_sub</value>
|
||||
</option>
|
||||
<option label="When a new subscription is processed and whenever a subscriber comes online">
|
||||
<value>on_sub_and_presence</value>
|
||||
</option>
|
||||
<value>never</value>
|
||||
</field>
|
||||
<field label="Deliver event notifications only to available users" type="boolean" var="pubsub#presence_based_delivery">
|
||||
<value>0</value>
|
||||
</field>
|
||||
<field label="Specify the delivery style for event notifications" type="list-single" var="pubsub#notification_type">
|
||||
<option>
|
||||
<value>normal</value>
|
||||
</option>
|
||||
<option>
|
||||
<value>headline</value>
|
||||
</option>
|
||||
<value>headline</value>
|
||||
</field>
|
||||
<field label="Specify the type of payload data to be provided at this node" type="text-single" var="pubsub#type">
|
||||
<value>http://www.w3.org/2005/Atom</value>
|
||||
</field>
|
||||
<field label="Payload XSLT" type="text-single" var="pubsub#dataform_xslt"/>
|
||||
</x>
|
||||
</configure>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
|
||||
pubsub, err := getPubSubOwnerPayload(response)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
if pubsub.OwnerUseCase == nil {
|
||||
t.Fatalf("owner use case is nil")
|
||||
}
|
||||
|
||||
ownrUsecase, ok := pubsub.OwnerUseCase.(*stanza.ConfigureOwner)
|
||||
if !ok {
|
||||
t.Fatalf("owner use case is not a configure tag")
|
||||
}
|
||||
|
||||
if ownrUsecase.Form == nil {
|
||||
t.Fatalf("form is nil in the parsed config tag")
|
||||
}
|
||||
|
||||
if len(ownrUsecase.Form.Fields) != 8 {
|
||||
t.Fatalf("one or more fields in the response form could not be parsed correctly")
|
||||
}
|
||||
}
|
||||
|
||||
// ***********************
|
||||
// * 8.4 Delete a Node
|
||||
// ***********************
|
||||
|
||||
func TestNewDelNode(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\" id=\"delete1\" to=\"pubsub.shakespeare.lit\" >" +
|
||||
" <pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> " +
|
||||
"<delete node=\"princely_musings\"></delete> </pubsub> </iq>"
|
||||
|
||||
subR, err := stanza.NewDelNode("pubsub.shakespeare.lit", "princely_musings")
|
||||
subR.Id = "delete1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
|
||||
if pubsub.OwnerUseCase == nil {
|
||||
t.Fatalf("owner use case is nil")
|
||||
}
|
||||
|
||||
_, ok = pubsub.OwnerUseCase.(*stanza.DeleteOwner)
|
||||
if !ok {
|
||||
t.Fatalf("owner use case is not a delete tag")
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDelNodeResp(t *testing.T) {
|
||||
response := `
|
||||
<iq id="delete1" to="pubsub.shakespeare.lit" type="set">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<delete node="princely_musings">
|
||||
<redirect uri="xmpp:hamlet@denmark.lit"/>
|
||||
</delete>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
|
||||
pubsub, err := getPubSubOwnerPayload(response)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
if pubsub.OwnerUseCase == nil {
|
||||
t.Fatalf("owner use case is nil")
|
||||
}
|
||||
|
||||
ownrUsecase, ok := pubsub.OwnerUseCase.(*stanza.DeleteOwner)
|
||||
if !ok {
|
||||
t.Fatalf("owner use case is not a configure tag")
|
||||
}
|
||||
|
||||
if ownrUsecase.RedirectOwner == nil {
|
||||
t.Fatalf("redirect is nil in the delete tag")
|
||||
}
|
||||
|
||||
if ownrUsecase.RedirectOwner.URI == "" {
|
||||
t.Fatalf("could not parse redirect uri")
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************
|
||||
// * 8.5 Purge All Node Items
|
||||
// ****************************
|
||||
|
||||
func TestNewPurgeAllItems(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\" id=\"purge1\" to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> " +
|
||||
"<purge node=\"princely_musings\"></purge> </pubsub> </iq>"
|
||||
|
||||
subR, err := stanza.NewPurgeAllItems("pubsub.shakespeare.lit", "princely_musings")
|
||||
subR.Id = "purge1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
|
||||
if pubsub.OwnerUseCase == nil {
|
||||
t.Fatalf("owner use case is nil")
|
||||
}
|
||||
|
||||
purge, ok := pubsub.OwnerUseCase.(*stanza.PurgeOwner)
|
||||
if !ok {
|
||||
t.Fatalf("owner use case is not a delete tag")
|
||||
}
|
||||
|
||||
if purge.Node == "" {
|
||||
t.Fatalf("could not parse purge targer node")
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************
|
||||
// * 8.6 Manage Subscription Requests
|
||||
// ************************************
|
||||
func TestNewApproveSubRequest(t *testing.T) {
|
||||
expectedReq := "<message id=\"approve1\" to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<x xmlns=\"jabber:x:data\" type=\"submit\"> <field var=\"FORM_TYPE\" type=\"hidden\"> " +
|
||||
"<value>http://jabber.org/protocol/pubsub#subscribe_authorization</value> </field> <field var=\"pubsub#subid\">" +
|
||||
" <value>123-abc</value> </field> <field var=\"pubsub#node\"> <value>princely_musings</value> </field> " +
|
||||
"<field var=\"pubsub#subscriber_jid\"> <value>horatio@denmark.lit</value> </field> <field var=\"pubsub#allow\"> " +
|
||||
"<value>true</value> </field> </x> </message>"
|
||||
|
||||
apprForm := &stanza.Form{
|
||||
Type: stanza.FormTypeSubmit,
|
||||
Fields: []stanza.Field{
|
||||
{Var: "FORM_TYPE", Type: stanza.FieldTypeHidden, ValuesList: []string{"http://jabber.org/protocol/pubsub#subscribe_authorization"}},
|
||||
{Var: "pubsub#subid", ValuesList: []string{"123-abc"}},
|
||||
{Var: "pubsub#node", ValuesList: []string{"princely_musings"}},
|
||||
{Var: "pubsub#subscriber_jid", ValuesList: []string{"horatio@denmark.lit"}},
|
||||
{Var: "pubsub#allow", ValuesList: []string{"true"}},
|
||||
},
|
||||
}
|
||||
|
||||
subR, err := stanza.NewApproveSubRequest("pubsub.shakespeare.lit", "approve1", apprForm)
|
||||
subR.Id = "approve1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create request : %s", err)
|
||||
}
|
||||
|
||||
frm, ok := subR.Extensions[0].(*stanza.Form)
|
||||
if !ok {
|
||||
t.Fatalf("extension is not a from !")
|
||||
}
|
||||
|
||||
var allowField *stanza.Field
|
||||
|
||||
for _, f := range frm.Fields {
|
||||
if f.Var == "pubsub#allow" {
|
||||
allowField = &f
|
||||
}
|
||||
}
|
||||
if allowField == nil || allowField.ValuesList[0] != "true" {
|
||||
t.Fatalf("could not correctly parse the allow field in the response from")
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************
|
||||
// * 8.7 Process Pending Subscription Requests
|
||||
// ********************************************
|
||||
|
||||
func TestNewGetPendingSubRequests(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\" id=\"pending1\" to=\"pubsub.shakespeare.lit\" > " +
|
||||
"<command xmlns=\"http://jabber.org/protocol/commands\" action=\"execute\" node=\"http://jabber.org/protocol/pubsub#get-pending\" >" +
|
||||
"</command> </iq>"
|
||||
|
||||
subR, err := stanza.NewGetPendingSubRequests("pubsub.shakespeare.lit")
|
||||
subR.Id = "pending1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
command, ok := subR.Payload.(*stanza.Command)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a command !")
|
||||
}
|
||||
|
||||
if command.Action != stanza.CommandActionExecute {
|
||||
t.Fatalf("command should be execute !")
|
||||
}
|
||||
|
||||
if command.Node != "http://jabber.org/protocol/pubsub#get-pending" {
|
||||
t.Fatalf("command node should be http://jabber.org/protocol/pubsub#get-pending !")
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewGetPendingSubRequestsResp(t *testing.T) {
|
||||
response := `
|
||||
<iq from="pubsub.shakespeare.lit" id="pending1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||
<command action="execute" node="http://jabber.org/protocol/pubsub#get-pending" sessionid="pubsub-get-pending:20031021T150901Z-600" status="executing" xmlns="http://jabber.org/protocol/commands">
|
||||
<x type="form" xmlns="jabber:x:data">
|
||||
<field type="hidden" var="FORM_TYPE">
|
||||
<value>http://jabber.org/protocol/pubsub#subscribe_authorization</value>
|
||||
</field>
|
||||
<field type="list-single" var="pubsub#node">
|
||||
<option>
|
||||
<value>princely_musings</value>
|
||||
</option>
|
||||
<option>
|
||||
<value>news_from_elsinore</value>
|
||||
</option>
|
||||
</field>
|
||||
</x>
|
||||
</command>
|
||||
</iq>
|
||||
`
|
||||
|
||||
var respIQ stanza.IQ
|
||||
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse iq")
|
||||
}
|
||||
|
||||
_, ok := respIQ.Payload.(*stanza.Command)
|
||||
if !ok {
|
||||
errors.New("this iq payload is not a command")
|
||||
}
|
||||
|
||||
fMap, err := respIQ.GetFormFields()
|
||||
if err != nil || len(fMap) != 2 {
|
||||
errors.New("could not parse command form fields")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ********************************************
|
||||
// * 8.7 Process Pending Subscription Requests
|
||||
// ********************************************
|
||||
|
||||
func TestNewApprovePendingSubRequest(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\" id=\"pending2\" to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<command xmlns=\"http://jabber.org/protocol/commands\" action=\"execute\"" +
|
||||
"node=\"http://jabber.org/protocol/pubsub#get-pending\"sessionid=\"pubsub-get-pending:20031021T150901Z-600\"> " +
|
||||
"<x xmlns=\"jabber:x:data\" type=\"submit\"> <field xmlns=\"jabber:x:data\" var=\"pubsub#node\"> " +
|
||||
"<value xmlns=\"jabber:x:data\">princely_musings</value> </field> </x> </command> </iq>"
|
||||
|
||||
subR, err := stanza.NewApprovePendingSubRequest("pubsub.shakespeare.lit",
|
||||
"pubsub-get-pending:20031021T150901Z-600",
|
||||
"princely_musings")
|
||||
subR.Id = "pending2"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
command, ok := subR.Payload.(*stanza.Command)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a command !")
|
||||
}
|
||||
|
||||
if command.Action != stanza.CommandActionExecute {
|
||||
t.Fatalf("command should be execute !")
|
||||
}
|
||||
|
||||
//if command.Node != "http://jabber.org/protocol/pubsub#get-pending"{
|
||||
// t.Fatalf("command node should be http://jabber.org/protocol/pubsub#get-pending !")
|
||||
//}
|
||||
//
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************
|
||||
// * 8.8.1 Retrieve Subscriptions List
|
||||
// ********************************************
|
||||
|
||||
func TestNewSubListRqPl(t *testing.T) {
|
||||
expectedReq := "<iq type=\"get\" id=\"subman1\" to=\"pubsub.shakespeare.lit\" > " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> " +
|
||||
"<subscriptions node=\"princely_musings\"></subscriptions> </pubsub> </iq>"
|
||||
|
||||
subR, err := stanza.NewSubListRqPl("pubsub.shakespeare.lit", "princely_musings")
|
||||
subR.Id = "subman1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub in namespace owner !")
|
||||
}
|
||||
|
||||
subs, ok := pubsub.OwnerUseCase.(*stanza.SubscriptionsOwner)
|
||||
if !ok {
|
||||
t.Fatalf("pubsub doesn not contain a subscriptions node !")
|
||||
}
|
||||
|
||||
if subs.Node != "princely_musings" {
|
||||
t.Fatalf("subs node attribute should be princely_musings. Found %s", subs.Node)
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSubListRqPlResp(t *testing.T) {
|
||||
response := `
|
||||
<iq from="pubsub.shakespeare.lit" id="subman1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<subscriptions node="princely_musings">
|
||||
<subscription jid="hamlet@denmark.lit" subscription="subscribed"></subscription>
|
||||
<subscription jid="polonius@denmark.lit" subscription="unconfigured"></subscription>
|
||||
<subscription jid="bernardo@denmark.lit" subid="123-abc" subscription="subscribed"></subscription>
|
||||
<subscription jid="bernardo@denmark.lit" subid="004-yyy" subscription="subscribed"></subscription>
|
||||
</subscriptions>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
|
||||
var respIQ stanza.IQ
|
||||
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse iq")
|
||||
}
|
||||
|
||||
pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner)
|
||||
if !ok {
|
||||
errors.New("this iq payload is not a command")
|
||||
}
|
||||
|
||||
subs, ok := pubsub.OwnerUseCase.(*stanza.SubscriptionsOwner)
|
||||
if !ok {
|
||||
t.Fatalf("pubsub doesn not contain a subscriptions node !")
|
||||
}
|
||||
|
||||
if len(subs.Subscriptions) != 4 {
|
||||
t.Fatalf("expected to find 4 subscriptions but got %d", len(subs.Subscriptions))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ********************************************
|
||||
// * 8.9.1 Retrieve Affiliations List
|
||||
// ********************************************
|
||||
|
||||
func TestNewAffiliationListRequest(t *testing.T) {
|
||||
expectedReq := "<iq type=\"get\" id=\"ent1\" to=\"pubsub.shakespeare.lit\" > " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> " +
|
||||
"<affiliations node=\"princely_musings\"></affiliations> </pubsub> </iq>"
|
||||
|
||||
subR, err := stanza.NewAffiliationListRequest("pubsub.shakespeare.lit", "princely_musings")
|
||||
subR.Id = "ent1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub in namespace owner !")
|
||||
}
|
||||
|
||||
affils, ok := pubsub.OwnerUseCase.(*stanza.AffiliationsOwner)
|
||||
if !ok {
|
||||
t.Fatalf("pubsub doesn not contain an affiliations node !")
|
||||
}
|
||||
|
||||
if affils.Node != "princely_musings" {
|
||||
t.Fatalf("affils node attribute should be princely_musings. Found %s", affils.Node)
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAffiliationListRequestResp(t *testing.T) {
|
||||
response := `
|
||||
<iq from="pubsub.shakespeare.lit" id="ent1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<affiliations node="princely_musings">
|
||||
<affiliation affiliation="owner" jid="hamlet@denmark.lit"/>
|
||||
<affiliation affiliation="outcast" jid="polonius@denmark.lit"/>
|
||||
</affiliations>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
|
||||
var respIQ stanza.IQ
|
||||
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse iq")
|
||||
}
|
||||
|
||||
pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner)
|
||||
if !ok {
|
||||
errors.New("this iq payload is not a command")
|
||||
}
|
||||
|
||||
affils, ok := pubsub.OwnerUseCase.(*stanza.AffiliationsOwner)
|
||||
if !ok {
|
||||
t.Fatalf("pubsub doesn not contain an affiliations node !")
|
||||
}
|
||||
|
||||
if len(affils.Affiliations) != 2 {
|
||||
t.Fatalf("expected to find 2 subscriptions but got %d", len(affils.Affiliations))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ********************************************
|
||||
// * 8.9.2 Modify Affiliation
|
||||
// ********************************************
|
||||
|
||||
func TestNewModifAffiliationRequest(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\" id=\"ent3\" to=\"pubsub.shakespeare.lit\" > " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> <affiliations node=\"princely_musings\"> " +
|
||||
"<affiliation affiliation=\"none\" jid=\"hamlet@denmark.lit\"></affiliation> " +
|
||||
"<affiliation affiliation=\"none\" jid=\"polonius@denmark.lit\"></affiliation> " +
|
||||
"<affiliation affiliation=\"publisher\" jid=\"bard@shakespeare.lit\"></affiliation> </affiliations> </pubsub> " +
|
||||
"</iq>"
|
||||
|
||||
affils := []stanza.AffiliationOwner{
|
||||
{
|
||||
AffiliationStatus: stanza.AffiliationStatusNone,
|
||||
Jid: "hamlet@denmark.lit",
|
||||
},
|
||||
{
|
||||
AffiliationStatus: stanza.AffiliationStatusNone,
|
||||
Jid: "polonius@denmark.lit",
|
||||
},
|
||||
{
|
||||
AffiliationStatus: stanza.AffiliationStatusPublisher,
|
||||
Jid: "bard@shakespeare.lit",
|
||||
},
|
||||
}
|
||||
|
||||
subR, err := stanza.NewModifAffiliationRequest("pubsub.shakespeare.lit", "princely_musings", affils)
|
||||
subR.Id = "ent3"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub in namespace owner !")
|
||||
}
|
||||
|
||||
as, ok := pubsub.OwnerUseCase.(*stanza.AffiliationsOwner)
|
||||
if !ok {
|
||||
t.Fatalf("pubsub doesn not contain an affiliations node !")
|
||||
}
|
||||
|
||||
if as.Node != "princely_musings" {
|
||||
t.Fatalf("affils node attribute should be princely_musings. Found %s", as.Node)
|
||||
}
|
||||
if len(as.Affiliations) != 3 {
|
||||
t.Fatalf("expected 3 affiliations, found %d", len(as.Affiliations))
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFormFields(t *testing.T) {
|
||||
response := `
|
||||
<iq from="pubsub.shakespeare.lit" id="config1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<configure node="princely_musings">
|
||||
<x type="form" xmlns="jabber:x:data">
|
||||
<field type="hidden" var="FORM_TYPE">
|
||||
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||
</field>
|
||||
<field label="Purge all items when the relevant publisher goes offline?" type="boolean" var="pubsub#purge_offline">
|
||||
<value>0</value>
|
||||
</field>
|
||||
<field label="Max Payload size in bytes" type="text-single" var="pubsub#max_payload_size">
|
||||
<value>1028</value>
|
||||
</field>
|
||||
<field label="When to send the last published item" type="list-single" var="pubsub#send_last_published_item">
|
||||
<option label="Never">
|
||||
<value>never</value>
|
||||
</option>
|
||||
<option label="When a new subscription is processed">
|
||||
<value>on_sub</value>
|
||||
</option>
|
||||
<option label="When a new subscription is processed and whenever a subscriber comes online">
|
||||
<value>on_sub_and_presence</value>
|
||||
</option>
|
||||
<value>never</value>
|
||||
</field>
|
||||
<field label="Deliver event notifications only to available users" type="boolean" var="pubsub#presence_based_delivery">
|
||||
<value>0</value>
|
||||
</field>
|
||||
<field label="Specify the delivery style for event notifications" type="list-single" var="pubsub#notification_type">
|
||||
<option>
|
||||
<value>normal</value>
|
||||
</option>
|
||||
<option>
|
||||
<value>headline</value>
|
||||
</option>
|
||||
<value>headline</value>
|
||||
</field>
|
||||
<field label="Specify the type of payload data to be provided at this node" type="text-single" var="pubsub#type">
|
||||
<value>http://www.w3.org/2005/Atom</value>
|
||||
</field>
|
||||
<field label="Payload XSLT" type="text-single" var="pubsub#dataform_xslt"/>
|
||||
</x>
|
||||
</configure>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
var iq stanza.IQ
|
||||
err := xml.Unmarshal([]byte(response), &iq)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse IQ")
|
||||
}
|
||||
|
||||
fields, err := iq.GetFormFields()
|
||||
if len(fields) != 8 {
|
||||
t.Fatalf("could not correctly parse fields. Expected 8, found : %v", len(fields))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetFormFieldsCmd(t *testing.T) {
|
||||
response := `
|
||||
<iq from="pubsub.shakespeare.lit" id="pending1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||
<command action="execute" node="http://jabber.org/protocol/pubsub#get-pending" sessionid="pubsub-get-pending:20031021T150901Z-600" status="executing" xmlns="http://jabber.org/protocol/commands">
|
||||
<x type="form" xmlns="jabber:x:data">
|
||||
<field type="hidden" var="FORM_TYPE">
|
||||
<value>http://jabber.org/protocol/pubsub#subscribe_authorization</value>
|
||||
</field>
|
||||
<field type="list-single" var="pubsub#node">
|
||||
<option>
|
||||
<value>princely_musings</value>
|
||||
</option>
|
||||
<option>
|
||||
<value>news_from_elsinore</value>
|
||||
</option>
|
||||
</field>
|
||||
</x>
|
||||
</command>
|
||||
</iq>
|
||||
`
|
||||
var iq stanza.IQ
|
||||
err := xml.Unmarshal([]byte(response), &iq)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse IQ")
|
||||
}
|
||||
|
||||
fields, err := iq.GetFormFields()
|
||||
if len(fields) != 2 {
|
||||
t.Fatalf("could not correctly parse fields. Expected 2, found : %v", len(fields))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getPubSubOwnerPayload(response string) (*stanza.PubSubOwner, error) {
|
||||
var respIQ stanza.IQ
|
||||
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||
|
||||
if err != nil {
|
||||
return &stanza.PubSubOwner{}, err
|
||||
}
|
||||
|
||||
pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner)
|
||||
if !ok {
|
||||
errors.New("this iq payload is not a pubsub of the owner namespace")
|
||||
}
|
||||
|
||||
return pubsub, nil
|
||||
}
|
@ -0,0 +1,921 @@
|
||||
package stanza_test
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var submitFormExample = stanza.NewForm([]stanza.Field{
|
||||
{Var: "FORM_TYPE", Type: stanza.FieldTypeHidden, ValuesList: []string{"http://jabber.org/protocol/pubsub#node_config"}},
|
||||
{Var: "pubsub#title", ValuesList: []string{"Princely Musings (Atom)"}},
|
||||
{Var: "pubsub#deliver_notifications", ValuesList: []string{"1"}},
|
||||
{Var: "pubsub#access_model", ValuesList: []string{"roster"}},
|
||||
{Var: "pubsub#roster_groups_allowed", ValuesList: []string{"friends", "servants", "courtiers"}},
|
||||
{Var: "pubsub#type", ValuesList: []string{"http://www.w3.org/2005/Atom"}},
|
||||
{
|
||||
Var: "pubsub#notification_type",
|
||||
Type: "list-single",
|
||||
Label: "Specify the delivery style for event notifications",
|
||||
ValuesList: []string{"headline"},
|
||||
Options: []stanza.Option{
|
||||
{ValuesList: []string{"normal"}},
|
||||
{ValuesList: []string{"headline"}},
|
||||
},
|
||||
},
|
||||
}, stanza.FormTypeSubmit)
|
||||
|
||||
// ***********************************
|
||||
// * 6.1 Subscribe to a Node
|
||||
// ***********************************
|
||||
|
||||
func TestNewSubRequest(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\"id=\"sub1\"to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <subscribe node=\"princely_musings\"jid=\"francisco@denmark.lit\"></subscribe>" +
|
||||
" </pubsub> </iq>"
|
||||
|
||||
subInfo := stanza.SubInfo{
|
||||
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||
}
|
||||
subR, err := stanza.NewSubRq("pubsub.shakespeare.lit", subInfo)
|
||||
subR.Id = "sub1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create a sub request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNewSubResp(t *testing.T) {
|
||||
response := `
|
||||
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit/barracks" id="sub1">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscription node="princely_musings" jid="francisco@denmark.lit"
|
||||
subid="ba49252aaa4f5d320c24d3766f0bdcade78c78d3" subscription="subscribed"/>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
|
||||
pubsub, err := getPubSubGenericPayload(response)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if pubsub.Subscription == nil {
|
||||
t.Fatalf("subscription node is nil")
|
||||
}
|
||||
if pubsub.Subscription.Node == "" ||
|
||||
pubsub.Subscription.Jid == "" ||
|
||||
pubsub.Subscription.SubId == nil ||
|
||||
pubsub.Subscription.SubStatus == "" {
|
||||
t.Fatalf("one or more of the subscription attributes was not successfully decoded")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ***********************************
|
||||
// * 6.2 Unsubscribe from a Node
|
||||
// ***********************************
|
||||
|
||||
func TestNewUnSubRequest(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\"id=\"unsub1\"to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> " +
|
||||
"<unsubscribe node=\"princely_musings\"jid=\"francisco@denmark.lit\"></unsubscribe> </pubsub> </iq>"
|
||||
|
||||
subInfo := stanza.SubInfo{
|
||||
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||
}
|
||||
subR, err := stanza.NewUnsubRq("pubsub.shakespeare.lit", subInfo)
|
||||
subR.Id = "unsub1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create a sub request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
if pubsub.Unsubscribe == nil {
|
||||
t.Fatalf("Unsubscribe tag should be present in sub config options request")
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUnsubResp(t *testing.T) {
|
||||
response := `
|
||||
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit/barracks" id="unsub1">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscription node="princely_musings" jid="francisco@denmark.lit" subscription="none"
|
||||
subid="ba49252aaa4f5d320c24d3766f0bdcade78c78d3"/>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
|
||||
pubsub, err := getPubSubGenericPayload(response)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if pubsub.Subscription == nil {
|
||||
t.Fatalf("subscription node is nil")
|
||||
}
|
||||
if pubsub.Subscription.Node == "" ||
|
||||
pubsub.Subscription.Jid == "" ||
|
||||
pubsub.Subscription.SubId == nil ||
|
||||
pubsub.Subscription.SubStatus == "" {
|
||||
t.Fatalf("one or more of the subscription attributes was not successfully decoded")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ***************************************
|
||||
// * 6.3 Configure Subscription Options
|
||||
// ***************************************
|
||||
func TestNewSubOptsRq(t *testing.T) {
|
||||
expectedReq := "<iq type=\"get\"id=\"options1\"to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> " +
|
||||
"<options node=\"princely_musings\" jid=\"francisco@denmark.lit\"></options> </pubsub> </iq>"
|
||||
|
||||
subInfo := stanza.SubInfo{
|
||||
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||
}
|
||||
subR, err := stanza.NewSubOptsRq("pubsub.shakespeare.lit", subInfo)
|
||||
subR.Id = "options1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create a sub request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
if pubsub.SubOptions == nil {
|
||||
t.Fatalf("Options tag should be present in sub config options request")
|
||||
}
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewNewConfOptsRsp(t *testing.T) {
|
||||
response := `
|
||||
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit/barracks" id="options1">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<options node="princely_musings" jid="francisco@denmark.lit">
|
||||
<x xmlns="jabber:x:data" type="form">
|
||||
<field var="FORM_TYPE" type="hidden">
|
||||
<value>http://jabber.org/protocol/pubsub#subscribe_options</value>
|
||||
</field>
|
||||
<field var="pubsub#deliver" type="boolean" label="Enable delivery?">
|
||||
<value>1</value>
|
||||
</field>
|
||||
<field var="pubsub#digest" type="boolean"
|
||||
label="Receive digest notifications (approx. one per day)?">
|
||||
<value>0</value>
|
||||
</field>
|
||||
<field var="pubsub#include_body" type="boolean"
|
||||
label="Receive message body in addition to payload?">
|
||||
<value>false</value>
|
||||
</field>
|
||||
<field var="pubsub#show-values" type="list-multi"
|
||||
label="Select the presence types which are
|
||||
allowed to receive event notifications">
|
||||
<option label="Want to Chat">
|
||||
<value>chat</value>
|
||||
</option>
|
||||
<option label="Available">
|
||||
<value>online</value>
|
||||
</option>
|
||||
<option label="Away">
|
||||
<value>away</value>
|
||||
</option>
|
||||
<option label="Extended Away">
|
||||
<value>xa</value>
|
||||
</option>
|
||||
<option label="Do Not Disturb">
|
||||
<value>dnd</value>
|
||||
</option>
|
||||
<value>chat</value>
|
||||
<value>online</value>
|
||||
</field>
|
||||
</x>
|
||||
</options>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
|
||||
pubsub, err := getPubSubGenericPayload(response)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if pubsub.SubOptions == nil {
|
||||
t.Fatalf("sub options node is nil")
|
||||
}
|
||||
if pubsub.SubOptions.Form == nil {
|
||||
t.Fatalf("the response form is nil")
|
||||
}
|
||||
|
||||
if len(pubsub.SubOptions.Form.Fields) != 5 {
|
||||
t.Fatalf("one or more fields in the response form could not be parsed correctly")
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************
|
||||
// * 6.3.5 Form Submission
|
||||
// ***************************************
|
||||
func TestNewFormSubmission(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\" id=\"options2\" to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <options node=\"princely_musings\" jid=\"francisco@denmark.lit\"> " +
|
||||
"<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\"> " +
|
||||
"<value>Princely Musings (Atom)</value> </field> <field var=\"pubsub#deliver_notifications\"> " +
|
||||
"<value>1</value> </field> <field var=\"pubsub#access_model\"> <value>roster</value> </field> " +
|
||||
"<field var=\"pubsub#roster_groups_allowed\"> <value>friends</value> <value>servants</value>" +
|
||||
" <value>courtiers</value> </field> <field var=\"pubsub#type\"> <value>http://www.w3.org/2005/Atom</value> " +
|
||||
"</field> <field var=\"pubsub#notification_type\" type=\"list-single\"label=\"Specify the delivery style for event notifications\"> " +
|
||||
"<value>headline</value> <option> <value>normal</value> </option> <option> <value>headline</value> </option> " +
|
||||
"</field> </x> </options> </pubsub> </iq>"
|
||||
|
||||
subInfo := stanza.SubInfo{
|
||||
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||
}
|
||||
|
||||
subR, err := stanza.NewFormSubmission("pubsub.shakespeare.lit", subInfo, submitFormExample)
|
||||
subR.Id = "options2"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create a sub request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
if pubsub.SubOptions == nil {
|
||||
t.Fatalf("Options tag should be present in sub config options request")
|
||||
}
|
||||
if pubsub.SubOptions.Form == nil {
|
||||
t.Fatalf("No form in form submit request !")
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************
|
||||
// * 6.3.7 Subscribe and Configure
|
||||
// ***************************************
|
||||
|
||||
func TestNewSubAndConfig(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\"id=\"sub1\"to=\"pubsub.shakespeare.lit\">" +
|
||||
" <pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <subscribe node=\"princely_musings\" jid=\"francisco@denmark.lit\"> " +
|
||||
"</subscribe>" +
|
||||
"<options> <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\"> " +
|
||||
"<value>Princely Musings (Atom)</value> </field> <field var=\"pubsub#deliver_notifications\"> " +
|
||||
"<value>1</value> </field> <field var=\"pubsub#access_model\"> <value>roster</value> </field> " +
|
||||
"<field var=\"pubsub#roster_groups_allowed\"> <value>friends</value> <value>servants</value>" +
|
||||
" <value>courtiers</value> </field> <field var=\"pubsub#type\"> <value>http://www.w3.org/2005/Atom</value> " +
|
||||
"</field> <field var=\"pubsub#notification_type\" type=\"list-single\"label=\"Specify the delivery style for event notifications\"> " +
|
||||
"<value>headline</value> <option> <value>normal</value> </option> <option> <value>headline</value> </option> " +
|
||||
"</field> </x> </options> </pubsub> </iq>"
|
||||
|
||||
subInfo := stanza.SubInfo{
|
||||
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||
}
|
||||
|
||||
subR, err := stanza.NewSubAndConfig("pubsub.shakespeare.lit", subInfo, submitFormExample)
|
||||
subR.Id = "sub1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create a sub request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
if pubsub.SubOptions == nil {
|
||||
t.Fatalf("Options tag should be present in sub config options request")
|
||||
}
|
||||
if pubsub.SubOptions.Form == nil {
|
||||
t.Fatalf("No form in form submit request !")
|
||||
}
|
||||
|
||||
// The <options/> element MUST NOT possess a 'node' attribute or 'jid' attribute
|
||||
// See XEP-0060
|
||||
if pubsub.SubOptions.SubInfo.Node != "" || pubsub.SubOptions.SubInfo.Jid != "" {
|
||||
t.Fatalf("SubInfo node and jid should be empty for the options tag !")
|
||||
}
|
||||
if pubsub.Subscribe.Node == "" || pubsub.Subscribe.Jid == "" {
|
||||
t.Fatalf("SubInfo node and jid should NOT be empty for the subscribe tag !")
|
||||
}
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSubAndConfigResp(t *testing.T) {
|
||||
response := `
|
||||
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit/barracks" id="sub1">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscription node="princely_musings" jid="francisco@denmark.lit"
|
||||
subid="ba49252aaa4f5d320c24d3766f0bdcade78c78d3" subscription="subscribed"/>
|
||||
<options>
|
||||
<x xmlns="jabber:x:data" type="result">
|
||||
<field var="FORM_TYPE" type="hidden">
|
||||
<value>http://jabber.org/protocol/pubsub#subscribe_options</value>
|
||||
</field>
|
||||
<field var="pubsub#deliver">
|
||||
<value>1</value>
|
||||
</field>
|
||||
<field var="pubsub#digest">
|
||||
<value>0</value>
|
||||
</field>
|
||||
<field var="pubsub#include_body">
|
||||
<value>false</value>
|
||||
</field>
|
||||
<field var="pubsub#show-values">
|
||||
<value>chat</value>
|
||||
<value>online</value>
|
||||
<value>away</value>
|
||||
</field>
|
||||
</x>
|
||||
</options>
|
||||
</pubsub>
|
||||
</iq>
|
||||
|
||||
`
|
||||
|
||||
pubsub, err := getPubSubGenericPayload(response)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
if pubsub.Subscription == nil {
|
||||
t.Fatalf("sub node is nil")
|
||||
}
|
||||
|
||||
if pubsub.SubOptions == nil {
|
||||
t.Fatalf("sub options node is nil")
|
||||
}
|
||||
if pubsub.SubOptions.Form == nil {
|
||||
t.Fatalf("the response form is nil")
|
||||
}
|
||||
|
||||
if len(pubsub.SubOptions.Form.Fields) != 5 {
|
||||
t.Fatalf("one or more fields in the response form could not be parsed correctly")
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************
|
||||
// * 6.5.2 Requesting All List
|
||||
// ***************************************
|
||||
func TestNewItemsRequest(t *testing.T) {
|
||||
subR, err := stanza.NewItemsRequest("pubsub.shakespeare.lit", "princely_musings", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create an items request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
if pubsub.Items == nil {
|
||||
t.Fatalf("List tag should be present to request items from a service")
|
||||
}
|
||||
if len(pubsub.Items.List) != 0 {
|
||||
t.Fatalf("There should be no items in the <items> tag to request all items from a service")
|
||||
}
|
||||
}
|
||||
func TestNewItemsResp(t *testing.T) {
|
||||
response := `
|
||||
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit/barracks" id="items2">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<items node="princely_musings">
|
||||
<item id="4e30f35051b7b8b42abe083742187228">
|
||||
<entry xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Alone</title>
|
||||
<summary> Now I am alone. O, what a rogue and peasant slave am I! </summary>
|
||||
<link rel="alternate" type="text/html"
|
||||
href="http://denmark.lit/2003/12/13/atom03"/>
|
||||
<id>tag:denmark.lit,2003:entry-32396</id>
|
||||
<published>2003-12-13T11:09:53Z</published>
|
||||
<updated>2003-12-13T11:09:53Z</updated>
|
||||
</entry>
|
||||
</item>
|
||||
<item id="ae890ac52d0df67ed7cfdf51b644e901">
|
||||
<entry xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Soliloquy</title>
|
||||
<summary> To be, or not to be: that is the question: Whether 'tis nobler in the
|
||||
mind to suffer The slings and arrows of outrageous fortune, Or to take arms
|
||||
against a sea of troubles, And by opposing end them? </summary>
|
||||
<link rel="alternate" type="text/html"
|
||||
href="http://denmark.lit/2003/12/13/atom03"/>
|
||||
<id>tag:denmark.lit,2003:entry-32397</id>
|
||||
<published>2003-12-13T18:30:02Z</published>
|
||||
<updated>2003-12-13T18:30:02Z</updated>
|
||||
</entry>
|
||||
</item>
|
||||
</items>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
|
||||
pubsub, err := getPubSubGenericPayload(response)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
if pubsub.Items == nil {
|
||||
t.Fatalf("sub options node is nil")
|
||||
}
|
||||
if pubsub.Items.List == nil {
|
||||
t.Fatalf("the response form is nil")
|
||||
}
|
||||
|
||||
if len(pubsub.Items.List) != 2 {
|
||||
t.Fatalf("one or more items in the response could not be parsed correctly")
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************
|
||||
// * 6.5.8 Requesting a Particular Item
|
||||
// ***************************************
|
||||
func TestNewSpecificItemRequest(t *testing.T) {
|
||||
expectedReq := "<iq type=\"get\" id=\"items3\"to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <items node=\"princely_musings\"> " +
|
||||
"<item id=\"ae890ac52d0df67ed7cfdf51b644e901\"></item> </items> </pubsub> </iq>"
|
||||
|
||||
subR, err := stanza.NewSpecificItemRequest("pubsub.shakespeare.lit", "princely_musings", "ae890ac52d0df67ed7cfdf51b644e901")
|
||||
subR.Id = "items3"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create an items request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
if pubsub.Items == nil {
|
||||
t.Fatalf("List tag should be present to request items from a service")
|
||||
}
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************
|
||||
// * 7.1 Publish an Item to a Node
|
||||
// ***************************************
|
||||
func TestNewPublishItemRq(t *testing.T) {
|
||||
item := stanza.Item{
|
||||
XMLName: xml.Name{},
|
||||
Id: "",
|
||||
Publisher: "",
|
||||
Any: &stanza.Node{
|
||||
XMLName: xml.Name{
|
||||
Space: "http://www.w3.org/2005/Atom",
|
||||
Local: "entry",
|
||||
},
|
||||
Attrs: nil,
|
||||
Content: "",
|
||||
Nodes: []stanza.Node{
|
||||
{
|
||||
XMLName: xml.Name{Space: "", Local: "title"},
|
||||
Attrs: nil,
|
||||
Content: "My pub item title",
|
||||
Nodes: nil,
|
||||
},
|
||||
{
|
||||
XMLName: xml.Name{Space: "", Local: "summary"},
|
||||
Attrs: nil,
|
||||
Content: "My pub item content summary",
|
||||
Nodes: nil,
|
||||
},
|
||||
{
|
||||
XMLName: xml.Name{Space: "", Local: "link"},
|
||||
Attrs: []xml.Attr{
|
||||
{
|
||||
Name: xml.Name{Space: "", Local: "rel"},
|
||||
Value: "alternate",
|
||||
},
|
||||
{
|
||||
Name: xml.Name{Space: "", Local: "type"},
|
||||
Value: "text/html",
|
||||
},
|
||||
{
|
||||
Name: xml.Name{Space: "", Local: "href"},
|
||||
Value: "http://denmark.lit/2003/12/13/atom03",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
XMLName: xml.Name{Space: "", Local: "id"},
|
||||
Attrs: nil,
|
||||
Content: "My pub item content ID",
|
||||
Nodes: nil,
|
||||
},
|
||||
{
|
||||
XMLName: xml.Name{Space: "", Local: "published"},
|
||||
Attrs: nil,
|
||||
Content: "2003-12-13T18:30:02Z",
|
||||
Nodes: nil,
|
||||
},
|
||||
{
|
||||
XMLName: xml.Name{Space: "", Local: "updated"},
|
||||
Attrs: nil,
|
||||
Content: "2003-12-13T18:30:02Z",
|
||||
Nodes: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
subR, err := stanza.NewPublishItemRq("pubsub.shakespeare.lit", "princely_musings", "bnd81g37d61f49fgn581", item)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create an item pub request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(pubsub.Publish.Node) == "" {
|
||||
t.Fatalf("the <publish/> element MUST possess a 'node' attribute, specifying the NodeID of the node.")
|
||||
}
|
||||
if pubsub.Publish.Items[0].Id == "" {
|
||||
t.Fatalf("an id was provided for the item and it should be used")
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************
|
||||
// * 7.1.5 Publishing Options
|
||||
// ***************************************
|
||||
|
||||
func TestNewPublishItemOptsRq(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\"id=\"pub1\"to=\"pubsub.shakespeare.lit\"> <pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> " +
|
||||
"<publish node=\"princely_musings\"> <item id=\"ae890ac52d0df67ed7cfdf51b644e901\"> " +
|
||||
"<entry xmlns=\"http://www.w3.org/2005/Atom\"> <title>Soliloquy</title> " +
|
||||
"<summary> To be, or not to be: that is the question: Whether \"tis nobler in the mind to suffer The " +
|
||||
"slings and arrows of outrageous fortune, Or to take arms against a sea of troubles, And by opposing end them? " +
|
||||
"</summary> <link rel=\"alternate\" type=\"text/html\"href=\"http://denmark.lit/2003/12/13/atom03\"></link> " +
|
||||
"<id>tag:denmark.lit,2003:entry-32397</id> <published>2003-12-13T18:30:02Z</published> " +
|
||||
"<updated>2003-12-13T18:30:02Z</updated> </entry> </item> </publish> <publish-options> " +
|
||||
"<x xmlns=\"jabber:x:data\" type=\"submit\"> <field var=\"FORM_TYPE\" type=\"hidden\"> " +
|
||||
"<value>http://jabber.org/protocol/pubsub#publish-options</value> </field> <field var=\"pubsub#access_model\"> " +
|
||||
"<value>presence</value> </field> </x> </publish-options> </pubsub> </iq>"
|
||||
|
||||
var iq stanza.IQ
|
||||
err := xml.Unmarshal([]byte(expectedReq), &iq)
|
||||
if err != nil {
|
||||
t.Fatalf("could not unmarshal example request : %s", err)
|
||||
}
|
||||
|
||||
pubsub, ok := iq.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
if pubsub.Publish == nil {
|
||||
t.Fatalf("Publish tag is empty")
|
||||
}
|
||||
if len(pubsub.Publish.Items) != 1 {
|
||||
t.Fatalf("could not parse item properly")
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************
|
||||
// * 7.2 Delete an Item from a Node
|
||||
// ***************************************
|
||||
|
||||
func TestNewDelItemFromNode(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\"id=\"retract1\"to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <retract node=\"princely_musings\"> " +
|
||||
"<item id=\"ae890ac52d0df67ed7cfdf51b644e901\"></item> </retract> </pubsub> </iq>"
|
||||
|
||||
subR, err := stanza.NewDelItemFromNode("pubsub.shakespeare.lit", "princely_musings", "ae890ac52d0df67ed7cfdf51b644e901", nil)
|
||||
subR.Id = "retract1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create a del item request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated del item request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
if pubsub.Retract == nil {
|
||||
t.Fatalf("Retract tag should be present to del an item from a service")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(pubsub.Retract.Items[0].Id) == "" {
|
||||
t.Fatalf("Item id, for the item to delete, should be non empty")
|
||||
}
|
||||
if pubsub.Retract.Items[0].Any != nil {
|
||||
t.Fatalf("Item node must be empty")
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************
|
||||
// * 8.1 Create a Node
|
||||
// ***************************************
|
||||
|
||||
func TestNewCreateNode(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\"id=\"create1\"to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <create node=\"princely_musings\"></create> </pubsub> </iq>"
|
||||
|
||||
subR, err := stanza.NewCreateNode("pubsub.shakespeare.lit", "princely_musings")
|
||||
subR.Id = "create1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create a create node request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated del item request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
if pubsub.Create == nil {
|
||||
t.Fatalf("Create tag should be present to create a node on a service")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(pubsub.Create.Node) == "" {
|
||||
t.Fatalf("Expected node name to be present")
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCreateNodeResp(t *testing.T) {
|
||||
response := `
|
||||
<iq type="result" from="pubsub.shakespeare.lit" to="hamlet@denmark.lit/elsinore" id="create2">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<create node="25e3d37dabbab9541f7523321421edc5bfeb2dae"/>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
pubsub, err := getPubSubGenericPayload(response)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
if pubsub.Create == nil {
|
||||
t.Fatalf("create segment is nil")
|
||||
}
|
||||
if pubsub.Create.Node == "" {
|
||||
t.Fatalf("could not parse generated nodeId")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ***************************************
|
||||
// * 8.1.3 Create and Configure a Node
|
||||
// ***************************************
|
||||
|
||||
func TestNewCreateAndConfigNode(t *testing.T) {
|
||||
expectedReq := "<iq type=\"set\" id=\"create1\" to=\"pubsub.shakespeare.lit\" > " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <create node=\"princely_musings\"></create> " +
|
||||
"<configure> <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#notify_retract\"> " +
|
||||
"<value>0</value> </field> <field var=\"pubsub#notify_sub\"> <value>0</value> </field> " +
|
||||
"<field var=\"pubsub#max_payload_size\"> <value>1028</value> </field> </x> </configure> </pubsub> </iq>"
|
||||
|
||||
subR, err := stanza.NewCreateAndConfigNode("pubsub.shakespeare.lit",
|
||||
"princely_musings",
|
||||
&stanza.Form{
|
||||
Type: stanza.FormTypeSubmit,
|
||||
Fields: []stanza.Field{
|
||||
{Var: "FORM_TYPE", Type: stanza.FieldTypeHidden, ValuesList: []string{"http://jabber.org/protocol/pubsub#node_config"}},
|
||||
{Var: "pubsub#notify_retract", ValuesList: []string{"0"}},
|
||||
{Var: "pubsub#notify_sub", ValuesList: []string{"0"}},
|
||||
{Var: "pubsub#max_payload_size", ValuesList: []string{"1028"}},
|
||||
},
|
||||
})
|
||||
subR.Id = "create1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create a create node request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated del item request : %s", e)
|
||||
}
|
||||
|
||||
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("payload is not a pubsub !")
|
||||
}
|
||||
if pubsub.Create == nil {
|
||||
t.Fatalf("Create tag should be present to create a node on a service")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(pubsub.Create.Node) == "" {
|
||||
t.Fatalf("Expected node name to be present")
|
||||
}
|
||||
|
||||
if pubsub.Configure == nil {
|
||||
t.Fatalf("Configure tag should be present to configure a node during its creation on a service")
|
||||
}
|
||||
|
||||
if pubsub.Configure.Form == nil {
|
||||
t.Fatalf("Expected a form to be present, to configure the node")
|
||||
}
|
||||
if len(pubsub.Configure.Form.Fields) != 4 {
|
||||
t.Fatalf("Expected 4 elements to be present in the config form but got : %v", len(pubsub.Configure.Form.Fields))
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ********************************
|
||||
// * 5.7 Retrieve Subscriptions
|
||||
// ********************************
|
||||
|
||||
func TestNewRetrieveAllSubsRequest(t *testing.T) {
|
||||
expected := "<iq type=\"get\" id=\"subscriptions1\" to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <subscriptions></subscriptions> </pubsub> </iq>"
|
||||
|
||||
subR, err := stanza.NewRetrieveAllSubsRequest("pubsub.shakespeare.lit")
|
||||
subR.Id = "subscriptions1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create a create node request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated del item request : %s", e)
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expected, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrieveAllSubsResp(t *testing.T) {
|
||||
response := `
|
||||
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit" id="subscriptions1">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscriptions>
|
||||
<subscription node="node1" jid="francisco@denmark.lit" subscription="subscribed"/>
|
||||
<subscription node="node2" jid="francisco@denmark.lit" subscription="subscribed"/>
|
||||
<subscription node="node5" jid="francisco@denmark.lit" subscription="unconfigured"/>
|
||||
<subscription node="node6" jid="francisco@denmark.lit" subscription="subscribed"
|
||||
subid="123-abc"/>
|
||||
<subscription node="node6" jid="francisco@denmark.lit" subscription="subscribed"
|
||||
subid="004-yyy"/>
|
||||
</subscriptions>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
var respIQ stanza.IQ
|
||||
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("could not unmarshal response: %s", err)
|
||||
}
|
||||
|
||||
pubsub, ok := respIQ.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("umarshalled payload is not a pubsub")
|
||||
}
|
||||
|
||||
if pubsub.Subscriptions == nil {
|
||||
t.Fatalf("subscriptions node is nil")
|
||||
}
|
||||
if len(pubsub.Subscriptions.List) != 5 {
|
||||
t.Fatalf("incorrect number of decoded subscriptions")
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************
|
||||
// * 5.7 Retrieve Affiliations
|
||||
// ********************************
|
||||
|
||||
func TestNewRetrieveAllAffilsRequest(t *testing.T) {
|
||||
expected := "<iq type=\"get\"id=\"affil1\"to=\"pubsub.shakespeare.lit\"> " +
|
||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <affiliations></affiliations> </pubsub> </iq>"
|
||||
|
||||
subR, err := stanza.NewRetrieveAllAffilsRequest("pubsub.shakespeare.lit")
|
||||
subR.Id = "affil1"
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create retreive all affiliations request : %s", err)
|
||||
}
|
||||
|
||||
if _, e := checkMarshalling(t, subR); e != nil {
|
||||
t.Fatalf("Failed to check marshalling for generated retreive all affiliations request : %s", e)
|
||||
}
|
||||
|
||||
data, err := xml.Marshal(subR)
|
||||
if err := compareMarshal(expected, string(data)); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrieveAllAffilsResp(t *testing.T) {
|
||||
response := `
|
||||
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit" id="affil1">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<affiliations>
|
||||
<affiliation node="node1" affiliation="owner"/>
|
||||
<affiliation node="node2" affiliation="publisher"/>
|
||||
<affiliation node="node5" affiliation="outcast"/>
|
||||
<affiliation node="node6" affiliation="owner"/>
|
||||
</affiliations>
|
||||
</pubsub>
|
||||
</iq>
|
||||
`
|
||||
var respIQ stanza.IQ
|
||||
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("could not unmarshal response: %s", err)
|
||||
}
|
||||
|
||||
pubsub, ok := respIQ.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
t.Fatalf("umarshalled payload is not a pubsub")
|
||||
}
|
||||
|
||||
if pubsub.Affiliations == nil {
|
||||
t.Fatalf("subscriptions node is nil")
|
||||
}
|
||||
if len(pubsub.Affiliations.List) != 4 {
|
||||
t.Fatalf("incorrect number of decoded subscriptions")
|
||||
}
|
||||
}
|
||||
|
||||
func getPubSubGenericPayload(response string) (*stanza.PubSubGeneric, error) {
|
||||
var respIQ stanza.IQ
|
||||
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||
|
||||
if err != nil {
|
||||
return &stanza.PubSubGeneric{}, err
|
||||
}
|
||||
|
||||
pubsub, ok := respIQ.Payload.(*stanza.PubSubGeneric)
|
||||
if !ok {
|
||||
errors.New("this iq payload is not a pubsub")
|
||||
}
|
||||
|
||||
return pubsub, nil
|
||||
}
|
Loading…
Reference in New Issue