teams_connector 0.1.1 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +19 -0
- data/README.md +98 -7
- data/lib/teams_connector/configuration.rb +12 -1
- data/lib/teams_connector/matchers/have_sent_notification_to.rb +163 -0
- data/lib/teams_connector/matchers.rb +11 -0
- data/lib/teams_connector/notification/adaptive_card.rb +1 -1
- data/lib/teams_connector/notification/message.rb +1 -1
- data/lib/teams_connector/notification.rb +16 -12
- data/lib/teams_connector/rspec.rb +5 -0
- data/lib/teams_connector/testing.rb +13 -0
- data/lib/teams_connector/version.rb +1 -1
- data/lib/teams_connector.rb +11 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ed38230a562264b9117ecea0aef7cd66b1bb7d947056f5b892e88cfaed906af
|
4
|
+
data.tar.gz: e3d01cfa5b8de620938e3f8bbd0c012591dc1ecbaf5b9dc742b03801b20c4d00
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 683b6a1095609d9f13263c71d7d17c4c466bd3041c334675985d13d4f2e9fd6f9b484354f21779c07b1ea4574fb11e3b10b6fae5e9d5f8fe9fdf2b34f72c27ea
|
7
|
+
data.tar.gz: c0144642fff244b53410dc78f4f6f59570f6354af3f431c6d429878c2fd7c9af377edcafe55a020b08765e40db11bac5a64819321a350d4d1ed05d92379645f6
|
data/CHANGES.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# Teams Connector Changelog
|
2
2
|
|
3
|
+
0.1.5
|
4
|
+
---
|
5
|
+
- RSpec Matchers for testing, thanks to [rspec-rails](https://github.com/rspec/rspec-rails) for their ActionCable `have_broadcasted_to` matcher as reference
|
6
|
+
- README update for testing
|
7
|
+
- Sometimes use testing mode internally
|
8
|
+
- Fixed code smells
|
9
|
+
|
10
|
+
0.1.4
|
11
|
+
---
|
12
|
+
- Add rudimentary testing method
|
13
|
+
|
14
|
+
0.1.3
|
15
|
+
---
|
16
|
+
- Allow sending a notification to multiple channels at the same time
|
17
|
+
|
18
|
+
0.1.2
|
19
|
+
---
|
20
|
+
- Use `TeamsConnector::Configuration#load_from_rails_credentials` to load encrypted channel URLs in your Rails environment
|
21
|
+
|
3
22
|
0.1.1
|
4
23
|
---
|
5
24
|
- Adaptive Card Notification
|
data/README.md
CHANGED
@@ -25,16 +25,18 @@ Or install it yourself as:
|
|
25
25
|
$ gem install teams_connector
|
26
26
|
|
27
27
|
## Usage
|
28
|
-
After setting up the Incoming Webhook Connector
|
28
|
+
After setting up the Incoming Webhook Connector for your Microsoft Teams channel, it is as simple as configuring the channel and creating a new `TeamsConnector::Notification`.
|
29
|
+
|
30
|
+
The `channels` parameter can either be a single channel identifier or an array of multiple channel identifiers, that each will receive the notification.
|
29
31
|
|
30
32
|
```ruby
|
31
|
-
#
|
33
|
+
# TeamsConnector initializer
|
32
34
|
TeamsConnector.configure do |config|
|
33
35
|
config.channel :channel_id, "https://<YOUR COMPLETE WEBHOOK URL GOES HERE>"
|
34
36
|
end
|
35
37
|
|
36
38
|
# Send a test card to your channel
|
37
|
-
TeamsConnector::Notification.new(:test_card, :channel_id).deliver_later
|
39
|
+
TeamsConnector::Notification.new(template: :test_card, channels: :channel_id).deliver_later
|
38
40
|
|
39
41
|
# Send a card with a list of facts
|
40
42
|
content = {
|
@@ -46,9 +48,38 @@ content = {
|
|
46
48
|
}
|
47
49
|
TeamsConnector::Notification::Message.new(:facts_card, "This is a summary", content).deliver_later
|
48
50
|
```
|
49
|
-
This gem provides some basic templates in its default template path. You can also define your own templates in your own path. The default templates will be still available so you can mix and match.
|
50
51
|
|
51
|
-
###
|
52
|
+
### Secure Channel Configuration
|
53
|
+
Since the Incoming Webhook Connector does not allow any authentication at the endpoint it is crucial that you keep your channel urls secret.
|
54
|
+
At best nobody finds the url but it can also lead to spam or even faking of critical messages.
|
55
|
+
|
56
|
+
In Rails provides the credentials functionality for [environmental security](https://edgeguides.rubyonrails.org/security.html#environmental-security). This mechanism can be used by TeamsConnector to load channels from an encrypted file. This also allows easy separation of production and development channel URLs.
|
57
|
+
All channels are defined under the top-level entry `teams_connector` and will be identified by their key.
|
58
|
+
```yaml
|
59
|
+
# $ bin/rails credentials:edit
|
60
|
+
teams_connector:
|
61
|
+
default: "<INSERT DEFAULT URL HERE>"
|
62
|
+
sales: "<INSERT URL FOR THE :sales CHANNEL HERE>"
|
63
|
+
```
|
64
|
+
|
65
|
+
After configuration of the credentials you can load the channels in your initializer.
|
66
|
+
Since `#load_from_rails_configuration` is a wrapper around `#channel` both methods can be used together.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
# TeamsConnector initializer
|
70
|
+
TeamsConnector.configure do |config|
|
71
|
+
config.load_from_rails_credentials
|
72
|
+
# After loading the :default channel is available and can be set as the default
|
73
|
+
config.default = :default
|
74
|
+
config.channel :another_channel, "<URL>"
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
### Templates
|
79
|
+
This gem provides some basic templates in its default template path. You can also define your own templates in your own path.
|
80
|
+
The default templates will be still available so you can mix and match.
|
81
|
+
|
82
|
+
#### Default templates
|
52
83
|
|
53
84
|
Template name | Description
|
54
85
|
-----|-------
|
@@ -56,13 +87,13 @@ Template name | Description
|
|
56
87
|
:facts_card | A card with title, subtitle and a list of facts
|
57
88
|
:test_card | A simple text message without any configurable content for testing
|
58
89
|
|
59
|
-
|
90
|
+
#### Custom Templates
|
60
91
|
|
61
92
|
Custom templates are stored in the directory specified by the configuration option `template_dir`. As an array of strings, describing the path relative to the project root. When using Rails or Bundler their root is used, otherwise it is the current working directory.
|
62
93
|
|
63
94
|
Templates are json files with the extension `.json.erb`. The file is parsed and populated by the ruby ERB module.
|
64
95
|
|
65
|
-
|
96
|
+
#### Builder
|
66
97
|
|
67
98
|
You can use TeamsConnector::Builder to create Adaptive Cards directly in ruby. YOu can output the result of the builder as JSON for future use with `TeamsController::Notification::AdaptiveCard#pretty_print`.
|
68
99
|
|
@@ -78,6 +109,66 @@ end
|
|
78
109
|
TeamsConnector::Notification::AdaptiveCard.new(content: builder).deliver_later
|
79
110
|
```
|
80
111
|
|
112
|
+
## Testing
|
113
|
+
|
114
|
+
To test TeamsConnector integration in your application you can use the `:testing` method.
|
115
|
+
Instead of performing real HTTP requests, an array in `TeamsConnector.testing.requests` is filled with your notifications in chronological order.
|
116
|
+
|
117
|
+
The request elements have the following structure:
|
118
|
+
```ruby
|
119
|
+
{
|
120
|
+
channel: :default,
|
121
|
+
template: :facts_card,
|
122
|
+
content: '{"rendered content": "in JSON format"}',
|
123
|
+
time: Time.now
|
124
|
+
}
|
125
|
+
```
|
126
|
+
|
127
|
+
### RSpec Matcher
|
128
|
+
TeamsConnector provides the `have_sent_notification_to(channel = nil, template = nil)` matcher for RSpec.
|
129
|
+
It is available by adding `require "teams_connector/rspec"` to your `spec_helper.rb`.
|
130
|
+
The matcher supports filtering notifications by channel and template. If one is not given, it does not filter the notifications by it.
|
131
|
+
There exists the alias `send_notification_to` for `have_sent_notification_to`.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
it "has sent exactly one notification to the channel" do
|
135
|
+
expect { notification.deliver_later }.to have_sent_notification_to(:channel)
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
#### Expecting number of notifications
|
140
|
+
By default `have_sent_notification_to` expects exactly one matching notification.
|
141
|
+
You can change the expected amount by chaining `exactly`, `at_least` or `at_most`.
|
142
|
+
|
143
|
+
Example:
|
144
|
+
```ruby
|
145
|
+
it "has sent less than 10 notifications to the channel" do
|
146
|
+
expect { notification.deliver_later }.to have_sent_notification_to(:channel).at_most(10)
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
You can also use `once`, `twice` and `thrice` as an alias for `exactly(1..3)`.
|
151
|
+
For more readable expectations `times` can be chained.
|
152
|
+
|
153
|
+
#### Expecting templates
|
154
|
+
The template argument in the matcher does filter the notifications.
|
155
|
+
If you expect a template instead, you can chain with `with_template(:template)`.
|
156
|
+
|
157
|
+
#### Expecting content
|
158
|
+
To expect specific content, you can chain with `with(data = nil, &block)`.
|
159
|
+
Data supports other RSpec matchers like `hash_including`.
|
160
|
+
The block is called for every notification with the notification content hash and the raw notification itself.
|
161
|
+
|
162
|
+
Example:
|
163
|
+
```ruby
|
164
|
+
expect {
|
165
|
+
notification(:default, :test_card).deliver_later
|
166
|
+
}.to have_sent_notification_to(:default).with { |content, notification|
|
167
|
+
expect(notification[:channel]).to eq :default
|
168
|
+
expect(notification[:template]).to eq :test_card
|
169
|
+
expect(content["sections"]).to include(hash_including("activityTitle", "activitySubtitle", "facts", "markdown" => true))
|
170
|
+
}
|
171
|
+
```
|
81
172
|
## Development
|
82
173
|
|
83
174
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -19,7 +19,7 @@ module TeamsConnector
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def method=(method)
|
22
|
-
raise ArgumentError, "Method '#{method.to_s}' is not supported" unless [:direct, :sidekiq].include? method
|
22
|
+
raise ArgumentError, "Method '#{method.to_s}' is not supported" unless [:direct, :sidekiq, :testing].include? method
|
23
23
|
raise ArgumentError, "Sidekiq is not available" if method == :sidekiq && !defined? Sidekiq
|
24
24
|
@method = method
|
25
25
|
end
|
@@ -27,5 +27,16 @@ module TeamsConnector
|
|
27
27
|
def channel(name, url)
|
28
28
|
@channels[name] = url;
|
29
29
|
end
|
30
|
+
|
31
|
+
def load_from_rails_credentials
|
32
|
+
unless defined? Rails
|
33
|
+
raise RuntimeError, "This method is only available in Ruby on Rails."
|
34
|
+
end
|
35
|
+
|
36
|
+
webhook_urls = Rails.application.credentials.teams_connector!
|
37
|
+
webhook_urls.each do |entry|
|
38
|
+
channel(entry[0], entry[1])
|
39
|
+
end
|
40
|
+
end
|
30
41
|
end
|
31
42
|
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module TeamsConnector
|
2
|
+
module Matchers
|
3
|
+
class HaveSentNotificationTo
|
4
|
+
include RSpec::Matchers::Composable
|
5
|
+
|
6
|
+
def initialize(channel, template)
|
7
|
+
@filter = {
|
8
|
+
channel: channel,
|
9
|
+
template: template
|
10
|
+
}
|
11
|
+
@block = proc {}
|
12
|
+
@data = nil
|
13
|
+
@template_data = nil
|
14
|
+
set_expected_number(:exactly, 1)
|
15
|
+
end
|
16
|
+
|
17
|
+
def with(data = nil, &block)
|
18
|
+
@data = data
|
19
|
+
@block = block if block
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_template(template = nil)
|
24
|
+
@template_data = template
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def exactly(count)
|
29
|
+
set_expected_number(:exactly, count)
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def at_least(count)
|
34
|
+
set_expected_number(:at_least, count)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def at_most(count)
|
39
|
+
set_expected_number(:at_most, count)
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def times
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def once
|
48
|
+
exactly(:once)
|
49
|
+
end
|
50
|
+
|
51
|
+
def twice
|
52
|
+
exactly(:twice)
|
53
|
+
end
|
54
|
+
|
55
|
+
def thrice
|
56
|
+
exactly(:thrice)
|
57
|
+
end
|
58
|
+
|
59
|
+
def failure_message
|
60
|
+
"expected to send #{base_message}".tap do |msg|
|
61
|
+
if @unmatching_ntfcts.any?
|
62
|
+
msg << "\nSent notifications"
|
63
|
+
msg << " to #{@filter[:channel]}" if @filter[:channel]
|
64
|
+
msg << " of #{@filter[:template]}" if @filter[:template]
|
65
|
+
msg << ":"
|
66
|
+
@unmatching_ntfcts.each do |data|
|
67
|
+
msg << "\n #{data}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def failure_message_when_negated
|
74
|
+
"expected not to send #{base_message}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def matches?(expectation)
|
78
|
+
if Proc === expectation
|
79
|
+
original_count = TeamsConnector.testing.requests.size
|
80
|
+
expectation.call
|
81
|
+
in_block_notifications = TeamsConnector.testing.requests.drop(original_count)
|
82
|
+
else
|
83
|
+
in_block_notifications = expectation
|
84
|
+
end
|
85
|
+
|
86
|
+
in_block_notifications = in_block_notifications.select { |msg|
|
87
|
+
@filter.map { |k, v| msg[k] === v unless v.nil? }.compact.all?
|
88
|
+
}
|
89
|
+
|
90
|
+
check(in_block_notifications)
|
91
|
+
end
|
92
|
+
|
93
|
+
def supports_block_expectations?
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def check(notifications)
|
100
|
+
@matching_ntfcts, @unmatching_ntfcts = notifications.partition do |ntfct|
|
101
|
+
result = true
|
102
|
+
|
103
|
+
result &= ntfct[:template] == @template_data unless @template_data.nil?
|
104
|
+
|
105
|
+
decoded = JSON.parse(ntfct[:content])
|
106
|
+
if @data.nil? || @data === decoded
|
107
|
+
@block.call(decoded, ntfct)
|
108
|
+
result &= true
|
109
|
+
else
|
110
|
+
result = false
|
111
|
+
end
|
112
|
+
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
@matching_count = @matching_ntfcts.size
|
117
|
+
|
118
|
+
case @expectation_type
|
119
|
+
when :exactly then @expected_number == @matching_count
|
120
|
+
when :at_most then @expected_number >= @matching_count
|
121
|
+
when :at_least then @expected_number <= @matching_count
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def set_expected_number(relativity, count)
|
126
|
+
@expectation_type = relativity
|
127
|
+
@expected_number =
|
128
|
+
case count
|
129
|
+
when :once then 1
|
130
|
+
when :twice then 2
|
131
|
+
when :thrice then 3
|
132
|
+
else Integer(count)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def base_message
|
137
|
+
"#{message_expectation_modifier} #{@expected_number} notifications".tap do |msg|
|
138
|
+
msg << " to #{@filter[:channel]}" if @filter[:channel]
|
139
|
+
msg << " of #{@filter[:template]}" if @filter[:template]
|
140
|
+
msg << " with template #{@template_data}" if @template_data
|
141
|
+
msg << " with content #{data_description(@data)}" if @data
|
142
|
+
msg << ", but sent #{@matching_count}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def message_expectation_modifier
|
147
|
+
case @expectation_type
|
148
|
+
when :exactly then "exactly"
|
149
|
+
when :at_most then "at most"
|
150
|
+
when :at_least then "at least"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def data_description(data)
|
155
|
+
if RSpec::Support.is_a_matcher?(data) && data.respond_to?(:description)
|
156
|
+
data.description
|
157
|
+
else
|
158
|
+
data
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'teams_connector/matchers/have_sent_notification_to'
|
2
|
+
|
3
|
+
module TeamsConnector
|
4
|
+
module Matchers
|
5
|
+
def have_sent_notification_to(channel = nil, template = nil)
|
6
|
+
HaveSentNotificationTo.new(channel, template)
|
7
|
+
end
|
8
|
+
|
9
|
+
alias_method :send_notification_to, :have_sent_notification_to
|
10
|
+
end
|
11
|
+
end
|
@@ -3,7 +3,7 @@ module TeamsConnector
|
|
3
3
|
attr_accessor :content
|
4
4
|
|
5
5
|
def initialize(template: :adaptive_card, content: {}, channel: TeamsConnector.configuration.default)
|
6
|
-
super(template: template,
|
6
|
+
super(template: template, channels: channel)
|
7
7
|
if content.instance_of? TeamsConnector::Builder
|
8
8
|
@content = {
|
9
9
|
card: [content.result]
|
@@ -3,7 +3,7 @@ module TeamsConnector
|
|
3
3
|
attr_accessor :summary, :content
|
4
4
|
|
5
5
|
def initialize(template, summary, content = {}, channel = TeamsConnector.configuration.default)
|
6
|
-
super(template: template,
|
6
|
+
super(template: template, channels: channel)
|
7
7
|
@summary = summary
|
8
8
|
@content = content
|
9
9
|
end
|
@@ -5,11 +5,11 @@ require 'teams_connector/post_worker' if defined? Sidekiq
|
|
5
5
|
|
6
6
|
module TeamsConnector
|
7
7
|
class Notification
|
8
|
-
attr_accessor :template, :
|
8
|
+
attr_accessor :template, :channels
|
9
9
|
|
10
|
-
def initialize(template: nil,
|
10
|
+
def initialize(template: nil, channels: TeamsConnector.configuration.default)
|
11
11
|
@template = template
|
12
|
-
@
|
12
|
+
@channels = channels.instance_of?(Array) ? channels : [channels]
|
13
13
|
end
|
14
14
|
|
15
15
|
def deliver_later
|
@@ -18,17 +18,21 @@ module TeamsConnector
|
|
18
18
|
renderer = ERB.new(File.read(template_path))
|
19
19
|
renderer.location = [template_path.to_s, 0]
|
20
20
|
|
21
|
-
url = TeamsConnector.configuration.channels[@channel]
|
22
|
-
url = TeamsConnector.configuration.channels[TeamsConnector.configuration.default] if TeamsConnector.configuration.always_use_default
|
23
|
-
raise ArgumentError, "The Teams channel '#{@channel}' is not available in the configuration." if url.nil?
|
24
|
-
|
25
21
|
content = renderer.result(binding)
|
26
22
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
channels = TeamsConnector.configuration.always_use_default ? [TeamsConnector.configuration.default] : @channels
|
24
|
+
channels.each do |channel|
|
25
|
+
url = TeamsConnector.configuration.channels[channel]
|
26
|
+
raise ArgumentError, "The Teams channel '#{channel}' is not available in the configuration." if url.nil?
|
27
|
+
|
28
|
+
if TeamsConnector.configuration.method == :sidekiq
|
29
|
+
TeamsConnector::PostWorker.perform_async(url, content)
|
30
|
+
elsif TeamsConnector.configuration.method == :testing
|
31
|
+
TeamsConnector.testing.perform_request channel, @template, content
|
32
|
+
else
|
33
|
+
response = Net::HTTP.post(URI(url), content, { "Content-Type" => "application/json" })
|
34
|
+
response.value
|
35
|
+
end
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module TeamsConnector
|
2
|
+
class Testing
|
3
|
+
attr_reader :requests
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@requests = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def perform_request(channel, template, content)
|
10
|
+
@requests.push({channel: channel, content: content, template: template, time: Time.now})
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/teams_connector.rb
CHANGED
@@ -7,7 +7,7 @@ require 'teams_connector/builder'
|
|
7
7
|
|
8
8
|
module TeamsConnector
|
9
9
|
class << self
|
10
|
-
attr_accessor :configuration
|
10
|
+
attr_accessor :configuration, :testing
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.configuration
|
@@ -22,6 +22,16 @@ module TeamsConnector
|
|
22
22
|
yield configuration
|
23
23
|
end
|
24
24
|
|
25
|
+
def self.testing
|
26
|
+
require 'teams_connector/testing'
|
27
|
+
@testing ||= Testing.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.reset_testing
|
31
|
+
require 'teams_connector/testing'
|
32
|
+
@testing = Testing.new
|
33
|
+
end
|
34
|
+
|
25
35
|
def self.project_root
|
26
36
|
if defined?(Rails)
|
27
37
|
return Rails.root
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: teams_connector
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lucas Keune
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -117,10 +117,14 @@ files:
|
|
117
117
|
- lib/teams_connector.rb
|
118
118
|
- lib/teams_connector/builder.rb
|
119
119
|
- lib/teams_connector/configuration.rb
|
120
|
+
- lib/teams_connector/matchers.rb
|
121
|
+
- lib/teams_connector/matchers/have_sent_notification_to.rb
|
120
122
|
- lib/teams_connector/notification.rb
|
121
123
|
- lib/teams_connector/notification/adaptive_card.rb
|
122
124
|
- lib/teams_connector/notification/message.rb
|
123
125
|
- lib/teams_connector/post_worker.rb
|
126
|
+
- lib/teams_connector/rspec.rb
|
127
|
+
- lib/teams_connector/testing.rb
|
124
128
|
- lib/teams_connector/version.rb
|
125
129
|
- teams_connector.gemspec
|
126
130
|
- templates/teams_connector/adaptive_card.json.erb
|