go-xmpp/client_test.go

208 lines
5.5 KiB
Go
Raw Normal View History

package xmpp // import "gosrc.io/xmpp"
import (
"encoding/xml"
"errors"
"fmt"
"net"
"testing"
2017-10-20 14:53:15 +00:00
"time"
)
const (
// Default port is not standard XMPP port to avoid interfering
// with local running XMPP server
testXMPPAddress = "localhost:15222"
2017-10-20 14:53:15 +00:00
defaultTimeout = 2 * time.Second
)
func TestClient_Connect(t *testing.T) {
// Setup Mock server
2017-10-06 00:11:28 +00:00
mock := ServerMock{}
mock.Start(t, testXMPPAddress, handlerConnectSuccess)
// Test / Check result
2018-09-26 14:25:04 +00:00
config := Config{Address: testXMPPAddress, Jid: "test@localhost", Password: "test", Insecure: true}
var client *Client
var err error
2018-09-26 14:25:04 +00:00
if client, err = NewClient(config); err != nil {
t.Errorf("connect create XMPP client: %s", err)
}
if err = client.Connect(); err != nil {
t.Errorf("XMPP connection failed: %s", err)
}
mock.Stop()
}
func TestClient_NoInsecure(t *testing.T) {
// Setup Mock server
mock := ServerMock{}
mock.Start(t, testXMPPAddress, handlerAbortTLS)
// Test / Check result
2018-09-26 14:25:04 +00:00
config := Config{Address: testXMPPAddress, Jid: "test@localhost", Password: "test"}
var client *Client
var err error
2018-09-26 14:25:04 +00:00
if client, err = NewClient(config); err != nil {
t.Errorf("cannot create XMPP client: %s", err)
}
if err = client.Connect(); err == nil {
// When insecure is not allowed:
t.Errorf("should fail as insecure connection is not allowed and server does not support TLS")
}
mock.Stop()
}
// Check that the client is properly tracking features, as session negotiation progresses.
func TestClient_FeaturesTracking(t *testing.T) {
// Setup Mock server
mock := ServerMock{}
mock.Start(t, testXMPPAddress, handlerAbortTLS)
// Test / Check result
config := Config{Address: testXMPPAddress, Jid: "test@localhost", Password: "test"}
var client *Client
var err error
if client, err = NewClient(config); err != nil {
t.Errorf("cannot create XMPP client: %s", err)
}
if err = client.Connect(); err == nil {
// When insecure is not allowed:
t.Errorf("should fail as insecure connection is not allowed and server does not support TLS")
}
mock.Stop()
}
//=============================================================================
// Basic XMPP Server Mock Handlers.
const serverStreamOpen = "<?xml version='1.0'?><stream:stream to='%s' id='%s' xmlns='%s' xmlns:stream='%s' version='1.0'>"
2017-10-06 00:11:28 +00:00
func handlerConnectSuccess(t *testing.T, c net.Conn) {
decoder := xml.NewDecoder(c)
2017-10-06 00:11:28 +00:00
checkOpenStream(t, c, decoder)
2017-10-20 14:53:15 +00:00
sendStreamFeatures(t, c, decoder) // Send initial features
readAuth(t, decoder)
fmt.Fprintln(c, "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>")
2017-10-20 14:53:15 +00:00
checkOpenStream(t, c, decoder) // Reset stream
sendBindFeature(t, c, decoder) // Send post auth features
bind(t, c, decoder)
}
// We expect client will abort on TLS
func handlerAbortTLS(t *testing.T, c net.Conn) {
decoder := xml.NewDecoder(c)
checkOpenStream(t, c, decoder)
sendStreamFeatures(t, c, decoder) // Send initial features
}
2017-10-06 00:11:28 +00:00
func checkOpenStream(t *testing.T, c net.Conn, decoder *xml.Decoder) {
2017-10-20 14:53:15 +00:00
c.SetDeadline(time.Now().Add(defaultTimeout))
defer c.SetDeadline(time.Time{})
for { // TODO clean up. That for loop is not elegant and I prefer bounded recursion.
var token xml.Token
token, err := decoder.Token()
if err != nil {
t.Errorf("cannot read next token: %s", err)
}
switch elem := token.(type) {
// Wait for first startElement
case xml.StartElement:
if elem.Name.Space != NSStream || elem.Name.Local != "stream" {
err = errors.New("xmpp: expected <stream> but got <" + elem.Name.Local + "> in " + elem.Name.Space)
2017-10-06 00:11:28 +00:00
return
}
if _, err := fmt.Fprintf(c, serverStreamOpen, "localhost", "streamid1", NSClient, NSStream); err != nil {
t.Errorf("cannot write server stream open: %s", err)
}
return
}
}
}
2017-10-20 14:53:15 +00:00
func sendStreamFeatures(t *testing.T, c net.Conn, _ *xml.Decoder) {
// This is a basic server, supporting only 1 stream feature: SASL Plain Auth
features := `<stream:features>
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>PLAIN</mechanism>
</mechanisms>
</stream:features>`
if _, err := fmt.Fprintln(c, features); err != nil {
t.Errorf("cannot send stream feature: %s", err)
}
}
// TODO return err in case of error reading the auth params
func readAuth(t *testing.T, decoder *xml.Decoder) string {
se, err := nextStart(decoder)
if err != nil {
t.Errorf("cannot read auth: %s", err)
return ""
}
var nv interface{}
nv = &auth{}
// Decode element into pointer storage
if err = decoder.DecodeElement(nv, &se); err != nil {
t.Errorf("cannot decode auth: %s", err)
return ""
}
switch v := nv.(type) {
case *auth:
return v.Value
}
return ""
}
2017-10-20 14:53:15 +00:00
func sendBindFeature(t *testing.T, c net.Conn, _ *xml.Decoder) {
// This is a basic server, supporting only 1 stream feature: SASL Plain Auth
features := `<stream:features>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
</stream:features>`
if _, err := fmt.Fprintln(c, features); err != nil {
t.Errorf("cannot send stream feature: %s", err)
}
}
func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
se, err := nextStart(decoder)
if err != nil {
t.Errorf("cannot read bind: %s", err)
return
}
2018-01-13 17:50:17 +00:00
iq := &IQ{}
// Decode element into pointer storage
if err = decoder.DecodeElement(&iq, &se); err != nil {
t.Errorf("cannot decode bind iq: %s", err)
return
}
2018-01-15 11:28:34 +00:00
// TODO Check all elements
switch iq.Payload[0].(type) {
2018-01-16 21:33:21 +00:00
case *BindBind:
2017-10-20 14:53:15 +00:00
result := `<iq id='%s' type='result'>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
<jid>%s</jid>
</bind>
</iq>`
2017-10-20 14:53:15 +00:00
fmt.Fprintf(c, result, iq.Id, "test@localhost/test") // TODO use real JID
}
}