lita-slack 0.1.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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