lita-slack 0.1.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,110 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapters::Slack::API do
4
+ subject { described_class.new(token, stubs) }
5
+
6
+ let(:http_status) { 200 }
7
+ let(:token) { 'abcd-1234567890-hWYd21AmMH2UHAkx29vb5c1Y' }
8
+
9
+ describe "#im_open" do
10
+ let(:channel_id) { 'D024BFF1M' }
11
+ let(:stubs) do
12
+ Faraday::Adapter::Test::Stubs.new do |stub|
13
+ stub.post('https://slack.com/api/im.open', token: token, user: user_id) do
14
+ [http_status, {}, http_response]
15
+ end
16
+ end
17
+ end
18
+ let(:user_id) { 'U023BECGF' }
19
+
20
+ describe "with a successful response" do
21
+ let(:http_response) do
22
+ MultiJson.dump({
23
+ ok: true,
24
+ channel: {
25
+ id: 'D024BFF1M'
26
+ }
27
+ })
28
+ end
29
+
30
+ it "returns a response with the IM's ID" do
31
+ response = subject.im_open(user_id)
32
+
33
+ expect(response.id).to eq(channel_id)
34
+ end
35
+ end
36
+
37
+ describe "with a Slack error" do
38
+ let(:http_response) do
39
+ MultiJson.dump({
40
+ ok: false,
41
+ error: 'invalid_auth'
42
+ })
43
+ end
44
+
45
+ it "raises a RuntimeError" do
46
+ expect { subject.im_open(user_id) }.to raise_error(
47
+ "Slack API call to im.open returned an error: invalid_auth."
48
+ )
49
+ end
50
+ end
51
+
52
+ describe "with an HTTP error" do
53
+ let(:http_status) { 422 }
54
+ let(:http_response) { '' }
55
+
56
+ it "raises a RuntimeError" do
57
+ expect { subject.im_open(user_id) }.to raise_error(
58
+ "Slack API call to im.open failed with status code 422."
59
+ )
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "#rtm_start" do
65
+ let(:http_status) { 200 }
66
+ let(:stubs) do
67
+ Faraday::Adapter::Test::Stubs.new do |stub|
68
+ stub.post('https://slack.com/api/rtm.start', token: token) do
69
+ [http_status, {}, http_response]
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "with a successful response" do
75
+ let(:http_response) do
76
+ MultiJson.dump({
77
+ ok: true,
78
+ url: 'wss://example.com/',
79
+ users: [{ id: 'U023BECGF' }],
80
+ ims: [{ id: 'D024BFF1M' }],
81
+ self: { id: 'U12345678' }
82
+ })
83
+ end
84
+
85
+ it "has data on the bot user" do
86
+ response = subject.rtm_start
87
+
88
+ expect(response.self.id).to eq('U12345678')
89
+ end
90
+
91
+ it "has an array of IMs" do
92
+ response = subject.rtm_start
93
+
94
+ expect(response.ims[0].id).to eq('D024BFF1M')
95
+ end
96
+
97
+ it "has an array of users" do
98
+ response = subject.rtm_start
99
+
100
+ expect(response.users[0].id).to eq('U023BECGF')
101
+ end
102
+
103
+ it "has a WebSocket URL" do
104
+ response = subject.rtm_start
105
+
106
+ expect(response.websocket_url).to eq('wss://example.com/')
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapters::Slack::IMMapping do
4
+ subject { described_class.new(api, ims) }
5
+
6
+ let(:api) { instance_double('Lita::Adapters::Slack::API') }
7
+ let(:im) { Lita::Adapters::Slack::SlackIM.new('D1234567890', 'U023BECGF') }
8
+
9
+ describe "#im_for" do
10
+ context "when a mapping is already stored" do
11
+ let(:ims) { [im] }
12
+
13
+ it "returns the IM ID for the given user ID" do
14
+ expect(subject.im_for('U023BECGF')).to eq('D1234567890')
15
+ end
16
+ end
17
+
18
+ context "when a mapping is not yet stored" do
19
+ before do
20
+ allow(api).to receive(:im_open).with('U023BECGF').and_return(im).once
21
+ end
22
+
23
+ let(:ims) { [] }
24
+
25
+ it "fetches the IM ID from the API and returns it" do
26
+ expect(subject.im_for('U023BECGF')).to eq('D1234567890')
27
+ end
28
+
29
+ it "doesn't hit the API on subsequent look ups of the same user ID" do
30
+ expect(subject.im_for('U023BECGF')).to eq(subject.im_for('U023BECGF'))
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,178 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapters::Slack::MessageHandler, lita: true do
4
+ subject { described_class.new(robot, robot_id, data) }
5
+
6
+ let(:robot) { instance_double('Lita::Robot', name: 'Lita', mention_name: 'lita') }
7
+ let(:robot_id) { 'U12345678' }
8
+
9
+ describe "#handle" do
10
+ context "with a hello message" do
11
+ let(:data) { { "type" => "hello" } }
12
+
13
+ it "triggers a connected event" do
14
+ expect(robot).to receive(:trigger).with(:connected)
15
+
16
+ subject.handle
17
+ end
18
+ end
19
+
20
+ context "with a normal message" do
21
+ let(:data) do
22
+ {
23
+ "type" => "message",
24
+ "channel" => "C2147483705",
25
+ "user" => "U023BECGF",
26
+ "text" => "Hello"
27
+ }
28
+ end
29
+ let(:message) { instance_double('Lita::Message') }
30
+ let(:source) { instance_double('Lita::Source') }
31
+ let(:user) { instance_double('Lita::User', id: 'U023BECGF') }
32
+
33
+ before do
34
+ allow(Lita::User).to receive(:find_by_id).and_return(user)
35
+ allow(Lita::Source).to receive(:new).with(
36
+ user: user,
37
+ room: "C2147483705"
38
+ ).and_return(source)
39
+ allow(Lita::Message).to receive(:new).with(robot, "Hello", source).and_return(message)
40
+ allow(robot).to receive(:receive).with(message)
41
+ end
42
+
43
+ it "dispatches the message to Lita" do
44
+ expect(robot).to receive(:receive).with(message)
45
+
46
+ subject.handle
47
+ end
48
+
49
+ context "when the message starts with a Slack-style @-mention" do
50
+ let(:data) do
51
+ {
52
+ "type" => "message",
53
+ "channel" => "C2147483705",
54
+ "user" => "U023BECGF",
55
+ "text" => "<@#{robot_id}>: Hello"
56
+ }
57
+ end
58
+
59
+ it "converts it to a Lita-style @-mention" do
60
+ expect(Lita::Message).to receive(:new).with(
61
+ robot,
62
+ "@lita: Hello",
63
+ source
64
+ ).and_return(message)
65
+
66
+ subject.handle
67
+ end
68
+ end
69
+ end
70
+
71
+ context "with a message with an unsupported subtype" do
72
+ let(:data) do
73
+ {
74
+ "type" => "message",
75
+ "subtype" => "???"
76
+ }
77
+ end
78
+
79
+ it "does not dispatch the message to Lita" do
80
+ expect(robot).not_to receive(:receive)
81
+
82
+ subject.handle
83
+ end
84
+ end
85
+
86
+ context "with a message from the robot itself" do
87
+ let(:data) do
88
+ {
89
+ "type" => "message",
90
+ "subtype" => "bot_message"
91
+ }
92
+ end
93
+ let(:user) { instance_double('Lita::User', id: 12345) }
94
+
95
+ before do
96
+ # TODO: This probably shouldn't be tested with stubs.
97
+ allow(Lita::User).to receive(:find_by_id).and_return(user)
98
+ allow(Lita::User).to receive(:find_by_name).and_return(user)
99
+ end
100
+
101
+ it "does not dispatch the message to Lita" do
102
+ expect(robot).not_to receive(:receive)
103
+
104
+ subject.handle
105
+ end
106
+ end
107
+
108
+ context "with a team join message" do
109
+ let(:data) do
110
+ {
111
+ "type" => "team_join",
112
+ "user" => "some user data"
113
+ }
114
+ end
115
+
116
+ it "creates the new user" do
117
+ expect(
118
+ Lita::Adapters::Slack::UserCreator
119
+ ).to receive(:create_user) do |user_data, robot, robot_id|
120
+ expect(user_data).to eq("some user data")
121
+ end
122
+
123
+ subject.handle
124
+ end
125
+ end
126
+
127
+ context "with a bot added message" do
128
+ let(:data) do
129
+ {
130
+ "type" => "bot_added",
131
+ "bot" => "some user data"
132
+ }
133
+ end
134
+
135
+ it "creates a new user for the bot" do
136
+ expect(
137
+ Lita::Adapters::Slack::UserCreator
138
+ ).to receive(:create_user) do |user_data, robot, robot_id|
139
+ expect(user_data).to eq("some user data")
140
+ end
141
+
142
+ subject.handle
143
+ end
144
+ end
145
+
146
+ context "with an error message" do
147
+ let(:data) do
148
+ {
149
+ "type" => "error",
150
+ "error" => {
151
+ "code" => 2,
152
+ "msg" => "message text is missing"
153
+ }
154
+ }
155
+ end
156
+
157
+ it "logs the error" do
158
+ expect(Lita.logger).to receive(:error).with(
159
+ "Error with code 2 received from Slack: message text is missing"
160
+ )
161
+
162
+ subject.handle
163
+ end
164
+ end
165
+
166
+ context "with an unknown message" do
167
+ let(:data) { { "type" => "???" } }
168
+
169
+ it "logs the type" do
170
+ expect(Lita.logger).to receive(:debug).with(
171
+ "??? event received from Slack and will be ignored."
172
+ )
173
+
174
+ subject.handle
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,102 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapters::Slack::RTMConnection, lita: true do
4
+ def with_websocket(subject, queue)
5
+ thread = Thread.new { subject.run(queue) }
6
+ thread.abort_on_exception = true
7
+ yield queue.pop
8
+ subject.shut_down
9
+ thread.join
10
+ end
11
+
12
+ subject { described_class.new(robot, token, rtm_start_response) }
13
+
14
+ let(:api) { instance_double("Lita::Adapters::Slack::API") }
15
+ let(:registry) { Lita::Registry.new }
16
+ let(:robot) { Lita::Robot.new(registry) }
17
+ let(:rtm_start_response) do
18
+ Lita::Adapters::Slack::TeamData.new(
19
+ [],
20
+ Lita::Adapters::Slack::SlackUser.new('U12345678', 'carl', nil),
21
+ [Lita::Adapters::Slack::SlackUser.new('U12345678', 'carl', '')],
22
+ "wss://example.com/"
23
+ )
24
+ end
25
+ let(:token) { 'abcd-1234567890-hWYd21AmMH2UHAkx29vb5c1Y' }
26
+ let(:queue) { Queue.new }
27
+
28
+ describe ".build" do
29
+ before do
30
+ allow(Lita::Adapters::Slack::API).to receive(:new).with(token).and_return(api)
31
+ allow(api).to receive(:rtm_start).and_return(rtm_start_response)
32
+ end
33
+
34
+ it "constructs a new RTMConnection with the results of rtm.start data" do
35
+ expect(described_class.build(robot, token)).to be_an_instance_of(described_class)
36
+ end
37
+
38
+ it "creates users with the results of rtm.start data" do
39
+ expect(Lita::Adapters::Slack::UserCreator).to receive(:create_users)
40
+
41
+ described_class.build(robot, token)
42
+ end
43
+ end
44
+
45
+ describe "#im_for" do
46
+ before do
47
+ allow(Lita::Adapters::Slack::IMMapping).to receive(:new).and_return(im_mapping)
48
+ allow(im_mapping).to receive(:im_for).with('U12345678').and_return('D024BFF1M')
49
+ end
50
+
51
+ let(:im_mapping) { instance_double('Lita::Adapters::Slack::IMMapping') }
52
+
53
+ it "delegates to the IMMapping" do
54
+ with_websocket(subject, queue) do |websocket|
55
+ expect(subject.im_for('U12345678')).to eq('D024BFF1M')
56
+ end
57
+ end
58
+ end
59
+
60
+ describe "#run" do
61
+ it "starts the reactor" do
62
+ with_websocket(subject, queue) do |websocket|
63
+ expect(EM.reactor_running?).to be_truthy
64
+ end
65
+ end
66
+
67
+ it "creates the WebSocket" do
68
+ with_websocket(subject, queue) do |websocket|
69
+ expect(websocket).to be_an_instance_of(Faye::WebSocket::Client)
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "#send_messages" do
75
+ let(:message_json) { MultiJson.dump(id: 1, type: 'message', text: 'hi', channel: channel_id) }
76
+ let(:channel_id) { 'C024BE91L' }
77
+ let(:websocket) { instance_double("Faye::WebSocket::Client") }
78
+
79
+ before do
80
+ # TODO: Don't stub what you don't own!
81
+ allow(Faye::WebSocket::Client).to receive(:new).and_return(websocket)
82
+ allow(websocket).to receive(:on)
83
+ allow(websocket).to receive(:close)
84
+ end
85
+
86
+ it "writes messages to the WebSocket" do
87
+ with_websocket(subject, queue) do |websocket|
88
+ expect(websocket).to receive(:send).with(message_json)
89
+
90
+ subject.send_messages(channel_id, ['hi'])
91
+ end
92
+ end
93
+
94
+ it "raises an ArgumentError if the payload is too large" do
95
+ with_websocket(subject, queue) do |websocket|
96
+ expect do
97
+ subject.send_messages(channel_id, ['x' * 16_001])
98
+ end.to raise_error(ArgumentError)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapters::Slack::SlackIM do
4
+ let(:im_data_1) do
5
+ {
6
+ "id" => "D024BFF1M",
7
+ "is_im" => true,
8
+ "user" => "U024BE7LH",
9
+ "created" => 1360782804,
10
+ }
11
+ end
12
+ let(:im_data_2) do
13
+ {
14
+ "id" => "D012ABC3E",
15
+ "is_im" => true,
16
+ "user" => "U098ZYX7W",
17
+ "created" => 1360782904,
18
+ }
19
+ end
20
+ let(:ims_data) { [im_data_1, im_data_2] }
21
+
22
+ describe ".from_data_array" do
23
+ subject { described_class.from_data_array(ims_data) }
24
+
25
+ it "returns an object for each hash of IM data" do
26
+ expect(subject.size).to eq(2)
27
+ end
28
+
29
+ it "creates SlackIM objects" do
30
+ expect(subject[0].id).to eq('D024BFF1M')
31
+ expect(subject[0].user_id).to eq('U024BE7LH')
32
+ expect(subject[1].id).to eq('D012ABC3E')
33
+ expect(subject[1].user_id).to eq('U098ZYX7W')
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapters::Slack::SlackUser do
4
+ let(:user_data_1) do
5
+ {
6
+ "id" => "U023BECGF",
7
+ "name" => "bobby",
8
+ "real_name" => "Bobby Tables"
9
+ }
10
+ end
11
+ let(:user_data_2) do
12
+ {
13
+ "id" => "U024BE7LH",
14
+ "name" => "carl",
15
+ }
16
+ end
17
+ let(:users_data) { [user_data_1, user_data_2] }
18
+
19
+ describe ".from_data_array" do
20
+ subject { described_class.from_data_array(users_data) }
21
+
22
+ it "returns an object for each hash of user data" do
23
+ expect(subject.size).to eq(2)
24
+ end
25
+
26
+ it "creates SlackUser objects" do
27
+ expect(subject[0].id).to eq('U023BECGF')
28
+ expect(subject[0].name).to eq('bobby')
29
+ expect(subject[0].real_name).to eq('Bobby Tables')
30
+ expect(subject[1].id).to eq('U024BE7LH')
31
+ expect(subject[1].name).to eq('carl')
32
+ expect(subject[1].real_name).to be_nil
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapters::Slack::UserCreator do
4
+ before { allow(Lita::User).to receive(:create) }
5
+
6
+ let(:robot) { instance_double('Lita::Robot') }
7
+
8
+ describe ".create_users" do
9
+ let(:real_name) { 'Bobby Tables' }
10
+ let(:bobby) { Lita::Adapters::Slack::SlackUser.new('U023BECGF', 'bobby', real_name) }
11
+ let(:robot_id) { 'U12345678' }
12
+
13
+ it "creates Lita users for each user in the provided data" do
14
+ expect(Lita::User).to receive(:create).with(
15
+ 'U023BECGF',
16
+ name: 'Bobby Tables',
17
+ mention_name: 'bobby'
18
+ )
19
+
20
+ described_class.create_users([bobby], robot, robot_id)
21
+ end
22
+
23
+ context "when the Slack user has no real name set" do
24
+ let(:real_name) { "" }
25
+
26
+ it "uses the mention name if no real name is available" do
27
+ expect(Lita::User).to receive(:create).with(
28
+ 'U023BECGF',
29
+ name: 'bobby',
30
+ mention_name: 'bobby'
31
+ )
32
+
33
+ described_class.create_users([bobby], robot, robot_id)
34
+ end
35
+ end
36
+ end
37
+
38
+ describe ".create_user" do
39
+ let(:robot_id) { 'U12345678' }
40
+ let(:slack_user) { Lita::Adapters::Slack::SlackUser.new(robot_id, 'litabot', 'Lita Bot') }
41
+
42
+ it "updates the robot's name and mention name if it applicable" do
43
+ expect(robot).to receive(:name=).with('Lita Bot')
44
+ expect(robot).to receive(:mention_name=).with('litabot')
45
+
46
+ described_class.create_user(slack_user, robot, robot_id)
47
+ end
48
+ end
49
+ end
@@ -1,53 +1,86 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Lita::Adapters::Slack, lita: true do
4
- before do
5
- Lita.configure do |config|
6
- config.adapter.incoming_token = 'aN1NvAlIdDuMmYt0k3n'
7
- config.adapter.team_domain = 'example'
8
- config.adapter.username = 'lita'
9
- config.adapter.add_mention = true
10
- end
11
- end
12
-
13
- subject { described_class.new(robot) }
14
- let(:robot) { double("Lita::Robot") }
15
-
16
- it "registers with Lita" do
17
- expect(Lita.adapters[:slack]).to eql(described_class)
18
- end
19
-
20
- it "fails without valid config: incoming_token and team_domain" do
21
- Lita.clear_config
22
- expect(Lita.logger).to receive(:fatal).with(/incoming_token, team_domain/)
23
- expect { subject }.to raise_error(SystemExit)
24
- end
25
-
26
- describe "#send_messages" do
27
- it "sends JSON payload via HTTP POST to Slack channel" do
28
- target = double("Lita::Source", room: "CR00M1D")
29
- payload = {'channel' => target.room, 'username' => Lita.config.adapter.username, 'text' => 'Hello!'}
30
- expect(subject).to receive(:http_post).with(payload)
31
- subject.send_messages(target, ["Hello!"])
32
- end
33
-
34
- it "sends message with mention if user info is provided" do
35
- user = double("Lita::User", id: "UM3NT10N")
36
- target = double("Lita::Source", room: "CR00M1D", user: user)
37
- text = "<@#{user.id}> Hello!"
38
- payload = {'channel' => target.room, 'username' => Lita.config.adapter.username, 'text' => text}
39
- expect(subject).to receive(:http_post).with(payload)
40
- subject.send_messages(target, ["Hello!"])
41
- end
42
-
43
- it "proceeds but logs WARN when directed to an user without channel(room) info" do
44
- user = double("Lita::User", id: "UM3NT10N")
45
- target = double("Lita::Source", user: user)
46
- text = "<@#{user.id}> Hello!"
47
- payload = {'channel' => nil, 'username' => Lita.config.adapter.username, 'text' => text}
48
- expect(subject).to receive(:http_post).with(payload)
49
- expect(Lita.logger).to receive(:warn).with(/without channel/)
50
- subject.send_messages(target, ["Hello!"])
51
- end
52
- end
4
+ subject { described_class.new(robot) }
5
+
6
+ let(:robot) { Lita::Robot.new(registry) }
7
+ let(:rtm_connection) { instance_double('Lita::Adapters::Slack::RTMConnection') }
8
+ let(:token) { 'abcd-1234567890-hWYd21AmMH2UHAkx29vb5c1Y' }
9
+
10
+ before do
11
+ registry.register_adapter(:slack, described_class)
12
+ registry.config.adapters.slack.token = token
13
+
14
+ allow(
15
+ described_class::RTMConnection
16
+ ).to receive(:build).with(robot, token).and_return(rtm_connection)
17
+ allow(rtm_connection).to receive(:run)
18
+ end
19
+
20
+ it "registers with Lita" do
21
+ expect(Lita.adapters[:slack]).to eql(described_class)
22
+ end
23
+
24
+ describe "#run" do
25
+ it "starts the RTM connection" do
26
+ expect(rtm_connection).to receive(:run)
27
+
28
+ subject.run
29
+ end
30
+
31
+ it "does nothing if the RTM connection is already created" do
32
+ expect(rtm_connection).to receive(:run).once
33
+
34
+ subject.run
35
+ subject.run
36
+ end
37
+ end
38
+
39
+ describe "#send_messages" do
40
+ let(:room_source) { Lita::Source.new(room: 'C024BE91L') }
41
+ let(:user) { Lita::User.new('U023BECGF') }
42
+ let(:user_source) { Lita::Source.new(user: user) }
43
+
44
+ it "sends messages to rooms" do
45
+ expect(rtm_connection).to receive(:send_messages).with(room_source.room, ['foo'])
46
+
47
+ subject.run
48
+
49
+ subject.send_messages(room_source, ['foo'])
50
+ end
51
+
52
+ it "sends messages to users" do
53
+ allow(rtm_connection).to receive(:im_for).with(user.id).and_return('D024BFF1M')
54
+
55
+ expect(rtm_connection).to receive(:send_messages).with('D024BFF1M', ['foo'])
56
+
57
+ subject.run
58
+
59
+ subject.send_messages(Lita::Source.new(user: user), ['foo'])
60
+ end
61
+ end
62
+
63
+ describe "#shut_down" do
64
+ before { allow(rtm_connection).to receive(:shut_down) }
65
+
66
+ it "shuts down the RTM connection" do
67
+ expect(rtm_connection).to receive(:shut_down)
68
+
69
+ subject.run
70
+ subject.shut_down
71
+ end
72
+
73
+ it "triggers a :disconnected event" do
74
+ expect(robot).to receive(:trigger).with(:disconnected)
75
+
76
+ subject.run
77
+ subject.shut_down
78
+ end
79
+
80
+ it "does nothing if the RTM connection hasn't been created yet" do
81
+ expect(rtm_connection).not_to receive(:shut_down)
82
+
83
+ subject.shut_down
84
+ end
85
+ end
53
86
  end