Codebase list ruby-slack-notifier / 57cfb5c
New upstream snapshot. Debian Janitor 1 year, 5 months ago
47 changed file(s) with 2108 addition(s) and 496 deletion(s). Raw diff Collapse all Expand all
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 ruby:
1 config_file: .rubocop.yml
0 --color
1 --require 'spec_helper'
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
11
22 [ Utkarsh Gupta ]
33 * Add salsa-ci.yml
1515 * Remove constraints unnecessary since buster:
1616 + Build-Depends: Drop versioned constraint on ruby-rspec.
1717 * Bump debhelper from old 12 to 13.
18 * New upstream snapshot.
1819
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
2021
2122 ruby-slack-notifier (1.5.1-2) unstable; urgency=medium
2223
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
-51
lib/slack-notifier/default_http_client.rb less more
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
-62
lib/slack-notifier/link_formatter.rb less more
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 = { "&" => "&amp;", ">" => "&gt;", "<" => "&lt;" }.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
02 module Slack
13 class Notifier
2 VERSION = "1.5.1"
4 VERSION = "2.4.0".freeze # rubocop:disable Style/RedundantFreeze
35 end
46 end
0 require 'net/http'
1 require 'uri'
2 require 'json'
0 # frozen_string_literal: true
31
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"
610
711 module Slack
812 class Notifier
9 attr_reader :endpoint, :default_payload
13 attr_reader :endpoint
1014
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
1427 end
1528
1629 def ping message, options={}
1730 if message.is_a?(Hash)
18 message, options = nil, message
31 options = message
32 else
33 options[:text] = message
1934 end
2035
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
4137 end
4238
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
4550 end
4651
47 def channel
48 default_payload[:channel]
49 end
52 private
5053
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 = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;' }
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)
7756 end
78 end
7957 end
8058 end
+0
-58
metadata.yml less more
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
01 # encoding: utf-8
1 require_relative '../../lib/slack-notifier'
22
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
-37
spec/lib/slack-notifier/default_http_client_spec.rb less more
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
-78
spec/lib/slack-notifier/link_formatter_spec.rb less more
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
11
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 }
48
59 describe "#initialize" do
610 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")
812 end
913
1014 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"
1317 end
1418
1519 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
1935 end
2036 end
2137
2238 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"
2544 end
45 end
2646
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
12254 end
12355 end
12456
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
13559
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"
13866 end
139 end
14067
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"
14577 end
146 end
14778
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)
15483
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 &gt; with &lt;" &amp; 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])
16095 end
16196 end
16297 end
0 require 'rspec'
1 require 'slack-notifier'
0 # frozen_string_literal: true
21
3 if ENV['DEBUG']
4 require 'pry'
5 end
2 require "rspec"
3 require "slack-notifier"
4 require "pry" if ENV["DEBUG"]
65
76 RSpec.configure do |config|
7 config.expect_with :rspec do |expectations|
8 expectations.include_chain_clauses_in_custom_matcher_descriptions = true
9 end
10
811 config.mock_with :rspec do |mocks|
912 mocks.verify_doubled_constant_names = true
1013 mocks.verify_partial_doubles = true
1114 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
1227 end