pax_global_header00006660000000000000000000000064140453143340014513gustar00rootroot0000000000000052 comment=29e51132ce0ef649035d9b5cbd5dedf66f8dffd3 slack-notifier-2.4.0/000077500000000000000000000000001404531433400144305ustar00rootroot00000000000000slack-notifier-2.4.0/.editorconfig000066400000000000000000000003541404531433400171070ustar00rootroot00000000000000; EditorConfig is awesome: http://EditorConfig.org root = true ; top-most EditorConfig file ; Unix-style newlines with a newline ending every file [*] end_of_line = lf indent_style = space indent_size = 2 insert_final_newline = true slack-notifier-2.4.0/.env-example000066400000000000000000000000501404531433400166450ustar00rootroot00000000000000SLACK_WEBHOOK_URL: "https://example.com"slack-notifier-2.4.0/.gitignore000066400000000000000000000001371404531433400164210ustar00rootroot00000000000000.DS_Store Gemfile.lock .rvmrc .ruby-version .ruby-gemset .env *.gem coverage spec/examples.txt slack-notifier-2.4.0/.hound.yml000066400000000000000000000000421404531433400163420ustar00rootroot00000000000000ruby: config_file: .rubocop.yml slack-notifier-2.4.0/.rspec000066400000000000000000000000401404531433400155370ustar00rootroot00000000000000--color --require 'spec_helper' slack-notifier-2.4.0/.rubocop.yml000066400000000000000000000025331404531433400167050ustar00rootroot00000000000000AllCops: DisplayCopNames: true DisplayStyleGuide: true Include: - 'Rakefile' - 'bin/test' # Naming ------------------------------------ Naming/FileName: Exclude: - lib/slack-notifier.rb - spec/lib/slack-notifier_spec.rb - slack-notifier.gemspec - Gemfile # Style ------------------------------------ Style/StringLiterals: EnforcedStyle: double_quotes Style/MethodDefParentheses: EnforcedStyle: require_no_parentheses_except_multiline Style/Documentation: Enabled: false Style/RegexpLiteral: Enabled: false Style/SignalException: Enabled: false Style/PercentLiteralDelimiters: PreferredDelimiters: '%w': '[]' # Layout ---------------------------------- Layout/MultilineOperationIndentation: EnforcedStyle: aligned Layout/MultilineMethodCallIndentation: EnforcedStyle: aligned Layout/MultilineOperationIndentation: Enabled: false Layout/SpaceAroundEqualsInParameterDefault: EnforcedStyle: no_space Layout/IndentationConsistency: EnforcedStyle: rails Layout/DotPosition: EnforcedStyle: leading # Metrics ---------------------------------- Metrics/LineLength: Max: 128 Metrics/AbcSize: Enabled: false Metrics/MethodLength: Enabled: false Metrics/BlockLength: Exclude: - spec/**/* # Lint ------------------------------------- Lint/EndAlignment: EnforcedStyleAlignWith: variable slack-notifier-2.4.0/.travis.yml000066400000000000000000000007721404531433400165470ustar00rootroot00000000000000language: ruby sudo: false cache: bundler bundler_args: "--without development" before_install: - "gem update --system" - "gem install bundler" rvm: - 3.0 - 2.7 - 2.6 - ruby-head - jruby-9.1.9.0 - jruby-head matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head notifications: slack: secure: Ld0tGBmwLG/ADOlLjO6ILq98+u/iq5qkuxAwN1E0SBOooALZZEJSc74jEMpgpnb22tk8QimUmLiCTE+8tWKaGiXTrvK6uvvfP6iiL9850NezHCxA3YMuWPnQQtJpTJ4135MMO8gJXu9vcswb9vW9N3v/A7VJdHbVZyT0vIMGas0= slack-notifier-2.4.0/Gemfile000066400000000000000000000006211404531433400157220ustar00rootroot00000000000000# frozen_string_literal: true source "https://rubygems.org" gemspec group :development do if RUBY_VERSION >= "2.0.0" gem "pry-byebug" else gem "pry-debugger" end gem "benchmark-ips" end group :test do gem "rake", "~> 12.0" gem "rspec", "~> 3.5.0" gem "rubocop", "~> 0.51", require: false if RUBY_VERSION >= "2.1" gem "string-scrub" if RUBY_VERSION <= "1.9.3" end slack-notifier-2.4.0/LICENSE000066400000000000000000000020671404531433400154420ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Steven Sloan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. slack-notifier-2.4.0/Rakefile000066400000000000000000000004621404531433400160770ustar00rootroot00000000000000# frozen_string_literal: true require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) begin require "rubocop/rake_task" rubocop = RuboCop::RakeTask.new rubocop.fail_on_error = false rescue LoadError task :rubocop do puts "Rubocop not loaded" end end task default: %i[spec rubocop] slack-notifier-2.4.0/bin/000077500000000000000000000000001404531433400152005ustar00rootroot00000000000000slack-notifier-2.4.0/bin/test000077500000000000000000000007641404531433400161140ustar00rootroot00000000000000#!/usr/bin/env ruby require 'yaml' YAML.load_file('.env').each do |key, var| ENV[key] = var end rubies = ['2.0.0', '2.1', '2.2', '2.3.3', '2.4.0'] rubies.each do |ruby| # cleanup gemfile.locks Dir['spec*/**/*.lock'].each do |lockfile| puts "removing #{lockfile}" system "rm #{lockfile}" end pid = Process.fork do exec "rvm #{ruby} do ruby spec/integration/ping_integration_test.rb" end trap 'INT' do puts 'exiting' pid.send(:exit) end Process.wait(pid) end slack-notifier-2.4.0/changelog.md000066400000000000000000000101161404531433400167000ustar00rootroot00000000000000# 2.4.0 - Make keyword argument usage compatible for ruby 3.x [@walski #123, @yuuu #119] # 2.3.2 - Improve compatability with CommonMark spec for markdown link formatting [@revolter #91] Still not 100% compliant, but it is now much closer. # 2.3.1 - use `map` to return the array of responses instead of payload in `ping` & `post` [@yhatt #88] # 2.3.0 - feat: add `channels` middleware to split payloads to ping multiple channels [#40] - feat: support any middleware splittin payload into an array to allow multiple payloads from a single process. # 2.2.2 - fix wrapping of attachments passed as a hash - fix error in `LinkFormatter` if a text payload was nil [#81] # 2.2.1 - fix loading error caused by uninitialized constant [@pocke #78] # 2.2.0 - raise exception when API responds with an error [@siegy22] # 2.1.0 - addition of :at middleware to simplify notifying users & rooms [@kazuooooo #66] # 2.0.0 [BREAKING] This is a fairly large change to how defaults are set and how messages are processed. **Setting Defaults** Setter methods are no longer available for setting defaults on a notifier instance, you'll now set defaults with a block on initialization. ```ruby # previously in 1.x notifier = Slack::Notifier.new WEBHOOK_URL, http_client: CustomClient notifier.channel = "sup" # in 2.x notifier = Slack::Notifier.new WEBHOOK_URL do http_client CustomClient defaults channel: "sup" end ``` Read more about [setting defaults in the readme](readme.md#setting-defaults) **Message Processing** 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) # 1.5.1 - allow using a single attachment w/o putting it in an array [@Elektron1c97 #47] # 1.5.0 - allow sending with attachments only [#48] # 1.4.0 - Format attachment messages with the LinkFormatter [@bhuga #37] - Add support for mailto links in markdown formatted links [@keithpitty #43] # 1.3.1 - Fix bug with link formatter for markdown links wrapped in square braces [@bhuga #36] # 1.3.0 - Add `#escape` to allow clients to escape special characters [@monkbroc #35] # 1.2.1 - 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. 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. # 1.2.0 - 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] # 1.1.0 - 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. # 1.0.0 - [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) # 0.6.1 - fix bug in link_formatter to allow multiple links in a message # 0.6.0 - add ability to pass in your own http client - [BREAKING!] hook name moves to options array # 0.5.0 - allow defaults to be set on initialization - remove channel formatting [#8] # 0.4.1 - allow default channel's to start with a "@" or "#" [#7] # 0.4.0 - try and correct for a channel name being set without a leading "#" [@dlackty] # 0.3.2 - add Net::HTTP wrapper to include support for ruby 1.9.3 # 0.3.1 - remove requirement for channel, no longer required by slack [@dlackty] # 0.3.0 - add custom hook endpoint parameter [@razielgn] # 0.2.0 - remove HTTParty dependency # 0.1.1 - loosen httparty dependency - refactor codebase & add specs # 0.1.0 - now formats html or markdown links in your message to match slack's format # 0.0.2 - fix a fat finger if a default channel is set # 0.0.1 - initial release slack-notifier-2.4.0/docs/000077500000000000000000000000001404531433400153605ustar00rootroot00000000000000slack-notifier-2.4.0/docs/upgrade-from-0.6.1.md000066400000000000000000000014761404531433400207420ustar00rootroot00000000000000Recently slack changed the way incoming webhooks are handled. Instead of taking a team name and token, they now provide a unique (obfuscated) webhook url. To upgrade the slack-notifier gem, you'll need to find your webhook url. In slack: - go to you're configured integrations (https://team-name.slack.com/services) - select **Incoming Webhooks** - select the webhook that uses the slack-notifier gem - find the webhook url under the heading **Integration Settings** You'll then change the way you initialize your notifier From: ```ruby notifier = Slack::Notifier.new 'team', 'token' ``` To: ```ruby notifier = Slack::Notifier.new 'WEBHOOK_URL' ``` Defaults & attachemnts will continue to work like they have ```ruby notifier = Slack::Notifier.new 'WEBHOOK_URL', icon_emoji: ":ghost:" notifier.ping "I'm feeling spooky" ``` slack-notifier-2.4.0/lib/000077500000000000000000000000001404531433400151765ustar00rootroot00000000000000slack-notifier-2.4.0/lib/slack-notifier.rb000066400000000000000000000025661404531433400204460ustar00rootroot00000000000000# frozen_string_literal: true require "uri" require "json" require_relative "slack-notifier/util/http_client" require_relative "slack-notifier/util/link_formatter" require_relative "slack-notifier/util/escape" require_relative "slack-notifier/payload_middleware" require_relative "slack-notifier/config" module Slack class Notifier attr_reader :endpoint def initialize webhook_url, options={}, &block @endpoint = URI.parse webhook_url config.http_client(options.delete(:http_client)) if options.key?(:http_client) config.defaults options config.instance_exec(&block) if block_given? middleware.set config.middleware end def config @_config ||= Config.new end def ping message, options={} if message.is_a?(Hash) options = message else options[:text] = message end post options end def post payload={} params = {} client = payload.delete(:http_client) || config.http_client payload = config.defaults.merge(payload) params[:http_options] = payload.delete(:http_options) if payload.key?(:http_options) middleware.call(payload).map do |pld| params[:payload] = pld.to_json client.post endpoint, params end end private def middleware @middleware ||= PayloadMiddleware::Stack.new(self) end end end slack-notifier-2.4.0/lib/slack-notifier/000077500000000000000000000000001404531433400201105ustar00rootroot00000000000000slack-notifier-2.4.0/lib/slack-notifier/config.rb000066400000000000000000000017621404531433400217100ustar00rootroot00000000000000# frozen_string_literal: true module Slack class Notifier class Config def initialize @http_client = Util::HTTPClient @defaults = {} @middleware = %i[ format_message format_attachments at channels ] end def http_client client=nil return @http_client if client.nil? raise ArgumentError, "the http client must respond to ::post" unless client.respond_to?(:post) @http_client = client end def defaults new_defaults=nil return @defaults if new_defaults.nil? raise ArgumentError, "the defaults must be a Hash" unless new_defaults.is_a?(Hash) @defaults = new_defaults end def middleware *args return @middleware if args.empty? @middleware = if args.length == 1 && args.first.is_a?(Array) || args.first.is_a?(Hash) args.first else args end end end end end slack-notifier-2.4.0/lib/slack-notifier/payload_middleware.rb000066400000000000000000000010741404531433400242650ustar00rootroot00000000000000# frozen_string_literal: true module Slack class Notifier class PayloadMiddleware class << self def registry @registry ||= {} end def register middleware, name registry[name] = middleware end end end end end require_relative "payload_middleware/stack" require_relative "payload_middleware/base" require_relative "payload_middleware/format_message" require_relative "payload_middleware/format_attachments" require_relative "payload_middleware/at" require_relative "payload_middleware/channels" slack-notifier-2.4.0/lib/slack-notifier/payload_middleware/000077500000000000000000000000001404531433400237365ustar00rootroot00000000000000slack-notifier-2.4.0/lib/slack-notifier/payload_middleware/at.rb000066400000000000000000000013201404531433400246630ustar00rootroot00000000000000# frozen_string_literal: true module Slack class Notifier class PayloadMiddleware class At < Base middleware_name :at options at: [] def call payload={} return payload unless payload[:at] payload[:text] = "#{format_ats(payload.delete(:at))}#{payload[:text]}" payload end private def format_ats ats Array(ats).map { |at| "<#{at_cmd_char(at)}#{at}> " } .join("") end def at_cmd_char at case at when :here, :channel, :everyone, :group "!" else "@" end end end end end end slack-notifier-2.4.0/lib/slack-notifier/payload_middleware/base.rb000066400000000000000000000013541404531433400252000ustar00rootroot00000000000000# frozen_string_literal: true module Slack class Notifier class PayloadMiddleware class Base class << self def middleware_name name PayloadMiddleware.register self, name.to_sym end def options default_opts @default_opts = default_opts end def default_opts @default_opts ||= {} end end attr_reader :notifier, :options def initialize notifier, opts={} @notifier = notifier @options = self.class.default_opts.merge opts end def call _payload={} raise NoMethodError, "method `call` not defined for class #{self.class}" end end end end end slack-notifier-2.4.0/lib/slack-notifier/payload_middleware/channels.rb000066400000000000000000000006701404531433400260610ustar00rootroot00000000000000# frozen_string_literal: true module Slack class Notifier class PayloadMiddleware class Channels < Base middleware_name :channels def call payload={} return payload unless payload[:channel].respond_to?(:to_ary) payload[:channel].to_ary.map do |channel| pld = payload.dup pld[:channel] = channel pld end end end end end end slack-notifier-2.4.0/lib/slack-notifier/payload_middleware/format_attachments.rb000066400000000000000000000020751404531433400301520ustar00rootroot00000000000000# frozen_string_literal: true module Slack class Notifier class PayloadMiddleware class FormatAttachments < Base middleware_name :format_attachments options formats: %i[html markdown] def call payload={} payload = payload.dup attachments = payload.delete(:attachments) attachments ||= payload.delete("attachments") attachments = wrap_array(attachments).map do |attachment| ["text", :text].each do |key| if attachment.key?(key) attachment[key] = Util::LinkFormatter.format(attachment[key], options) end end attachment end payload[:attachments] = attachments if attachments && !attachments.empty? payload end private def wrap_array object if object.nil? [] elsif object.respond_to?(:to_ary) object.to_ary || [object] else [object] end end end end end end slack-notifier-2.4.0/lib/slack-notifier/payload_middleware/format_message.rb000066400000000000000000000006441404531433400272630ustar00rootroot00000000000000# frozen_string_literal: true module Slack class Notifier class PayloadMiddleware class FormatMessage < Base middleware_name :format_message options formats: %i[html markdown] def call payload={} return payload unless payload[:text] payload[:text] = Util::LinkFormatter.format(payload[:text], options) payload end end end end end slack-notifier-2.4.0/lib/slack-notifier/payload_middleware/stack.rb000066400000000000000000000021051404531433400253660ustar00rootroot00000000000000# frozen_string_literal: true module Slack class Notifier class PayloadMiddleware class Stack attr_reader :notifier, :stack def initialize notifier @notifier = notifier @stack = [] end def set *middlewares middlewares = if middlewares.length == 1 && middlewares.first.is_a?(Hash) middlewares.first else middlewares.flatten end @stack = middlewares.map do |key, opts| PayloadMiddleware.registry.fetch(key).new(*[notifier, opts].compact) end end def call payload={} result = stack.inject payload do |pld, middleware| as_array(pld).flat_map do |p| middleware.call(p) end end as_array(result) end private def as_array args if args.respond_to?(:to_ary) args.to_ary else [args] end end end end end end slack-notifier-2.4.0/lib/slack-notifier/util/000077500000000000000000000000001404531433400210655ustar00rootroot00000000000000slack-notifier-2.4.0/lib/slack-notifier/util/escape.rb000066400000000000000000000005061404531433400226530ustar00rootroot00000000000000# frozen_string_literal: true module Slack class Notifier module Util module Escape HTML_REGEXP = /[&><]/ HTML_REPLACE = { "&" => "&", ">" => ">", "<" => "<" }.freeze def self.html string string.gsub(HTML_REGEXP, HTML_REPLACE) end end end end end slack-notifier-2.4.0/lib/slack-notifier/util/http_client.rb000066400000000000000000000031331404531433400237270ustar00rootroot00000000000000# frozen_string_literal: true require "net/http" module Slack class Notifier class APIError < StandardError; end module Util class HTTPClient class << self def post uri, params HTTPClient.new(uri, params).call end end attr_reader :uri, :params, :http_options def initialize uri, params @uri = uri @http_options = params.delete(:http_options) || {} @params = params end # rubocop:disable Layout/IndentHeredoc def call http_obj.request(request_obj).tap do |response| unless response.is_a?(Net::HTTPSuccess) raise Slack::Notifier::APIError, <<-MSG The slack API returned an error: #{response.body} (HTTP Code #{response.code}) Check the "Handling Errors" section on https://api.slack.com/incoming-webhooks for more information MSG end end end # rubocop:enable Layout/IndentHeredoc private def request_obj req = Net::HTTP::Post.new uri.request_uri req.set_form_data params req end def http_obj http = Net::HTTP.new uri.host, uri.port http.use_ssl = (uri.scheme == "https") http_options.each do |opt, val| if http.respond_to? "#{opt}=" http.send "#{opt}=", val else warn "Net::HTTP doesn't respond to `#{opt}=`, ignoring that option" end end http end end end end end slack-notifier-2.4.0/lib/slack-notifier/util/link_formatter.rb000066400000000000000000000042771404531433400244440ustar00rootroot00000000000000# frozen_string_literal: true module Slack class Notifier module Util class LinkFormatter # http://rubular.com/r/19cNXW5qbH HTML_PATTERN = %r{ (.+?) }x # the path portion of a url can contain these characters VALID_PATH_CHARS = '\w\-\.\~\/\?\#\=' # Attempt at only matching pairs of parens per # the markdown spec http://spec.commonmark.org/0.27/#links # # http://rubular.com/r/y107aevxqT MARKDOWN_PATTERN = %r{ \[ ([^\[\]]*?) \] \( ((https?://.*?) | (mailto:.*?)) \) (?! [#{VALID_PATH_CHARS}]* \) ) }x class << self def format string, opts={} LinkFormatter.new(string, **opts).formatted end end attr_reader :formats def initialize string, formats: %i[html markdown] @formats = formats @orig = string.respond_to?(:scrub) ? string.scrub : string end # rubocop:disable Lint/RescueWithoutErrorClass def formatted return @orig unless @orig.respond_to?(:gsub) sub_markdown_links(sub_html_links(@orig)) rescue => e raise e unless RUBY_VERSION < "2.1" && e.message.include?("invalid byte sequence") raise e, "#{e.message}. Consider including the 'string-scrub' gem to strip invalid characters" end # rubocop:enable Lint/RescueWithoutErrorClass private def sub_html_links string return string unless formats.include?(:html) string.gsub(HTML_PATTERN) do slack_link Regexp.last_match[1], Regexp.last_match[2] end end def sub_markdown_links string return string unless formats.include?(:markdown) string.gsub(MARKDOWN_PATTERN) do slack_link Regexp.last_match[2], Regexp.last_match[1] end end def slack_link link, text=nil "<#{link}" \ "#{text && !text.empty? ? "|#{text}" : ''}" \ ">" end end end end end slack-notifier-2.4.0/lib/slack-notifier/version.rb000066400000000000000000000002141404531433400221170ustar00rootroot00000000000000# frozen_string_literal: true module Slack class Notifier VERSION = "2.4.0".freeze # rubocop:disable Style/RedundantFreeze end end slack-notifier-2.4.0/readme.md000066400000000000000000000320571404531433400162160ustar00rootroot00000000000000A simple wrapper to send notifications to [Slack](https://slack.com/) webhooks. [![Build Status](https://travis-ci.org/stevenosloan/slack-notifier.svg?branch=master)](https://travis-ci.org/stevenosloan/slack-notifier) [![Code Climate](https://codeclimate.com/github/stevenosloan/slack-notifier.svg)](https://codeclimate.com/github/stevenosloan/slack-notifier) [![Gem Version](https://badge.fury.io/rb/slack-notifier.svg)](https://rubygems.org/gems/slack-notifier) [![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) ## Example ```ruby require 'slack-notifier' notifier = Slack::Notifier.new "WEBHOOK_URL" notifier.ping "Hello World" # => if your webhook is setup, will message "Hello World" # => to the default channel you set in slack ``` #### Installation Install the latest stable release: ``` $ gem install slack-notifier ``` Or with [Bundler](http://bundler.io/), add it to your Gemfile: ```ruby gem "slack-notifier" ``` #### Setting Defaults On initialization you can set default payloads by calling `defaults` in an initialization block: ```ruby notifier = Slack::Notifier.new "WEBHOOK_URL" do defaults channel: "#default", username: "notifier" end notifier.ping "Hello default" # => will message "Hello default" # => to the "#default" channel as 'notifier' ``` To get the WEBHOOK_URL you need: 1. go to https://slack.com/apps/A0F7XDUAZ-incoming-webhooks 2. choose your team, press configure 3. in configurations press add configuration 4. choose channel, press "Add Incoming WebHooks integration" You can also set defaults through an options hash: ```ruby notifier = Slack::Notifier.new "WEBHOOK_URL", channel: "#default", username: "notifier" ``` These defaults are over-ridable for any individual ping. ```ruby notifier.ping "Hello random", channel: "#random" # => will ping the "#random" channel ``` ## Links 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. Here's what it's doing under the covers: ```ruby message = "Hello world, [check](http://example.com) it out" Slack::Notifier::Util::LinkFormatter.format(message) # => "Hello world, it " ``` ## Formatting Slack supports various different formatting options. For example, if you want to alert an entire channel you include `` in your message ```ruby message = " hey check this out" notifier.ping message #ends up posting "@channel hey check this out" in your Slack channel ``` You can see [Slack's message documentation here](https://api.slack.com/docs/formatting) ## Escaping Since sequences starting with < have special meaning in Slack, you should use `Slack::Notifier::Util::Escape.html` if your messages may contain &, < or >. ```ruby link_text = Slack::Notifier::Util::Escape.html("User ") message = "Write to [#{link_text}](mailto:user@example.com)" notifier.ping message ``` ## Blocks 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. ```ruby blocks = [ { "type": "image", "title": { "type": "plain_text", "text": "image1", "emoji": true }, "image_url": "https://api.slack.com/img/blocks/bkb_template_images/onboardingComplex.jpg", "alt_text": "image1" }, { "type": "section", "text": { "type": "mrkdwn", "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:" } } ] notifier.post(blocks: blocks) ``` ## Additional parameters 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. Setting an icon: ```ruby notifier.post text: "feeling spooky", icon_emoji: ":ghost:" # or notifier.post text: "feeling chimpy", icon_url: "http://static.mailchimp.com/web/favicon.png" ``` Adding attachments: ```ruby a_ok_note = { fallback: "Everything looks peachy", text: "Everything looks peachy", color: "good" } notifier.post text: "with an attachment", attachments: [a_ok_note] ``` ## HTTP options 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. ```ruby notifier = Slack::Notifier.new 'WEBHOOK_URL', http_options: { open_timeout: 5 } notifier.post text: "hello", http_options: { open_timeout: 10 } ``` **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. ### Proxies `:http_options` can be used if you need to connect to Slack via an HTTP proxy. For example, to connect through a local squid proxy the following options would be used. ```ruby notifier = Slack::Notifier.new 'WEBHOOK_URL', http_options: { proxy_address: 'localhost', proxy_port: 3128, proxy_from_env: false } ``` ## Custom HTTP Client 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). A simple example: ```ruby module Client def self.post uri, params={} Net::HTTP.post_form uri, params end end notifier = Slack::Notifier.new 'WEBHOOK_URL' do http_client Client end ``` It's also encouraged for any custom HTTP implementations to accept the `:http_options` key in params. **Setting client per post** You can also set the http_client per-post if you need to special case certain pings. ```ruby notifier.post text: "hello", http_client: CustomClient ``` **Setting a No-Op client** 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. ```ruby class NoOpHTTPClient def self.post uri, params={} # bonus, you could log or observe posted params here end end notifier = Slack::Notifier.new 'WEBHOOK_URL' do http_client NoOpHTTPClient end ``` ## Middleware 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: ```ruby notifier = Slack::Notifier.new "WEBHOOK_URL" do middleware format_message: { formats: [:html] } end # this example will *only* use the format_message middleware and only format :html links notifier.post text: "Hello world! [visit this](http://example.com)" # => will post "Hello ! [visit this](http://example.com)" ``` The middleware can be set with a their name, or by name and options. They will be triggered in order. ```ruby notifier = Slack::Notifier.new "WEBHOOK_URL" do middleware :format_message, :format_attachments end # will run format_message then format_attachments with default options notifier = Slack::Notifier.new "WEBHOOK_URL" do middleware format_message: { formats: [:html] }, format_attachments: { formats: [:markdown] } end # will run format_message w/ formats [:html] then format_attachments with formats [:markdown] ``` Available middleware: **`format_message`** 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). **`format_attachments`** 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). **`at`** 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. For example: ```ruby notifier.post text: "hello", at: :casper # => "<@casper> hello" notifier.post text: "hello", at: [:here, :waldo] # => " <@waldo> hello" ``` **`channels`** If the `channel` argument of a payload is an array this splits the payload to be posted to each channel. For example: ```ruby notifier.post text: "hello", channel: ["default", "all_the_things"] # => will post "hello" to the default and all_the_things channel ``` 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. At the time of writing, one way to get a user's ID is to: - go to their profile - click **...** ("More actions") - click **Copy Member ID** ### Writing your own Middleware 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. For example, lets say we want to replace words in every message, we could write a middleware like this: ```ruby class SwapWords < Slack::Notifier::PayloadMiddleware::Base middleware_name :swap_words # this is the key we use when setting # the middleware stack for a notifier options pairs: ["hipchat" => "slack"] # the options takes a hash that will # serve as the default if not given any # when initialized def call payload={} return payload unless payload[:text] # noope if there is no message to work on # not efficient, but it's an example :) options[:pairs].each do |from, to| payload[:text] = payload[:text].gsub from, to end payload # always return the payload from your middleware end end notifier = Slack::Notifier.new "WEBHOOK_URL" do middleware :swap_words # setting our stack w/ just defaults end notifier.ping "hipchat is awesome!" # => pings slack with "slack is awesome!" notifier = Slack::Notifier.new "WEBHOOK_URL" do # here we set new options for the middleware middleware swap_words: { pairs: ["hipchat" => "slack", "awesome" => "really awesome"]} end notifier.ping "hipchat is awesome!" # => pings slack with "slack is really awesome!" ``` If your middleware returns an array, that will split the message into multiple pings. An example for pinging multiple channels: ```ruby class MultiChannel < Slack::Notifier::PayloadMiddleware::Base middleware_name :channels def call payload={} return payload unless payload[:channel].respond_to?(:to_ary) payload[:channel].to_ary.map do |channel| pld = payload.dup pld[:channel] = channel pld end end end ``` Versioning ---------- 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. To summarize the reasoning for versioning: ``` Given a version number MAJOR.MINOR.PATCH, increment: - MAJOR version when incompatible API changes are made - MINOR version for adding functionality in a backwards-compatible manner or bug fixes that *may* change behavior - PATCH version for make backwards-compatible bug fixes ``` Testing ------- ```bash $ rspec ``` There is also an integration test setup to just double check pinging across the supported rubies. To run: 1. Copy the `.env-example` file to `.env` and replace with your details. 2. Make sure `bin/test` is executable 3. then run and watch for the pings in your slack room ```bash $ bin/test ``` Contributing ------------ If there is any thing you'd like to contribute or fix, please: - Fork the repo - Add tests for any new functionality - Make your changes - Verify all new & existing tests pass - Make a pull request License ------- The slack-notifier gem is distributed under the MIT License. slack-notifier-2.4.0/slack-notifier.gemspec000066400000000000000000000012611404531433400207070ustar00rootroot00000000000000# frozen_string_literal: true require File.expand_path("../lib/slack-notifier/version", __FILE__) Gem::Specification.new do |s| s.name = "slack-notifier" s.version = Slack::Notifier::VERSION s.platform = Gem::Platform::RUBY s.summary = "A slim ruby wrapper for posting to slack webhooks" s.description = " A slim ruby wrapper for posting to slack webhooks " s.authors = ["Steven Sloan"] s.email = ["stevenosloan@gmail.com"] s.homepage = "http://github.com/stevenosloan/slack-notifier" s.license = "MIT" s.files = Dir["{lib}/**/*.rb"] s.test_files = Dir["spec/**/*.rb"] s.require_path = "lib" end slack-notifier-2.4.0/spec/000077500000000000000000000000001404531433400153625ustar00rootroot00000000000000slack-notifier-2.4.0/spec/end_to_end_spec.rb000066400000000000000000000075161404531433400210300ustar00rootroot00000000000000# frozen_string_literal: true # encoding: utf-8 require "spec_helper" RSpec.describe Slack::Notifier do { { text: "hello" } => { payload: { text: "hello" } }, { text: "[hello](http://example.com/world)" } => { payload: { text: "" } }, { text: 'example' } => { payload: { text: "" } }, { text: "hello/こんにちは from notifier test" } => { payload: { text: "hello/こんにちは from notifier test" } }, { text: "Hello World, enjoy [](http://example.com)." } => { payload: { text: "Hello World, enjoy ." } }, { text: "Hello World, enjoy [this](http://example.com)[this2](http://example2.com)" } => { payload: { text: "Hello World, enjoy " } }, { text: "[John](mailto:john@example.com)" } => { payload: { text: "" } }, { text: 'John' } => { payload: { text: "" } }, { text: "hello", channel: "hodor" } => { payload: { text: "hello", channel: "hodor" } }, { text: nil, attachments: [{ text: "attachment message" }] } => { payload: { text: nil, attachments: [{ text: "attachment message" }] } }, { text: "the message", channel: "foo", attachments: [{ color: "#000", text: "attachment message", fallback: "fallback message" }] } => { payload: { text: "the message", channel: "foo", attachments: [{ color: "#000", text: "attachment message", fallback: "fallback message" }] } }, { attachments: [{ color: "#000", text: "attachment message", fallback: "fallback message" }] } => { payload: { attachments: [{ color: "#000", text: "attachment message", fallback: "fallback message" }] } }, { attachments: { color: "#000", text: "attachment message [hodor](http://winterfell.com)", fallback: "fallback message" } } => { payload: { attachments: [{ color: "#000", text: "attachment message ", fallback: "fallback message" }] } }, { attachments: { color: "#000", text: nil, fallback: "fallback message" } } => { payload: { attachments: [{ color: "#000", text: nil, fallback: "fallback message" }] } }, { text: "hello", http_options: { timeout: 5 } } => { http_options: { timeout: 5 }, payload: { text: "hello" } } }.each do |args, payload| it "sends correct payload for #post(#{args})" do http_client = class_double("Slack::Notifier::Util::HTTPClient", post: nil) notifier = Slack::Notifier.new "http://example.com", http_client: http_client payload[:payload] = payload[:payload].to_json expect(http_client).to receive(:post) .with(URI.parse("http://example.com"), payload) notifier.post(args) end end it "applies options given to middleware" do client = class_double("Slack::Notifier::Util::HTTPClient", post: nil) notifier = Slack::Notifier.new "http://example.com" do http_client client middleware format_message: { formats: [] } end expect(client).to receive(:post) .with(URI.parse("http://example.com"), payload: { text: "Hello [world](http://example.com)!" }.to_json) notifier.post text: "Hello [world](http://example.com)!" end end slack-notifier-2.4.0/spec/integration/000077500000000000000000000000001404531433400177055ustar00rootroot00000000000000slack-notifier-2.4.0/spec/integration/ping_integration_test.rb000066400000000000000000000010131404531433400246240ustar00rootroot00000000000000# frozen_string_literal: true # encoding: utf-8 require_relative "../../lib/slack-notifier" ruby = if defined?(JRUBY_VERSION) "jruby #{JRUBY_VERSION}" else "ruby #{RUBY_VERSION}" end puts "testing with #{ruby}" notifier = Slack::Notifier.new ENV["SLACK_WEBHOOK_URL"], username: "notifier" notifier.ping "hello", channel: ["#general", "#random"] notifier.ping "hello/こんにちは from notifier test script on #{ruby}\225" notifier.ping attachments: [{ color: "#1BF5AF", fallback: "fallback", text: "attachment" }] slack-notifier-2.4.0/spec/lib/000077500000000000000000000000001404531433400161305ustar00rootroot00000000000000slack-notifier-2.4.0/spec/lib/slack-notifier/000077500000000000000000000000001404531433400210425ustar00rootroot00000000000000slack-notifier-2.4.0/spec/lib/slack-notifier/config_spec.rb000066400000000000000000000040671404531433400236550ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Slack::Notifier::Config do describe "#http_client" do it "is Util::HTTPClient if not set" do subject = described_class.new expect(subject.http_client).to eq Slack::Notifier::Util::HTTPClient end it "sets a new client class if given one" do new_client = class_double("Slack::Notifier::Util::HTTPClient", post: nil) subject = described_class.new subject.http_client new_client expect(subject.http_client).to eq new_client end it "raises an ArgumentError if given class does not respond to ::post" do subject = described_class.new expect do subject.http_client :nope end.to raise_error ArgumentError end end describe "#defaults" do it "is an empty hash by default" do subject = described_class.new expect(subject.defaults).to eq({}) end it "sets a hash to default if given" do subject = described_class.new subject.defaults foo: :bar expect(subject.defaults).to eq foo: :bar end it "raises ArgumentError if not given a hash" do subject = described_class.new expect do subject.defaults :nope end.to raise_error ArgumentError end end describe "#middleware" do it "is [:format_message, :format_attachments, :at] if not set" do subject = described_class.new expect(subject.middleware).to eq %i[format_message format_attachments at channels] end it "takes an array or a splat of args" do subject = described_class.new subject.middleware :layer, :two expect(subject.middleware).to eq %i[layer two] subject.middleware %i[one layer] expect(subject.middleware).to eq %i[one layer] end it "allows passing options to middleware stack" do subject = described_class.new subject.middleware one: { opts: :for_one }, two: { opts: :for_two } expect(subject.middleware).to eq one: { opts: :for_one }, two: { opts: :for_two } end end end slack-notifier-2.4.0/spec/lib/slack-notifier/payload_middleware/000077500000000000000000000000001404531433400246705ustar00rootroot00000000000000slack-notifier-2.4.0/spec/lib/slack-notifier/payload_middleware/at_spec.rb000066400000000000000000000014721404531433400266370ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Slack::Notifier::PayloadMiddleware::At do it "can handle array at" do subject = described_class.new(:notifier) payload = { text: "hello", at: %i[john ken here] } expect(subject.call(payload)).to eq text: "<@john> <@ken> hello" end it "can handle single at option" do subject = described_class.new(:notifier) payload = { text: "hello", at: :alice } expect(subject.call(payload)).to eq text: "<@alice> hello" end it "generates :text in payload if given :at & no :text" do subject = described_class.new(:notifier) input_payload = { at: [:here], attachments: [{ text: "hello" }] } output_payload = { text: " ", attachments: [{ text: "hello" }] } expect(subject.call(input_payload)).to eq output_payload end end slack-notifier-2.4.0/spec/lib/slack-notifier/payload_middleware/base_spec.rb000066400000000000000000000042071404531433400271440ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Slack::Notifier::PayloadMiddleware::Base do before(:each) do @registry_backup = Slack::Notifier::PayloadMiddleware.registry.dup Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry) end after(:each) do # cleanup middleware registry Slack::Notifier::PayloadMiddleware.registry Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry) # cleanup object constants Object.send(:remove_const, :Subject) if Object.constants.include?(:Subject) Slack::Notifier::PayloadMiddleware.send(:instance_variable_set, :@registry, @registry_backup) end describe "::middleware_name" do it "registers class w/ given name" do class Subject < Slack::Notifier::PayloadMiddleware::Base end expect(Slack::Notifier::PayloadMiddleware) .to receive(:register).with(Subject, :subject) class Subject middleware_name :subject end end it "uses symbolized name to register" do class Subject < Slack::Notifier::PayloadMiddleware::Base end expect(Slack::Notifier::PayloadMiddleware) .to receive(:register).with(Subject, :subject) class Subject middleware_name "subject" end end end describe "::options" do it "allows setting default options for a middleware" do class Subject < Slack::Notifier::PayloadMiddleware::Base options foo: :bar end subject = Subject.new(:notifier) expect(subject.options).to eq foo: :bar subject = Subject.new(:notifier, foo: :baz) expect(subject.options).to eq foo: :baz end end describe "#initialize" do it "sets given notifier as notifier" do expect(described_class.new(:notifier).notifier).to eq :notifier end it "sets given options as opts" do expect(described_class.new(:notifier, opts: :options).options).to eq opts: :options end end describe "#call" do it "raises NoMethodError (expects subclass to define)" do expect do described_class.new(:notifier).call end.to raise_exception NoMethodError end end end slack-notifier-2.4.0/spec/lib/slack-notifier/payload_middleware/channels_spec.rb000066400000000000000000000011541404531433400300230ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Slack::Notifier::PayloadMiddleware::Channels do it "leaves string channels alone" do subject = described_class.new(:notifier) payload = { text: "hello", channel: "hodor" } expect(subject.call(payload)).to eq text: "hello", channel: "hodor" end it "splits payload into multiple if given an array of channels" do subject = described_class.new(:notifier) payload = { text: "hello", channel: %w[foo hodor] } expect(subject.call(payload)).to eq [ { text: "hello", channel: "foo" }, { text: "hello", channel: "hodor" } ] end end slack-notifier-2.4.0/spec/lib/slack-notifier/payload_middleware/format_attachments_spec.rb000066400000000000000000000031631404531433400321150ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Slack::Notifier::PayloadMiddleware::FormatAttachments do it "passes the text of attachments through linkformatter with options[:formats]" do subject = described_class.new(:notifier, formats: [:html]) expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) .with("hello", formats: [:html]) subject.call(attachments: [{ text: "hello" }]) end it "searches through string or symbol keys" do subject = described_class.new(:notifier) expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) .with("hello", formats: %i[html markdown]) subject.call("attachments" => [{ "text" => "hello" }]) subject = described_class.new(:notifier) expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) .with("hello", formats: %i[html markdown]) subject.call(attachments: [{ text: "hello" }]) end it "can handle a single attachment" do subject = described_class.new(:notifier) expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) .with("hello", formats: %i[html markdown]) subject.call(attachments: { text: "hello" }) end it "wraps attachment into array if given as a single hash" do params = { attachments: { text: "hello" } } payload = { attachments: [{ text: "hello" }] } subject = described_class.new(:notifier) expect(subject.call(params)).to eq payload end it "returns the payload unmodified if not :attachments key" do payload = { foo: :bar } subject = described_class.new(:notifier) expect(subject.call(payload)).to eq payload end end slack-notifier-2.4.0/spec/lib/slack-notifier/payload_middleware/format_message_spec.rb000066400000000000000000000017221404531433400312250ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Slack::Notifier::PayloadMiddleware::FormatMessage do it "passes the text through linkformatter with options[:formats]" do subject = described_class.new(:notifier, formats: [:html]) expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) .with("hello", formats: [:html]) subject.call(text: "hello") subject = described_class.new(:notifier) expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) .with("hello", formats: %i[html markdown]) subject.call(text: "hello") subject = described_class.new(:notifier, formats: [:markdown]) expect(Slack::Notifier::Util::LinkFormatter).to receive(:format) .with("hello", formats: [:markdown]) subject.call(text: "hello") end it "returns the payload unmodified if not :text key" do payload = { foo: :bar } subject = described_class.new(:notifier) expect(subject.call(payload)).to eq payload end end slack-notifier-2.4.0/spec/lib/slack-notifier/payload_middleware/stack_spec.rb000066400000000000000000000075271404531433400273470ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Slack::Notifier::PayloadMiddleware::Stack do let(:return_one) do double(call: 1) end let(:return_one_twice) do double(call: [1, 1]) end let(:return_two) do double(call: 2) end let(:return_three) do double(call: 3) end before(:each) do # setup our middleware in the registry @registry_backup = Slack::Notifier::PayloadMiddleware.registry.dup Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry) Slack::Notifier::PayloadMiddleware.register return_one, :return_one Slack::Notifier::PayloadMiddleware.register return_one_twice, :return_one_twice Slack::Notifier::PayloadMiddleware.register return_two, :return_two Slack::Notifier::PayloadMiddleware.register return_three, :return_three end after(:each) do # cleanup middleware registry Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry) Slack::Notifier::PayloadMiddleware.send(:instance_variable_set, :@registry, @registry_backup) end describe "::initialize" do it "sets notifier to given notifier" do expect(described_class.new(:notifier).notifier).to eq :notifier end it "has empty stack" do expect(described_class.new(:notifier).stack).to match_array [] end end describe "#set" do it "initializes each middleware w/ the notifier instance" do expect(return_one).to receive(:new).with(:notifier) expect(return_two).to receive(:new).with(:notifier) described_class.new(:notifier).set(:return_one, :return_two) end it "creates the stack in an array" do allow(return_one).to receive(:new).and_return(return_one) allow(return_two).to receive(:new).and_return(return_two) subject = described_class.new(:notifier) subject.set(:return_one, :return_two) expect(subject.stack).to be_a Array expect(subject.stack.first.call).to eq 1 expect(subject.stack.last.call).to eq 2 end it "creates a stack from hashes passing them as opts" do expect(return_one).to receive(:new).with(:notifier, opts: :for_one) expect(return_two).to receive(:new).with(:notifier, opts: :for_two) subject = described_class.new(:notifier) subject.set return_one: { opts: :for_one }, return_two: { opts: :for_two } end it "raises if a middleware is missing" do expect do described_class.new(:notifier).set(:missing) end.to raise_exception KeyError end end describe "#call" do it "calls the middleware in order, passing return of each to the next" do allow(return_one).to receive(:new).and_return(return_one) allow(return_two).to receive(:new).and_return(return_two) allow(return_three).to receive(:new).and_return(return_three) subject = described_class.new(:notifier) subject.set(:return_one, :return_three, :return_two) expect(return_one).to receive(:call).with(5) expect(return_three).to receive(:call).with(1) expect(return_two).to receive(:call).with(3) expect(subject.call(5)).to eq [2] end it "allows any middleware to return an array but other's don't need special behavior" do allow(return_one_twice).to receive(:new).and_return(return_one_twice) allow(return_two).to receive(:new).and_return(return_two) subject = described_class.new(:notifier) subject.set(:return_one_twice, :return_two) expect(subject.call(5)).to eq [2, 2] end it "handles multiple middleware splitting payload" do allow(return_one_twice).to receive(:new).and_return(return_one_twice) allow(return_two).to receive(:new).and_return(return_two) subject = described_class.new(:notifier) subject.set(:return_one_twice, :return_one_twice, :return_two) expect(subject.call(5)).to eq [2, 2, 2, 2] end end end slack-notifier-2.4.0/spec/lib/slack-notifier/payload_middleware_spec.rb000066400000000000000000000017121404531433400262300ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Slack::Notifier::PayloadMiddleware do before(:each) do @registry_backup = described_class.registry.dup Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry) end after(:each) do described_class.send(:remove_instance_variable, :@registry) described_class.send(:instance_variable_set, :@registry, @registry_backup) end describe "::registry" do it "returns a hash if nothing set" do expect(described_class.registry).to eq({}) end it "returns memoized version if already set" do described_class.instance_variable_set(:@registry, "hodor") expect(described_class.registry).to eq "hodor" end end describe "::register" do it "adds given class to key in registry" do MyClass = Struct.new(:myclass) described_class.register MyClass, :my_class expect(described_class.registry[:my_class]).to eq MyClass end end end slack-notifier-2.4.0/spec/lib/slack-notifier/util/000077500000000000000000000000001404531433400220175ustar00rootroot00000000000000slack-notifier-2.4.0/spec/lib/slack-notifier/util/http_client_spec.rb000066400000000000000000000037161404531433400257020ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Slack::Notifier::Util::HTTPClient do describe "::post" do it "initializes Util::HTTPClient with the given uri and params then calls" do http_post_double = instance_double("Slack::Notifier::Util::HTTPClient") expect(described_class) .to receive(:new).with("uri", "params") .and_return(http_post_double) expect(http_post_double).to receive(:call) described_class.post "uri", "params" end # http_post is really tested in the integration spec, # where the internals are run through end describe "#initialize" do it "allows setting of options for Net::HTTP" do net_http_double = instance_double("Net::HTTP") http_client = described_class.new URI.parse("http://example.com"), http_options: { open_timeout: 5 } allow(Net::HTTP).to receive(:new).and_return(net_http_double) allow(net_http_double).to receive(:use_ssl=) allow(net_http_double).to receive(:request).with(anything) do Net::HTTPOK.new("GET", "200", "OK") end expect(net_http_double).to receive(:open_timeout=).with(5) http_client.call end end describe "#call" do it "raises an error when the response is unsuccessful" do net_http_double = instance_double("Net::HTTP") http_client = described_class.new URI.parse("http://example.com"), {} bad_request = Net::HTTPBadRequest.new("GET", "400", "Bad Request") allow(bad_request).to receive(:body).and_return("something_bad") allow(Net::HTTP).to receive(:new).and_return(net_http_double) allow(net_http_double).to receive(:use_ssl=) allow(net_http_double).to receive(:request).with(anything) do bad_request end expect { http_client.call }.to raise_error(Slack::Notifier::APIError, /something_bad \(HTTP Code 400\)/) end end end slack-notifier-2.4.0/spec/lib/slack-notifier/util/link_formatter_spec.rb000066400000000000000000000170711404531433400264040ustar00rootroot00000000000000# frozen_string_literal: true # encoding: utf-8 # rubocop:disable Metrics/LineLength RSpec.describe Slack::Notifier::Util::LinkFormatter do describe "initialize & formatted" do it "can be initialized without format args" do subject = described_class.new("Hello World") expect(subject.formatted()).to eq("Hello World") end it "can be initialized with format args" do subject = described_class.new("Hello World", formats: [:html]) expect(subject.formatted()).to eq("Hello World") end end describe "::format" do it "formats html links" do formatted = described_class.format("Hello World, enjoy this.") expect(formatted).to include("") end it "formats markdown links" do formatted = described_class.format("Hello World, enjoy [this](http://example.com).") expect(formatted).to include("") end it "formats markdown links in brackets" do formatted = described_class.format("Hello World, enjoy [[this](http://example.com) in brackets].") expect(formatted).to eq("Hello World, enjoy [ in brackets].") end it "formats markdown links with no title" do formatted = described_class.format("Hello World, enjoy [](http://example.com).") expect(formatted).to include("") end it "handles multiple html links" do formatted = described_class.format("Hello World, enjoy thisthis2.") expect(formatted).to include("") expect(formatted).to include("") end it "handles multiple markdown links" do formatted = described_class.format("Hello World, enjoy [this](http://example.com)[this2](http://example2.com).") expect(formatted).to include("") expect(formatted).to include("") end it "handles mixed html & markdown links" do formatted = described_class.format("Hello World, enjoy [this](http://example.com)this2.") expect(formatted).to include("") expect(formatted).to include("") end if "".respond_to? :scrub context "when on ruby 2.1+ or have string-scrub installed" do it "handles invalid unicode sequences" do expect do described_class.format("This sequence is invalid: \255") end.not_to raise_error end it "replaces invalid unicode sequences with the unicode replacement character" do formatted = described_class.format("\255") expect(formatted).to eq "\uFFFD" end end end it "doesn't replace valid Japanese" do formatted = described_class.format("こんにちは") expect(formatted).to eq "こんにちは" end it "handles mailto links in markdown" do formatted = described_class.format("[John](mailto:john@example.com)") expect(formatted).to eq "" end it "handles mailto links in html" do formatted = described_class.format("John") expect(formatted).to eq "" end it "handles links with trailing parentheses" do formatted = described_class.format("Hello World, enjoy [foo(bar)](http://example.com/foo(bar))bar(foo)") expect(formatted).to include("http://example.com/foo(bar)|foo(bar)") expect(formatted).to include("http://example.com/bar(foo)|bar(foo)") end it "formats a number of differently formatted links" do input_output = { "Hello World, enjoy [this](http://example.com)." => "Hello World, enjoy .", "Hello World, enjoy [[this](http://example.com) in brackets]." => "Hello World, enjoy [ in brackets].", "Hello World, enjoy ([this](http://example.com) in parens)." => "Hello World, enjoy ( in parens).", "Hello World, enjoy [](http://example.com)." => "Hello World, enjoy .", "Hello World, enjoy [link with query](http://example.com?foo=bar)." => "Hello World, enjoy .", "Hello World, enjoy [link with fragment](http://example.com/#foo-bar)." => "Hello World, enjoy .", "Hello World, enjoy [link with parens](http://example.com/foo(bar)/baz)." => "Hello World, enjoy .", "Hello World, enjoy [link with query](http://example.com/(parens)?foo=bar)." => "Hello World, enjoy .", "Hello World, enjoy [link with parens](http://example.com/baz?bang=foo(bar))." => "Hello World, enjoy .", "Hello World, enjoy [link with fragment](http://example.com/(parens)#foo-bar)." => "Hello World, enjoy .", "Hello World, enjoy [link with fragment](http://example.com/#foo-bar=(baz))." => "Hello World, enjoy .", "Hello World, enjoy [this](http://example.com?foo=bar)[this2](http://example2.com)." => "Hello World, enjoy .", "Hello World, enjoy [this](http://example.com?foo=bar) [this2](http://example2.com/#fragment)." => "Hello World, enjoy .", "Hello World, enjoy [this](http://example.com)this2." => "Hello World, enjoy .", "Hello world, [John](mailto:john@example.com)." => "Hello world, .", "Hello World, enjoy [foo(bar)](http://example.com/foo(bar))bar(foo)" => "Hello World, enjoy " } input_output.each do |input, output| expect(described_class.format(input)).to eq output end end context "with a configured stack" do it "only formats html if html is the only item in formats" do formatted = described_class.format("Hello World, enjoy [this](http://example.com)this2.", formats: [:html]) expect(formatted).to eq "Hello World, enjoy [this](http://example.com)." end it "only formats markdown if markdown is the only item in formats" do formatted = described_class.format("Hello World, enjoy [this](http://example.com)this2.", formats: [:markdown]) expect(formatted).to eq "Hello World, enjoy this2." end it "doesn't format if formats is empty" do formatted = described_class.format("Hello World, enjoy [this](http://example.com)this2.", formats: []) expect(formatted).to eq "Hello World, enjoy [this](http://example.com)this2." end end end end slack-notifier-2.4.0/spec/lib/slack-notifier_spec.rb000066400000000000000000000055711404531433400224110ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Slack::Notifier do let(:mock_http) do class_double("Slack::Notifier::Util::HTTPClient", post: :posted) end subject { described_class.new "http://example.com", http_client: mock_http } describe "#initialize" do it "sets the given hook_url to the endpoint URI" do expect(subject.endpoint).to eq URI.parse("http://example.com") end it "sets the default_payload options" do subject = described_class.new "http://example.com", channel: "foo" expect(subject.config.defaults[:channel]).to eq "foo" end it "sets a custom http client" do subject = described_class.new "http://example.com", http_client: mock_http expect(subject.config.http_client).to eq mock_http end describe "when given a block" do it "yields the config object" do test_double = double("Slack::Notifier::Config", defaults: {}, middleware: []) allow_any_instance_of(Slack::Notifier).to receive(:config).and_return(test_double) expect(test_double).to receive(:test_init_method).with("foo") described_class.new "http://example.com" do test_init_method "foo" end end end end describe "#ping" do it "calls #post with the message as the text key in #post" do subject = described_class.new "http://example.com" expect(subject).to receive(:post).with text: "message" subject.ping "message" end end describe "#post" do def notifier_with_defaults mock_client = mock_http described_class.new "http://example.com" do defaults channel: "default", user: "rocket" http_client mock_client end end it "uses the defaults set on initialization" do subject = notifier_with_defaults expect(mock_http).to receive(:post).with( URI.parse("http://example.com"), payload: '{"channel":"default","user":"rocket","text":"hello"}' ) subject.post text: "hello" end it "allows overriding the set defaults" do subject = notifier_with_defaults expect(mock_http).to receive(:post).with( URI.parse("http://example.com"), payload: '{"channel":"new","user":"ship","text":"hello"}' ) subject.post text: "hello", channel: "new", user: "ship" end it "calls the middleware stack with the payload" do subject = notifier_with_defaults stack = instance_double("Slack::Notifier::PayloadMiddleware::Stack") subject.instance_variable_set(:@middleware, stack) expect(stack).to receive(:call) .with(channel: "default", user: "rocket") .and_return([test: "stack"]) expect(mock_http).to receive(:post).with( URI.parse("http://example.com"), payload: '{"test":"stack"}' ) responses = subject.post expect(responses).to eq([:posted]) end end end slack-notifier-2.4.0/spec/spec_helper.rb000066400000000000000000000013531404531433400202020ustar00rootroot00000000000000# frozen_string_literal: true require "rspec" require "slack-notifier" require "pry" if ENV["DEBUG"] RSpec.configure do |config| config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end config.mock_with :rspec do |mocks| mocks.verify_doubled_constant_names = true mocks.verify_partial_doubles = true end config.filter_run :focus config.run_all_when_everything_filtered = true config.disable_monkey_patching! config.example_status_persistence_file_path = "spec/examples.txt" config.warnings = ENV["DEBUG"] ? false : true config.default_formatter = "doc" if config.files_to_run.one? config.order = :random Kernel.srand config.seed end