1package slacktest
2
3import (
4	"encoding/json"
5	"fmt"
6	"log"
7	"net/http"
8	"net/http/httptest"
9	"time"
10
11	"github.com/nlopes/slack"
12)
13
14func newMessageChannels() *messageChannels {
15	sent := make(chan (string))
16	seen := make(chan (string))
17	mc := messageChannels{
18		seen: seen,
19		sent: sent,
20	}
21	return &mc
22}
23
24// Customize the server's responses.
25type Customize interface {
26	Handle(pattern string, handler http.HandlerFunc)
27}
28
29type binder func(Customize)
30
31// NewTestServer returns a slacktest.Server ready to be started
32func NewTestServer(custom ...binder) *Server {
33	serverChans := newMessageChannels()
34
35	channels := &serverChannels{}
36	groups := &serverGroups{}
37	s := &Server{
38		registered:           map[string]struct{}{},
39		mux:                  http.NewServeMux(),
40		seenInboundMessages:  &messageCollection{},
41		seenOutboundMessages: &messageCollection{},
42	}
43
44	for _, c := range custom {
45		c(s)
46	}
47
48	s.Handle("/ws", s.wsHandler)
49	s.Handle("/rtm.start", rtmStartHandler)
50	s.Handle("/rtm.connect", rtmConnectHandler)
51	s.Handle("/chat.postMessage", s.postMessageHandler)
52	s.Handle("/channels.list", listChannelsHandler)
53	s.Handle("/groups.list", listGroupsHandler)
54	s.Handle("/users.info", usersInfoHandler)
55	s.Handle("/bots.info", botsInfoHandler)
56	s.Handle("/auth.test", authTestHandler)
57
58	httpserver := httptest.NewUnstartedServer(s.mux)
59	addr := httpserver.Listener.Addr().String()
60
61	s.ServerAddr = addr
62	s.server = httpserver
63	s.BotName = defaultBotName
64	s.BotID = defaultBotID
65	s.SeenFeed = serverChans.seen
66	s.channels = channels
67	s.groups = groups
68
69	addErr := addServerToHub(s, serverChans)
70	if addErr != nil {
71		log.Printf("Unable to add server to hub: %s", addErr.Error())
72	}
73
74	return s
75}
76
77// Handle allow for customizing endpoints
78func (sts *Server) Handle(pattern string, handler http.HandlerFunc) {
79	if _, found := sts.registered[pattern]; found {
80		log.Printf("route already registered: %s\n", pattern)
81		return
82	}
83
84	sts.registered[pattern] = struct{}{}
85	sts.mux.Handle(pattern, contextHandler(sts, handler))
86}
87
88// GetChannels returns all the fake channels registered
89func (sts *Server) GetChannels() []slack.Channel {
90	sts.channels.RLock()
91	defer sts.channels.RUnlock()
92	return sts.channels.channels
93}
94
95// GetGroups returns all the fake groups registered
96func (sts *Server) GetGroups() []slack.Group {
97	return sts.groups.channels
98}
99
100// GetSeenInboundMessages returns all messages seen via websocket excluding pings
101func (sts *Server) GetSeenInboundMessages() []string {
102	sts.seenInboundMessages.RLock()
103	m := sts.seenInboundMessages.messages
104	sts.seenInboundMessages.RUnlock()
105	return m
106}
107
108// GetSeenOutboundMessages returns all messages seen via websocket excluding pings
109func (sts *Server) GetSeenOutboundMessages() []string {
110	sts.seenOutboundMessages.RLock()
111	m := sts.seenOutboundMessages.messages
112	sts.seenOutboundMessages.RUnlock()
113	return m
114}
115
116// SawOutgoingMessage checks if a message was sent to connected websocket clients
117func (sts *Server) SawOutgoingMessage(msg string) bool {
118	sts.seenOutboundMessages.RLock()
119	defer sts.seenOutboundMessages.RUnlock()
120	for _, m := range sts.seenOutboundMessages.messages {
121		evt := &slack.MessageEvent{}
122		jErr := json.Unmarshal([]byte(m), evt)
123		if jErr != nil {
124			continue
125		}
126
127		if evt.Text == msg {
128			return true
129		}
130	}
131	return false
132}
133
134// SawMessage checks if an incoming message was seen
135func (sts *Server) SawMessage(msg string) bool {
136	sts.seenInboundMessages.RLock()
137	defer sts.seenInboundMessages.RUnlock()
138	for _, m := range sts.seenInboundMessages.messages {
139		evt := &slack.MessageEvent{}
140		jErr := json.Unmarshal([]byte(m), evt)
141		if jErr != nil {
142			// This event isn't a message event so we'll skip it
143			continue
144		}
145		if evt.Text == msg {
146			return true
147		}
148	}
149	return false
150}
151
152// GetAPIURL returns the api url you can pass to slack.SLACK_API
153func (sts *Server) GetAPIURL() string {
154	return "http://" + sts.ServerAddr + "/"
155}
156
157// GetWSURL returns the websocket url
158func (sts *Server) GetWSURL() string {
159	return "ws://" + sts.ServerAddr + "/ws"
160}
161
162// Stop stops the test server
163func (sts *Server) Stop() {
164	sts.server.Close()
165}
166
167// Start starts the test server
168func (sts *Server) Start() {
169	log.Print("starting server")
170	sts.server.Start()
171}
172
173// SendMessageToBot sends a message addressed to the Bot
174func (sts *Server) SendMessageToBot(channel, msg string) {
175	m := slack.Message{}
176	m.Type = slack.TYPE_MESSAGE
177	m.Channel = channel
178	m.User = defaultNonBotUserID
179	m.Text = fmt.Sprintf("<@%s> %s", sts.BotID, msg)
180	m.Timestamp = fmt.Sprintf("%d", time.Now().Unix())
181	j, jErr := json.Marshal(m)
182	if jErr != nil {
183		log.Printf("Unable to marshal message for bot: %s", jErr.Error())
184		return
185	}
186	go sts.queueForWebsocket(string(j), sts.ServerAddr)
187}
188
189// SendDirectMessageToBot sends a direct message to the bot
190func (sts *Server) SendDirectMessageToBot(msg string) {
191	m := slack.Message{}
192	m.Type = slack.TYPE_MESSAGE
193	m.Channel = "D024BE91L"
194	m.User = defaultNonBotUserID
195	m.Text = msg
196	m.Timestamp = fmt.Sprintf("%d", time.Now().Unix())
197	j, jErr := json.Marshal(m)
198	if jErr != nil {
199		log.Printf("Unable to marshal private message for bot: %s", jErr.Error())
200		return
201	}
202	go sts.queueForWebsocket(string(j), sts.ServerAddr)
203}
204
205// SendMessageToChannel sends a message to a channel
206func (sts *Server) SendMessageToChannel(channel, msg string) {
207	m := slack.Message{}
208	m.Type = slack.TYPE_MESSAGE
209	m.Channel = channel
210	m.Text = msg
211	m.User = defaultNonBotUserID
212	m.Timestamp = fmt.Sprintf("%d", time.Now().Unix())
213	j, jErr := json.Marshal(m)
214	if jErr != nil {
215		log.Printf("Unable to marshal message for channel: %s", jErr.Error())
216		return
217	}
218	stringMsg := string(j)
219	go sts.queueForWebsocket(stringMsg, sts.ServerAddr)
220}
221
222// SendToWebsocket send `s` as is to connected clients.
223// This is useful for sending your own custom json to the websocket
224func (sts *Server) SendToWebsocket(s string) {
225	go sts.queueForWebsocket(s, sts.ServerAddr)
226}
227
228// SetBotName sets a custom botname
229func (sts *Server) SetBotName(b string) {
230	sts.BotName = b
231}
232
233// SendBotChannelInvite invites the bot to a channel
234func (sts *Server) SendBotChannelInvite() {
235	joinMsg := `
236	{
237			"type":"channel_joined",
238			"channel":
239					{
240							"id": "C024BE92L",
241							"name": "bot-playground",
242							"is_channel": true,
243							"created": 1360782804,
244							"creator": "W012A3CDE",
245							"is_archived": false,
246							"is_general": true,
247							"members": [
248									"W012A3CDE"
249							],
250							"topic": {
251									"value": "Fun times",
252									"creator": "W012A3CDE",
253									"last_set": 1360782804
254							},
255							"purpose": {
256									"value": "This channel is for fun",
257									"creator": "W012A3CDE",
258									"last_set": 1360782804
259							},
260							"is_member": true
261					}
262	}`
263	sts.SendToWebsocket(joinMsg)
264}
265
266// SendBotGroupInvite invites the bot to a channel
267func (sts *Server) SendBotGroupInvite() {
268	joinMsg := `
269	{
270			"type":"group_joined",
271			"channel":
272			{
273				"id": "G024BE91L",
274				"name": "secretplans",
275				"is_group": true,
276				"created": 1360782804,
277				"creator": "W012A3CDE",
278				"is_archived": false,
279				"members": [
280					"W012A3CDE"
281				],
282				"topic": {
283					"value": "Secret plans on hold",
284					"creator": "W012A3CDE",
285					"last_set": 1360782804
286				},
287				"purpose": {
288					"value": "Discuss secret plans that no-one else should know",
289					"creator": "W012A3CDE",
290					"last_set": 1360782804
291				}
292			}
293	}`
294	sts.SendToWebsocket(joinMsg)
295}
296
297// GetTestRTMInstance will give you an RTM instance in the context of the current fake server
298func (sts *Server) GetTestRTMInstance() *slack.RTM {
299	api := slack.New("ABCEFG", slack.OptionAPIURL(sts.GetAPIURL()))
300	rtm := api.NewRTM()
301	return rtm
302}
303