lita-pagerduty 0.2.0 → 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.
- checksums.yaml +4 -4
- data/.rspec +1 -1
- data/.rubocop.yml +6 -5
- data/README.md +11 -9
- data/Rakefile +4 -2
- data/lib/exceptions.rb +11 -0
- data/lib/lita-pagerduty.rb +7 -29
- data/lib/lita/commands/ack.rb +15 -0
- data/lib/lita/commands/ack_all.rb +21 -0
- data/lib/lita/commands/ack_mine.rb +26 -0
- data/lib/lita/commands/base.rb +75 -0
- data/lib/lita/commands/forget.rb +13 -0
- data/lib/lita/commands/identify.rb +13 -0
- data/lib/lita/commands/incident.rb +13 -0
- data/lib/lita/commands/incidents_all.rb +18 -0
- data/lib/lita/commands/incidents_mine.rb +23 -0
- data/lib/lita/commands/notes.rb +15 -0
- data/lib/lita/commands/on_call_list.rb +13 -0
- data/lib/lita/commands/on_call_lookup.rb +41 -0
- data/lib/lita/commands/pager_me.rb +48 -0
- data/lib/lita/commands/resolve.rb +15 -0
- data/lib/lita/commands/resolve_all.rb +21 -0
- data/lib/lita/commands/resolve_mine.rb +26 -0
- data/lib/lita/handlers/commands.yml +30 -0
- data/lib/lita/handlers/pagerduty.rb +57 -0
- data/lib/pagerduty.rb +94 -0
- data/lib/store.rb +20 -0
- data/lita-pagerduty.gemspec +1 -2
- data/locales/en.yml +2 -2
- data/spec/lita/handlers/ack_all_spec.rb +26 -0
- data/spec/lita/handlers/ack_id_spec.rb +23 -0
- data/spec/lita/handlers/ack_mine_spec.rb +38 -0
- data/spec/lita/handlers/forget_spec.rb +22 -0
- data/spec/lita/handlers/identify_email_spec.rb +22 -0
- data/spec/lita/handlers/incident_id_spec.rb +23 -0
- data/spec/lita/handlers/incidents_all_spec.rb +31 -0
- data/spec/lita/handlers/incidents_mine_spec.rb +35 -0
- data/spec/lita/handlers/notes_spec.rb +29 -0
- data/spec/lita/handlers/on_call_list_spec.rb +21 -0
- data/spec/lita/handlers/on_call_lookup_spec.rb +29 -0
- data/spec/lita/handlers/pager_me_spec.rb +56 -0
- data/spec/lita/handlers/resolve_all_spec.rb +26 -0
- data/spec/lita/handlers/resolve_id_spec.rb +21 -0
- data/spec/lita/handlers/resolve_mine_spec.rb +38 -0
- data/spec/pagerduty_spec.rb +106 -0
- data/spec/spec_helper.rb +3 -189
- data/templates/.gitkeep +0 -0
- metadata +57 -35
- data/lib/lita/handlers/pagerduty_ack.rb +0 -77
- data/lib/lita/handlers/pagerduty_incident.rb +0 -68
- data/lib/lita/handlers/pagerduty_note.rb +0 -50
- data/lib/lita/handlers/pagerduty_resolve.rb +0 -77
- data/lib/lita/handlers/pagerduty_utility.rb +0 -136
- data/lib/pagerduty_helper/incident.rb +0 -64
- data/lib/pagerduty_helper/regex.rb +0 -8
- data/lib/pagerduty_helper/utility.rb +0 -43
- data/spec/lita/handlers/pagerduty_ack_spec.rb +0 -95
- data/spec/lita/handlers/pagerduty_incident_spec.rb +0 -103
- data/spec/lita/handlers/pagerduty_note_spec.rb +0 -43
- data/spec/lita/handlers/pagerduty_resolve_spec.rb +0 -95
- data/spec/lita/handlers/pagerduty_utility_spec.rb +0 -59
@@ -0,0 +1,48 @@
|
|
1
|
+
module Commands
|
2
|
+
class PagerMe
|
3
|
+
include Base
|
4
|
+
|
5
|
+
def call
|
6
|
+
response message: 'pager_me.success', params: success_response_params
|
7
|
+
rescue Exceptions::SchedulesEmptyList
|
8
|
+
response schedules_empty_list
|
9
|
+
rescue Exceptions::UserNotIdentified
|
10
|
+
response message: 'identify.missing'
|
11
|
+
rescue Exceptions::UsersEmptyList
|
12
|
+
response message: 'identify.unrecognised'
|
13
|
+
rescue Exceptions::OverrideUnsuccess
|
14
|
+
response message: 'pager_me.failure'
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def schedules_empty_list
|
20
|
+
{
|
21
|
+
message: 'on_call_lookup.no_matching_schedule',
|
22
|
+
params: {
|
23
|
+
schedule_name: message.match_data[1].strip
|
24
|
+
}
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def schedule
|
29
|
+
@schedule ||= pagerduty.get_schedules(
|
30
|
+
query: message.match_data[1].strip
|
31
|
+
).first
|
32
|
+
end
|
33
|
+
|
34
|
+
def override
|
35
|
+
@override ||= pagerduty.override(
|
36
|
+
schedule[:id], current_user[:id], message.match_data[2].strip.to_i
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def success_response_params
|
41
|
+
{
|
42
|
+
name: override[:user][:summary],
|
43
|
+
email: current_user[:email],
|
44
|
+
finish: override[:end]
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Commands
|
2
|
+
class Resolve
|
3
|
+
include Base
|
4
|
+
|
5
|
+
def call
|
6
|
+
incident_id = message.match_data['incident_id']
|
7
|
+
return if incident_id =~ /\A(all|mine)\z/i
|
8
|
+
|
9
|
+
pagerduty.manage_incidents(:resolve, [incident_id])
|
10
|
+
response message: 'all.resolved', params: { list: incident_id.to_s }
|
11
|
+
rescue Exceptions::IncidentManageUnsuccess
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Commands
|
2
|
+
class ResolveAll
|
3
|
+
include Base
|
4
|
+
|
5
|
+
def call
|
6
|
+
ids = pagerduty.get_incidents(query_params).map { |i| i[:id] }
|
7
|
+
pagerduty.manage_incidents(:resolve, ids)
|
8
|
+
response message: 'all.resolved', params: { list: ids.join(', ') }
|
9
|
+
rescue Exceptions::IncidentsEmptyList
|
10
|
+
response message: 'incident.none'
|
11
|
+
rescue Exceptions::IncidentManageUnsuccess
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def query_params
|
18
|
+
{ statuses: %w[triggered acknowledged] }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Commands
|
2
|
+
class ResolveMine
|
3
|
+
include Base
|
4
|
+
|
5
|
+
def call
|
6
|
+
ids = pagerduty.get_incidents(query_params).map { |i| i[:id] }
|
7
|
+
pagerduty.manage_incidents(:resolve, ids)
|
8
|
+
response message: 'all.resolved', params: { list: ids.join(', ') }
|
9
|
+
rescue Exceptions::UserNotIdentified
|
10
|
+
response message: 'incident.none_mine'
|
11
|
+
rescue Exceptions::IncidentsEmptyList
|
12
|
+
response message: 'incident.none_mine'
|
13
|
+
rescue Exceptions::IncidentManageUnsuccess
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def query_params
|
20
|
+
{
|
21
|
+
statuses: %w[triggered acknowledged],
|
22
|
+
'user_ids[]' => current_user[:id]
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
- pattern: ^pager\sidentify\s(?<email>[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+)$
|
2
|
+
method: identify
|
3
|
+
- pattern: ^pager\sforget$
|
4
|
+
method: forget
|
5
|
+
- pattern: ^pager\sincidents\sall$
|
6
|
+
method: incidents_all
|
7
|
+
- pattern: ^pager\sincidents\smine$
|
8
|
+
method: incidents_mine
|
9
|
+
- pattern: ^pager\sincident\s(?<incident_id>[a-zA-Z0-9+]+)$
|
10
|
+
method: incident
|
11
|
+
- pattern: ^pager\snotes\s(?<incident_id>[a-zA-Z0-9+]+)$
|
12
|
+
method: notes
|
13
|
+
- pattern: ^pager\sack\sall$
|
14
|
+
method: ack_all
|
15
|
+
- pattern: ^pager\sack\smine$
|
16
|
+
method: ack_mine
|
17
|
+
- pattern: ^pager\sack\s(?<incident_id>[a-zA-Z0-9+]+)$
|
18
|
+
method: ack
|
19
|
+
- pattern: ^pager\sresolve\sall$
|
20
|
+
method: resolve_all
|
21
|
+
- pattern: ^pager\sresolve\smine$
|
22
|
+
method: resolve_mine
|
23
|
+
- pattern: ^pager\sresolve\s(?<incident_id>[a-zA-Z0-9+]+)$
|
24
|
+
method: resolve
|
25
|
+
- pattern: ^pager\soncall$
|
26
|
+
method: on_call_list
|
27
|
+
- pattern: ^pager\soncall\s(.*)$
|
28
|
+
method: on_call_lookup
|
29
|
+
- pattern: ^pager\s+me\s+(.+?)\s+(\d+)m?$
|
30
|
+
method: pager_me
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Lita
|
2
|
+
module Handlers
|
3
|
+
class Pagerduty < Handler
|
4
|
+
namespace 'Pagerduty'
|
5
|
+
config :api_key, required: true
|
6
|
+
config :email, required: true
|
7
|
+
|
8
|
+
COMMANDS_PATH = File.read("#{File.dirname(__FILE__)}/commands.yml")
|
9
|
+
COMMANDS = YAML.safe_load(COMMANDS_PATH)
|
10
|
+
COMMANDS.each do |command|
|
11
|
+
route(
|
12
|
+
/#{command['pattern']}/,
|
13
|
+
command['method'].to_sym,
|
14
|
+
command: true,
|
15
|
+
help: {
|
16
|
+
"help.#{command['method']}.syntax" =>
|
17
|
+
"help.#{command['method']}.desc"
|
18
|
+
}
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(method, message)
|
23
|
+
super if COMMANDS.map { |i| i['method'] }.include? method
|
24
|
+
response = Object.const_get(
|
25
|
+
'Commands::' << method.to_s.split('_').map(&:capitalize).join
|
26
|
+
).send(:call, message, pagerduty, store)
|
27
|
+
handle_response(message, response) if response
|
28
|
+
end
|
29
|
+
|
30
|
+
def respond_to_missing?(method, include_private = false)
|
31
|
+
COMMANDS.map { |i| i['method'] }.include?(method) || super
|
32
|
+
end
|
33
|
+
|
34
|
+
def pagerduty
|
35
|
+
@pagerduty ||= ::Pagerduty.new(http, config.api_key, config.email)
|
36
|
+
end
|
37
|
+
|
38
|
+
def store
|
39
|
+
@store ||= Store.new(redis)
|
40
|
+
end
|
41
|
+
|
42
|
+
def handle_response(message, response)
|
43
|
+
message.reply case response
|
44
|
+
when String
|
45
|
+
response
|
46
|
+
when Hash
|
47
|
+
t response[:message], response[:params]
|
48
|
+
when Array
|
49
|
+
response.map { |item| t item[:message], item[:params] }
|
50
|
+
.join("\n")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
Lita.register_handler(self)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/pagerduty.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
class Pagerduty
|
2
|
+
def initialize(http, token, email)
|
3
|
+
@token = token
|
4
|
+
@http = http
|
5
|
+
http.url_prefix = 'https://api.pagerduty.com/'
|
6
|
+
http.headers = headers(email)
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_incidents(params = {})
|
10
|
+
response = @http.get '/incidents', params
|
11
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
12
|
+
.fetch(:incidents, [])
|
13
|
+
raise Exceptions::IncidentsEmptyList if data.empty?
|
14
|
+
|
15
|
+
data
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_users(params = {})
|
19
|
+
response = @http.get '/users', params
|
20
|
+
data = JSON.parse(response.body, symbolize_names: true).fetch(:users, [])
|
21
|
+
raise Exceptions::UsersEmptyList if data.empty?
|
22
|
+
|
23
|
+
data
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_schedules(params = {})
|
27
|
+
response = @http.get '/schedules', params
|
28
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
29
|
+
.fetch(:schedules, [])
|
30
|
+
raise Exceptions::SchedulesEmptyList if data.empty?
|
31
|
+
|
32
|
+
data
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_oncall_user(params = {})
|
36
|
+
response = @http.get '/oncalls', params
|
37
|
+
data = JSON.parse(response.body, symbolize_names: true).fetch(:oncalls, [])
|
38
|
+
raise Exceptions::NoOncallUser if data.empty?
|
39
|
+
|
40
|
+
data.first.fetch(:user)
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_incident(id = '404stub')
|
44
|
+
response = @http.get "/incidents/#{id}"
|
45
|
+
raise Exceptions::IncidentNotFound if response.status == 404
|
46
|
+
|
47
|
+
JSON.parse(response.body, symbolize_names: true).fetch(:incident, nil)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_notes_by_incident_id(incident_id)
|
51
|
+
response = @http.get "/incidents/#{incident_id}/notes"
|
52
|
+
raise Exceptions::IncidentNotFound if response.status == 404
|
53
|
+
|
54
|
+
data = JSON.parse(response.body, symbolize_names: true).fetch(:notes, [])
|
55
|
+
raise Exceptions::NotesEmptyList if data.empty?
|
56
|
+
|
57
|
+
data
|
58
|
+
end
|
59
|
+
|
60
|
+
def manage_incidents(action, ids)
|
61
|
+
incidents = ids.map do |id|
|
62
|
+
{ id: id, type: 'incident_reference', status: "#{action}d" }
|
63
|
+
end
|
64
|
+
payload = { incidents: incidents }
|
65
|
+
response = @http.put '/incidents', payload
|
66
|
+
raise Exceptions::IncidentManageUnsuccess if response.status != 200
|
67
|
+
|
68
|
+
response
|
69
|
+
end
|
70
|
+
|
71
|
+
def override(schedule_id, user_id, minutes)
|
72
|
+
from = ::Time.now.utc + 10
|
73
|
+
to = from + (60 * minutes)
|
74
|
+
payload = { override: {
|
75
|
+
start: from.iso8601,
|
76
|
+
end: to.iso8601,
|
77
|
+
user: { id: user_id, type: 'user_reference' }
|
78
|
+
} }
|
79
|
+
response = @http.post "/schedules/#{schedule_id}/overrides", payload
|
80
|
+
raise Exceptions::OverrideUnsuccess if response.status != 201
|
81
|
+
|
82
|
+
JSON.parse(response.body, symbolize_names: true).fetch(:override, nil)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def headers(email)
|
88
|
+
{
|
89
|
+
'Accept' => 'application/vnd.pagerduty+json;version=2',
|
90
|
+
'Authorization' => "Token token=#{@token}",
|
91
|
+
'From' => email
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
data/lib/store.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
class Store
|
2
|
+
def initialize(redis)
|
3
|
+
@redis = redis
|
4
|
+
end
|
5
|
+
|
6
|
+
def get_user(response)
|
7
|
+
email = @redis.get("user_#{response.user.id}")
|
8
|
+
raise Exceptions::UserNotIdentified unless email
|
9
|
+
|
10
|
+
email
|
11
|
+
end
|
12
|
+
|
13
|
+
def remember_user(response)
|
14
|
+
@redis.set("user_#{response.user.id}", response.match_data['email'])
|
15
|
+
end
|
16
|
+
|
17
|
+
def forget_user(response)
|
18
|
+
@redis.del("user_#{response.user.id}")
|
19
|
+
end
|
20
|
+
end
|
data/lita-pagerduty.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
spec.name = 'lita-pagerduty'
|
3
|
-
spec.version = '0.
|
3
|
+
spec.version = '1.0.0'
|
4
4
|
spec.authors = ['Eric Sigler']
|
5
5
|
spec.email = ['eric@pagerduty.com']
|
6
6
|
spec.description = 'A Lita handler to interact with PagerDuty'
|
@@ -15,7 +15,6 @@ Gem::Specification.new do |spec|
|
|
15
15
|
spec.require_paths = ['lib']
|
16
16
|
|
17
17
|
spec.add_runtime_dependency 'lita', '>= 4.0'
|
18
|
-
spec.add_runtime_dependency 'pagerduty-sdk', '>= 1.0.9'
|
19
18
|
|
20
19
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
21
20
|
spec.add_development_dependency 'coveralls'
|
data/locales/en.yml
CHANGED
@@ -78,13 +78,13 @@ en:
|
|
78
78
|
acknowledged: "Acknowledged: %{list}"
|
79
79
|
resolved: "Resolved: %{list}"
|
80
80
|
note:
|
81
|
-
show: "%{id}: %{content} (%{
|
81
|
+
show: "%{id}: %{content} (%{user})"
|
82
82
|
on_call_list:
|
83
83
|
response: "Available schedules:\n%{schedules}"
|
84
84
|
no_schedules_found: "No schedules found"
|
85
85
|
on_call_lookup:
|
86
86
|
response: "%{name} (%{email}) is currently on call for %{schedule_name}"
|
87
|
-
no_matching_schedule: "No matching schedules found for '%{schedule_name}'"
|
87
|
+
no_matching_schedule: "No matching schedules found for '%{schedule_name}'"
|
88
88
|
no_one_on_call: "No one is currently on call for %{schedule_name}"
|
89
89
|
pager_me:
|
90
90
|
success: "%{name} (%{email}) is now on call until %{finish}"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lita::Handlers::Pagerduty, lita_handler: true do
|
4
|
+
context 'ack all' do
|
5
|
+
it do
|
6
|
+
is_expected.to route_command('pager ack all').to(:ack_all)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'empty list' do
|
10
|
+
expect_any_instance_of(Pagerduty).to receive(:get_incidents).and_raise(Exceptions::IncidentsEmptyList)
|
11
|
+
send_command('pager ack all')
|
12
|
+
expect(replies.last).to eq('No triggered, open, or acknowledged incidents')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'list of incidents' do
|
16
|
+
expect_any_instance_of(Pagerduty).to receive(:get_incidents).and_return([
|
17
|
+
{ id: 'ABC123' }, { id: 'ABC124' }
|
18
|
+
])
|
19
|
+
expect_any_instance_of(Pagerduty).to receive(:manage_incidents).and_return(
|
20
|
+
OpenStruct.new(status: 200)
|
21
|
+
)
|
22
|
+
send_command('pager ack all')
|
23
|
+
expect(replies.last).to eq('Acknowledged: ABC123, ABC124')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lita::Handlers::Pagerduty, lita_handler: true do
|
4
|
+
context 'ack ABC123' do
|
5
|
+
it do
|
6
|
+
is_expected.to route_command('pager ack ABC123').to(:ack)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'not found' do
|
10
|
+
expect_any_instance_of(Pagerduty).to receive(:manage_incidents).and_raise(Exceptions::IncidentManageUnsuccess)
|
11
|
+
send_command('pager ack ABC123')
|
12
|
+
expect(replies.last).to be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'found' do
|
16
|
+
expect_any_instance_of(Pagerduty).to receive(:manage_incidents).and_return(
|
17
|
+
OpenStruct.new(status: 200)
|
18
|
+
)
|
19
|
+
send_command('pager ack ABC123')
|
20
|
+
expect(replies.last).to eq 'Acknowledged: ABC123'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lita::Handlers::Pagerduty, lita_handler: true do
|
4
|
+
context 'ack mine' do
|
5
|
+
it do
|
6
|
+
is_expected.to route_command('pager ack mine').to(:ack_mine)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'unknown user' do
|
10
|
+
user = Lita::User.create(123, name: 'foo')
|
11
|
+
send_command('pager ack mine', as: user)
|
12
|
+
expect(replies.last).to eq('You have no triggered, open, or acknowledged incidents')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'empty list' do
|
16
|
+
user = Lita::User.create(123, name: 'foo')
|
17
|
+
send_command('pager identify foo@pagerduty.com', as: user)
|
18
|
+
expect_any_instance_of(Pagerduty).to receive(:get_users).and_return([{ id: 'abc123' }])
|
19
|
+
expect_any_instance_of(Pagerduty).to receive(:get_incidents).and_raise(Exceptions::IncidentsEmptyList)
|
20
|
+
send_command('pager ack mine', as: user)
|
21
|
+
expect(replies.last).to eq('You have no triggered, open, or acknowledged incidents')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'list of incidents' do
|
25
|
+
user = Lita::User.create(123, name: 'foo')
|
26
|
+
send_command('pager identify foo@pagerduty.com', as: user)
|
27
|
+
expect_any_instance_of(Pagerduty).to receive(:get_users).and_return([{ id: 'abc123' }])
|
28
|
+
expect_any_instance_of(Pagerduty).to receive(:get_incidents).and_return([
|
29
|
+
{ id: 'ABC123' }, { id: 'ABC124' }
|
30
|
+
])
|
31
|
+
expect_any_instance_of(Pagerduty).to receive(:manage_incidents).and_return(
|
32
|
+
OpenStruct.new(status: 200)
|
33
|
+
)
|
34
|
+
send_command('pager ack mine', as: user)
|
35
|
+
expect(replies.last).to eq('Acknowledged: ABC123, ABC124')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|