New upstream snapshot.
Debian Janitor
1 year, 5 months ago
0 | ; EditorConfig is awesome: http://EditorConfig.org | |
1 | ||
2 | root = true ; top-most EditorConfig file | |
3 | ||
4 | ; Unix-style newlines with a newline ending every file | |
5 | [*] | |
6 | end_of_line = lf | |
7 | indent_style = space | |
8 | indent_size = 2 | |
9 | insert_final_newline = true |
0 | SLACK_WEBHOOK_URL: "https://example.com"⏎ |
0 | AllCops: | |
1 | DisplayCopNames: true | |
2 | DisplayStyleGuide: true | |
3 | ||
4 | Include: | |
5 | - 'Rakefile' | |
6 | - 'bin/test' | |
7 | ||
8 | # Naming ------------------------------------ | |
9 | ||
10 | Naming/FileName: | |
11 | Exclude: | |
12 | - lib/slack-notifier.rb | |
13 | - spec/lib/slack-notifier_spec.rb | |
14 | - slack-notifier.gemspec | |
15 | - Gemfile | |
16 | ||
17 | # Style ------------------------------------ | |
18 | ||
19 | Style/StringLiterals: | |
20 | EnforcedStyle: double_quotes | |
21 | ||
22 | Style/MethodDefParentheses: | |
23 | EnforcedStyle: require_no_parentheses_except_multiline | |
24 | ||
25 | Style/Documentation: | |
26 | Enabled: false | |
27 | ||
28 | Style/RegexpLiteral: | |
29 | Enabled: false | |
30 | ||
31 | Style/SignalException: | |
32 | Enabled: false | |
33 | ||
34 | Style/PercentLiteralDelimiters: | |
35 | PreferredDelimiters: | |
36 | '%w': '[]' | |
37 | ||
38 | # Layout ---------------------------------- | |
39 | ||
40 | Layout/MultilineOperationIndentation: | |
41 | EnforcedStyle: aligned | |
42 | ||
43 | Layout/MultilineMethodCallIndentation: | |
44 | EnforcedStyle: aligned | |
45 | ||
46 | Layout/MultilineOperationIndentation: | |
47 | Enabled: false | |
48 | ||
49 | Layout/SpaceAroundEqualsInParameterDefault: | |
50 | EnforcedStyle: no_space | |
51 | ||
52 | Layout/IndentationConsistency: | |
53 | EnforcedStyle: rails | |
54 | ||
55 | Layout/DotPosition: | |
56 | EnforcedStyle: leading | |
57 | ||
58 | # Metrics ---------------------------------- | |
59 | ||
60 | Metrics/LineLength: | |
61 | Max: 128 | |
62 | ||
63 | Metrics/AbcSize: | |
64 | Enabled: false | |
65 | ||
66 | Metrics/MethodLength: | |
67 | Enabled: false | |
68 | ||
69 | Metrics/BlockLength: | |
70 | Exclude: | |
71 | - spec/**/* | |
72 | ||
73 | # Lint ------------------------------------- | |
74 | ||
75 | Lint/EndAlignment: | |
76 | EnforcedStyleAlignWith: variable |
0 | language: ruby | |
1 | sudo: false | |
2 | cache: bundler | |
3 | bundler_args: "--without development" | |
4 | ||
5 | before_install: | |
6 | - "gem update --system" | |
7 | - "gem install bundler" | |
8 | ||
9 | rvm: | |
10 | - 3.0 | |
11 | - 2.7 | |
12 | - 2.6 | |
13 | - ruby-head | |
14 | - jruby-9.1.9.0 | |
15 | - jruby-head | |
16 | ||
17 | matrix: | |
18 | allow_failures: | |
19 | - rvm: ruby-head | |
20 | - rvm: jruby-head | |
21 | ||
22 | notifications: | |
23 | slack: | |
24 | secure: Ld0tGBmwLG/ADOlLjO6ILq98+u/iq5qkuxAwN1E0SBOooALZZEJSc74jEMpgpnb22tk8QimUmLiCTE+8tWKaGiXTrvK6uvvfP6iiL9850NezHCxA3YMuWPnQQtJpTJ4135MMO8gJXu9vcswb9vW9N3v/A7VJdHbVZyT0vIMGas0= |
0 | # frozen_string_literal: true | |
1 | ||
2 | source "https://rubygems.org" | |
3 | ||
4 | gemspec | |
5 | ||
6 | group :development do | |
7 | if RUBY_VERSION >= "2.0.0" | |
8 | gem "pry-byebug" | |
9 | else | |
10 | gem "pry-debugger" | |
11 | end | |
12 | ||
13 | gem "benchmark-ips" | |
14 | end | |
15 | ||
16 | group :test do | |
17 | gem "rake", "~> 12.0" | |
18 | gem "rspec", "~> 3.5.0" | |
19 | gem "rubocop", "~> 0.51", require: false if RUBY_VERSION >= "2.1" | |
20 | ||
21 | gem "string-scrub" if RUBY_VERSION <= "1.9.3" | |
22 | end |
0 | The MIT License (MIT) | |
1 | ||
2 | Copyright (c) 2014 Steven Sloan | |
3 | ||
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | |
5 | this software and associated documentation files (the "Software"), to deal in | |
6 | the Software without restriction, including without limitation the rights to | |
7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | |
8 | the Software, and to permit persons to whom the Software is furnished to do so, | |
9 | subject to the following conditions: | |
10 | ||
11 | The above copyright notice and this permission notice shall be included in all | |
12 | copies or substantial portions of the Software. | |
13 | ||
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | |
16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | |
17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
0 | # frozen_string_literal: true | |
1 | ||
2 | require "rspec/core/rake_task" | |
3 | RSpec::Core::RakeTask.new(:spec) | |
4 | ||
5 | begin | |
6 | require "rubocop/rake_task" | |
7 | rubocop = RuboCop::RakeTask.new | |
8 | rubocop.fail_on_error = false | |
9 | rescue LoadError | |
10 | task :rubocop do | |
11 | puts "Rubocop not loaded" | |
12 | end | |
13 | end | |
14 | ||
15 | task default: %i[spec rubocop] |
0 | #!/usr/bin/env ruby | |
1 | ||
2 | require 'yaml' | |
3 | YAML.load_file('.env').each do |key, var| | |
4 | ENV[key] = var | |
5 | end | |
6 | ||
7 | rubies = ['2.0.0', '2.1', '2.2', '2.3.3', '2.4.0'] | |
8 | rubies.each do |ruby| | |
9 | # cleanup gemfile.locks | |
10 | Dir['spec*/**/*.lock'].each do |lockfile| | |
11 | puts "removing #{lockfile}" | |
12 | system "rm #{lockfile}" | |
13 | end | |
14 | ||
15 | pid = Process.fork do | |
16 | exec "rvm #{ruby} do ruby spec/integration/ping_integration_test.rb" | |
17 | end | |
18 | ||
19 | trap 'INT' do | |
20 | puts 'exiting' | |
21 | pid.send(:exit) | |
22 | end | |
23 | ||
24 | Process.wait(pid) | |
25 | end |
0 | # 2.4.0 | |
1 | ||
2 | - Make keyword argument usage compatible for ruby 3.x [@walski #123, @yuuu #119] | |
3 | ||
4 | # 2.3.2 | |
5 | - Improve compatability with CommonMark spec for markdown link formatting [@revolter #91] | |
6 | ||
7 | Still not 100% compliant, but it is now much closer. | |
8 | ||
9 | # 2.3.1 | |
10 | - use `map` to return the array of responses instead of payload in `ping` & `post` [@yhatt #88] | |
11 | ||
12 | # 2.3.0 | |
13 | - feat: add `channels` middleware to split payloads to ping multiple channels [#40] | |
14 | - feat: support any middleware splittin payload into an array to allow multiple payloads from a single process. | |
15 | ||
16 | # 2.2.2 | |
17 | - fix wrapping of attachments passed as a hash | |
18 | - fix error in `LinkFormatter` if a text payload was nil [#81] | |
19 | ||
20 | # 2.2.1 | |
21 | - fix loading error caused by uninitialized constant [@pocke #78] | |
22 | ||
23 | # 2.2.0 | |
24 | - raise exception when API responds with an error [@siegy22] | |
25 | ||
26 | # 2.1.0 | |
27 | - addition of :at middleware to simplify notifying users & rooms [@kazuooooo #66] | |
28 | ||
29 | # 2.0.0 | |
30 | ||
31 | [BREAKING] This is a fairly large change to how defaults are set and how messages are processed. | |
32 | ||
33 | **Setting Defaults** | |
34 | ||
35 | Setter methods are no longer available for setting defaults on a notifier instance, you'll now set defaults with a block on initialization. | |
36 | ||
37 | ```ruby | |
38 | # previously in 1.x | |
39 | notifier = Slack::Notifier.new WEBHOOK_URL, http_client: CustomClient | |
40 | notifier.channel = "sup" | |
41 | ||
42 | # in 2.x | |
43 | notifier = Slack::Notifier.new WEBHOOK_URL do | |
44 | http_client CustomClient | |
45 | defaults channel: "sup" | |
46 | end | |
47 | ``` | |
48 | ||
49 | Read more about [setting defaults in the readme](readme.md#setting-defaults) | |
50 | ||
51 | **Message Processing** | |
52 | ||
53 | Message are now processed through a configurable middleware stack. By default it acts exactly the same as the 1.x versions. [More information is available in the readme](readme.md#middleware) | |
54 | ||
55 | # 1.5.1 | |
56 | - allow using a single attachment w/o putting it in an array [@Elektron1c97 #47] | |
57 | ||
58 | # 1.5.0 | |
59 | - allow sending with attachments only [#48] | |
60 | ||
61 | # 1.4.0 | |
62 | - Format attachment messages with the LinkFormatter [@bhuga #37] | |
63 | - Add support for mailto links in markdown formatted links [@keithpitty #43] | |
64 | ||
65 | # 1.3.1 | |
66 | - Fix bug with link formatter for markdown links wrapped in square braces [@bhuga #36] | |
67 | ||
68 | # 1.3.0 | |
69 | - Add `#escape` to allow clients to escape special characters [@monkbroc #35] | |
70 | ||
71 | # 1.2.1 | |
72 | - use `#scrub` to (more selectively) strip invalid characters from strings before attempting to format. This allows valid japanese (and more) characters to be used. Thanks to @fukayatsu for reporting. | |
73 | ||
74 | This checks for the presence of the `scrub` method on string, so if on ruby < 2.1 you'll need to include & require the `string-scrub` gem to handle invalid characters. | |
75 | ||
76 | # 1.2.0 | |
77 | - Strip invalid UTF-8 characters from message before attempting to format links. They are replaced with the unicode replacement character '[�](http://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character)'. [@ushu #26] | |
78 | ||
79 | # 1.1.0 | |
80 | - add ability to pass `:http_options` to the initializer or `#ping`. this allows you to set options like `read_timeout` or `open_timeout`. See [issue #17](https://github.com/stevenosloan/slack-notifier/issues/17) for more information. | |
81 | ||
82 | # 1.0.0 | |
83 | - [BREAKING!] To follow changes with slack, client is now initialized with a webhook url instead of team & token. For help upgrading read the [upgrade from 0.6.1 guide](docs/upgrade-from-0.6.1.md) | |
84 | ||
85 | # 0.6.1 | |
86 | - fix bug in link_formatter to allow multiple links in a message | |
87 | ||
88 | # 0.6.0 | |
89 | - add ability to pass in your own http client | |
90 | - [BREAKING!] hook name moves to options array | |
91 | ||
92 | # 0.5.0 | |
93 | - allow defaults to be set on initialization | |
94 | - remove channel formatting [#8] | |
95 | ||
96 | # 0.4.1 | |
97 | - allow default channel's to start with a "@" or "#" [#7] | |
98 | ||
99 | # 0.4.0 | |
100 | - try and correct for a channel name being set without a leading "#" [@dlackty] | |
101 | ||
102 | # 0.3.2 | |
103 | - add Net::HTTP wrapper to include support for ruby 1.9.3 | |
104 | ||
105 | # 0.3.1 | |
106 | - remove requirement for channel, no longer required by slack [@dlackty] | |
107 | ||
108 | # 0.3.0 | |
109 | - add custom hook endpoint parameter [@razielgn] | |
110 | ||
111 | # 0.2.0 | |
112 | - remove HTTParty dependency | |
113 | ||
114 | # 0.1.1 | |
115 | - loosen httparty dependency | |
116 | - refactor codebase & add specs | |
117 | ||
118 | # 0.1.0 | |
119 | - now formats html or markdown links in your message to match slack's format | |
120 | ||
121 | # 0.0.2 | |
122 | - fix a fat finger if a default channel is set | |
123 | ||
124 | # 0.0.1 | |
125 | - initial release |
0 | ruby-slack-notifier (1.5.1-3) UNRELEASED; urgency=medium | |
0 | ruby-slack-notifier (2.4.0+git20210507.1.e9aace7-1) UNRELEASED; urgency=medium | |
1 | 1 | |
2 | 2 | [ Utkarsh Gupta ] |
3 | 3 | * Add salsa-ci.yml |
15 | 15 | * Remove constraints unnecessary since buster: |
16 | 16 | + Build-Depends: Drop versioned constraint on ruby-rspec. |
17 | 17 | * Bump debhelper from old 12 to 13. |
18 | * New upstream snapshot. | |
18 | 19 | |
19 | -- Utkarsh Gupta <guptautkarsh2102@gmail.com> Tue, 13 Aug 2019 07:39:11 +0530 | |
20 | -- Utkarsh Gupta <guptautkarsh2102@gmail.com> Sun, 20 Nov 2022 06:15:51 -0000 | |
20 | 21 | |
21 | 22 | ruby-slack-notifier (1.5.1-2) unstable; urgency=medium |
22 | 23 |
0 | Recently slack changed the way incoming webhooks are handled. Instead of taking a team name and token, they now provide a unique (obfuscated) webhook url. | |
1 | ||
2 | To upgrade the slack-notifier gem, you'll need to find your webhook url. In slack: | |
3 | - go to you're configured integrations (https://team-name.slack.com/services) | |
4 | - select **Incoming Webhooks** | |
5 | - select the webhook that uses the slack-notifier gem | |
6 | - find the webhook url under the heading **Integration Settings** | |
7 | ||
8 | You'll then change the way you initialize your notifier | |
9 | ||
10 | From: | |
11 | ```ruby | |
12 | notifier = Slack::Notifier.new 'team', 'token' | |
13 | ``` | |
14 | ||
15 | To: | |
16 | ```ruby | |
17 | notifier = Slack::Notifier.new 'WEBHOOK_URL' | |
18 | ``` | |
19 | ||
20 | Defaults & attachemnts will continue to work like they have | |
21 | ||
22 | ```ruby | |
23 | notifier = Slack::Notifier.new 'WEBHOOK_URL', icon_emoji: ":ghost:" | |
24 | notifier.ping "I'm feeling spooky" | |
25 | ``` |
0 | # frozen_string_literal: true | |
1 | ||
2 | module Slack | |
3 | class Notifier | |
4 | class Config | |
5 | def initialize | |
6 | @http_client = Util::HTTPClient | |
7 | @defaults = {} | |
8 | @middleware = %i[ | |
9 | format_message | |
10 | format_attachments | |
11 | at | |
12 | channels | |
13 | ] | |
14 | end | |
15 | ||
16 | def http_client client=nil | |
17 | return @http_client if client.nil? | |
18 | raise ArgumentError, "the http client must respond to ::post" unless client.respond_to?(:post) | |
19 | ||
20 | @http_client = client | |
21 | end | |
22 | ||
23 | def defaults new_defaults=nil | |
24 | return @defaults if new_defaults.nil? | |
25 | raise ArgumentError, "the defaults must be a Hash" unless new_defaults.is_a?(Hash) | |
26 | ||
27 | @defaults = new_defaults | |
28 | end | |
29 | ||
30 | def middleware *args | |
31 | return @middleware if args.empty? | |
32 | ||
33 | @middleware = | |
34 | if args.length == 1 && args.first.is_a?(Array) || args.first.is_a?(Hash) | |
35 | args.first | |
36 | else | |
37 | args | |
38 | end | |
39 | end | |
40 | end | |
41 | end | |
42 | end |
0 | module Slack | |
1 | class Notifier | |
2 | ||
3 | class DefaultHTTPClient | |
4 | ||
5 | class << self | |
6 | def post uri, params | |
7 | DefaultHTTPClient.new( uri, params ).call | |
8 | end | |
9 | end | |
10 | ||
11 | attr_reader :uri, :params, :http_options | |
12 | ||
13 | def initialize uri, params | |
14 | @uri = uri | |
15 | @http_options = params.delete(:http_options) || {} | |
16 | @params = params | |
17 | end | |
18 | ||
19 | def call | |
20 | http_obj.request request_obj | |
21 | end | |
22 | ||
23 | private | |
24 | ||
25 | def request_obj | |
26 | req = Net::HTTP::Post.new uri.request_uri | |
27 | req.set_form_data params | |
28 | ||
29 | return req | |
30 | end | |
31 | ||
32 | def http_obj | |
33 | http = Net::HTTP.new uri.host, uri.port | |
34 | http.use_ssl = (uri.scheme == "https") | |
35 | ||
36 | http_options.each do |opt, val| | |
37 | if http.respond_to? "#{opt}=" | |
38 | http.send "#{opt}=", val | |
39 | else | |
40 | warn "Net::HTTP doesn't respond to `#{opt}=`, ignoring that option" | |
41 | end | |
42 | end | |
43 | ||
44 | return http | |
45 | end | |
46 | ||
47 | end | |
48 | ||
49 | end | |
50 | end |
0 | module Slack | |
1 | class Notifier | |
2 | class LinkFormatter | |
3 | ||
4 | class << self | |
5 | ||
6 | def format string | |
7 | LinkFormatter.new(string).formatted | |
8 | end | |
9 | ||
10 | end | |
11 | ||
12 | def initialize string | |
13 | @orig = if string.respond_to? :scrub | |
14 | string.scrub | |
15 | else | |
16 | string | |
17 | end | |
18 | end | |
19 | ||
20 | def formatted | |
21 | @orig.gsub( html_pattern ) do |match| | |
22 | link = Regexp.last_match[1] | |
23 | text = Regexp.last_match[2] | |
24 | slack_link link, text | |
25 | end.gsub( markdown_pattern ) do |match| | |
26 | link = Regexp.last_match[2] | |
27 | text = Regexp.last_match[1] | |
28 | slack_link link, text | |
29 | end | |
30 | ||
31 | rescue => e | |
32 | if RUBY_VERSION < '2.1' && e.message.include?('invalid byte sequence') | |
33 | raise e, "#{e.message}. Consider including the 'string-scrub' gem to strip invalid characters" | |
34 | else | |
35 | raise e | |
36 | end | |
37 | end | |
38 | ||
39 | private | |
40 | ||
41 | def slack_link link, text=nil | |
42 | out = "<#{link}" | |
43 | out << "|#{text}" if text && !text.empty? | |
44 | out << ">" | |
45 | ||
46 | return out | |
47 | end | |
48 | ||
49 | # http://rubular.com/r/19cNXW5qbH | |
50 | def html_pattern | |
51 | / <a (?:.*?) href=['"](.+?)['"] (?:.*?)> (.+?) <\/a> /x | |
52 | end | |
53 | ||
54 | # http://rubular.com/r/guJbTK6x1f | |
55 | def markdown_pattern | |
56 | /\[ ([^\[\]]*?) \] \( ((https?:\/\/.*?) | (mailto:.*?)) \) /x | |
57 | end | |
58 | ||
59 | end | |
60 | end | |
61 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | module Slack | |
3 | class Notifier | |
4 | class PayloadMiddleware | |
5 | class At < Base | |
6 | middleware_name :at | |
7 | ||
8 | options at: [] | |
9 | ||
10 | def call payload={} | |
11 | return payload unless payload[:at] | |
12 | ||
13 | payload[:text] = "#{format_ats(payload.delete(:at))}#{payload[:text]}" | |
14 | payload | |
15 | end | |
16 | ||
17 | private | |
18 | ||
19 | def format_ats ats | |
20 | Array(ats).map { |at| "<#{at_cmd_char(at)}#{at}> " } | |
21 | .join("") | |
22 | end | |
23 | ||
24 | def at_cmd_char at | |
25 | case at | |
26 | when :here, :channel, :everyone, :group | |
27 | "!" | |
28 | else | |
29 | "@" | |
30 | end | |
31 | end | |
32 | end | |
33 | end | |
34 | end | |
35 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | module Slack | |
3 | class Notifier | |
4 | class PayloadMiddleware | |
5 | class Base | |
6 | class << self | |
7 | def middleware_name name | |
8 | PayloadMiddleware.register self, name.to_sym | |
9 | end | |
10 | ||
11 | def options default_opts | |
12 | @default_opts = default_opts | |
13 | end | |
14 | ||
15 | def default_opts | |
16 | @default_opts ||= {} | |
17 | end | |
18 | end | |
19 | ||
20 | attr_reader :notifier, :options | |
21 | ||
22 | def initialize notifier, opts={} | |
23 | @notifier = notifier | |
24 | @options = self.class.default_opts.merge opts | |
25 | end | |
26 | ||
27 | def call _payload={} | |
28 | raise NoMethodError, "method `call` not defined for class #{self.class}" | |
29 | end | |
30 | end | |
31 | end | |
32 | end | |
33 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | module Slack | |
3 | class Notifier | |
4 | class PayloadMiddleware | |
5 | class Channels < Base | |
6 | middleware_name :channels | |
7 | ||
8 | def call payload={} | |
9 | return payload unless payload[:channel].respond_to?(:to_ary) | |
10 | ||
11 | payload[:channel].to_ary.map do |channel| | |
12 | pld = payload.dup | |
13 | pld[:channel] = channel | |
14 | pld | |
15 | end | |
16 | end | |
17 | end | |
18 | end | |
19 | end | |
20 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | module Slack | |
3 | class Notifier | |
4 | class PayloadMiddleware | |
5 | class FormatAttachments < Base | |
6 | middleware_name :format_attachments | |
7 | ||
8 | options formats: %i[html markdown] | |
9 | ||
10 | def call payload={} | |
11 | payload = payload.dup | |
12 | attachments = payload.delete(:attachments) | |
13 | attachments ||= payload.delete("attachments") | |
14 | ||
15 | attachments = wrap_array(attachments).map do |attachment| | |
16 | ["text", :text].each do |key| | |
17 | if attachment.key?(key) | |
18 | attachment[key] = Util::LinkFormatter.format(attachment[key], options) | |
19 | end | |
20 | end | |
21 | ||
22 | attachment | |
23 | end | |
24 | ||
25 | payload[:attachments] = attachments if attachments && !attachments.empty? | |
26 | payload | |
27 | end | |
28 | ||
29 | private | |
30 | ||
31 | def wrap_array object | |
32 | if object.nil? | |
33 | [] | |
34 | elsif object.respond_to?(:to_ary) | |
35 | object.to_ary || [object] | |
36 | else | |
37 | [object] | |
38 | end | |
39 | end | |
40 | end | |
41 | end | |
42 | end | |
43 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | module Slack | |
3 | class Notifier | |
4 | class PayloadMiddleware | |
5 | class FormatMessage < Base | |
6 | middleware_name :format_message | |
7 | ||
8 | options formats: %i[html markdown] | |
9 | ||
10 | def call payload={} | |
11 | return payload unless payload[:text] | |
12 | payload[:text] = Util::LinkFormatter.format(payload[:text], options) | |
13 | ||
14 | payload | |
15 | end | |
16 | end | |
17 | end | |
18 | end | |
19 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | module Slack | |
3 | class Notifier | |
4 | class PayloadMiddleware | |
5 | class Stack | |
6 | attr_reader :notifier, | |
7 | :stack | |
8 | ||
9 | def initialize notifier | |
10 | @notifier = notifier | |
11 | @stack = [] | |
12 | end | |
13 | ||
14 | def set *middlewares | |
15 | middlewares = | |
16 | if middlewares.length == 1 && middlewares.first.is_a?(Hash) | |
17 | middlewares.first | |
18 | else | |
19 | middlewares.flatten | |
20 | end | |
21 | ||
22 | @stack = middlewares.map do |key, opts| | |
23 | PayloadMiddleware.registry.fetch(key).new(*[notifier, opts].compact) | |
24 | end | |
25 | end | |
26 | ||
27 | def call payload={} | |
28 | result = stack.inject payload do |pld, middleware| | |
29 | as_array(pld).flat_map do |p| | |
30 | middleware.call(p) | |
31 | end | |
32 | end | |
33 | ||
34 | as_array(result) | |
35 | end | |
36 | ||
37 | private | |
38 | ||
39 | def as_array args | |
40 | if args.respond_to?(:to_ary) | |
41 | args.to_ary | |
42 | else | |
43 | [args] | |
44 | end | |
45 | end | |
46 | end | |
47 | end | |
48 | end | |
49 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | module Slack | |
3 | class Notifier | |
4 | class PayloadMiddleware | |
5 | class << self | |
6 | def registry | |
7 | @registry ||= {} | |
8 | end | |
9 | ||
10 | def register middleware, name | |
11 | registry[name] = middleware | |
12 | end | |
13 | end | |
14 | end | |
15 | end | |
16 | end | |
17 | ||
18 | require_relative "payload_middleware/stack" | |
19 | require_relative "payload_middleware/base" | |
20 | require_relative "payload_middleware/format_message" | |
21 | require_relative "payload_middleware/format_attachments" | |
22 | require_relative "payload_middleware/at" | |
23 | require_relative "payload_middleware/channels" |
0 | # frozen_string_literal: true | |
1 | ||
2 | module Slack | |
3 | class Notifier | |
4 | module Util | |
5 | module Escape | |
6 | HTML_REGEXP = /[&><]/ | |
7 | HTML_REPLACE = { "&" => "&", ">" => ">", "<" => "<" }.freeze | |
8 | ||
9 | def self.html string | |
10 | string.gsub(HTML_REGEXP, HTML_REPLACE) | |
11 | end | |
12 | end | |
13 | end | |
14 | end | |
15 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | require "net/http" | |
3 | ||
4 | module Slack | |
5 | class Notifier | |
6 | class APIError < StandardError | |
7 | attr_reader :response | |
8 | ||
9 | def initialize(response) | |
10 | @response = response | |
11 | end | |
12 | ||
13 | def message | |
14 | <<-MSG | |
15 | The slack API returned an error: #{@response.body} (HTTP Code #{@response.code}) | |
16 | Check the "Handling Errors" section on https://api.slack.com/incoming-webhooks for more information | |
17 | MSG | |
18 | end | |
19 | end | |
20 | ||
21 | module Util | |
22 | class HTTPClient | |
23 | class << self | |
24 | def post uri, params | |
25 | HTTPClient.new(uri, params).call | |
26 | end | |
27 | end | |
28 | ||
29 | attr_reader :uri, :params, :http_options | |
30 | ||
31 | def initialize uri, params | |
32 | @uri = uri | |
33 | @http_options = params.delete(:http_options) || {} | |
34 | @params = params | |
35 | end | |
36 | ||
37 | # rubocop:disable Layout/IndentHeredoc | |
38 | def call | |
39 | http_obj.request(request_obj).tap do |response| | |
40 | unless response.is_a?(Net::HTTPSuccess) | |
41 | raise Slack::Notifier::APIError.new(response) | |
42 | end | |
43 | end | |
44 | end | |
45 | # rubocop:enable Layout/IndentHeredoc | |
46 | ||
47 | private | |
48 | ||
49 | def request_obj | |
50 | req = Net::HTTP::Post.new uri.request_uri | |
51 | req.set_form_data params | |
52 | ||
53 | req | |
54 | end | |
55 | ||
56 | def http_obj | |
57 | http = Net::HTTP.new uri.host, uri.port | |
58 | http.use_ssl = (uri.scheme == "https") | |
59 | ||
60 | http_options.each do |opt, val| | |
61 | if http.respond_to? "#{opt}=" | |
62 | http.send "#{opt}=", val | |
63 | else | |
64 | warn "Net::HTTP doesn't respond to `#{opt}=`, ignoring that option" | |
65 | end | |
66 | end | |
67 | ||
68 | http | |
69 | end | |
70 | end | |
71 | end | |
72 | end | |
73 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | module Slack | |
3 | class Notifier | |
4 | module Util | |
5 | class LinkFormatter | |
6 | # http://rubular.com/r/19cNXW5qbH | |
7 | HTML_PATTERN = %r{ | |
8 | <a | |
9 | (?:.*?) | |
10 | href=['"](.+?)['"] | |
11 | (?:.*?)> | |
12 | (.+?) | |
13 | </a> | |
14 | }x | |
15 | ||
16 | # the path portion of a url can contain these characters | |
17 | VALID_PATH_CHARS = '\w\-\.\~\/\?\#\=' | |
18 | ||
19 | # Attempt at only matching pairs of parens per | |
20 | # the markdown spec http://spec.commonmark.org/0.27/#links | |
21 | # | |
22 | # http://rubular.com/r/y107aevxqT | |
23 | MARKDOWN_PATTERN = %r{ | |
24 | \[ ([^\[\]]*?) \] | |
25 | \( ((https?://.*?) | (mailto:.*?)) \) | |
26 | (?! [#{VALID_PATH_CHARS}]* \) ) | |
27 | }x | |
28 | ||
29 | class << self | |
30 | def format string, opts={} | |
31 | LinkFormatter.new(string, **opts).formatted | |
32 | end | |
33 | end | |
34 | ||
35 | attr_reader :formats | |
36 | ||
37 | def initialize string, formats: %i[html markdown] | |
38 | @formats = formats | |
39 | @orig = string.respond_to?(:scrub) ? string.scrub : string | |
40 | end | |
41 | ||
42 | # rubocop:disable Lint/RescueWithoutErrorClass | |
43 | def formatted | |
44 | return @orig unless @orig.respond_to?(:gsub) | |
45 | ||
46 | sub_markdown_links(sub_html_links(@orig)) | |
47 | rescue => e | |
48 | raise e unless RUBY_VERSION < "2.1" && e.message.include?("invalid byte sequence") | |
49 | raise e, "#{e.message}. Consider including the 'string-scrub' gem to strip invalid characters" | |
50 | end | |
51 | # rubocop:enable Lint/RescueWithoutErrorClass | |
52 | ||
53 | private | |
54 | ||
55 | def sub_html_links string | |
56 | return string unless formats.include?(:html) | |
57 | ||
58 | string.gsub(HTML_PATTERN) do | |
59 | slack_link Regexp.last_match[1], Regexp.last_match[2] | |
60 | end | |
61 | end | |
62 | ||
63 | def sub_markdown_links string | |
64 | return string unless formats.include?(:markdown) | |
65 | ||
66 | string.gsub(MARKDOWN_PATTERN) do | |
67 | slack_link Regexp.last_match[2], Regexp.last_match[1] | |
68 | end | |
69 | end | |
70 | ||
71 | def slack_link link, text=nil | |
72 | "<#{link}" \ | |
73 | "#{text && !text.empty? ? "|#{text}" : ''}" \ | |
74 | ">" | |
75 | end | |
76 | end | |
77 | end | |
78 | end | |
79 | end |
0 | # frozen_string_literal: true | |
1 | ||
0 | 2 | module Slack |
1 | 3 | class Notifier |
2 | VERSION = "1.5.1" | |
4 | VERSION = "2.4.0".freeze # rubocop:disable Style/RedundantFreeze | |
3 | 5 | end |
4 | 6 | end |
0 | require 'net/http' | |
1 | require 'uri' | |
2 | require 'json' | |
0 | # frozen_string_literal: true | |
3 | 1 | |
4 | require_relative 'slack-notifier/default_http_client' | |
5 | require_relative 'slack-notifier/link_formatter' | |
2 | require "uri" | |
3 | require "json" | |
4 | ||
5 | require_relative "slack-notifier/util/http_client" | |
6 | require_relative "slack-notifier/util/link_formatter" | |
7 | require_relative "slack-notifier/util/escape" | |
8 | require_relative "slack-notifier/payload_middleware" | |
9 | require_relative "slack-notifier/config" | |
6 | 10 | |
7 | 11 | module Slack |
8 | 12 | class Notifier |
9 | attr_reader :endpoint, :default_payload | |
13 | attr_reader :endpoint | |
10 | 14 | |
11 | def initialize webhook_url, options={} | |
12 | @endpoint = URI.parse webhook_url | |
13 | @default_payload = options | |
15 | def initialize webhook_url, options={}, &block | |
16 | @endpoint = URI.parse webhook_url | |
17 | ||
18 | config.http_client(options.delete(:http_client)) if options.key?(:http_client) | |
19 | config.defaults options | |
20 | config.instance_exec(&block) if block_given? | |
21 | ||
22 | middleware.set config.middleware | |
23 | end | |
24 | ||
25 | def config | |
26 | @_config ||= Config.new | |
14 | 27 | end |
15 | 28 | |
16 | 29 | def ping message, options={} |
17 | 30 | if message.is_a?(Hash) |
18 | message, options = nil, message | |
31 | options = message | |
32 | else | |
33 | options[:text] = message | |
19 | 34 | end |
20 | 35 | |
21 | if attachments = options[:attachments] || options["attachments"] | |
22 | wrap_array(attachments).each do |attachment| | |
23 | ["text", :text].each do |key| | |
24 | attachment[key] = LinkFormatter.format(attachment[key]) if attachment.has_key?(key) | |
25 | end | |
26 | end | |
27 | end | |
28 | ||
29 | payload = default_payload.merge(options) | |
30 | client = payload.delete(:http_client) || http_client | |
31 | http_options = payload.delete(:http_options) | |
32 | ||
33 | unless message.nil? | |
34 | payload.merge!(text: LinkFormatter.format(message)) | |
35 | end | |
36 | ||
37 | params = { payload: payload.to_json } | |
38 | params[:http_options] = http_options if http_options | |
39 | ||
40 | client.post endpoint, params | |
36 | post options | |
41 | 37 | end |
42 | 38 | |
43 | def http_client | |
44 | default_payload.fetch :http_client, DefaultHTTPClient | |
39 | def post payload={} | |
40 | params = {} | |
41 | client = payload.delete(:http_client) || config.http_client | |
42 | payload = config.defaults.merge(payload) | |
43 | ||
44 | params[:http_options] = payload.delete(:http_options) if payload.key?(:http_options) | |
45 | ||
46 | middleware.call(payload).map do |pld| | |
47 | params[:payload] = pld.to_json | |
48 | client.post endpoint, params | |
49 | end | |
45 | 50 | end |
46 | 51 | |
47 | def channel | |
48 | default_payload[:channel] | |
49 | end | |
52 | private | |
50 | 53 | |
51 | def channel= channel | |
52 | default_payload[:channel] = channel | |
53 | end | |
54 | ||
55 | def username | |
56 | default_payload[:username] | |
57 | end | |
58 | ||
59 | def username= username | |
60 | default_payload[:username] = username | |
61 | end | |
62 | ||
63 | HTML_ESCAPE_REGEXP = /[&><]/ | |
64 | HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<' } | |
65 | ||
66 | def escape(text) | |
67 | text.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE) | |
68 | end | |
69 | ||
70 | def wrap_array(object) | |
71 | if object.nil? | |
72 | [] | |
73 | elsif object.respond_to?(:to_ary) | |
74 | object.to_ary || [object] | |
75 | else | |
76 | [object] | |
54 | def middleware | |
55 | @middleware ||= PayloadMiddleware::Stack.new(self) | |
77 | 56 | end |
78 | end | |
79 | 57 | end |
80 | 58 | end |
0 | --- !ruby/object:Gem::Specification | |
1 | name: slack-notifier | |
2 | version: !ruby/object:Gem::Version | |
3 | version: 1.5.1 | |
4 | platform: ruby | |
5 | authors: | |
6 | - Steven Sloan | |
7 | autorequire: | |
8 | bindir: bin | |
9 | cert_chain: [] | |
10 | date: 2015-12-01 00:00:00.000000000 Z | |
11 | dependencies: [] | |
12 | description: " A slim ruby wrapper for posting to slack webhooks " | |
13 | email: | |
14 | - stevenosloan@gmail.com | |
15 | executables: [] | |
16 | extensions: [] | |
17 | extra_rdoc_files: [] | |
18 | files: | |
19 | - lib/slack-notifier.rb | |
20 | - lib/slack-notifier/default_http_client.rb | |
21 | - lib/slack-notifier/link_formatter.rb | |
22 | - lib/slack-notifier/version.rb | |
23 | - spec/integration/ping_integration_test.rb | |
24 | - spec/lib/slack-notifier/default_http_client_spec.rb | |
25 | - spec/lib/slack-notifier/link_formatter_spec.rb | |
26 | - spec/lib/slack-notifier_spec.rb | |
27 | - spec/spec_helper.rb | |
28 | homepage: http://github.com/stevenosloan/slack-notifier | |
29 | licenses: | |
30 | - MIT | |
31 | metadata: {} | |
32 | post_install_message: | |
33 | rdoc_options: [] | |
34 | require_paths: | |
35 | - lib | |
36 | required_ruby_version: !ruby/object:Gem::Requirement | |
37 | requirements: | |
38 | - - ">=" | |
39 | - !ruby/object:Gem::Version | |
40 | version: '0' | |
41 | required_rubygems_version: !ruby/object:Gem::Requirement | |
42 | requirements: | |
43 | - - ">=" | |
44 | - !ruby/object:Gem::Version | |
45 | version: '0' | |
46 | requirements: [] | |
47 | rubyforge_project: | |
48 | rubygems_version: 2.4.5.1 | |
49 | signing_key: | |
50 | specification_version: 4 | |
51 | summary: A slim ruby wrapper for posting to slack webhooks | |
52 | test_files: | |
53 | - spec/integration/ping_integration_test.rb | |
54 | - spec/lib/slack-notifier/default_http_client_spec.rb | |
55 | - spec/lib/slack-notifier/link_formatter_spec.rb | |
56 | - spec/lib/slack-notifier_spec.rb | |
57 | - spec/spec_helper.rb |
0 | A simple wrapper to send notifications to [Slack](https://slack.com/) webhooks. | |
1 | ||
2 | [![Build Status](https://travis-ci.org/slack-notifier/slack-notifier.svg?branch=master)](https://travis-ci.org/slack-notifier/slack-notifier) | |
3 | [![Code Climate](https://codeclimate.com/github/slack-notifier/slack-notifier.svg)](https://codeclimate.com/github/slack-notifier/slack-notifier) | |
4 | [![Gem Version](https://badge.fury.io/rb/slack-notifier.svg)](https://rubygems.org/gems/slack-notifier) | |
5 | [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=slack-notifier&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=slack-notifier&package-manager=bundler&version-scheme=semver) | |
6 | ||
7 | ## Example | |
8 | ||
9 | ```ruby | |
10 | require 'slack-notifier' | |
11 | ||
12 | notifier = Slack::Notifier.new "WEBHOOK_URL" | |
13 | notifier.ping "Hello World" | |
14 | # => if your webhook is setup, will message "Hello World" | |
15 | # => to the default channel you set in slack | |
16 | ``` | |
17 | ||
18 | ||
19 | #### Installation | |
20 | ||
21 | Install the latest stable release: | |
22 | ||
23 | ``` | |
24 | $ gem install slack-notifier | |
25 | ``` | |
26 | ||
27 | Or with [Bundler](http://bundler.io/), add it to your Gemfile: | |
28 | ||
29 | ```ruby | |
30 | gem "slack-notifier" | |
31 | ``` | |
32 | ||
33 | ||
34 | #### Setting Defaults | |
35 | ||
36 | On initialization you can set default payloads by calling `defaults` in an initialization block: | |
37 | ||
38 | ```ruby | |
39 | notifier = Slack::Notifier.new "WEBHOOK_URL" do | |
40 | defaults channel: "#default", | |
41 | username: "notifier" | |
42 | end | |
43 | ||
44 | notifier.ping "Hello default" | |
45 | # => will message "Hello default" | |
46 | # => to the "#default" channel as 'notifier' | |
47 | ``` | |
48 | ||
49 | To get the WEBHOOK_URL you need: | |
50 | ||
51 | 1. go to https://slack.com/apps/A0F7XDUAZ-incoming-webhooks | |
52 | 2. choose your team, press configure | |
53 | 3. in configurations press add configuration | |
54 | 4. choose channel, press "Add Incoming WebHooks integration" | |
55 | ||
56 | ||
57 | You can also set defaults through an options hash: | |
58 | ||
59 | ```ruby | |
60 | notifier = Slack::Notifier.new "WEBHOOK_URL", channel: "#default", | |
61 | username: "notifier" | |
62 | ``` | |
63 | ||
64 | These defaults are over-ridable for any individual ping. | |
65 | ||
66 | ```ruby | |
67 | notifier.ping "Hello random", channel: "#random" | |
68 | # => will ping the "#random" channel | |
69 | ``` | |
70 | ||
71 | ||
72 | ## Links | |
73 | ||
74 | Slack requires links to be formatted a certain way, so the default middlware stack of slack-notifier will look through your message and attempt to convert any html or markdown links to slack's format before posting. | |
75 | ||
76 | Here's what it's doing under the covers: | |
77 | ||
78 | ```ruby | |
79 | message = "Hello world, [check](http://example.com) it <a href='http://example.com'>out</a>" | |
80 | Slack::Notifier::Util::LinkFormatter.format(message) | |
81 | # => "Hello world, <http://example.com|check> it <http://example.com|out>" | |
82 | ``` | |
83 | ||
84 | ## Formatting | |
85 | ||
86 | Slack supports various different formatting options. For example, if you want to alert an entire channel you include `<!channel>` in your message | |
87 | ||
88 | ```ruby | |
89 | message = "<!channel> hey check this out" | |
90 | notifier.ping message | |
91 | ||
92 | #ends up posting "@channel hey check this out" in your Slack channel | |
93 | ``` | |
94 | ||
95 | You can see [Slack's message documentation here](https://api.slack.com/docs/formatting) | |
96 | ||
97 | ## Escaping | |
98 | ||
99 | Since sequences starting with < have special meaning in Slack, you should use `Slack::Notifier::Util::Escape.html` if your messages may contain &, < or >. | |
100 | ||
101 | ```ruby | |
102 | link_text = Slack::Notifier::Util::Escape.html("User <user@example.com>") | |
103 | message = "Write to [#{link_text}](mailto:user@example.com)" | |
104 | notifier.ping message | |
105 | ``` | |
106 | ||
107 | ## Blocks | |
108 | ||
109 | This plugin supports the [Slack blocks format](https://app.slack.com/block-kit-builder/) and [block kit builder](https://app.slack.com/block-kit-builder/). This is useful for displaying buttons, dropdowns, and images. | |
110 | ||
111 | ```ruby | |
112 | blocks = [ | |
113 | { | |
114 | "type": "image", | |
115 | "title": { | |
116 | "type": "plain_text", | |
117 | "text": "image1", | |
118 | "emoji": true | |
119 | }, | |
120 | "image_url": "https://api.slack.com/img/blocks/bkb_template_images/onboardingComplex.jpg", | |
121 | "alt_text": "image1" | |
122 | }, | |
123 | { | |
124 | "type": "section", | |
125 | "text": { | |
126 | "type": "mrkdwn", | |
127 | "text": "Hey there 👋 I'm TaskBot. I'm here to help you create and manage tasks in Slack.\nThere are two ways to quickly create tasks:" | |
128 | } | |
129 | } | |
130 | ] | |
131 | ||
132 | notifier.post(blocks: blocks) | |
133 | ``` | |
134 | ||
135 | ## Additional parameters | |
136 | ||
137 | Any key passed to the `post` method is posted to the webhook endpoint. Check out the [Slack webhook documentation](https://api.slack.com/incoming-webhooks) for the available parameters. | |
138 | ||
139 | Setting an icon: | |
140 | ||
141 | ```ruby | |
142 | notifier.post text: "feeling spooky", icon_emoji: ":ghost:" | |
143 | # or | |
144 | notifier.post text: "feeling chimpy", icon_url: "http://static.mailchimp.com/web/favicon.png" | |
145 | ``` | |
146 | ||
147 | Adding attachments: | |
148 | ||
149 | ```ruby | |
150 | a_ok_note = { | |
151 | fallback: "Everything looks peachy", | |
152 | text: "Everything looks peachy", | |
153 | color: "good" | |
154 | } | |
155 | notifier.post text: "with an attachment", attachments: [a_ok_note] | |
156 | ``` | |
157 | ||
158 | ||
159 | ## HTTP options | |
160 | ||
161 | With the default HTTP client, you can send along options to customize its behavior as `:http_options` params when you post or initialize the notifier. | |
162 | ||
163 | ```ruby | |
164 | notifier = Slack::Notifier.new 'WEBHOOK_URL', http_options: { open_timeout: 5 } | |
165 | notifier.post text: "hello", http_options: { open_timeout: 10 } | |
166 | ``` | |
167 | ||
168 | **Note**: you should only send along options that [`Net::HTTP`](http://ruby-doc.org/stdlib-2.2.0/libdoc/net/http/rdoc/Net/HTTP.html) has as setters, otherwise the option will be ignored and show a warning. | |
169 | ||
170 | ### Proxies | |
171 | ||
172 | `:http_options` can be used if you need to connect to Slack via an HTTP proxy. | |
173 | For example, to connect through a local squid proxy the following options would be used. | |
174 | ||
175 | ```ruby | |
176 | notifier = Slack::Notifier.new 'WEBHOOK_URL', http_options: { | |
177 | proxy_address: 'localhost', | |
178 | proxy_port: 3128, | |
179 | proxy_from_env: false | |
180 | } | |
181 | ``` | |
182 | ||
183 | ## Custom HTTP Client | |
184 | ||
185 | There is a packaged default client wrapping Net::HTTP, but your HTTP needs might be a little different. In that case, you can pass in your own wrapper to handle sending the notifications. It just needs to respond to `::post` with the arguments of the endpoint URI, and the payload [pretty much the same as Net:HTTP.post_form](http://ruby-doc.org/stdlib-2.1.2/libdoc/net/http/rdoc/Net/HTTP.html#method-c-post_form). | |
186 | ||
187 | A simple example: | |
188 | ```ruby | |
189 | module Client | |
190 | def self.post uri, params={} | |
191 | Net::HTTP.post_form uri, params | |
192 | end | |
193 | end | |
194 | ||
195 | notifier = Slack::Notifier.new 'WEBHOOK_URL' do | |
196 | http_client Client | |
197 | end | |
198 | ``` | |
199 | ||
200 | It's also encouraged for any custom HTTP implementations to accept the `:http_options` key in params. | |
201 | ||
202 | **Setting client per post** | |
203 | ||
204 | You can also set the http_client per-post if you need to special case certain pings. | |
205 | ||
206 | ```ruby | |
207 | notifier.post text: "hello", http_client: CustomClient | |
208 | ``` | |
209 | ||
210 | **Setting a No-Op client** | |
211 | ||
212 | In development (or testing), you may want to watch the behavior of the notifier without posting to slack. This can be handled with a no-op client. | |
213 | ||
214 | ```ruby | |
215 | class NoOpHTTPClient | |
216 | def self.post uri, params={} | |
217 | # bonus, you could log or observe posted params here | |
218 | end | |
219 | end | |
220 | ||
221 | notifier = Slack::Notifier.new 'WEBHOOK_URL' do | |
222 | http_client NoOpHTTPClient | |
223 | end | |
224 | ``` | |
225 | ||
226 | ||
227 | ## Middleware | |
228 | ||
229 | By default slack-notifier ships with middleware to format links in the message & text field of attachments. You can configure the middleware a notifier will use on initialization: | |
230 | ||
231 | ```ruby | |
232 | notifier = Slack::Notifier.new "WEBHOOK_URL" do | |
233 | middleware format_message: { formats: [:html] } | |
234 | end | |
235 | # this example will *only* use the format_message middleware and only format :html links | |
236 | ||
237 | notifier.post text: "Hello <a href='http://example.com'>world</a>! [visit this](http://example.com)" | |
238 | # => will post "Hello <http://example.com|world>! [visit this](http://example.com)" | |
239 | ``` | |
240 | ||
241 | The middleware can be set with a their name, or by name and options. They will be triggered in order. | |
242 | ||
243 | ```ruby | |
244 | notifier = Slack::Notifier.new "WEBHOOK_URL" do | |
245 | middleware :format_message, :format_attachments | |
246 | end | |
247 | # will run format_message then format_attachments with default options | |
248 | ||
249 | notifier = Slack::Notifier.new "WEBHOOK_URL" do | |
250 | middleware format_message: { formats: [:html] }, | |
251 | format_attachments: { formats: [:markdown] } | |
252 | end | |
253 | # will run format_message w/ formats [:html] then format_attachments with formats [:markdown] | |
254 | ``` | |
255 | ||
256 | Available middleware: | |
257 | ||
258 | **`format_message`** | |
259 | ||
260 | This middleware takes the `:text` key of the payload and runs it through the [`Linkformatter`](#links). You can configure which link formats to look for with a `:formats` option. You can set `[:html]` (only html links), `[:markdown]` (only markdown links) or `[:html, :markdown]` (the default, will format both). | |
261 | ||
262 | **`format_attachments`** | |
263 | ||
264 | This middleware takes the `:text` key of any attachment and runs it through the [`Linkformatter`](#links). You can configure which link formats to look for with a `:formats` option. You can set `[:html]` (only html links), `[:markdown]` (only markdown links) or `[:html, :markdown]` (the default, will format both). | |
265 | ||
266 | **`at`** | |
267 | ||
268 | This simplifies the process of notifying users and rooms to messages. By adding an `:at` key to the payload w/ an array of symbols the appropriately formatted commands will be prepended to the message. It will accept a single name, or an array. | |
269 | ||
270 | For example: | |
271 | ||
272 | ```ruby | |
273 | notifier.post text: "hello", at: :casper | |
274 | # => "<@casper> hello" | |
275 | ||
276 | notifier.post text: "hello", at: [:here, :waldo] | |
277 | # => "<!here> <@waldo> hello" | |
278 | ``` | |
279 | ||
280 | **`channels`** | |
281 | ||
282 | If the `channel` argument of a payload is an array this splits the payload to be posted to each channel. | |
283 | ||
284 | For example: | |
285 | ||
286 | ```ruby | |
287 | notifier.post text: "hello", channel: ["default", "all_the_things"] | |
288 | # => will post "hello" to the default and all_the_things channel | |
289 | ``` | |
290 | ||
291 | To send a message directly to a user, their username [no longer works](https://github.com/stevenosloan/slack-notifier/issues/51#issuecomment-414138622). Instead you'll need to get the user's ID and set that as the channel. | |
292 | ||
293 | At the time of writing, one way to get a user's ID is to: | |
294 | ||
295 | - go to their profile | |
296 | - click **...** ("More actions") | |
297 | - click **Copy Member ID** | |
298 | ||
299 | ### Writing your own Middleware | |
300 | ||
301 | Middleware is fairly straightforward, it is any class that inherits from `Slack::Notifier::PayloadMiddleware::Base` and responds to `#call`. It will always be given the payload as a hash and should return the modified payload as a hash. | |
302 | ||
303 | For example, lets say we want to replace words in every message, we could write a middleware like this: | |
304 | ||
305 | ```ruby | |
306 | class SwapWords < Slack::Notifier::PayloadMiddleware::Base | |
307 | middleware_name :swap_words # this is the key we use when setting | |
308 | # the middleware stack for a notifier | |
309 | ||
310 | options pairs: ["hipchat" => "slack"] # the options takes a hash that will | |
311 | # serve as the default if not given any | |
312 | # when initialized | |
313 | ||
314 | def call payload={} | |
315 | return payload unless payload[:text] # noope if there is no message to work on | |
316 | ||
317 | # not efficient, but it's an example :) | |
318 | options[:pairs].each do |from, to| | |
319 | payload[:text] = payload[:text].gsub from, to | |
320 | end | |
321 | ||
322 | payload # always return the payload from your middleware | |
323 | end | |
324 | end | |
325 | ||
326 | ||
327 | notifier = Slack::Notifier.new "WEBHOOK_URL" do | |
328 | middleware :swap_words # setting our stack w/ just defaults | |
329 | end | |
330 | notifier.ping "hipchat is awesome!" | |
331 | # => pings slack with "slack is awesome!" | |
332 | ||
333 | notifier = Slack::Notifier.new "WEBHOOK_URL" do | |
334 | # here we set new options for the middleware | |
335 | middleware swap_words: { pairs: ["hipchat" => "slack", | |
336 | "awesome" => "really awesome"]} | |
337 | end | |
338 | ||
339 | notifier.ping "hipchat is awesome!" | |
340 | # => pings slack with "slack is really awesome!" | |
341 | ``` | |
342 | ||
343 | If your middleware returns an array, that will split the message into multiple pings. An example for pinging multiple channels: | |
344 | ||
345 | ```ruby | |
346 | class MultiChannel < Slack::Notifier::PayloadMiddleware::Base | |
347 | middleware_name :channels | |
348 | ||
349 | def call payload={} | |
350 | return payload unless payload[:channel].respond_to?(:to_ary) | |
351 | ||
352 | payload[:channel].to_ary.map do |channel| | |
353 | pld = payload.dup | |
354 | pld[:channel] = channel | |
355 | pld | |
356 | end | |
357 | end | |
358 | end | |
359 | ``` | |
360 | ||
361 | ||
362 | Versioning | |
363 | ---------- | |
364 | ||
365 | Since version `1.0` has been released, the aim is to follow [Semantic Versioning](http://semver.org/) as much as possible. However, it is encouraged to check the [changelog](changelog.md) when updating to see what changes have been made. | |
366 | ||
367 | To summarize the reasoning for versioning: | |
368 | ||
369 | ``` | |
370 | Given a version number MAJOR.MINOR.PATCH, increment: | |
371 | ||
372 | - MAJOR version when incompatible API changes are made | |
373 | - MINOR version for adding functionality in a backwards-compatible manner or bug fixes that *may* change behavior | |
374 | - PATCH version for make backwards-compatible bug fixes | |
375 | ``` | |
376 | ||
377 | Testing | |
378 | ------- | |
379 | ||
380 | ```bash | |
381 | $ rspec | |
382 | ``` | |
383 | ||
384 | There is also an integration test setup to just double check pinging across the supported rubies. To run: | |
385 | ||
386 | 1. Copy the `.env-example` file to `.env` and replace with your details. | |
387 | 2. Make sure `bin/test` is executable | |
388 | 3. then run and watch for the pings in your slack room | |
389 | ||
390 | ```bash | |
391 | $ bin/test | |
392 | ``` | |
393 | ||
394 | ||
395 | Contributing | |
396 | ------------ | |
397 | ||
398 | If there is any thing you'd like to contribute or fix, please: | |
399 | ||
400 | - Fork the repo | |
401 | - Add tests for any new functionality | |
402 | - Make your changes | |
403 | - Verify all new & existing tests pass | |
404 | - Make a pull request | |
405 | ||
406 | ||
407 | License | |
408 | ------- | |
409 | The slack-notifier gem is distributed under the MIT License. |
0 | # frozen_string_literal: true | |
1 | ||
2 | require File.expand_path("../lib/slack-notifier/version", __FILE__) | |
3 | ||
4 | Gem::Specification.new do |s| | |
5 | s.name = "slack-notifier" | |
6 | s.version = Slack::Notifier::VERSION | |
7 | s.platform = Gem::Platform::RUBY | |
8 | ||
9 | s.summary = "A slim ruby wrapper for posting to slack webhooks" | |
10 | s.description = " A slim ruby wrapper for posting to slack webhooks " | |
11 | s.authors = ["Steven Sloan"] | |
12 | s.email = ["stevenosloan@gmail.com"] | |
13 | s.homepage = "https://github.com/slack-notifier/slack-notifier" | |
14 | s.license = "MIT" | |
15 | ||
16 | s.files = Dir["{lib}/**/*.rb"] | |
17 | s.test_files = Dir["spec/**/*.rb"] | |
18 | s.require_path = "lib" | |
19 | end |
0 | # frozen_string_literal: true | |
1 | # encoding: utf-8 | |
2 | ||
3 | require "spec_helper" | |
4 | ||
5 | RSpec.describe Slack::Notifier do | |
6 | { | |
7 | { text: "hello" } => | |
8 | { payload: { text: "hello" } }, | |
9 | ||
10 | { text: "[hello](http://example.com/world)" } => | |
11 | { payload: { text: "<http://example.com/world|hello>" } }, | |
12 | ||
13 | { text: '<a href="http://example.com">example</a>' } => | |
14 | { payload: { text: "<http://example.com|example>" } }, | |
15 | ||
16 | { text: "hello/こんにちは from notifier test" } => | |
17 | { payload: { text: "hello/こんにちは from notifier test" } }, | |
18 | ||
19 | { text: "Hello World, enjoy [](http://example.com)." } => | |
20 | { payload: { text: "Hello World, enjoy <http://example.com>." } }, | |
21 | ||
22 | { text: "Hello World, enjoy [this](http://example.com)[this2](http://example2.com)" } => | |
23 | { payload: { text: "Hello World, enjoy <http://example.com|this><http://example2.com|this2>" } }, | |
24 | ||
25 | { text: "[John](mailto:john@example.com)" } => | |
26 | { payload: { text: "<mailto:john@example.com|John>" } }, | |
27 | ||
28 | { text: '<a href="mailto:john@example.com">John</a>' } => | |
29 | { payload: { text: "<mailto:john@example.com|John>" } }, | |
30 | ||
31 | { text: "hello", channel: "hodor" } => | |
32 | { payload: { text: "hello", channel: "hodor" } }, | |
33 | ||
34 | { text: nil, attachments: [{ text: "attachment message" }] } => | |
35 | { payload: { text: nil, attachments: [{ text: "attachment message" }] } }, | |
36 | ||
37 | { text: "the message", channel: "foo", attachments: [{ color: "#000", | |
38 | text: "attachment message", | |
39 | fallback: "fallback message" }] } => | |
40 | { payload: { text: "the message", | |
41 | channel: "foo", | |
42 | attachments: [{ color: "#000", | |
43 | text: "attachment message", | |
44 | fallback: "fallback message" }] } }, | |
45 | ||
46 | { attachments: [{ color: "#000", | |
47 | text: "attachment message", | |
48 | fallback: "fallback message" }] } => | |
49 | { payload: { attachments: [{ color: "#000", | |
50 | text: "attachment message", | |
51 | fallback: "fallback message" }] } }, | |
52 | ||
53 | { attachments: { color: "#000", | |
54 | text: "attachment message [hodor](http://winterfell.com)", | |
55 | fallback: "fallback message" } } => | |
56 | { payload: { attachments: [{ color: "#000", | |
57 | text: "attachment message <http://winterfell.com|hodor>", | |
58 | fallback: "fallback message" }] } }, | |
59 | ||
60 | { attachments: { color: "#000", | |
61 | text: nil, | |
62 | fallback: "fallback message" } } => | |
63 | { payload: { attachments: [{ color: "#000", | |
64 | text: nil, | |
65 | fallback: "fallback message" }] } }, | |
66 | ||
67 | { text: "hello", http_options: { timeout: 5 } } => | |
68 | { http_options: { timeout: 5 }, payload: { text: "hello" } } | |
69 | }.each do |args, payload| | |
70 | it "sends correct payload for #post(#{args})" do | |
71 | http_client = class_double("Slack::Notifier::Util::HTTPClient", post: nil) | |
72 | notifier = Slack::Notifier.new "http://example.com", http_client: http_client | |
73 | payload[:payload] = payload[:payload].to_json | |
74 | ||
75 | expect(http_client).to receive(:post) | |
76 | .with(URI.parse("http://example.com"), payload) | |
77 | ||
78 | notifier.post(args) | |
79 | end | |
80 | end | |
81 | ||
82 | it "applies options given to middleware" do | |
83 | client = class_double("Slack::Notifier::Util::HTTPClient", post: nil) | |
84 | notifier = Slack::Notifier.new "http://example.com" do | |
85 | http_client client | |
86 | middleware format_message: { formats: [] } | |
87 | end | |
88 | ||
89 | expect(client).to receive(:post) | |
90 | .with(URI.parse("http://example.com"), payload: { text: "Hello [world](http://example.com)!" }.to_json) | |
91 | ||
92 | notifier.post text: "Hello [world](http://example.com)!" | |
93 | end | |
94 | end |
0 | # frozen_string_literal: true | |
0 | 1 | # encoding: utf-8 |
1 | require_relative '../../lib/slack-notifier' | |
2 | 2 | |
3 | notifier = Slack::Notifier.new ENV['SLACK_WEBHOOK_URL'], username: 'notifier' | |
4 | puts "testing with ruby #{RUBY_VERSION}" | |
5 | notifier.ping "hello/こんにちは from notifier test script on ruby: #{RUBY_VERSION}\225" | |
6 | notifier.ping attachments: [{color:"#1BF5AF",fallback:"fallback",text:"attachment"}] | |
3 | require_relative "../../lib/slack-notifier" | |
4 | ||
5 | ruby = if defined?(JRUBY_VERSION) | |
6 | "jruby #{JRUBY_VERSION}" | |
7 | else | |
8 | "ruby #{RUBY_VERSION}" | |
9 | end | |
10 | puts "testing with #{ruby}" | |
11 | ||
12 | notifier = Slack::Notifier.new ENV["SLACK_WEBHOOK_URL"], username: "notifier" | |
13 | notifier.ping "hello", channel: ["#general", "#random"] | |
14 | notifier.ping "hello/こんにちは from notifier test script on #{ruby}\225" | |
15 | notifier.ping attachments: [{ color: "#1BF5AF", fallback: "fallback", text: "attachment" }] |
0 | # frozen_string_literal: true | |
1 | ||
2 | RSpec.describe Slack::Notifier::Config do | |
3 | describe "#http_client" do | |
4 | it "is Util::HTTPClient if not set" do | |
5 | subject = described_class.new | |
6 | expect(subject.http_client).to eq Slack::Notifier::Util::HTTPClient | |
7 | end | |
8 | ||
9 | it "sets a new client class if given one" do | |
10 | new_client = class_double("Slack::Notifier::Util::HTTPClient", post: nil) | |
11 | subject = described_class.new | |
12 | subject.http_client new_client | |
13 | expect(subject.http_client).to eq new_client | |
14 | end | |
15 | ||
16 | it "raises an ArgumentError if given class does not respond to ::post" do | |
17 | subject = described_class.new | |
18 | expect do | |
19 | subject.http_client :nope | |
20 | end.to raise_error ArgumentError | |
21 | end | |
22 | end | |
23 | ||
24 | describe "#defaults" do | |
25 | it "is an empty hash by default" do | |
26 | subject = described_class.new | |
27 | expect(subject.defaults).to eq({}) | |
28 | end | |
29 | ||
30 | it "sets a hash to default if given" do | |
31 | subject = described_class.new | |
32 | subject.defaults foo: :bar | |
33 | expect(subject.defaults).to eq foo: :bar | |
34 | end | |
35 | ||
36 | it "raises ArgumentError if not given a hash" do | |
37 | subject = described_class.new | |
38 | expect do | |
39 | subject.defaults :nope | |
40 | end.to raise_error ArgumentError | |
41 | end | |
42 | end | |
43 | ||
44 | describe "#middleware" do | |
45 | it "is [:format_message, :format_attachments, :at] if not set" do | |
46 | subject = described_class.new | |
47 | ||
48 | expect(subject.middleware).to eq %i[format_message format_attachments at channels] | |
49 | end | |
50 | ||
51 | it "takes an array or a splat of args" do | |
52 | subject = described_class.new | |
53 | ||
54 | subject.middleware :layer, :two | |
55 | expect(subject.middleware).to eq %i[layer two] | |
56 | ||
57 | subject.middleware %i[one layer] | |
58 | expect(subject.middleware).to eq %i[one layer] | |
59 | end | |
60 | ||
61 | it "allows passing options to middleware stack" do | |
62 | subject = described_class.new | |
63 | subject.middleware one: { opts: :for_one }, | |
64 | two: { opts: :for_two } | |
65 | ||
66 | expect(subject.middleware).to eq one: { opts: :for_one }, | |
67 | two: { opts: :for_two } | |
68 | end | |
69 | end | |
70 | end |
0 | require 'spec_helper' | |
1 | ||
2 | describe Slack::Notifier::DefaultHTTPClient do | |
3 | ||
4 | describe "::post" do | |
5 | it "initializes DefaultHTTPClient with the given uri and params then calls" do | |
6 | http_post_double = instance_double("Slack::Notifier::DefaultHTTPClient") | |
7 | ||
8 | expect( described_class ).to receive(:new) | |
9 | .with( 'uri', 'params' ) | |
10 | .and_return( http_post_double ) | |
11 | expect( http_post_double ).to receive(:call) | |
12 | ||
13 | described_class.post 'uri', 'params' | |
14 | end | |
15 | ||
16 | # http_post is really tested in the integration spec, | |
17 | # where the internals are run through | |
18 | end | |
19 | ||
20 | describe "#initialize" do | |
21 | it "allows setting of options for Net::HTTP" do | |
22 | net_http_double = instance_double("Net::HTTP") | |
23 | http_client = described_class.new( URI.parse('http://example.com'), http_options: { open_timeout: 5 }) | |
24 | ||
25 | allow( Net::HTTP ).to receive(:new) | |
26 | .and_return(net_http_double) | |
27 | allow( net_http_double ).to receive(:use_ssl=) | |
28 | allow( net_http_double ).to receive(:request) | |
29 | ||
30 | expect( net_http_double ).to receive(:open_timeout=).with(5) | |
31 | ||
32 | http_client.call | |
33 | end | |
34 | end | |
35 | ||
36 | end |
0 | # encoding: utf-8 | |
1 | require 'spec_helper' | |
2 | ||
3 | describe Slack::Notifier::LinkFormatter do | |
4 | ||
5 | describe "::format" do | |
6 | ||
7 | it "formats html links" do | |
8 | formatted = described_class.format("Hello World, enjoy <a href='http://example.com'>this</a>.") | |
9 | expect( formatted ).to include("<http://example.com|this>") | |
10 | end | |
11 | ||
12 | it "formats markdown links" do | |
13 | formatted = described_class.format("Hello World, enjoy [this](http://example.com).") | |
14 | expect( formatted ).to include("<http://example.com|this>") | |
15 | end | |
16 | ||
17 | it "formats markdown links in brackets" do | |
18 | formatted = described_class.format("Hello World, enjoy [[this](http://example.com) in brackets].") | |
19 | expect( formatted ).to eq("Hello World, enjoy [<http://example.com|this> in brackets].") | |
20 | end | |
21 | ||
22 | it "formats markdown links with no title" do | |
23 | formatted = described_class.format("Hello World, enjoy [](http://example.com).") | |
24 | expect( formatted ).to include("<http://example.com>") | |
25 | end | |
26 | ||
27 | it "handles multiple html links" do | |
28 | formatted = described_class.format("Hello World, enjoy <a href='http://example.com'>this</a><a href='http://example2.com'>this2</a>.") | |
29 | expect( formatted ).to include("<http://example.com|this>") | |
30 | expect( formatted ).to include("<http://example2.com|this2>") | |
31 | end | |
32 | ||
33 | it "handles multiple markdown links" do | |
34 | formatted = described_class.format("Hello World, enjoy [this](http://example.com)[this2](http://example2.com).") | |
35 | expect( formatted ).to include("<http://example.com|this>") | |
36 | expect( formatted ).to include("<http://example2.com|this2>") | |
37 | end | |
38 | ||
39 | it "handles mixed html & markdown links" do | |
40 | formatted = described_class.format("Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>.") | |
41 | expect( formatted ).to include("<http://example.com|this>") | |
42 | expect( formatted ).to include("<http://example2.com|this2>") | |
43 | end | |
44 | ||
45 | if "".respond_to? :scrub | |
46 | context "when on ruby 2.1+ or have string-scrub installed" do | |
47 | it "handles invalid unicode sequences" do | |
48 | expect { | |
49 | described_class.format("This sequence is invalid: \255") | |
50 | }.not_to raise_error | |
51 | end | |
52 | ||
53 | it "replaces invalid unicode sequences with the unicode replacement character" do | |
54 | formatted = described_class.format("\255") | |
55 | expect(formatted).to eq "\uFFFD" | |
56 | end | |
57 | end | |
58 | end | |
59 | ||
60 | it "doesn't replace valid Japanese" do | |
61 | formatted = described_class.format("こんにちは") | |
62 | expect(formatted).to eq "こんにちは" | |
63 | end | |
64 | ||
65 | it "handles mailto links in markdown" do | |
66 | formatted = described_class.format("[John](mailto:john@example.com)") | |
67 | expect(formatted).to eq "<mailto:john@example.com|John>" | |
68 | end | |
69 | ||
70 | it "handles mailto links in html" do | |
71 | formatted = described_class.format("<a href='mailto:john@example.com'>John</a>") | |
72 | expect(formatted).to eq "<mailto:john@example.com|John>" | |
73 | end | |
74 | ||
75 | end | |
76 | ||
77 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | RSpec.describe Slack::Notifier::PayloadMiddleware::At do | |
3 | it "can handle array at" do | |
4 | subject = described_class.new(:notifier) | |
5 | payload = { text: "hello", at: %i[john ken here] } | |
6 | ||
7 | expect(subject.call(payload)).to eq text: "<@john> <@ken> <!here> hello" | |
8 | end | |
9 | ||
10 | it "can handle single at option" do | |
11 | subject = described_class.new(:notifier) | |
12 | payload = { text: "hello", at: :alice } | |
13 | ||
14 | expect(subject.call(payload)).to eq text: "<@alice> hello" | |
15 | end | |
16 | ||
17 | it "generates :text in payload if given :at & no :text" do | |
18 | subject = described_class.new(:notifier) | |
19 | input_payload = { at: [:here], attachments: [{ text: "hello" }] } | |
20 | output_payload = { text: "<!here> ", attachments: [{ text: "hello" }] } | |
21 | ||
22 | expect(subject.call(input_payload)).to eq output_payload | |
23 | end | |
24 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | RSpec.describe Slack::Notifier::PayloadMiddleware::Base do | |
3 | before(:each) do | |
4 | @registry_backup = Slack::Notifier::PayloadMiddleware.registry.dup | |
5 | Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry) | |
6 | end | |
7 | ||
8 | after(:each) do | |
9 | # cleanup middleware registry | |
10 | Slack::Notifier::PayloadMiddleware.registry | |
11 | Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry) | |
12 | ||
13 | # cleanup object constants | |
14 | Object.send(:remove_const, :Subject) if Object.constants.include?(:Subject) | |
15 | Slack::Notifier::PayloadMiddleware.send(:instance_variable_set, :@registry, @registry_backup) | |
16 | end | |
17 | ||
18 | describe "::middleware_name" do | |
19 | it "registers class w/ given name" do | |
20 | class Subject < Slack::Notifier::PayloadMiddleware::Base | |
21 | end | |
22 | ||
23 | expect(Slack::Notifier::PayloadMiddleware) | |
24 | .to receive(:register).with(Subject, :subject) | |
25 | ||
26 | class Subject | |
27 | middleware_name :subject | |
28 | end | |
29 | end | |
30 | ||
31 | it "uses symbolized name to register" do | |
32 | class Subject < Slack::Notifier::PayloadMiddleware::Base | |
33 | end | |
34 | ||
35 | expect(Slack::Notifier::PayloadMiddleware) | |
36 | .to receive(:register).with(Subject, :subject) | |
37 | ||
38 | class Subject | |
39 | middleware_name "subject" | |
40 | end | |
41 | end | |
42 | end | |
43 | ||
44 | describe "::options" do | |
45 | it "allows setting default options for a middleware" do | |
46 | class Subject < Slack::Notifier::PayloadMiddleware::Base | |
47 | options foo: :bar | |
48 | end | |
49 | ||
50 | subject = Subject.new(:notifier) | |
51 | expect(subject.options).to eq foo: :bar | |
52 | ||
53 | subject = Subject.new(:notifier, foo: :baz) | |
54 | expect(subject.options).to eq foo: :baz | |
55 | end | |
56 | end | |
57 | ||
58 | describe "#initialize" do | |
59 | it "sets given notifier as notifier" do | |
60 | expect(described_class.new(:notifier).notifier).to eq :notifier | |
61 | end | |
62 | ||
63 | it "sets given options as opts" do | |
64 | expect(described_class.new(:notifier, opts: :options).options).to eq opts: :options | |
65 | end | |
66 | end | |
67 | ||
68 | describe "#call" do | |
69 | it "raises NoMethodError (expects subclass to define)" do | |
70 | expect do | |
71 | described_class.new(:notifier).call | |
72 | end.to raise_exception NoMethodError | |
73 | end | |
74 | end | |
75 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | RSpec.describe Slack::Notifier::PayloadMiddleware::Channels do | |
3 | it "leaves string channels alone" do | |
4 | subject = described_class.new(:notifier) | |
5 | payload = { text: "hello", channel: "hodor" } | |
6 | ||
7 | expect(subject.call(payload)).to eq text: "hello", channel: "hodor" | |
8 | end | |
9 | ||
10 | it "splits payload into multiple if given an array of channels" do | |
11 | subject = described_class.new(:notifier) | |
12 | payload = { text: "hello", channel: %w[foo hodor] } | |
13 | ||
14 | expect(subject.call(payload)).to eq [ | |
15 | { text: "hello", channel: "foo" }, | |
16 | { text: "hello", channel: "hodor" } | |
17 | ] | |
18 | end | |
19 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | RSpec.describe Slack::Notifier::PayloadMiddleware::FormatAttachments do | |
3 | it "passes the text of attachments through linkformatter with options[:formats]" do | |
4 | subject = described_class.new(:notifier, formats: [:html]) | |
5 | expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) | |
6 | .with("hello", formats: [:html]) | |
7 | subject.call(attachments: [{ text: "hello" }]) | |
8 | end | |
9 | ||
10 | it "searches through string or symbol keys" do | |
11 | subject = described_class.new(:notifier) | |
12 | expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) | |
13 | .with("hello", formats: %i[html markdown]) | |
14 | subject.call("attachments" => [{ "text" => "hello" }]) | |
15 | ||
16 | subject = described_class.new(:notifier) | |
17 | expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) | |
18 | .with("hello", formats: %i[html markdown]) | |
19 | subject.call(attachments: [{ text: "hello" }]) | |
20 | end | |
21 | ||
22 | it "can handle a single attachment" do | |
23 | subject = described_class.new(:notifier) | |
24 | expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) | |
25 | .with("hello", formats: %i[html markdown]) | |
26 | subject.call(attachments: { text: "hello" }) | |
27 | end | |
28 | ||
29 | it "wraps attachment into array if given as a single hash" do | |
30 | params = { | |
31 | attachments: { text: "hello" } | |
32 | } | |
33 | payload = { | |
34 | attachments: [{ text: "hello" }] | |
35 | } | |
36 | subject = described_class.new(:notifier) | |
37 | ||
38 | expect(subject.call(params)).to eq payload | |
39 | end | |
40 | ||
41 | it "returns the payload unmodified if not :attachments key" do | |
42 | payload = { foo: :bar } | |
43 | subject = described_class.new(:notifier) | |
44 | ||
45 | expect(subject.call(payload)).to eq payload | |
46 | end | |
47 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | RSpec.describe Slack::Notifier::PayloadMiddleware::FormatMessage do | |
3 | it "passes the text through linkformatter with options[:formats]" do | |
4 | subject = described_class.new(:notifier, formats: [:html]) | |
5 | expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) | |
6 | .with("hello", formats: [:html]) | |
7 | subject.call(text: "hello") | |
8 | ||
9 | subject = described_class.new(:notifier) | |
10 | expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) | |
11 | .with("hello", formats: %i[html markdown]) | |
12 | subject.call(text: "hello") | |
13 | ||
14 | subject = described_class.new(:notifier, formats: [:markdown]) | |
15 | expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) | |
16 | .with("hello", formats: [:markdown]) | |
17 | subject.call(text: "hello") | |
18 | end | |
19 | ||
20 | it "returns the payload unmodified if not :text key" do | |
21 | payload = { foo: :bar } | |
22 | subject = described_class.new(:notifier) | |
23 | ||
24 | expect(subject.call(payload)).to eq payload | |
25 | end | |
26 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | RSpec.describe Slack::Notifier::PayloadMiddleware::Stack do | |
3 | let(:return_one) do | |
4 | double(call: 1) | |
5 | end | |
6 | ||
7 | let(:return_one_twice) do | |
8 | double(call: [1, 1]) | |
9 | end | |
10 | ||
11 | let(:return_two) do | |
12 | double(call: 2) | |
13 | end | |
14 | ||
15 | let(:return_three) do | |
16 | double(call: 3) | |
17 | end | |
18 | ||
19 | before(:each) do | |
20 | # setup our middleware in the registry | |
21 | @registry_backup = Slack::Notifier::PayloadMiddleware.registry.dup | |
22 | Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry) | |
23 | ||
24 | Slack::Notifier::PayloadMiddleware.register return_one, :return_one | |
25 | Slack::Notifier::PayloadMiddleware.register return_one_twice, :return_one_twice | |
26 | Slack::Notifier::PayloadMiddleware.register return_two, :return_two | |
27 | Slack::Notifier::PayloadMiddleware.register return_three, :return_three | |
28 | end | |
29 | ||
30 | after(:each) do | |
31 | # cleanup middleware registry | |
32 | Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry) | |
33 | Slack::Notifier::PayloadMiddleware.send(:instance_variable_set, :@registry, @registry_backup) | |
34 | end | |
35 | ||
36 | describe "::initialize" do | |
37 | it "sets notifier to given notifier" do | |
38 | expect(described_class.new(:notifier).notifier).to eq :notifier | |
39 | end | |
40 | ||
41 | it "has empty stack" do | |
42 | expect(described_class.new(:notifier).stack).to match_array [] | |
43 | end | |
44 | end | |
45 | ||
46 | describe "#set" do | |
47 | it "initializes each middleware w/ the notifier instance" do | |
48 | expect(return_one).to receive(:new).with(:notifier) | |
49 | expect(return_two).to receive(:new).with(:notifier) | |
50 | ||
51 | described_class.new(:notifier).set(:return_one, :return_two) | |
52 | end | |
53 | ||
54 | it "creates the stack in an array" do | |
55 | allow(return_one).to receive(:new).and_return(return_one) | |
56 | allow(return_two).to receive(:new).and_return(return_two) | |
57 | ||
58 | subject = described_class.new(:notifier) | |
59 | subject.set(:return_one, :return_two) | |
60 | ||
61 | expect(subject.stack).to be_a Array | |
62 | expect(subject.stack.first.call).to eq 1 | |
63 | expect(subject.stack.last.call).to eq 2 | |
64 | end | |
65 | ||
66 | it "creates a stack from hashes passing them as opts" do | |
67 | expect(return_one).to receive(:new).with(:notifier, opts: :for_one) | |
68 | expect(return_two).to receive(:new).with(:notifier, opts: :for_two) | |
69 | ||
70 | subject = described_class.new(:notifier) | |
71 | subject.set return_one: { opts: :for_one }, | |
72 | return_two: { opts: :for_two } | |
73 | end | |
74 | ||
75 | it "raises if a middleware is missing" do | |
76 | expect do | |
77 | described_class.new(:notifier).set(:missing) | |
78 | end.to raise_exception KeyError | |
79 | end | |
80 | end | |
81 | ||
82 | describe "#call" do | |
83 | it "calls the middleware in order, passing return of each to the next" do | |
84 | allow(return_one).to receive(:new).and_return(return_one) | |
85 | allow(return_two).to receive(:new).and_return(return_two) | |
86 | allow(return_three).to receive(:new).and_return(return_three) | |
87 | ||
88 | subject = described_class.new(:notifier) | |
89 | subject.set(:return_one, :return_three, :return_two) | |
90 | ||
91 | expect(return_one).to receive(:call).with(5) | |
92 | expect(return_three).to receive(:call).with(1) | |
93 | expect(return_two).to receive(:call).with(3) | |
94 | ||
95 | expect(subject.call(5)).to eq [2] | |
96 | end | |
97 | ||
98 | it "allows any middleware to return an array but other's don't need special behavior" do | |
99 | allow(return_one_twice).to receive(:new).and_return(return_one_twice) | |
100 | allow(return_two).to receive(:new).and_return(return_two) | |
101 | ||
102 | subject = described_class.new(:notifier) | |
103 | subject.set(:return_one_twice, :return_two) | |
104 | ||
105 | expect(subject.call(5)).to eq [2, 2] | |
106 | end | |
107 | ||
108 | it "handles multiple middleware splitting payload" do | |
109 | allow(return_one_twice).to receive(:new).and_return(return_one_twice) | |
110 | allow(return_two).to receive(:new).and_return(return_two) | |
111 | ||
112 | subject = described_class.new(:notifier) | |
113 | subject.set(:return_one_twice, :return_one_twice, :return_two) | |
114 | ||
115 | expect(subject.call(5)).to eq [2, 2, 2, 2] | |
116 | end | |
117 | end | |
118 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | RSpec.describe Slack::Notifier::PayloadMiddleware do | |
3 | before(:each) do | |
4 | @registry_backup = described_class.registry.dup | |
5 | Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry) | |
6 | end | |
7 | ||
8 | after(:each) do | |
9 | described_class.send(:remove_instance_variable, :@registry) | |
10 | described_class.send(:instance_variable_set, :@registry, @registry_backup) | |
11 | end | |
12 | ||
13 | describe "::registry" do | |
14 | it "returns a hash if nothing set" do | |
15 | expect(described_class.registry).to eq({}) | |
16 | end | |
17 | ||
18 | it "returns memoized version if already set" do | |
19 | described_class.instance_variable_set(:@registry, "hodor") | |
20 | expect(described_class.registry).to eq "hodor" | |
21 | end | |
22 | end | |
23 | ||
24 | describe "::register" do | |
25 | it "adds given class to key in registry" do | |
26 | MyClass = Struct.new(:myclass) | |
27 | described_class.register MyClass, :my_class | |
28 | ||
29 | expect(described_class.registry[:my_class]).to eq MyClass | |
30 | end | |
31 | end | |
32 | end |
0 | # frozen_string_literal: true | |
1 | ||
2 | RSpec.describe Slack::Notifier::Util::HTTPClient do | |
3 | describe "::post" do | |
4 | it "initializes Util::HTTPClient with the given uri and params then calls" do | |
5 | http_post_double = instance_double("Slack::Notifier::Util::HTTPClient") | |
6 | ||
7 | expect(described_class) | |
8 | .to receive(:new).with("uri", "params") | |
9 | .and_return(http_post_double) | |
10 | expect(http_post_double).to receive(:call) | |
11 | ||
12 | described_class.post "uri", "params" | |
13 | end | |
14 | ||
15 | # http_post is really tested in the integration spec, | |
16 | # where the internals are run through | |
17 | end | |
18 | ||
19 | describe "#initialize" do | |
20 | it "allows setting of options for Net::HTTP" do | |
21 | net_http_double = instance_double("Net::HTTP") | |
22 | http_client = described_class.new URI.parse("http://example.com"), | |
23 | http_options: { open_timeout: 5 } | |
24 | ||
25 | allow(Net::HTTP).to receive(:new).and_return(net_http_double) | |
26 | allow(net_http_double).to receive(:use_ssl=) | |
27 | allow(net_http_double).to receive(:request).with(anything) do | |
28 | Net::HTTPOK.new("GET", "200", "OK") | |
29 | end | |
30 | ||
31 | expect(net_http_double).to receive(:open_timeout=).with(5) | |
32 | ||
33 | http_client.call | |
34 | end | |
35 | end | |
36 | ||
37 | describe "#call" do | |
38 | it "raises an error when the response is unsuccessful" do | |
39 | net_http_double = instance_double("Net::HTTP") | |
40 | http_client = described_class.new URI.parse("http://example.com"), {} | |
41 | bad_request = Net::HTTPBadRequest.new("GET", "400", "Bad Request") | |
42 | ||
43 | allow(bad_request).to receive(:body).and_return("something_bad") | |
44 | allow(Net::HTTP).to receive(:new).and_return(net_http_double) | |
45 | allow(net_http_double).to receive(:use_ssl=) | |
46 | allow(net_http_double).to receive(:request).with(anything) do | |
47 | bad_request | |
48 | end | |
49 | ||
50 | expect { http_client.call }.to raise_error(Slack::Notifier::APIError, | |
51 | /something_bad \(HTTP Code 400\)/) | |
52 | end | |
53 | end | |
54 | end |
0 | # frozen_string_literal: true | |
1 | # encoding: utf-8 | |
2 | ||
3 | # rubocop:disable Metrics/LineLength | |
4 | RSpec.describe Slack::Notifier::Util::LinkFormatter do | |
5 | describe "initialize & formatted" do | |
6 | it "can be initialized without format args" do | |
7 | subject = described_class.new("Hello World") | |
8 | expect(subject.formatted()).to eq("Hello World") | |
9 | end | |
10 | ||
11 | it "can be initialized with format args" do | |
12 | subject = described_class.new("Hello World", formats: [:html]) | |
13 | expect(subject.formatted()).to eq("Hello World") | |
14 | end | |
15 | end | |
16 | describe "::format" do | |
17 | it "formats html links" do | |
18 | formatted = described_class.format("Hello World, enjoy <a href='http://example.com'>this</a>.") | |
19 | expect(formatted).to include("<http://example.com|this>") | |
20 | end | |
21 | ||
22 | it "formats markdown links" do | |
23 | formatted = described_class.format("Hello World, enjoy [this](http://example.com).") | |
24 | expect(formatted).to include("<http://example.com|this>") | |
25 | end | |
26 | ||
27 | it "formats markdown links in brackets" do | |
28 | formatted = described_class.format("Hello World, enjoy [[this](http://example.com) in brackets].") | |
29 | expect(formatted).to eq("Hello World, enjoy [<http://example.com|this> in brackets].") | |
30 | end | |
31 | ||
32 | it "formats markdown links with no title" do | |
33 | formatted = described_class.format("Hello World, enjoy [](http://example.com).") | |
34 | expect(formatted).to include("<http://example.com>") | |
35 | end | |
36 | ||
37 | it "handles multiple html links" do | |
38 | formatted = described_class.format("Hello World, enjoy <a href='http://example.com'>this</a><a href='http://example2.com'>this2</a>.") | |
39 | expect(formatted).to include("<http://example.com|this>") | |
40 | expect(formatted).to include("<http://example2.com|this2>") | |
41 | end | |
42 | ||
43 | it "handles multiple markdown links" do | |
44 | formatted = described_class.format("Hello World, enjoy [this](http://example.com)[this2](http://example2.com).") | |
45 | expect(formatted).to include("<http://example.com|this>") | |
46 | expect(formatted).to include("<http://example2.com|this2>") | |
47 | end | |
48 | ||
49 | it "handles mixed html & markdown links" do | |
50 | formatted = described_class.format("Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>.") | |
51 | expect(formatted).to include("<http://example.com|this>") | |
52 | expect(formatted).to include("<http://example2.com|this2>") | |
53 | end | |
54 | ||
55 | if "".respond_to? :scrub | |
56 | context "when on ruby 2.1+ or have string-scrub installed" do | |
57 | it "handles invalid unicode sequences" do | |
58 | expect do | |
59 | described_class.format("This sequence is invalid: \255") | |
60 | end.not_to raise_error | |
61 | end | |
62 | ||
63 | it "replaces invalid unicode sequences with the unicode replacement character" do | |
64 | formatted = described_class.format("\255") | |
65 | expect(formatted).to eq "\uFFFD" | |
66 | end | |
67 | end | |
68 | end | |
69 | ||
70 | it "doesn't replace valid Japanese" do | |
71 | formatted = described_class.format("こんにちは") | |
72 | expect(formatted).to eq "こんにちは" | |
73 | end | |
74 | ||
75 | it "handles mailto links in markdown" do | |
76 | formatted = described_class.format("[John](mailto:john@example.com)") | |
77 | expect(formatted).to eq "<mailto:john@example.com|John>" | |
78 | end | |
79 | ||
80 | it "handles mailto links in html" do | |
81 | formatted = described_class.format("<a href='mailto:john@example.com'>John</a>") | |
82 | expect(formatted).to eq "<mailto:john@example.com|John>" | |
83 | end | |
84 | ||
85 | it "handles links with trailing parentheses" do | |
86 | formatted = described_class.format("Hello World, enjoy [foo(bar)](http://example.com/foo(bar))<a href='http://example.com/bar(foo)'>bar(foo)</a>") | |
87 | expect(formatted).to include("http://example.com/foo(bar)|foo(bar)") | |
88 | expect(formatted).to include("http://example.com/bar(foo)|bar(foo)") | |
89 | end | |
90 | ||
91 | it "formats a number of differently formatted links" do | |
92 | input_output = { | |
93 | "Hello World, enjoy [this](http://example.com)." => | |
94 | "Hello World, enjoy <http://example.com|this>.", | |
95 | ||
96 | "Hello World, enjoy [[this](http://example.com) in brackets]." => | |
97 | "Hello World, enjoy [<http://example.com|this> in brackets].", | |
98 | ||
99 | "Hello World, enjoy ([this](http://example.com) in parens)." => | |
100 | "Hello World, enjoy (<http://example.com|this> in parens).", | |
101 | ||
102 | "Hello World, enjoy [](http://example.com)." => | |
103 | "Hello World, enjoy <http://example.com>.", | |
104 | ||
105 | "Hello World, enjoy [link with query](http://example.com?foo=bar)." => | |
106 | "Hello World, enjoy <http://example.com?foo=bar|link with query>.", | |
107 | ||
108 | "Hello World, enjoy [link with fragment](http://example.com/#foo-bar)." => | |
109 | "Hello World, enjoy <http://example.com/#foo-bar|link with fragment>.", | |
110 | ||
111 | "Hello World, enjoy [link with parens](http://example.com/foo(bar)/baz)." => | |
112 | "Hello World, enjoy <http://example.com/foo(bar)/baz|link with parens>.", | |
113 | ||
114 | "Hello World, enjoy [link with query](http://example.com/(parens)?foo=bar)." => | |
115 | "Hello World, enjoy <http://example.com/(parens)?foo=bar|link with query>.", | |
116 | ||
117 | "Hello World, enjoy [link with parens](http://example.com/baz?bang=foo(bar))." => | |
118 | "Hello World, enjoy <http://example.com/baz?bang=foo(bar)|link with parens>.", | |
119 | ||
120 | "Hello World, enjoy [link with fragment](http://example.com/(parens)#foo-bar)." => | |
121 | "Hello World, enjoy <http://example.com/(parens)#foo-bar|link with fragment>.", | |
122 | ||
123 | "Hello World, enjoy [link with fragment](http://example.com/#foo-bar=(baz))." => | |
124 | "Hello World, enjoy <http://example.com/#foo-bar=(baz)|link with fragment>.", | |
125 | ||
126 | "Hello World, enjoy [this](http://example.com?foo=bar)[this2](http://example2.com)." => | |
127 | "Hello World, enjoy <http://example.com?foo=bar|this><http://example2.com|this2>.", | |
128 | ||
129 | "Hello World, enjoy [this](http://example.com?foo=bar) [this2](http://example2.com/#fragment)." => | |
130 | "Hello World, enjoy <http://example.com?foo=bar|this> <http://example2.com/#fragment|this2>.", | |
131 | ||
132 | "Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>." => | |
133 | "Hello World, enjoy <http://example.com|this><http://example2.com|this2>.", | |
134 | ||
135 | "Hello world, [John](mailto:john@example.com)." => | |
136 | "Hello world, <mailto:john@example.com|John>.", | |
137 | ||
138 | "Hello World, enjoy [foo(bar)](http://example.com/foo(bar))<a href='http://example.com/bar(foo)'>bar(foo)</a>" => | |
139 | "Hello World, enjoy <http://example.com/foo(bar)|foo(bar)><http://example.com/bar(foo)|bar(foo)>" | |
140 | } | |
141 | ||
142 | input_output.each do |input, output| | |
143 | expect(described_class.format(input)).to eq output | |
144 | end | |
145 | end | |
146 | ||
147 | context "with a configured stack" do | |
148 | it "only formats html if html is the only item in formats" do | |
149 | formatted = described_class.format("Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>.", formats: [:html]) | |
150 | expect(formatted).to eq "Hello World, enjoy [this](http://example.com)<http://example2.com|this2>." | |
151 | end | |
152 | it "only formats markdown if markdown is the only item in formats" do | |
153 | formatted = described_class.format("Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>.", formats: [:markdown]) | |
154 | expect(formatted).to eq "Hello World, enjoy <http://example.com|this><a href='http://example2.com'>this2</a>." | |
155 | end | |
156 | it "doesn't format if formats is empty" do | |
157 | formatted = described_class.format("Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>.", formats: []) | |
158 | expect(formatted).to eq "Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>." | |
159 | end | |
160 | end | |
161 | end | |
162 | end |
0 | require 'spec_helper' | |
0 | # frozen_string_literal: true | |
1 | 1 | |
2 | describe Slack::Notifier do | |
3 | subject { described_class.new 'http://example.com' } | |
2 | RSpec.describe Slack::Notifier do | |
3 | let(:mock_http) do | |
4 | class_double("Slack::Notifier::Util::HTTPClient", post: :posted) | |
5 | end | |
6 | ||
7 | subject { described_class.new "http://example.com", http_client: mock_http } | |
4 | 8 | |
5 | 9 | describe "#initialize" do |
6 | 10 | it "sets the given hook_url to the endpoint URI" do |
7 | expect( subject.endpoint ).to eq URI.parse 'http://example.com' | |
11 | expect(subject.endpoint).to eq URI.parse("http://example.com") | |
8 | 12 | end |
9 | 13 | |
10 | 14 | it "sets the default_payload options" do |
11 | subject = described_class.new 'http://example.com', channel: 'foo' | |
12 | expect( subject.channel ).to eq 'foo' | |
15 | subject = described_class.new "http://example.com", channel: "foo" | |
16 | expect(subject.config.defaults[:channel]).to eq "foo" | |
13 | 17 | end |
14 | 18 | |
15 | 19 | it "sets a custom http client" do |
16 | client = double("CustomClient") | |
17 | subject = described_class.new 'http://example.com', http_client: client | |
18 | expect( subject.http_client ).to eq client | |
20 | subject = described_class.new "http://example.com", http_client: mock_http | |
21 | expect(subject.config.http_client).to eq mock_http | |
22 | end | |
23 | ||
24 | describe "when given a block" do | |
25 | it "yields the config object" do | |
26 | test_double = double("Slack::Notifier::Config", defaults: {}, middleware: []) | |
27 | allow_any_instance_of(Slack::Notifier).to receive(:config).and_return(test_double) | |
28 | ||
29 | expect(test_double).to receive(:test_init_method).with("foo") | |
30 | ||
31 | described_class.new "http://example.com" do | |
32 | test_init_method "foo" | |
33 | end | |
34 | end | |
19 | 35 | end |
20 | 36 | end |
21 | 37 | |
22 | 38 | describe "#ping" do |
23 | before :each do | |
24 | allow( Slack::Notifier::DefaultHTTPClient ).to receive(:post) | |
39 | it "calls #post with the message as the text key in #post" do | |
40 | subject = described_class.new "http://example.com" | |
41 | expect(subject).to receive(:post).with text: "message" | |
42 | ||
43 | subject.ping "message" | |
25 | 44 | end |
45 | end | |
26 | 46 | |
27 | it "passes the message through LinkFormatter" do | |
28 | expect( Slack::Notifier::LinkFormatter ).to receive(:format) | |
29 | .with("the message") | |
30 | ||
31 | described_class.new('http://example.com').ping "the message", channel: 'foo' | |
32 | end | |
33 | ||
34 | it "passes attachment messages through LinkFormatter" do | |
35 | expect( Slack::Notifier::LinkFormatter ).to receive(:format) | |
36 | .with("the message") | |
37 | expect( Slack::Notifier::LinkFormatter ).to receive(:format) | |
38 | .with("attachment message") | |
39 | ||
40 | described_class.new('http://example.com').ping "the message", channel: 'foo', | |
41 | attachments: [{ | |
42 | color: "#000", | |
43 | text: "attachment message", | |
44 | fallback: "fallback message" | |
45 | }] | |
46 | end | |
47 | ||
48 | it "allows sending only an attachment" do | |
49 | expect( Slack::Notifier::DefaultHTTPClient ).to receive(:post).with( | |
50 | URI.parse('http://example.com'), | |
51 | payload: '{"channel":"foo","attachments":[{"text":"attachment","fallback":"fallback"}]}' | |
52 | ) | |
53 | ||
54 | expect{ | |
55 | described_class.new('http://example.com') | |
56 | .ping channel: 'foo', | |
57 | attachments: [{ | |
58 | text: 'attachment', | |
59 | fallback: 'fallback' | |
60 | }] | |
61 | }.not_to raise_error | |
62 | end | |
63 | ||
64 | it "passes attachment messages through LinkFormatter, even if a single value is passed" do | |
65 | expect( Slack::Notifier::LinkFormatter ).to receive(:format) | |
66 | .with("a random message") | |
67 | expect( Slack::Notifier::LinkFormatter ).to receive(:format) | |
68 | .with("attachment message") | |
69 | attachment = { | |
70 | color: "#000", | |
71 | text: "attachment message", | |
72 | fallback: "fallback message" | |
73 | } | |
74 | subject.ping "a random message", attachments: attachment | |
75 | end | |
76 | ||
77 | context "with a default channel set" do | |
78 | ||
79 | before :each do | |
80 | @endpoint_double = instance_double "URI::HTTP" | |
81 | allow( URI ).to receive(:parse) | |
82 | .and_return(@endpoint_double) | |
83 | subject.channel = '#default' | |
84 | end | |
85 | ||
86 | it "does not require a channel to ping" do | |
87 | expect{ | |
88 | subject.ping "the message" | |
89 | }.not_to raise_error | |
90 | end | |
91 | ||
92 | it "uses default channel" do | |
93 | expect( Slack::Notifier::DefaultHTTPClient ).to receive(:post) | |
94 | .with @endpoint_double, | |
95 | payload: '{"channel":"#default","text":"the message"}' | |
96 | ||
97 | subject.ping "the message" | |
98 | end | |
99 | ||
100 | it "allows override channel to be set" do | |
101 | expect( Slack::Notifier::DefaultHTTPClient ).to receive(:post) | |
102 | .with @endpoint_double, | |
103 | payload: '{"channel":"new","text":"the message"}' | |
104 | ||
105 | subject.ping "the message", channel: "new" | |
106 | end | |
107 | ||
108 | end | |
109 | ||
110 | context "with default webhook" do | |
111 | it "posts with the correct endpoint & data" do | |
112 | @endpoint_double = instance_double "URI::HTTP" | |
113 | allow( URI ).to receive(:parse) | |
114 | .with("http://example.com") | |
115 | .and_return(@endpoint_double) | |
116 | ||
117 | expect( Slack::Notifier::DefaultHTTPClient ).to receive(:post) | |
118 | .with @endpoint_double, | |
119 | payload: '{"channel":"channel","text":"the message"}' | |
120 | ||
121 | described_class.new("http://example.com").ping "the message", channel: "channel" | |
47 | describe "#post" do | |
48 | def notifier_with_defaults | |
49 | mock_client = mock_http | |
50 | described_class.new "http://example.com" do | |
51 | defaults channel: "default", | |
52 | user: "rocket" | |
53 | http_client mock_client | |
122 | 54 | end |
123 | 55 | end |
124 | 56 | |
125 | context "with a custom http_client set" do | |
126 | it "uses it" do | |
127 | endpoint_double = instance_double "URI::HTTP" | |
128 | allow( URI ).to receive(:parse) | |
129 | .with("http://example.com") | |
130 | .and_return(endpoint_double) | |
131 | client = double("CustomClient") | |
132 | expect( client ).to receive(:post) | |
133 | .with endpoint_double, | |
134 | payload: '{"text":"the message"}' | |
57 | it "uses the defaults set on initialization" do | |
58 | subject = notifier_with_defaults | |
135 | 59 | |
136 | described_class.new('http://example.com',http_client: client).ping "the message" | |
137 | end | |
60 | expect(mock_http).to receive(:post).with( | |
61 | URI.parse("http://example.com"), | |
62 | payload: '{"channel":"default","user":"rocket","text":"hello"}' | |
63 | ) | |
64 | ||
65 | subject.post text: "hello" | |
138 | 66 | end |
139 | end | |
140 | 67 | |
141 | describe "#channel=" do | |
142 | it "sets the given channel" do | |
143 | subject.channel = "#foo" | |
144 | expect( subject.channel ).to eq "#foo" | |
68 | it "allows overriding the set defaults" do | |
69 | subject = notifier_with_defaults | |
70 | ||
71 | expect(mock_http).to receive(:post).with( | |
72 | URI.parse("http://example.com"), | |
73 | payload: '{"channel":"new","user":"ship","text":"hello"}' | |
74 | ) | |
75 | ||
76 | subject.post text: "hello", channel: "new", user: "ship" | |
145 | 77 | end |
146 | end | |
147 | 78 | |
148 | describe "#username=" do | |
149 | it "sets the given username" do | |
150 | subject.username = "foo" | |
151 | expect( subject.username ).to eq "foo" | |
152 | end | |
153 | end | |
79 | it "calls the middleware stack with the payload" do | |
80 | subject = notifier_with_defaults | |
81 | stack = instance_double("Slack::Notifier::PayloadMiddleware::Stack") | |
82 | subject.instance_variable_set(:@middleware, stack) | |
154 | 83 | |
155 | describe "#escape" do | |
156 | it "escapes sequences of < > &, but not quotes" do | |
157 | message = %q(I've heard "Do > with <" & that sounds ridiculous.) | |
158 | expected = %q(I've heard "Do > with <" & that sounds ridiculous.) | |
159 | expect( subject.escape(message) ).to eq expected | |
84 | expect(stack).to receive(:call) | |
85 | .with(channel: "default", user: "rocket") | |
86 | .and_return([test: "stack"]) | |
87 | ||
88 | expect(mock_http).to receive(:post).with( | |
89 | URI.parse("http://example.com"), | |
90 | payload: '{"test":"stack"}' | |
91 | ) | |
92 | ||
93 | responses = subject.post | |
94 | expect(responses).to eq([:posted]) | |
160 | 95 | end |
161 | 96 | end |
162 | 97 | end |
0 | require 'rspec' | |
1 | require 'slack-notifier' | |
0 | # frozen_string_literal: true | |
2 | 1 | |
3 | if ENV['DEBUG'] | |
4 | require 'pry' | |
5 | end | |
2 | require "rspec" | |
3 | require "slack-notifier" | |
4 | require "pry" if ENV["DEBUG"] | |
6 | 5 | |
7 | 6 | RSpec.configure do |config| |
7 | config.expect_with :rspec do |expectations| | |
8 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true | |
9 | end | |
10 | ||
8 | 11 | config.mock_with :rspec do |mocks| |
9 | 12 | mocks.verify_doubled_constant_names = true |
10 | 13 | mocks.verify_partial_doubles = true |
11 | 14 | end |
15 | ||
16 | config.filter_run :focus | |
17 | config.run_all_when_everything_filtered = true | |
18 | config.disable_monkey_patching! | |
19 | ||
20 | config.example_status_persistence_file_path = "spec/examples.txt" | |
21 | config.warnings = ENV["DEBUG"] ? false : true | |
22 | ||
23 | config.default_formatter = "doc" if config.files_to_run.one? | |
24 | ||
25 | config.order = :random | |
26 | Kernel.srand config.seed | |
12 | 27 | end |