1package xmpp 2 3import ( 4 "encoding/xml" 5 "io" 6 "io/ioutil" 7 "log" 8 "testing" 9 "time" 10 11 "github.com/coyim/coyim/i18n" 12 "github.com/coyim/coyim/xmpp/data" 13 "github.com/coyim/gotk3adapter/glib_mock" 14 15 . "gopkg.in/check.v1" 16) 17 18func Test(t *testing.T) { TestingT(t) } 19 20func init() { 21 log.SetOutput(ioutil.Discard) 22 i18n.InitLocalization(&glib_mock.Mock{}) 23} 24 25type XMPPSuite struct{} 26 27var _ = Suite(&XMPPSuite{}) 28 29func (s *XMPPSuite) TestDiscoReplyVerSimple(c *C) { 30 expect := "QgayPKawpkPSDYmwT/WM94uAlu0=" 31 input := []byte(` 32 <query xmlns='http://jabber.org/protocol/disco#info' 33 node='http://code.google.com/p/exodus#QgayPKawpkPSDYmwT/WM94uAlu0='> 34 <identity category='client' name='Exodus 0.9.1' type='pc'/> 35 <feature var='http://jabber.org/protocol/caps'/> 36 <feature var='http://jabber.org/protocol/disco#info'/> 37 <feature var='http://jabber.org/protocol/disco#items'/> 38 <feature var='http://jabber.org/protocol/muc'/> 39 </query> 40 `) 41 var dr data.DiscoveryInfoQuery 42 c.Assert(xml.Unmarshal(input, &dr), IsNil) 43 hash, err := VerificationString(&dr) 44 c.Assert(err, IsNil) 45 c.Assert(hash, Equals, expect) 46} 47 48func (s *XMPPSuite) TestDiscoReplyVerComplex(c *C) { 49 expect := "q07IKJEyjvHSyhy//CH0CxmKi8w=" 50 input := []byte(` 51 <query xmlns='http://jabber.org/protocol/disco#info' 52 node='http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w='> 53 <identity xml:lang='en' category='client' name='Psi 0.11' type='pc'/> 54 <identity xml:lang='el' category='client' name='Ψ 0.11' type='pc'/> 55 <feature var='http://jabber.org/protocol/caps'/> 56 <feature var='http://jabber.org/protocol/disco#info'/> 57 <feature var='http://jabber.org/protocol/disco#items'/> 58 <feature var='http://jabber.org/protocol/muc'/> 59 <x xmlns='jabber:x:data' type='result'> 60 <field var='FORM_TYPE' type='hidden'> 61 <value>urn:xmpp:dataforms:softwareinfo</value> 62 </field> 63 <field var='ip_version'> 64 <value>ipv4</value> 65 <value>ipv6</value> 66 </field> 67 <field var='os'> 68 <value>Mac</value> 69 </field> 70 <field var='os_version'> 71 <value>10.5.1</value> 72 </field> 73 <field var='software'> 74 <value>Psi</value> 75 </field> 76 <field var='software_version'> 77 <value>0.11</value> 78 </field> 79 </x> 80 </query> 81`) 82 var dr data.DiscoveryInfoQuery 83 c.Assert(xml.Unmarshal(input, &dr), IsNil) 84 hash, err := VerificationString(&dr) 85 c.Assert(err, IsNil) 86 c.Assert(hash, Equals, expect) 87} 88 89func (s *XMPPSuite) TestConnClose_sendsAStreamCloseTagWhenWeCloseFirst(c *C) { 90 mockIn := &mockConnIOReaderWriter{ 91 read: []byte("<?xml version='1.0'?><str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'></str:stream>"), 92 } 93 mockCloser := &mockConnIOReaderWriter{} 94 95 conn := newConn() 96 conn.in = xml.NewDecoder(mockIn) 97 conn.out = mockCloser 98 conn.rawOut = mockCloser 99 100 nextElement(conn.in) // Reads the opening tag and make the unmarshaller happy 101 102 done := make(chan bool) 103 go func() { 104 // This is sadly necessary, since the call to conn.Next() needs to happen AFTER the first few lines of conn.Close() 105 // has executed. Otherwise there will be a racecondition where conn.Close() sometimes will report that 106 // the connection has already been executed. 107 time.Sleep(time.Duration(2) * time.Second) 108 109 stanza, err := conn.Next() // Reads the closing tag 110 111 c.Assert(err, IsNil) 112 c.Assert(stanza, DeepEquals, data.Stanza{ 113 Name: xml.Name{Space: "http://etherx.jabber.org/streams", Local: "stream"}, 114 Value: &data.StreamClose{}, 115 }) 116 117 done <- true 118 }() 119 120 // blocks until it receives the </stream> or timeouts 121 c.Assert(conn.Close(), IsNil) 122 c.Assert(mockCloser.CalledClose(), Equals, true) 123 c.Assert(mockCloser.Written(), DeepEquals, []byte("</stream:stream>")) 124 125 <-done 126} 127 128func (s *XMPPSuite) TestConnNext_replyWithAStreamCloseTagWhenTheyCloseFirst(c *C) { 129 mockIn := &mockConnIOReaderWriter{ 130 read: []byte("<?xml version='1.0'?><str:stream xmlns:str='http://etherx.jabber.org/streams' version='1.0'></str:stream>"), 131 } 132 mockCloser := &mockConnIOReaderWriter{} 133 134 conn := newConn() 135 conn.in = xml.NewDecoder(mockIn) 136 conn.out = mockCloser 137 conn.rawOut = mockCloser 138 139 nextElement(conn.in) // Reads the opening tag and make the unmarshaller happy 140 stanza, err := conn.Next() // Reads the closing tag 141 142 c.Assert(err, IsNil) 143 c.Assert(stanza, DeepEquals, data.Stanza{ 144 Name: xml.Name{Space: "http://etherx.jabber.org/streams", Local: "stream"}, 145 Value: &data.StreamClose{}, 146 }) 147 148 c.Assert(mockCloser.CalledClose(), Equals, true) 149 c.Assert(mockCloser.write, DeepEquals, []byte("</stream:stream>")) 150 151 err = conn.Close() 152 c.Assert(err, NotNil) 153 c.Assert(err.Error(), Equals, "xmpp: the connection is already closed") 154} 155 156func (s *XMPPSuite) TestConnNextEOF(c *C) { 157 mockIn := &mockConnIOReaderWriter{err: io.EOF} 158 conn := conn{ 159 in: xml.NewDecoder(mockIn), 160 } 161 stanza, err := conn.Next() 162 c.Assert(stanza.Name, Equals, xml.Name{}) 163 c.Assert(stanza.Value, IsNil) 164 c.Assert(err, Equals, io.EOF) 165} 166 167func (s *XMPPSuite) TestConnNextErr(c *C) { 168 mockIn := &mockConnIOReaderWriter{ 169 read: []byte(` 170 <field var='os'> 171 <value>Mac</value> 172 </field> 173 `), 174 } 175 conn := conn{ 176 in: xml.NewDecoder(mockIn), 177 } 178 stanza, err := conn.Next() 179 c.Assert(stanza.Name, Equals, xml.Name{}) 180 c.Assert(stanza.Value, IsNil) 181 c.Assert(err.Error(), Equals, "unexpected XMPP message <field/>") 182} 183 184func (s *XMPPSuite) TestConnNextIQSet(c *C) { 185 mockIn := &mockConnIOReaderWriter{ 186 read: []byte(` 187<iq to='example.com' 188 xmlns='jabber:client' 189 type='set' 190 id='sess_1'> 191 <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/> 192</iq> 193 `), 194 } 195 conn := conn{ 196 in: xml.NewDecoder(mockIn), 197 } 198 stanza, err := conn.Next() 199 c.Assert(stanza.Name, Equals, xml.Name{Space: NsClient, Local: "iq"}) 200 iq, ok := stanza.Value.(*data.ClientIQ) 201 c.Assert(ok, Equals, true) 202 c.Assert(iq.To, Equals, "example.com") 203 c.Assert(iq.Type, Equals, "set") 204 c.Assert(err, IsNil) 205} 206 207func (s *XMPPSuite) TestConnNextIQResult(c *C) { 208 mockIn := &mockConnIOReaderWriter{ 209 read: []byte(` 210<iq from='example.com' 211 xmlns='jabber:client' 212 type='result' 213 id='sess_1'/> 214 `), 215 } 216 conn := conn{ 217 in: xml.NewDecoder(mockIn), 218 } 219 stanza, err := conn.Next() 220 c.Assert(stanza.Name, Equals, xml.Name{Space: NsClient, Local: "iq"}) 221 iq, ok := stanza.Value.(*data.ClientIQ) 222 c.Assert(ok, Equals, true) 223 c.Assert(iq.From, Equals, "example.com") 224 c.Assert(iq.Type, Equals, "result") 225 c.Assert(err, ErrorMatches, "xmpp: failed to parse id from iq: .*") 226} 227 228func (s *XMPPSuite) TestConnCancelError(c *C) { 229 conn := conn{} 230 ok := conn.Cancel(conn.getCookie()) 231 c.Assert(ok, Equals, false) 232} 233 234func (s *XMPPSuite) TestConnCancelOK(c *C) { 235 conn := conn{} 236 cookie := conn.getCookie() 237 ch := make(chan data.Stanza, 1) 238 conn.inflights = make(map[data.Cookie]inflight) 239 conn.inflights[cookie] = inflight{ch, ""} 240 ok := conn.Cancel(cookie) 241 c.Assert(ok, Equals, true) 242 _, ok = conn.inflights[cookie] 243 c.Assert(ok, Equals, false) 244} 245 246func (s *XMPPSuite) TestConnRequestRoster(c *C) { 247 mockOut := mockConnIOReaderWriter{} 248 conn := conn{ 249 out: &mockOut, 250 } 251 conn.inflights = make(map[data.Cookie]inflight) 252 ch, cookie, err := conn.RequestRoster() 253 c.Assert(string(mockOut.write), Matches, "<iq type='get' id='.*'><query xmlns='jabber:iq:roster'/></iq>") 254 c.Assert(ch, NotNil) 255 c.Assert(cookie, NotNil) 256 c.Assert(err, IsNil) 257} 258 259func (s *XMPPSuite) TestConnRequestRosterErr(c *C) { 260 mockOut := mockConnIOReaderWriter{err: io.EOF} 261 conn := conn{ 262 out: &mockOut, 263 } 264 conn.inflights = make(map[data.Cookie]inflight) 265 ch, cookie, err := conn.RequestRoster() 266 c.Assert(string(mockOut.write), Matches, "<iq type='get' id='.*'><query xmlns='jabber:iq:roster'/></iq>") 267 c.Assert(ch, IsNil) 268 c.Assert(cookie, NotNil) 269 c.Assert(err, Equals, io.EOF) 270} 271 272func (s *XMPPSuite) TestParseRoster(c *C) { 273 iq := data.ClientIQ{} 274 iq.Query = []byte(` 275 <query xmlns='jabber:iq:roster'> 276 <item jid='romeo@example.net' 277 name='Romeo' 278 subscription='both'> 279 <group>Friends</group> 280 </item> 281 <item jid='mercutio@example.org' 282 name='Mercutio' 283 subscription='from'> 284 <group>Friends</group> 285 </item> 286 <item jid='benvolio@example.org' 287 name='Benvolio' 288 subscription='both'> 289 <group>Friends</group> 290 </item> 291 </query> 292 `) 293 reply := data.Stanza{ 294 Value: &iq, 295 } 296 rosterEntrys, err := data.ParseRoster(reply) 297 c.Assert(rosterEntrys, NotNil) 298 c.Assert(err, IsNil) 299} 300 301func (s *XMPPSuite) TestConnSend(c *C) { 302 mockOut := mockConnIOReaderWriter{} 303 conn := conn{ 304 out: &mockOut, 305 jid: "jid", 306 } 307 err := conn.Send("example@xmpp.com", "message") 308 c.Assert(string(mockOut.write), Matches, "<message to='example@xmpp.com' from='jid' type='chat'><body>message</body><nos:x xmlns:nos='google:nosave' value='enabled'/><arc:record xmlns:arc='http://jabber.org/protocol/archive' otr='require'/><no-copy xmlns='urn:xmpp:hints'/><no-permanent-store xmlns='urn:xmpp:hints'/><private xmlns='urn:xmpp:carbons:2'/></message>") 309 c.Assert(err, IsNil) 310} 311