package slack

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/zerobotlabs/relax/Godeps/_workspace/src/github.com/gorilla/websocket"
	. "github.com/zerobotlabs/relax/Godeps/_workspace/src/github.com/onsi/ginkgo"
	. "github.com/zerobotlabs/relax/Godeps/_workspace/src/github.com/onsi/gomega"
	"github.com/zerobotlabs/relax/Godeps/_workspace/src/gopkg.in/redis.v3"
)

func Test(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "client")
}

func newTestServer(jsonResponse string, statusCode int, c chan<- []byte) *httptest.Server {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(statusCode)
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintln(w, jsonResponse)

		if c != nil {
			c <- []byte(jsonResponse)
		}
	}))

	return server
}

func setRedisQueueWebEnv() {
	now := time.Now()
	os.Setenv("RELAX_EVENTS_QUEUE", fmt.Sprintf("redis_key_queue_web_%d", now.Nanosecond()))
}

func newWSListenerServer(c chan<- []byte) *httptest.Server {
	wsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" {
			http.Error(w, "method not allowed", 405)
			return
		}

		var upgrader websocket.Upgrader

		ws, err := upgrader.Upgrade(w, r, http.Header{})
		if err != nil {
			return
		}
		defer ws.Close()

		for {
			_, msg, err := ws.ReadMessage()
			if err != nil {
				if strings.HasPrefix(err.Error(), "websocket: close") {
					c <- []byte("connection closed")
				}
				continue
			}

			c <- []byte(string(msg) + " returned")
		}
	}))

	return wsServer
}

func newWSServer(wsResponse string) *httptest.Server {
	wsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" {
			http.Error(w, "method not allowed", 405)
			return
		}

		var upgrader websocket.Upgrader

		ws, err := upgrader.Upgrade(w, r, http.Header{})
		if err != nil {
			return
		}
		defer ws.Close()

		err = ws.WriteMessage(websocket.TextMessage, []byte(wsResponse))
		if err != nil {
			panic(err)
		}
	}))

	return wsServer
}

func newRedisClient() *redis.Client {
	return redis.NewClient(&redis.Options{
		Addr:     os.Getenv("REDIS_HOST"),
		Password: os.Getenv("REDIS_PASSWORD"),
		DB:       0,
	})
}

// Stolen from: github.com/gorilla/websocket
func makeWsProto(s string) string {
	return "ws" + strings.TrimPrefix(s, "http")
}

var _ = Describe("Client", func() {
	var rc *redis.Client

	BeforeEach(func() {
		os.Setenv("REDIS_HOST", "localhost:6379")
		os.Setenv("RELAX_MUTEX_KEY", "relax_mutex_key")

		rc = newRedisClient()
		rc.FlushDb()
	})

	Describe("InitClient - team_removed event", func() {
		var server *httptest.Server
		var existingSlackHost string
		var wsServer *httptest.Server
		var receiverChan chan []byte

		BeforeEach(func() {
			setRedisQueueWebEnv()
			receiverChan = make(chan []byte)

			wsServer = newWSListenerServer(receiverChan)
			server = newTestServer(fmt.Sprintf(`
{
	"ok": true,
	"url": "%s",
	"self": {
		"id": "URELAXBOT",
		"name": "bot",
		"created": 1402463766,
		"manual_presence": "active"
	},
	"team": {
		"id": "TDEADBEEF",
		"name": "Example Team",
		"email_domain": "",
		"domain": "example",
		"msg_edit_window_mins": -1,
		"over_storage_limit": false,
		"plan": "std"
	},
	"users": [
		{
			"id": "U023BECGF",
			"name": "bobby",
			"deleted": false,
			"color": "9f69e7",
			"profile": {
					"first_name": "Bobby",
					"last_name": "Tables",
					"real_name": "Bobby Tables",
					"email": "bobby@slack.com",
					"skype": "my-skype-name",
					"phone": "+1 (123) 456 7890",
					"image_24": "https://...",
					"image_32": "https://...",
					"image_48": "https://...",
					"image_72": "https://...",
					"image_192": "https://..."
			},
			"is_admin": true,
			"is_owner": true,
			"has_2fa": false,
			"has_files": true
		}
	],
	"channels": [
		{
			"id": "C024BE91L",
			"name": "fun",
			"created": 1360782804,
			"creator": "U024BE7LH",
			"is_archived": false,
			"is_member": false,
			"num_members": 6,
			"topic": {
					"value": "Fun times",
					"creator": "U024BE7LV",
					"last_set": 1369677212
			},
			"purpose": {
					"value": "This channel is for fun",
					"creator": "U024BE7LH",
					"last_set": 1360782804
			}
		}
	],
	"groups": [
		{
			"id": "G0S90BMLM",
			"name":"mpdm-arun--nestordev--nestorbot-1",
			"is_group":true,
			"created":1457726041,
			"creator":"U0AJWAFAB",
			"is_archived":false,
			"is_mpim":true,
			"has_pins":false,
			"is_open":false,
			"last_read":"0000000000.000000",
			"latest":null,
			"unread_count":0,
			"unread_count_display":0,
			"members": ["U0AJWAFAB","U0G9XPJQ2","U0GNJR6QY"],
			"topic": {
				"value":"Group messaging",
				"creator":"U0AJWAFAB",
				"last_set":1457726041
			},
			"purpose": {
				"value":"Group messaging with: @arun @nestordev @nestorbot",
				"creator":"U0AJWAFAB",
				"last_set":1457726041
			}
		}
	]
}
`, makeWsProto(wsServer.URL)), 200, nil)
			existingSlackHost = os.Getenv("SLACK_HOST")
			os.Setenv("SLACK_HOST", server.URL)
			os.Setenv("RELAX_BOTS_KEY", "relax_redis_key")
			os.Setenv("RELAX_BOTS_PUBSUB", "redis_pubsub_relax")

			rc.HSet(os.Getenv("RELAX_BOTS_KEY"), "TDEADBEEF", `{
				"token": "xoxo_deadbeef",
				"team_id": "TDEADBEEF",
				"provider": "slack"
			}`)

			InitClients()
		})

		AfterEach(func() {
			server.Close()
			wsServer.Close()
		})

		It("should stop the client", func() {
			message := ""

			// Wait until the client has been initialized
			result := rc.HGet(os.Getenv("RELAX_MUTEX_KEY"), "bot-TDEADBEEF-started")
			for result == nil || result.Val() == "" {
				result = rc.HGet(os.Getenv("RELAX_MUTEX_KEY"), "bot-TDEADBEEF-started")
				time.Sleep(500 * time.Millisecond)
			}

			// The listener might not be ready yet, so let's loop until we do
			intCmd := rc.Publish(os.Getenv("RELAX_BOTS_PUBSUB"), `{"type":"team_removed","team_id":"TDEADBEEF"}`)
			for intCmd == nil || intCmd.Val() == 0 {
				intCmd = rc.Publish(os.Getenv("RELAX_BOTS_PUBSUB"), `{"type":"team_removed","team_id":"TDEADBEEF"}`)
				time.Sleep(500 * time.Millisecond)
			}

			Expect(intCmd).ToNot(BeNil())

			timeout := make(chan bool, 1)
			go func() {
				time.Sleep(2 * time.Second)
				timeout <- true
			}()

			select {
			case msg := <-receiverChan:
				message = string(msg)
			case <-timeout:
			}

			Expect(message).To(Equal("connection closed"))
			Expect(Clients.Count()).To(Equal(0))
		})
	})

	Describe("InitClient - message event", func() {
		var server *httptest.Server
		var existingSlackHost string
		var wsServer *httptest.Server
		var receiverChan chan []byte

		BeforeEach(func() {
			setRedisQueueWebEnv()
			receiverChan = make(chan []byte)

			wsServer = newWSListenerServer(receiverChan)
			server = newTestServer(fmt.Sprintf(`
{
	"ok": true,
	"url": "%s",
	"self": {
		"id": "URELAXBOT",
		"name": "bot",
		"created": 1402463766,
		"manual_presence": "active"
	},
	"team": {
		"id": "TDEADBEEF",
		"name": "Example Team",
		"email_domain": "",
		"domain": "example",
		"msg_edit_window_mins": -1,
		"over_storage_limit": false,
		"plan": "std"
	},
	"users": [
		{
			"id": "U023BECGF",
			"name": "bobby",
			"deleted": false,
			"color": "9f69e7",
			"profile": {
					"first_name": "Bobby",
					"last_name": "Tables",
					"real_name": "Bobby Tables",
					"email": "bobby@slack.com",
					"skype": "my-skype-name",
					"phone": "+1 (123) 456 7890",
					"image_24": "https://...",
					"image_32": "https://...",
					"image_48": "https://...",
					"image_72": "https://...",
					"image_192": "https://..."
			},
			"is_admin": true,
			"is_owner": true,
			"has_2fa": false,
			"has_files": true
		}
	],
	"channels": [
		{
			"id": "C024BE91L",
			"name": "fun",
			"created": 1360782804,
			"creator": "U024BE7LH",
			"is_archived": false,
			"is_member": false,
			"num_members": 6,
			"topic": {
					"value": "Fun times",
					"creator": "U024BE7LV",
					"last_set": 1369677212
			},
			"purpose": {
					"value": "This channel is for fun",
					"creator": "U024BE7LH",
					"last_set": 1360782804
			}
		}
	],
	"groups": [
		{
			"id": "G0S90BMLM",
			"name":"mpdm-arun--nestordev--nestorbot-1",
			"is_group":true,
			"created":1457726041,
			"creator":"U0AJWAFAB",
			"is_archived":false,
			"is_mpim":true,
			"has_pins":false,
			"is_open":false,
			"last_read":"0000000000.000000",
			"latest":null,
			"unread_count":0,
			"unread_count_display":0,
			"members": ["U0AJWAFAB","U0G9XPJQ2","U0GNJR6QY"],
			"topic": {
				"value":"Group messaging",
				"creator":"U0AJWAFAB",
				"last_set":1457726041
			},
			"purpose": {
				"value":"Group messaging with: @arun @nestordev @nestorbot",
				"creator":"U0AJWAFAB",
				"last_set":1457726041
			}
		}
	]
}
`, makeWsProto(wsServer.URL)), 200, nil)
			existingSlackHost = os.Getenv("SLACK_HOST")
			os.Setenv("SLACK_HOST", server.URL)
			os.Setenv("RELAX_BOTS_KEY", "relax_redis_key")
			os.Setenv("RELAX_BOTS_PUBSUB", "redis_pubsub_relax")

			rc.HSet(os.Getenv("RELAX_BOTS_KEY"), "TDEADBEEF", `{
				"token": "xoxo_deadbeef",
				"team_id": "TDEADBEEF",
				"provider": "slack"
			}`)

			InitClients()
		})

		AfterEach(func() {
			server.Close()
			wsServer.Close()
		})

		Context("message has not already been sent (redis mutex key has not been set)", func() {
			It("should send a message to Slack", func() {
				message := ""

				// Wait until the client has been initialized
				result := rc.HGet(os.Getenv("RELAX_MUTEX_KEY"), "bot-TDEADBEEF-started")
				for result == nil || result.Val() == "" {
					result = rc.HGet(os.Getenv("RELAX_MUTEX_KEY"), "bot-TDEADBEEF-started")
					time.Sleep(500 * time.Millisecond)
				}

				// The listener might not be ready yet, so let's loop until we do
				intCmd := rc.Publish(os.Getenv("RELAX_BOTS_PUBSUB"), `{"type":"message","team_id":"TDEADBEEF","id":"CAFEDEAD1","payload":"message to slack"}`)
				for intCmd == nil || intCmd.Val() == 0 {
					intCmd = rc.Publish(os.Getenv("RELAX_BOTS_PUBSUB"), `{"type":"message","team_id":"TDEADBEEF","id":"CAFEDEAD1","payload":"message to slack"}`)
					time.Sleep(500 * time.Millisecond)
				}

				Expect(intCmd).ToNot(BeNil())

				timeout := make(chan bool, 1)
				go func() {
					time.Sleep(2 * time.Second)
					timeout <- true
				}()

				select {
				case msg := <-receiverChan:
					message = string(msg)
				case <-timeout:
				}

				Expect(message).To(Equal("message to slack returned"))
				val := rc.HGet(os.Getenv("RELAX_MUTEX_KEY"), "send_slack_message:CAFEDEAD1")
				Expect(val).ToNot(BeNil())
				Expect(val.Val()).To(Equal("ok"))
			})
		})

		Context("message has already been sent (redis mutex key has been set)", func() {
			It("should send a message to Slack", func() {
				message := ""

				// Wait until the client has been initialized
				result := rc.HGet(os.Getenv("RELAX_MUTEX_KEY"), "bot-TDEADBEEF-started")
				for result == nil || result.Val() == "" {
					result = rc.HGet(os.Getenv("RELAX_MUTEX_KEY"), "bot-TDEADBEEF-started")
					time.Sleep(500 * time.Millisecond)
				}

				val := rc.HSet(os.Getenv("RELAX_MUTEX_KEY"), "send_slack_message:CAFEDEAD1", "ok")
				Expect(val).ToNot(BeNil())

				// The listener might not be ready yet, so let's loop until we do
				intCmd := rc.Publish(os.Getenv("RELAX_BOTS_PUBSUB"), `{"type":"message","team_id":"TDEADBEEF","id":"CAFEDEAD1","payload":"message to slack"}`)
				for intCmd == nil || intCmd.Val() == 0 {
					intCmd = rc.Publish(os.Getenv("RELAX_BOTS_PUBSUB"), `{"type":"message","team_id":"TDEADBEEF","id":"CAFEDEAD1","payload":"message to slack"}`)
					time.Sleep(500 * time.Millisecond)
				}

				Expect(intCmd).ToNot(BeNil())

				timeout := make(chan bool, 1)
				go func() {
					time.Sleep(2 * time.Second)
					timeout <- true
				}()

				select {
				case msg := <-receiverChan:
					message = string(msg)
				case <-timeout:
				}

				Expect(message).To(Equal(""))
			})
		})
	})

	Describe("InitClients - team_added event", func() {
		var server *httptest.Server
		var existingSlackHost string
		var wsServer *httptest.Server

		BeforeEach(func() {
			setRedisQueueWebEnv()

			wsServer = newWSServer(`
						{
							"type": "message",
							"channel": "C024BE91L",
							"user": "U023BECGF",
							"text": "<@URELAXBOT>: Hello world",
							"ts": "1355517523.000005"
						}
					`)

			server = newTestServer(fmt.Sprintf(`
{
		"ok": true,
		"url": "%s",
		"self": {
			"id": "URELAXBOT",
			"name": "bot",
			"created": 1402463766,
			"manual_presence": "active"
		},
		"team": {
			"id": "TDEADBEEF",
			"name": "Example Team",
			"email_domain": "",
			"domain": "example",
			"msg_edit_window_mins": -1,
			"over_storage_limit": false,
			"plan": "std"
		},
		"users": [
			{
				"id": "U023BECGF",
				"name": "bobby",
				"deleted": false,
				"color": "9f69e7",
				"profile": {
						"first_name": "Bobby",
						"last_name": "Tables",
						"real_name": "Bobby Tables",
						"email": "bobby@slack.com",
						"skype": "my-skype-name",
						"phone": "+1 (123) 456 7890",
						"image_24": "https://...",
						"image_32": "https://...",
						"image_48": "https://...",
						"image_72": "https://...",
						"image_192": "https://..."
				},
				"is_admin": true,
				"is_owner": true,
				"has_2fa": false,
				"has_files": true
			}
		],
		"channels": [
			{
				"id": "C024BE91L",
				"name": "fun",
				"created": 1360782804,
				"creator": "U024BE7LH",
				"is_archived": false,
				"is_member": false,
				"num_members": 6,
				"topic": {
						"value": "Fun times",
						"creator": "U024BE7LV",
						"last_set": 1369677212
				},
				"purpose": {
						"value": "This channel is for fun",
						"creator": "U024BE7LH",
						"last_set": 1360782804
				}
			}
    ]
}
`, makeWsProto(wsServer.URL)), 200, nil)
			existingSlackHost = os.Getenv("SLACK_HOST")
			os.Setenv("SLACK_HOST", server.URL)
			os.Setenv("RELAX_BOTS_KEY", "relax_redis_key")
			os.Setenv("RELAX_BOTS_PUBSUB", "redis_pubsub_relax")
		})

		AfterEach(func() {
			server.Close()
			wsServer.Close()
		})

		// InitClients() is what is called by main()
		// so this acts like an integration test since we're testing end to end
		Context("initializing clients from keys in Redis", func() {
			JustBeforeEach(func() {
				rc.HSet(os.Getenv("RELAX_BOTS_KEY"), "TDEADBEEF", `{
					"token": "xoxo_deadbeef",
					"team_id": "TDEADBEEF",
					"provider": "slack",
					"namespace": "namespace"
				}`)
			})

			It("should initialize clients and respond to messages from Slack", func() {
				var event Event

				InitClients()
				redisClient := newRedisClient()

				resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
				result := resultevent.Val()

				Expect(len(result)).To(Equal(2))
				err := json.Unmarshal([]byte(result[1]), &event)
				Expect(err).To(BeNil())

				Expect(event.Type).To(Equal("message_new"))
				Expect(event.ChannelUid).To(Equal("C024BE91L"))
				Expect(event.UserUid).To(Equal("U023BECGF"))
				Expect(event.Text).To(Equal("<@URELAXBOT>: Hello world"))
				Expect(event.Timestamp).To(Equal("1355517523.000005"))
				Expect(event.Im).To(BeFalse())
				Expect(event.RelaxBotUid).To(Equal("URELAXBOT"))
				Expect(event.TeamUid).To(Equal("TDEADBEEF"))
				Expect(event.Provider).To(Equal("slack"))
				Expect(event.EventTimestamp).To(Equal("1355517523.000005"))
				Expect(event.Namespace).To(Equal("namespace"))

				val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
				Expect(val).ToNot(BeNil())
				Expect(val.Val()).To(Equal("ok"))
			})

			Context("with botmetrics registration enabled", func() {
				var botmetricsServer *httptest.Server
				var botmetricsChan chan []byte

				JustBeforeEach(func() {
					botmetricsChan = make(chan []byte)
					os.Setenv("BOTMETRICS_ENABLED", "true")
					botmetricsServer = newTestServer("{\"ok\":true}", 201, botmetricsChan)

					os.Setenv("BOTMETRICS_API_HOST", botmetricsServer.URL)
					os.Setenv("BOTMETRICS_BOT_ID", "botmetrics_bot_id")
					os.Setenv("BOTMETRICS_API_KEY", "botmetrics_api_key")
				})

				AfterEach(func() {
					os.Unsetenv("BOTMETRICS_ENABLED")
				})

				Context("when the bot hasn't been registered with botmetrics already", func() {
					It("should call the botmetrics API to register a bot", func() {
						var message string
						InitClients()

						timeout := make(chan bool, 1)
						go func() {
							time.Sleep(2 * time.Second)
							timeout <- true
						}()

						select {
						case msg := <-botmetricsChan:
							message = string(msg)
						case <-timeout:
						}

						Expect(message).To(Equal("{\"ok\":true}"))
						val := rc.HGet(os.Getenv("RELAX_MUTEX_KEY"), "botmetrics:TDEADBEEF")
						Expect(val).ToNot(BeNil())
						Expect(val.Val()).To(Equal("ok"))
					})
				})

				Context("when the bot has been registered with botmetrics already", func() {
					JustBeforeEach(func() {
						val := rc.HSet(os.Getenv("RELAX_MUTEX_KEY"), "botmetrics:TDEADBEEF", "ok")
						Expect(val).ToNot(BeNil())
						Expect(val.Val()).To(Equal(true))
					})

					It("should call the botmetrics API to register a bot", func() {
						var message string
						InitClients()

						timeout := make(chan bool, 1)
						go func() {
							time.Sleep(2 * time.Second)
							timeout <- true
						}()

						select {
						case msg := <-botmetricsChan:
							message = string(msg)
						case <-timeout:
						}

						Expect(message).To(Equal(""))
						val := rc.HGet(os.Getenv("RELAX_MUTEX_KEY"), "botmetrics:TDEADBEEF")
						Expect(val).ToNot(BeNil())
						Expect(val.Val()).To(Equal("ok"))
					})
				})
			})
		})

		Context("RELAX_MUTEX_KEY already has the event registered", func() {
			JustBeforeEach(func() {
				val := rc.HSet(os.Getenv("RELAX_MUTEX_KEY"), "bot_message:C024BE91L:1355517523.000005", "ok")
				Expect(val).ToNot(BeNil())
				Expect(val.Val()).To(Equal(true))

				rc.HSet(os.Getenv("RELAX_BOTS_KEY"), "TDEADBEEF", `{
					"token": "xoxo_deadbeef",
					"team_id": "TDEADBEEF",
					"provider": "slack"
				}`)
			})

			It("should initialize clients but not send any events", func() {
				InitClients()
				redisClient := newRedisClient()

				resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
				result := resultevent.Val()

				Expect(len(result)).To(Equal(0))
			})
		})

		Context("initializing clients from pubsub in Redis after InitClients has been called", func() {
			JustBeforeEach(func() {
				rc.HSet(os.Getenv("RELAX_BOTS_KEY"), "TDEADBEEF", `{
					"token": "xoxo_deadbeef",
					"team_id": "TDEADBEEF",
					"provider": "slack"
				}`)

				InitClients()
			})

			It("should initialize clients and respond to messages from Slack", func() {
				var event Event

				// The listener might not be ready yet, so let's loop until we do
				intCmd := rc.Publish(os.Getenv("RELAX_BOTS_PUBSUB"), `{"type":"team_added","team_id":"TDEADBEEF"}`)
				for intCmd == nil || intCmd.Val() == 0 {
					intCmd = rc.Publish(os.Getenv("RELAX_BOTS_PUBSUB"), `{"type":"team_added","team_id":"TDEADBEEF"}`)
					time.Sleep(500 * time.Millisecond)
				}

				Expect(intCmd).ToNot(BeNil())

				redisClient := newRedisClient()

				resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
				result := resultevent.Val()

				Expect(len(result)).To(Equal(2))
				err := json.Unmarshal([]byte(result[1]), &event)
				Expect(err).To(BeNil())

				Expect(event.Type).To(Equal("message_new"))
				Expect(event.ChannelUid).To(Equal("C024BE91L"))
				Expect(event.UserUid).To(Equal("U023BECGF"))
				Expect(event.Text).To(Equal("<@URELAXBOT>: Hello world"))
				Expect(event.Timestamp).To(Equal("1355517523.000005"))
				Expect(event.Im).To(BeFalse())
				Expect(event.RelaxBotUid).To(Equal("URELAXBOT"))
				Expect(event.TeamUid).To(Equal("TDEADBEEF"))
				Expect(event.Provider).To(Equal("slack"))
				Expect(event.EventTimestamp).To(Equal("1355517523.000005"))
				Expect(event.Namespace).To(Equal(""))

				val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
				Expect(val).ToNot(BeNil())
				Expect(val.Val()).To(Equal("ok"))
			})
		})

	})

	Describe("NewClient", func() {
		Context("with valid params", func() {
			It("should initialize a new client with no error", func() {
				client, err := NewClient("{\"team_id\": \"TDEADBEEF\", \"token\":\"xoxo_deadbeef\"}")

				Expect(err).To(BeNil())
				Expect(client.TeamId).To(Equal("TDEADBEEF"))
				Expect(client.Token).To(Equal("xoxo_deadbeef"))
			})
		})

		Context("without valid params", func() {
			It("should initialize a new client with an error", func() {
				client, err := NewClient("")

				Expect(err).ToNot(BeNil())
				Expect(client.Token).To(Equal(""))
				Expect(client.TeamId).To(Equal(""))
				Expect(client.Provider).To(Equal(""))
			})
		})
	})

	Describe("Login", func() {
		var server *httptest.Server
		var existingSlackHost string
		var client *Client
		var err error

		Context("unsuccessful login", func() {
			BeforeEach(func() {
				server = newTestServer("{\"ok\": false}", 500, nil)
				existingSlackHost = os.Getenv("SLACK_HOST")
				os.Setenv("SLACK_HOST", server.URL)

				client, err = NewClient("{\"team_id\":\"TDEADBEEF\",\"bot_token\":\"xoxo_deadbeef\"}")
				Expect(err).To(BeNil())
			})

			AfterEach(func() {
				os.Setenv("SLACK_HOST", existingSlackHost)
				server.Close()
			})

			It("shouldn't set the metadata", func() {
				err := client.Login()
				Expect(err).ToNot(BeNil())
				Expect(client.data).To(BeNil())
			})
		})

		Context("successful login", func() {
			BeforeEach(func() {
				server = newTestServer(`
{
    "ok": true,
    "url": "wss://ms9.slack-msgs.com/websocket/7I5yBpcvk",

    "self": {
        "id": "U023BECGF",
        "name": "bot",
        "created": 1402463766,
        "manual_presence": "active"
    },
    "team": {
        "id": "T024BE7LD",
        "name": "Example Team",
        "email_domain": "",
        "domain": "example",
        "msg_edit_window_mins": -1,
        "over_storage_limit": false,
        "plan": "std"
    },
    "users": [
        {
            "id": "U023BECGF",
            "name": "bobby",
            "deleted": false,
            "color": "9f69e7",
            "profile": {
                "first_name": "Bobby",
                "last_name": "Tables",
                "real_name": "Bobby Tables",
                "email": "bobby@slack.com",
                "skype": "my-skype-name",
                "phone": "+1 (123) 456 7890",
                "image_24": "https://...",
                "image_32": "https://...",
                "image_48": "https://...",
                "image_72": "https://...",
                "image_192": "https://..."
            },
            "is_admin": true,
            "is_owner": true,
            "has_2fa": false,
            "has_files": true
        },
        {
            "id": "U023BECGG",
            "name": "johnny",
            "deleted": true,
            "color": "9f69e7",
            "profile": {
                "first_name": "Johnny",
                "last_name": "Tables",
                "real_name": "Johnny Tables",
                "email": "johnny@slack.com",
                "skype": "my-skype-name",
                "phone": "+1 (123) 456 7890",
                "image_24": "https://...",
                "image_32": "https://...",
                "image_48": "https://...",
                "image_72": "https://...",
                "image_192": "https://..."
            },
            "is_admin": true,
            "is_owner": true,
            "has_2fa": false,
            "has_files": true
        }
    ],
    "channels": [
        {
            "id": "C024BE91L",
            "name": "fun",
            "created": 1360782804,
            "creator": "U024BE7LH",
            "is_archived": false,
            "is_member": false,
            "num_members": 6,
            "topic": {
                "value": "Fun times",
                "creator": "U024BE7LV",
                "last_set": 1369677212
            },
            "purpose": {
                "value": "This channel is for fun",
                "creator": "U024BE7LH",
                "last_set": 1360782804
            }
        },
        {
            "id": "C024BE91M",
            "name": "sad",
            "created": 1360782804,
            "creator": "U024BE7LH",
            "is_archived": false,
            "is_member": false,
            "num_members": 6,
            "topic": {
                "value": "Sad times",
                "creator": "U024BE7LV",
                "last_set": 1369677212
            },
            "purpose": {
                "value": "This channel is for sads",
                "creator": "U024BE7LH",
                "last_set": 1360782804
            }
        }
    ],
    "ims": [
        {
           "id": "D024BFF1M",
           "is_im": true,
           "user": "USLACKBOT",
           "created": 1372105335,
           "is_user_deleted": false
        },
        {
           "id": "D024BE7RE",
           "is_im": true,
           "user": "U024BE7LH",
           "created": 1356250715,
           "is_user_deleted": false
        }
    ],
    "groups": [
      {
        "id": "G0S90BMLM",
        "name":"mpdm-arun--nestordev--nestorbot-1",
        "is_group":true,
        "created":1457726041,
        "creator":"U0AJWAFAB",
        "is_archived":false,
        "is_mpim":true,
        "has_pins":false,
        "is_open":false,
        "last_read":"0000000000.000000",
        "latest":null,
        "unread_count":0,
        "unread_count_display":0,
        "members": ["U0AJWAFAB","U0G9XPJQ2","U0GNJR6QY"],
        "topic": {
          "value":"Group messaging",
          "creator":"U0AJWAFAB",
          "last_set":1457726041
        },
        "purpose": {
          "value":"Group messaging with: @arun @nestordev @nestorbot",
          "creator":"U0AJWAFAB",
          "last_set":1457726041
        }
      }
   ]
}
`, 200, nil)
				existingSlackHost = os.Getenv("SLACK_HOST")
				os.Setenv("SLACK_HOST", server.URL)

				client, err = NewClient("{\"team_id\":\"TDEADBEEF\",\"bot_token\":\"xoxo_deadbeef\"}")

				Expect(err).To(BeNil())
			})

			AfterEach(func() {
				os.Setenv("SLACK_HOST", existingSlackHost)
			})

			It("should set the metadata for the client", func() {
				err := client.Login()

				Expect(err).To(BeNil())

				Expect(client.data.Ok).To(BeTrue())
				Expect(client.data.Url).To(Equal("wss://ms9.slack-msgs.com/websocket/7I5yBpcvk"))
				Expect(client.data.Self.Id).To(Equal("U023BECGF"))
				Expect(client.data.Self.Name).To(Equal("bot"))

				Expect(len(client.data.ImsList)).To(Equal(2))
				Expect(client.data.ImsList[0].Id).To(Equal("D024BFF1M"))
				Expect(client.data.ImsList[0].Created).To(Equal(int64(1372105335)))
				Expect(client.data.ImsList[0].CreatorId).To(Equal("USLACKBOT"))

				Expect(client.data.ImsList[1].Id).To(Equal("D024BE7RE"))
				Expect(client.data.ImsList[1].Created).To(Equal(int64(1356250715)))
				Expect(client.data.ImsList[1].CreatorId).To(Equal("U024BE7LH"))

				Expect(len(client.data.UsersList)).To(Equal(2))
				Expect(client.data.UsersList[0].Id).To(Equal("U023BECGF"))
				Expect(client.data.UsersList[0].Name).To(Equal("bobby"))
				Expect(client.data.UsersList[0].Color).To(Equal("9f69e7"))
				Expect(client.data.UsersList[0].IsDeleted).To(BeFalse())

				Expect(client.data.UsersList[1].Id).To(Equal("U023BECGG"))
				Expect(client.data.UsersList[1].Name).To(Equal("johnny"))
				Expect(client.data.UsersList[1].Color).To(Equal("9f69e7"))
				Expect(client.data.UsersList[1].IsDeleted).To(BeTrue())

				Expect(len(client.data.ChannelsList)).To(Equal(2))
				Expect(client.data.ChannelsList[0].Id).To(Equal("C024BE91L"))
				Expect(client.data.ChannelsList[0].Name).To(Equal("fun"))
				Expect(client.data.ChannelsList[0].CreatorId).To(Equal("U024BE7LH"))
				Expect(client.data.ChannelsList[0].Created).To(Equal(int64(1360782804)))

				Expect(client.data.ChannelsList[1].Id).To(Equal("C024BE91M"))
				Expect(client.data.ChannelsList[1].Name).To(Equal("sad"))
				Expect(client.data.ChannelsList[1].CreatorId).To(Equal("U024BE7LH"))
				Expect(client.data.ChannelsList[1].Created).To(Equal(int64(1360782804)))

				Expect(len(client.data.GroupsList)).To(Equal(1))
				Expect(client.data.GroupsList[0].Id).To(Equal("G0S90BMLM"))
				Expect(client.data.GroupsList[0].Name).To(Equal("mpdm-arun--nestordev--nestorbot-1"))
				Expect(client.data.GroupsList[0].CreatorId).To(Equal("U0AJWAFAB"))
				Expect(client.data.GroupsList[0].Created).To(Equal(int64(1457726041)))

				Expect(len(client.data.Channels)).To(Equal(5))
				Expect(len(client.data.Users)).To(Equal(2))
			})
		})
	})

	Describe("Start", func() {
		var client *Client
		var err error

		BeforeEach(func() {
			client, err = NewClient("{\"team_id\":\"TDEADBEEF\",\"bot_token\":\"xoxo_deadbeef\"}")
			Expect(err).To(BeNil())
		})

		Context("when the metadata.Ok flag is not true", func() {
			BeforeEach(func() {
				client.data = &Metadata{Ok: false}
				setRedisQueueWebEnv()
			})

			Context("when error is set to invalid_auth", func() {
				BeforeEach(func() {
					client.data.Error = "invalid_auth"
				})

				It("should return an error and send a response via Redis", func() {
					var event Event

					err = client.Start()
					Expect(err).ToNot(BeNil())

					redisClient := newRedisClient()

					resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
					result := resultevent.Val()

					Expect(len(result)).To(Equal(2))
					err := json.Unmarshal([]byte(result[1]), &event)
					Expect(err).To(BeNil())

					Expect(event.Type).To(Equal("disable_bot"))
					Expect(event.TeamUid).To(Equal("TDEADBEEF"))
					Expect(event.Provider).To(Equal("slack"))
				})
			})

			Context("when error is set to account_inactive", func() {
				BeforeEach(func() {
					client.data.Error = "account_inactive"
				})

				It("should return an error and send a response via Redis", func() {
					var event Event

					err = client.Start()
					Expect(err).ToNot(BeNil())

					redisClient := newRedisClient()

					resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
					result := resultevent.Val()

					Expect(len(result)).To(Equal(2))
					err := json.Unmarshal([]byte(result[1]), &event)
					Expect(err).To(BeNil())

					Expect(event.Type).To(Equal("disable_bot"))
					Expect(event.TeamUid).To(Equal("TDEADBEEF"))
					Expect(event.Provider).To(Equal("slack"))
				})
			})

			Context("when there is another error", func() {
				It("should return an error", func() {
					err = client.Start()
					Expect(err).ToNot(BeNil())
				})
			})
		})

		Context("when the metadata.Ok flag is true", func() {
			BeforeEach(func() {
				wsServer := newWSServer("{\"ok\":true}")
				client.data = &Metadata{Ok: true, Url: makeWsProto(wsServer.URL)}
			})

			It("should not return an error", func() {
				err = client.Start()
				Expect(err).To(BeNil())
				Expect(client.conn).ToNot(BeNil())
			})
		})
	})

	Context("Handling Events from Slack RTM API", func() {
		var client *Client
		var err error
		var redisClient *redis.Client
		var wsServer *httptest.Server

		BeforeEach(func() {
			client, err = NewClient("{\"team_id\":\"TDEADBEEF\",\"bot_token\":\"xoxo_deadbeef\"}")
			Expect(err).To(BeNil())
		})

		Context("message event", func() {
			BeforeEach(func() {
				redisClient = newRedisClient()
				setRedisQueueWebEnv()
			})

			Context("message comes in from Slack and is not meant for Nestor", func() {
				BeforeEach(func() {
					wsServer = newWSServer(`
						{
							"type": "message",
							"channel": "C2147483705",
							"user": "U2147483697",
							"text": "Hello world",
							"ts": "1355517523.000005"
						}
					`)

					client.data = &Metadata{
						Ok:       true,
						Url:      makeWsProto(wsServer.URL),
						Users:    map[string]User{},
						Channels: map[string]Channel{},
						Self:     User{Id: "UBOTUID"},
					}
					client.data.Channels["C2147483705"] = Channel{Id: "C2147483705", Im: false}
					client.data.Users["U2147483697"] = User{Id: "U2147483697"}
					client.TeamId = "TDEADBEEF"
					client.Start()
				})

				AfterEach(func() {
					wsServer.Close()
				})

				It("should send an event to Redis", func() {
					var event Event

					resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
					result := resultevent.Val()

					Expect(len(result)).To(Equal(2))
					err := json.Unmarshal([]byte(result[1]), &event)
					Expect(err).To(BeNil())

					Expect(event.Type).To(Equal("message_new"))
					Expect(event.ChannelUid).To(Equal("C2147483705"))
					Expect(event.UserUid).To(Equal("U2147483697"))
					Expect(event.Text).To(Equal("Hello world"))
					Expect(event.Timestamp).To(Equal("1355517523.000005"))
					Expect(event.Im).To(BeFalse())
					Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
					Expect(event.TeamUid).To(Equal("TDEADBEEF"))
					Expect(event.Provider).To(Equal("slack"))
					Expect(event.EventTimestamp).To(Equal("1355517523.000005"))

					val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
					Expect(val).ToNot(BeNil())
					Expect(val.Val()).To(Equal("ok"))
				})
			})

			Context("message comes in from Slack and is an IM", func() {
				BeforeEach(func() {
					wsServer = newWSServer(`
						{
							"type": "message",
							"channel": "D2147483705",
							"user": "U2147483697",
							"text": "Hello world",
							"ts": "1355517523.000005"
						}
					`)

					client.data = &Metadata{
						Ok:       true,
						Url:      makeWsProto(wsServer.URL),
						Users:    map[string]User{},
						Channels: map[string]Channel{},
						Self:     User{Id: "UBOTUID"},
					}
					client.data.Channels["D2147483705"] = Channel{Id: "D2147483705", Im: true}
					client.data.Users["U2147483697"] = User{Id: "U2147483697"}
					client.TeamId = "TDEADBEEF"

					client.Start()
				})

				AfterEach(func() {
					wsServer.Close()
				})

				It("should send an event to Redis", func() {
					var event Event

					resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
					result := resultevent.Val()

					Expect(len(result)).To(Equal(2))
					err := json.Unmarshal([]byte(result[1]), &event)
					Expect(err).To(BeNil())

					Expect(event.Type).To(Equal("message_new"))
					Expect(event.ChannelUid).To(Equal("D2147483705"))
					Expect(event.UserUid).To(Equal("U2147483697"))
					Expect(event.Text).To(Equal("Hello world"))
					Expect(event.Timestamp).To(Equal("1355517523.000005"))
					Expect(event.Im).To(BeTrue())
					Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
					Expect(event.TeamUid).To(Equal("TDEADBEEF"))
					Expect(event.Provider).To(Equal("slack"))
					Expect(event.EventTimestamp).To(Equal("1355517523.000005"))

					val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
					Expect(val).ToNot(BeNil())
					Expect(val.Val()).To(Equal("ok"))
				})
			})

			Context("message comes in from Slack and is addressed to the bot (with bot UID at the beginning of the message)", func() {
				BeforeEach(func() {
					wsServer = newWSServer(`
						{
							"type": "message",
							"channel": "C2147483705",
							"user": "U2147483697",
							"text": "<@UBOTUID>: Hello world",
							"ts": "1355517523.000005"
						}
					`)

					client.data = &Metadata{
						Ok:       true,
						Url:      makeWsProto(wsServer.URL),
						Users:    map[string]User{},
						Channels: map[string]Channel{},
						Self:     User{Id: "UBOTUID"},
					}
					client.data.Channels["C2147483705"] = Channel{Id: "C2147483705", Im: false}
					client.data.Users["U2147483697"] = User{Id: "U2147483697"}
					client.TeamId = "TDEADBEEF"

					client.Start()
				})

				AfterEach(func() {
					wsServer.Close()
				})

				It("should send an event to Redis", func() {
					var event Event

					resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
					result := resultevent.Val()

					Expect(len(result)).To(Equal(2))
					err := json.Unmarshal([]byte(result[1]), &event)
					Expect(err).To(BeNil())

					Expect(event.Type).To(Equal("message_new"))
					Expect(event.ChannelUid).To(Equal("C2147483705"))
					Expect(event.UserUid).To(Equal("U2147483697"))
					Expect(event.Text).To(Equal("<@UBOTUID>: Hello world"))
					Expect(event.Timestamp).To(Equal("1355517523.000005"))
					Expect(event.Im).To(BeFalse())
					Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
					Expect(event.TeamUid).To(Equal("TDEADBEEF"))
					Expect(event.Provider).To(Equal("slack"))
					Expect(event.EventTimestamp).To(Equal("1355517523.000005"))

					val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
					Expect(val).ToNot(BeNil())
					Expect(val.Val()).To(Equal("ok"))
				})
			})

			Context("message comes in from Slack and is addressed to the bot (with bot UID at in the middle of the message)", func() {
				BeforeEach(func() {
					wsServer = newWSServer(`
						{
							"type": "message",
							"channel": "C2147483705",
							"user": "U2147483697",
							"text": "Hey <@UBOTUID> Hello world",
							"ts": "1355517523.000005"
						}
					`)

					client.data = &Metadata{
						Ok:       true,
						Url:      makeWsProto(wsServer.URL),
						Users:    map[string]User{},
						Channels: map[string]Channel{},
						Self:     User{Id: "UBOTUID"},
					}
					client.data.Channels["C2147483705"] = Channel{Id: "C2147483705", Im: false}
					client.data.Users["U2147483697"] = User{Id: "U2147483697"}
					client.TeamId = "TDEADBEEF"

					client.Start()
				})

				AfterEach(func() {
					wsServer.Close()
				})

				It("should send an event to Redis", func() {
					var event Event

					resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
					result := resultevent.Val()

					Expect(len(result)).To(Equal(2))
					err := json.Unmarshal([]byte(result[1]), &event)

					Expect(err).To(BeNil())
					Expect(event.Type).To(Equal("message_new"))
					Expect(event.ChannelUid).To(Equal("C2147483705"))
					Expect(event.UserUid).To(Equal("U2147483697"))
					Expect(event.Text).To(Equal("Hey <@UBOTUID> Hello world"))
					Expect(event.Timestamp).To(Equal("1355517523.000005"))
					Expect(event.Im).To(BeFalse())
					Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
					Expect(event.TeamUid).To(Equal("TDEADBEEF"))
					Expect(event.Provider).To(Equal("slack"))
					Expect(event.EventTimestamp).To(Equal("1355517523.000005"))

					val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
					Expect(val).ToNot(BeNil())
					Expect(val.Val()).To(Equal("ok"))
				})
			})

			Context("message comes in from Slack (and has attachments)", func() {
				BeforeEach(func() {
					wsServer = newWSServer(`
						{
							"type": "message",
							"channel": "C2147483705",
							"user": "U2147483697",
							"text": "Hey <@UBOTUID> Hello world",
							"ts": "1355517523.000009",
							"attachments": [
								{
										"fallback": "Required plain-text summary of the attachment.",
										"color": "#36a64f",
										"pretext": "Optional text that appears above the attachment block",
										"author_name": "Bobby Tables",
										"author_link": "http://flickr.com/bobby/",
										"author_icon": "http://flickr.com/icons/bobby.jpg",
										"title": "Slack API Documentation",
										"title_link": "https://api.slack.com/",
										"text": "Optional text that appears within the attachment",
										"fields": [
											{
												"title": "Priority",
												"value": "High",
												"short": false
											}
										],
										"image_url": "http://my-website.com/path/to/image.jpg",
										"thumb_url": "http://example.com/path/to/thumb.png",
										"footer": "Slack API",
										"footer_icon": "https://platform.slack-edge.com/img/default_application_icon.png",
										"ts": 123456789,
										"attachment_type": "default",
										"actions": [
											{
												"name": "chess",
												"text": "Chess",
												"type": "button",
												"value": "chess"
											},
											{
												"name": "maze",
												"text": "Falken's Maze",
												"type": "button",
												"value": "maze"
											},
											{
												"name": "war",
												"text": "Thermonuclear War",
												"style": "danger",
												"type": "button",
												"value": "war",
												"confirm": {
													"title": "Are you sure?",
													"text": "Wouldn't you prefer a good game of chess?",
													"ok_text": "Yes",
													"dismiss_text": "No"
												}
											}
										]
								}
							]
						}
					`)

					client.data = &Metadata{
						Ok:       true,
						Url:      makeWsProto(wsServer.URL),
						Users:    map[string]User{},
						Channels: map[string]Channel{},
						Self:     User{Id: "UBOTUID"},
					}
					client.data.Channels["C2147483705"] = Channel{Id: "C2147483705", Im: false}
					client.data.Users["U2147483697"] = User{Id: "U2147483697"}
					client.TeamId = "TDEADBEEF"

					client.Start()
				})

				AfterEach(func() {
					wsServer.Close()
				})

				It("should send an event to Redis with the attachments", func() {
					var event Event

					resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
					result := resultevent.Val()

					Expect(len(result)).To(Equal(2))
					err := json.Unmarshal([]byte(result[1]), &event)

					Expect(err).To(BeNil())
					Expect(event.Type).To(Equal("message_new"))
					Expect(event.ChannelUid).To(Equal("C2147483705"))
					Expect(event.UserUid).To(Equal("U2147483697"))
					Expect(event.Text).To(Equal("Hey <@UBOTUID> Hello world"))
					Expect(event.Timestamp).To(Equal("1355517523.000009"))
					Expect(event.Im).To(BeFalse())
					Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
					Expect(event.TeamUid).To(Equal("TDEADBEEF"))
					Expect(event.Provider).To(Equal("slack"))
					Expect(event.EventTimestamp).To(Equal("1355517523.000009"))
					Expect(len(event.Attachments)).To(Equal(1))

					attachment := event.Attachments[0]

					Expect(attachment.Fallback).To(Equal("Required plain-text summary of the attachment."))
					Expect(attachment.Color).To(Equal("#36a64f"))
					Expect(attachment.Pretext).To(Equal("Optional text that appears above the attachment block"))
					Expect(attachment.AuthorName).To(Equal("Bobby Tables"))
					Expect(attachment.AuthorLink).To(Equal("http://flickr.com/bobby/"))
					Expect(attachment.AuthorIcon).To(Equal("http://flickr.com/icons/bobby.jpg"))
					Expect(attachment.Text).To(Equal("Optional text that appears within the attachment"))
					Expect(attachment.ImageUrl).To(Equal("http://my-website.com/path/to/image.jpg"))
					Expect(attachment.ThumbUrl).To(Equal("http://example.com/path/to/thumb.png"))
					Expect(attachment.Footer).To(Equal("Slack API"))
					Expect(attachment.FooterIcon).To(Equal("https://platform.slack-edge.com/img/default_application_icon.png"))
					Expect(attachment.Ts).To(Equal(int64(123456789)))
					Expect(attachment.AttachmentType).To(Equal("default"))
					Expect(len(attachment.Fields)).To(Equal(1))

					field := attachment.Fields[0]
					Expect(field.Title).To(Equal("Priority"))
					Expect(field.Value).To(Equal("High"))
					Expect(field.Short).To(Equal(false))

					Expect(len(attachment.Actions)).To(Equal(3))
					action1 := attachment.Actions[0]
					Expect(action1.Name).To(Equal("chess"))
					Expect(action1.Text).To(Equal("Chess"))
					Expect(action1.Type).To(Equal("button"))
					Expect(action1.Value).To(Equal("chess"))

					action2 := attachment.Actions[1]
					Expect(action2.Name).To(Equal("maze"))
					Expect(action2.Text).To(Equal("Falken's Maze"))
					Expect(action2.Type).To(Equal("button"))
					Expect(action2.Value).To(Equal("maze"))

					action3 := attachment.Actions[2]
					Expect(action3.Name).To(Equal("war"))
					Expect(action3.Text).To(Equal("Thermonuclear War"))
					Expect(action3.Style).To(Equal("danger"))
					Expect(action3.Type).To(Equal("button"))
					Expect(action3.Value).To(Equal("war"))
					Expect(action3.Confirm.Title).To(Equal("Are you sure?"))
					Expect(action3.Confirm.Text).To(Equal("Wouldn't you prefer a good game of chess?"))
					Expect(action3.Confirm.OkText).To(Equal("Yes"))
					Expect(action3.Confirm.DismissText).To(Equal("No"))

					val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
					Expect(val).ToNot(BeNil())
					Expect(val.Val()).To(Equal("ok"))
				})
			})

			Context("message comes in from Slack and is from the bot itself", func() {
				BeforeEach(func() {
					wsServer = newWSServer(`
						{
							"type": "message",
							"channel": "C2147483705",
							"user": "UBOTUID",
							"text": "Hey <@UBOTUID> Hello world",
							"ts": "1355517523.000005"
						}
					`)

					client.data = &Metadata{
						Ok:       true,
						Url:      makeWsProto(wsServer.URL),
						Users:    map[string]User{},
						Channels: map[string]Channel{},
						Self:     User{Id: "UBOTUID"},
					}
					client.data.Channels["C2147483705"] = Channel{Id: "C2147483705", Im: false}
					client.data.Users["U2147483697"] = User{Id: "U2147483697"}
					client.data.Users["UBOTUID"] = User{Id: "UBOTUID"}
					client.TeamId = "TDEADBEEF"
				})

				AfterEach(func() {
					wsServer.Close()
				})

				Context("RELAX_SEND_BOT_REPLIES environment variable is not set", func() {
					BeforeEach(func() {
						os.Unsetenv("RELAX_SEND_BOT_REPLIES")
						client.Start()
					})

					It("should NOT send an event to Redis", func() {
						resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
						result := resultevent.Val()

						Expect(len(result)).To(Equal(0))
					})
				})

				Context("RELAX_SEND_BOT_REPLIES environment variable is set", func() {
					BeforeEach(func() {
						os.Setenv("RELAX_SEND_BOT_REPLIES", "true")
						client.Start()
					})

					AfterEach(func() {
						os.Unsetenv("RELAX_SEND_BOT_REPLIES")
					})

					It("should send an event to Redis", func() {
						var event Event

						resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
						result := resultevent.Val()

						Expect(len(result)).To(Equal(2))
						err := json.Unmarshal([]byte(result[1]), &event)

						Expect(err).To(BeNil())
						Expect(event.Type).To(Equal("message_new"))
						Expect(event.ChannelUid).To(Equal("C2147483705"))
						Expect(event.UserUid).To(Equal("UBOTUID"))
						Expect(event.Text).To(Equal("Hey <@UBOTUID> Hello world"))
						Expect(event.Timestamp).To(Equal("1355517523.000005"))
						Expect(event.Im).To(BeFalse())
						Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
						Expect(event.TeamUid).To(Equal("TDEADBEEF"))
						Expect(event.Provider).To(Equal("slack"))
						Expect(event.EventTimestamp).To(Equal("1355517523.000005"))

						val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
						Expect(val).ToNot(BeNil())
						Expect(val.Val()).To(Equal("ok"))
					})
				})
			})

			Context("text message edited", func() {
				BeforeEach(func() {
					wsServer = newWSServer(`
						{
							"type": "message",
							"subtype": "message_changed",
							"hidden": true,
							"channel": "C2147483705",
							"ts": "1358878755.000001",
							"message": {
								"type": "message",
								"user": "U2147483697",
								"text": "Hello, world!",
								"ts": "1355517523.000005",
								"edited": {
									"user": "U2147483697",
									"ts": "1358878755.000001"
								}
							}
						}
					`)

					client.data = &Metadata{
						Ok:       true,
						Url:      makeWsProto(wsServer.URL),
						Users:    map[string]User{},
						Channels: map[string]Channel{},
						Self:     User{Id: "UBOTUID"},
					}
					client.data.Channels["C2147483705"] = Channel{Id: "C2147483705", Im: false}
					client.data.Users["U2147483697"] = User{Id: "U2147483697"}
					client.TeamId = "TDEADBEEF"

					client.Start()
				})

				AfterEach(func() {
					wsServer.Close()
				})

				It("should send a 'message_edited' event to Redis with the timestamp being the one set in message.ts", func() {
					// From the Slack API Docs: https://api.slack.com/events/message/message_changed
					// When clients recieve this message type, they should look for an existing message
					// with the same message.ts in that channel. If they find one the existing message should be replaced with the new one.
					var event Event

					resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
					result := resultevent.Val()

					Expect(len(result)).To(Equal(2))
					err := json.Unmarshal([]byte(result[1]), &event)

					Expect(err).To(BeNil())
					Expect(event.Type).To(Equal("message_edited"))
					Expect(event.ChannelUid).To(Equal("C2147483705"))
					Expect(event.UserUid).To(Equal("U2147483697"))
					Expect(event.Text).To(Equal("Hello, world!"))
					Expect(event.Timestamp).To(Equal("1355517523.000005"))
					Expect(event.Im).To(BeFalse())
					Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
					Expect(event.TeamUid).To(Equal("TDEADBEEF"))
					Expect(event.Provider).To(Equal("slack"))
					Expect(event.EventTimestamp).To(Equal("1358878755.000001"))

					val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
					Expect(val).ToNot(BeNil())
					Expect(val.Val()).To(Equal("ok"))
				})
			})

			Context("text message deleted", func() {
				BeforeEach(func() {
					wsServer = newWSServer(`
						{
							"type": "message",
							"subtype": "message_deleted",
							"hidden": true,
							"channel": "C2147483705",
							"user": "U2147483697",
							"ts": "1358878755.000001",
							"deleted_ts": "1358878749.000002"
						}
					`)

					client.data = &Metadata{
						Ok:       true,
						Url:      makeWsProto(wsServer.URL),
						Users:    map[string]User{},
						Channels: map[string]Channel{},
						Self:     User{Id: "UBOTUID"},
					}
					client.data.Channels["C2147483705"] = Channel{Id: "C2147483705", Im: false}
					client.data.Users["U2147483697"] = User{Id: "U2147483697"}
					client.TeamId = "TDEADBEEF"

					client.Start()
				})

				AfterEach(func() {
					wsServer.Close()
				})

				It("should send a 'message_deleted' event to Redis with the timestamp being the one set in message.deleted_ts", func() {
					// From the Slack API Docs: https://api.slack.com/events/message/message_deleted
					// When clients recieve this message type, they should look for an existing message
					// with the same message.deleted_ts in that channel. If they find one they should delete this message
					var event Event

					resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
					result := resultevent.Val()

					Expect(len(result)).To(Equal(2))
					err := json.Unmarshal([]byte(result[1]), &event)

					Expect(err).To(BeNil())
					Expect(event.Type).To(Equal("message_deleted"))
					Expect(event.ChannelUid).To(Equal("C2147483705"))
					Expect(event.UserUid).To(Equal("U2147483697"))
					Expect(event.Text).To(Equal(""))
					Expect(event.Timestamp).To(Equal("1358878749.000002"))
					Expect(event.Im).To(BeFalse())
					Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
					Expect(event.TeamUid).To(Equal("TDEADBEEF"))
					Expect(event.Provider).To(Equal("slack"))
					Expect(event.EventTimestamp).To(Equal("1358878755.000001"))

					val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
					Expect(val).ToNot(BeNil())
					Expect(val.Val()).To(Equal("ok"))
				})
			})
		})

		Context("reaction added", func() {
			BeforeEach(func() {
				redisClient = newRedisClient()
				setRedisQueueWebEnv()

				wsServer = newWSServer(`
					{
						"type": "reaction_added",
						"user": "U024BE7LH",
						"reaction": "+1",
						"item": {
							"type": "message",
							"channel": "C0304SBLA",
							"ts": "1435766912.000727"
						},
						"event_ts": "1360782804.083113"
					}
				`)

				client.data = &Metadata{
					Ok:       true,
					Url:      makeWsProto(wsServer.URL),
					Users:    map[string]User{},
					Channels: map[string]Channel{},
					Self:     User{Id: "UBOTUID"},
				}
				client.data.Channels["C0304SBLA"] = Channel{Id: "C0304SBLA", Im: false}
				client.data.Users["U024BE7LH"] = User{Id: "U024BE7LH"}
				client.TeamId = "TDEADBEEF"

				client.Start()
			})

			It("should send a 'reaction_added' event to Redis with the timestamp being the one set in item.ts", func() {
				var event Event

				resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
				result := resultevent.Val()

				Expect(len(result)).To(Equal(2))
				err := json.Unmarshal([]byte(result[1]), &event)

				Expect(err).To(BeNil())
				Expect(event.Type).To(Equal("reaction_added"))
				Expect(event.ChannelUid).To(Equal("C0304SBLA"))
				Expect(event.UserUid).To(Equal("U024BE7LH"))
				Expect(event.Text).To(Equal("+1"))
				Expect(event.Timestamp).To(Equal("1435766912.000727"))
				Expect(event.Im).To(BeFalse())
				Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
				Expect(event.TeamUid).To(Equal("TDEADBEEF"))
				Expect(event.Provider).To(Equal("slack"))
				Expect(event.EventTimestamp).To(Equal("1360782804.083113"))

				val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
				Expect(val).ToNot(BeNil())
				Expect(val.Val()).To(Equal("ok"))
			})
		})

		Context("reaction removed", func() {
			BeforeEach(func() {
				redisClient = newRedisClient()
				setRedisQueueWebEnv()

				wsServer = newWSServer(`
					{
						"type": "reaction_removed",
						"user": "U024BE7LH",
						"reaction": "+1",
						"item": {
							"type": "message",
							"channel": "C0304SBLA",
							"ts": "1435766912.000727"
						},
						"event_ts": "1360782804.083113"
					}
				`)

				client.data = &Metadata{
					Ok:       true,
					Url:      makeWsProto(wsServer.URL),
					Users:    map[string]User{},
					Channels: map[string]Channel{},
					Self:     User{Id: "UBOTUID"},
				}
				client.data.Channels["C0304SBLA"] = Channel{Id: "C0304SBLA", Im: false}
				client.data.Users["U024BE7LH"] = User{Id: "U024BE7LH"}
				client.TeamId = "TDEADBEEF"

				client.Start()
			})

			It("should send a 'reaction_removed' event to Redis with the timestamp being the one set in item.ts", func() {
				var event Event

				resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
				result := resultevent.Val()

				Expect(len(result)).To(Equal(2))
				err := json.Unmarshal([]byte(result[1]), &event)

				Expect(err).To(BeNil())
				Expect(event.Type).To(Equal("reaction_removed"))
				Expect(event.ChannelUid).To(Equal("C0304SBLA"))
				Expect(event.UserUid).To(Equal("U024BE7LH"))
				Expect(event.Text).To(Equal("+1"))
				Expect(event.Timestamp).To(Equal("1435766912.000727"))
				Expect(event.Im).To(BeFalse())
				Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
				Expect(event.TeamUid).To(Equal("TDEADBEEF"))
				Expect(event.Provider).To(Equal("slack"))
				Expect(event.EventTimestamp).To(Equal("1360782804.083113"))

				val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
				Expect(val).ToNot(BeNil())
				Expect(val.Val()).To(Equal("ok"))
			})
		})

		Context("team_join", func() {
			BeforeEach(func() {
				redisClient = newRedisClient()
				setRedisQueueWebEnv()

				wsServer = newWSServer(`
				{
					"type": "team_join",
					"user": {
						"id": "U023BECGF",
						"name": "bobby",
						"deleted": false,
						"color": "9f69e7",
						"profile": {
							"first_name": "Bobby",
							"last_name": "Tables",
							"real_name": "Bobby Tables",
							"email": "bobby@slack.com",
							"skype": "my-skype-name",
							"phone": "+1 (123) 456 7890",
							"image_24": "https://...",
							"image_32": "https://...",
							"image_48": "https://...",
							"image_72": "https://...",
							"image_192": "https://..."
						},
						"is_admin": true,
						"is_owner": true,
						"has_2fa": false,
						"has_files": true
					}
				}
			`)

				client.data = &Metadata{
					Ok:       true,
					Url:      makeWsProto(wsServer.URL),
					Users:    map[string]User{},
					Channels: map[string]Channel{},
					Self:     User{Id: "UBOTUID"},
				}
				client.TeamId = "TDEADBEEF"

				client.Start()
			})

			It("should send a 'users_bulk_create' event to Redis with the team ID and the user", func() {
				var event Event

				resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
				result := resultevent.Val()

				Expect(len(result)).To(Equal(2))
				err := json.Unmarshal([]byte(result[1]), &event)

				Expect(err).To(BeNil())
				Expect(event.Type).To(Equal("team_joined"))
				Expect(event.ChannelUid).To(Equal(""))
				Expect(event.UserUid).To(Equal("U023BECGF"))
				Expect(event.Text).To(Equal(""))
				Expect(event.Timestamp).To(Equal(""))
				Expect(event.Im).To(BeFalse())
				Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
				Expect(event.TeamUid).To(Equal("TDEADBEEF"))
				Expect(event.Provider).To(Equal("slack"))

				Expect(client.data.Users["U023BECGF"].Id).To(Equal("U023BECGF"))
				Expect(client.data.Users["U023BECGF"].Name).To(Equal("bobby"))

				val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
				Expect(val).ToNot(BeNil())
				Expect(val.Val()).To(Equal("ok"))
			})
		})

		Context("im_created", func() {
			BeforeEach(func() {
				redisClient = newRedisClient()
				setRedisQueueWebEnv()

				wsServer = newWSServer(`
					{
						"type": "im_created",
						"user": "U024BE7LH",
						"channel": {
							"id": "D024BE91L",
							"name": "fun",
							"created": 1360782804,
							"creator": "U024BE7LH",
							"is_archived": false,
							"is_member": true
						}
					}
				`)

				client.data = &Metadata{
					Ok:       true,
					Url:      makeWsProto(wsServer.URL),
					Users:    map[string]User{},
					Channels: map[string]Channel{},
					Self:     User{Id: "UBOTUID"},
				}
				client.data.Users["U024BE7LH"] = User{Id: "U024BE7LH"}
				client.TeamId = "TDEADBEEF"

				client.Start()
			})

			It("should send a 'im_created' event to Redis with the team ID and the user", func() {
				var event Event

				resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
				result := resultevent.Val()

				Expect(len(result)).To(Equal(2))
				err := json.Unmarshal([]byte(result[1]), &event)

				Expect(err).To(BeNil())
				Expect(event.Type).To(Equal("im_created"))
				Expect(event.ChannelUid).To(Equal("D024BE91L"))
				Expect(event.UserUid).To(Equal("U024BE7LH"))
				Expect(event.Text).To(Equal(""))
				Expect(event.Timestamp).To(Equal(""))
				Expect(event.Im).To(BeTrue())
				Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
				Expect(event.TeamUid).To(Equal("TDEADBEEF"))
				Expect(event.Provider).To(Equal("slack"))

				Expect(client.data.Channels["D024BE91L"].Id).To(Equal("D024BE91L"))

				val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
				Expect(val).ToNot(BeNil())
				Expect(val.Val()).To(Equal("ok"))
			})

		})

		Context("channel_joined", func() {
			BeforeEach(func() {
				redisClient = newRedisClient()
				setRedisQueueWebEnv()

				wsServer = newWSServer(`
					{
						"type": "channel_joined",
						"channel": {
							"id":"C0MF94DFZ",
							"name":"nestor-v5",
							"is_channel":true,
							"created":1455586931,
							"creator":"U0AJWAFAB",
							"is_archived":false,
							"is_general":false,
							"is_member":true,
							"last_read":"1455586930.000002",
							"latest":{
								"user":"U0AJWAFAB",
								"type":"message",
								"subtype":"channel_join",
								"text":"<@U0AJWAFAB|arun> has joined the channel",
								"ts":"1455586930.000002"
							},
							"unread_count":0,
							"unread_count_display":0,
							"members":[
								"U0AJWAFAB",
								"U0G9XPJQ2"
							],
							"topic":{
								"value":"",
								"creator":"",
								"last_set":0
							},
							"purpose":{
								"value":"",
								"creator":"",
								"last_set":0
							}
						}
					}
				`)

				client.data = &Metadata{
					Ok:       true,
					Url:      makeWsProto(wsServer.URL),
					Users:    map[string]User{},
					Channels: map[string]Channel{},
					Self:     User{Id: "UBOTUID"},
				}
				client.data.Users["U024BE7LH"] = User{Id: "U024BE7LH"}
				client.TeamId = "TDEADBEEF"

				client.Start()
			})

			It("should send a 'channel_joined' event to Redis with the team ID and the channel", func() {
				var event Event

				resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
				result := resultevent.Val()

				Expect(len(result)).To(Equal(2))
				err := json.Unmarshal([]byte(result[1]), &event)

				Expect(err).To(BeNil())
				Expect(event.Type).To(Equal("channel_joined"))
				Expect(event.ChannelUid).To(Equal("C0MF94DFZ"))
				Expect(event.Timestamp).ToNot(Equal(""))
				Expect(event.EventTimestamp).ToNot(Equal(""))
				Expect(event.Text).To(Equal(""))
				Expect(event.Im).To(BeFalse())
				Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
				Expect(event.TeamUid).To(Equal("TDEADBEEF"))
				Expect(event.Provider).To(Equal("slack"))

				Expect(client.data.Channels["C0MF94DFZ"].Id).To(Equal("C0MF94DFZ"))
				Expect(client.data.Channels["C0MF94DFZ"].Name).To(Equal("nestor-v5"))

				val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
				Expect(val).ToNot(BeNil())
				Expect(val.Val()).To(Equal("ok"))
			})
		})

		Context("group_joined", func() {
			BeforeEach(func() {
				redisClient = newRedisClient()
				setRedisQueueWebEnv()

				wsServer = newWSServer(`
					{
						"type":"group_joined",
						"channel":{
							"id":"G0S97D1V4",
							"name":"mpdm-arun--nestordev--user-1",
							"is_group":true,
							"created":1457729017,
							"creator":"U0AJWAFAB",
							"is_archived":false,
							"is_mpim":true,
							"is_open":false,
							"last_read":"0000000000.000000",
							"latest":null,
							"unread_count":0,
							"unread_count_display":0,
							"members":["U0AJWAFAB","U0K3WMQRL","U0G9XPJQ2"],
							"topic":{
								"value":"",
								"creator":"",
								"last_set":0
							},
							"purpose":{
								"value":"",
								"creator":"","last_set":0
							}
						}
					}
				`)

				client.data = &Metadata{
					Ok:       true,
					Url:      makeWsProto(wsServer.URL),
					Users:    map[string]User{},
					Channels: map[string]Channel{},
					Self:     User{Id: "UBOTUID"},
				}
				client.data.Users["U024BE7LH"] = User{Id: "U024BE7LH"}
				client.TeamId = "TDEADBEEF"

				client.Start()
			})

			It("should send a 'channel_joined' event to Redis with the team ID and the channel", func() {
				var event Event

				resultevent := redisClient.BLPop(1*time.Second, os.Getenv("RELAX_EVENTS_QUEUE"))
				result := resultevent.Val()

				Expect(len(result)).To(Equal(2))
				err := json.Unmarshal([]byte(result[1]), &event)

				Expect(err).To(BeNil())
				Expect(event.Type).To(Equal("channel_joined"))
				Expect(event.ChannelUid).To(Equal("G0S97D1V4"))
				Expect(event.Timestamp).ToNot(Equal(""))
				Expect(event.EventTimestamp).ToNot(Equal(""))
				Expect(event.Text).To(Equal(""))
				Expect(event.Im).To(BeFalse())
				Expect(event.RelaxBotUid).To(Equal("UBOTUID"))
				Expect(event.TeamUid).To(Equal("TDEADBEEF"))
				Expect(event.Provider).To(Equal("slack"))

				Expect(client.data.Channels["G0S97D1V4"].Id).To(Equal("G0S97D1V4"))
				Expect(client.data.Channels["G0S97D1V4"].Name).To(Equal("mpdm-arun--nestordev--user-1"))

				val := redisClient.HGet(os.Getenv("RELAX_MUTEX_KEY"), fmt.Sprintf("bot_message:%s:%s", event.ChannelUid, event.EventTimestamp))
				Expect(val).ToNot(BeNil())
				Expect(val.Val()).To(Equal("ok"))
			})
		})

	})
})
