~samwhited/xmpp

ref: 7da78eb2b3b378064f931bf2bf2a9c07adbf315d xmpp/internal/xmpptest/session.go -rw-r--r-- 3.2 KiB
7da78eb2Sam Whited xmpptest: add a client/server test mechanism 3 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// Copyright 2017 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.

// Package xmpptest provides utilities for XMPP testing.
package xmpptest // import "mellium.im/xmpp/internal/xmpptest"

import (
	"context"
	"io"
	"strings"

	"mellium.im/xmpp"
	"mellium.im/xmpp/internal/ns"
	"mellium.im/xmpp/jid"
	"mellium.im/xmpp/stream"
)

// NopNegotiator marks the state as ready (by returning state|xmpp.Ready) and
// pops the first token (likely <stream:stream>) but does not perform any
// validation on the token, transmit any data over the wire, or perform any
// other session negotiation.
func NopNegotiator(state xmpp.SessionState) xmpp.Negotiator {
	return func(ctx context.Context, s *xmpp.Session, data interface{}) (xmpp.SessionState, io.ReadWriter, interface{}, error) {
		// Pop the stream start token.
		rc := s.TokenReader()
		defer rc.Close()

		_, err := rc.Token()
		return state | xmpp.Ready, nil, nil, err
	}
}

// NewSession returns a new client-to-client XMPP session with the state bits
// set to state|xmpp.Ready, the origin JID set to "test@example.net" and the
// location JID set to "example.net".
//
// NewSession panics on error for ease of use in testing, where a panic is
// acceptable.
func NewSession(state xmpp.SessionState, rw io.ReadWriter) *xmpp.Session {
	location := jid.MustParse("example.net")
	origin := jid.MustParse("test@example.net")

	s, err := xmpp.NegotiateSession(
		context.Background(), location, origin,
		struct {
			io.Reader
			io.Writer
		}{
			Reader: io.MultiReader(
				strings.NewReader(`<stream:stream xmlns="`+ns.Client+`" xmlns:stream="`+stream.NS+`">`),
				rw,
				strings.NewReader(`</stream:stream>`),
			),
			Writer: rw,
		},
		false,
		NopNegotiator(state),
	)
	if err != nil {
		panic(err)
	}
	return s
}

// ClientServer is two coupled xmpp.Session's that can respond to one another in
// tests.
// The client session's methods are exposed to allow sending messages easily.
type ClientServer struct {
	*xmpp.Session
	server *xmpp.Session
}

// NewClientServer returns a ClientServer with the client and server goroutines
// started.
// The server handler will be used to handle any messages sent through the
// client (as if a server on the opposite end was responding).
// Both serve goroutines are started when NewClient is called and shut down when
// Client is closed.
func NewClientServer(state xmpp.SessionState, server xmpp.Handler) ClientServer {
	clientSessionReader, serverSessionWriter := io.Pipe()
	serverSessionReader, clientSessionWriter := io.Pipe()
	clientSession := NewSession(state, struct {
		io.Reader
		io.Writer
	}{
		Reader: clientSessionReader,
		Writer: clientSessionWriter,
	})
	serverSession := NewSession(0, struct {
		io.Reader
		io.Writer
	}{
		Reader: serverSessionReader,
		Writer: serverSessionWriter,
	})
	go func() {
		/* #nosec */
		clientSession.Serve(nil)
	}()
	go func() {
		/* #nosec */
		serverSession.Serve(server)
	}()
	return ClientServer{
		Session: clientSession,
		server:  serverSession,
	}
}

// Close calls the client and server sessions close methods.
func (c ClientServer) Close() error {
	err := c.Session.Close()
	if e := c.server.Close(); e != nil {
		err = e
	}
	return err
}