1
0
зеркало из https://github.com/glebtv/telegram-bot.git synced 2026-08-28 15:26:18 +03:00

Сравнить коммиты

..

17 Коммитов

Автор SHA1 Сообщение Дата
Max Melentiev
c86b2ef09b v0.13.0 2018-02-02 11:03:14 +03:00
Max Melentiev
bfcb1c5f41 Deleted page from wiki with old routes examples, remove this link from readme 2018-02-02 11:01:22 +03:00
Max Melentiev
88c8d4fd4e Deprecate telegram_webhooks in favor of telegram_webhook 2018-02-02 10:52:48 +03:00
Max Melentiev
06253c940e Extract methods in UpdatesPoller 2018-02-02 10:37:45 +03:00
printercu
eb24816e1e Merge pull request #61 from telegram-bot-rb/test-cc
Setup codeclimate integration
2018-01-18 14:12:11 +03:00
Max Melentiev
a67a643c7b Build new rubies on travis 2018-01-18 14:04:39 +03:00
Max Melentiev
28db729320 Update changelog 2018-01-18 12:43:10 +03:00
Max Melentiev
7f1f2bb884 Fix readme 2018-01-18 12:42:11 +03:00
Max Melentiev
88ae24b7e8 Fix codeclimate issues 2018-01-18 12:42:04 +03:00
Max Melentiev
3a876a42df Setup codeclimate 2018-01-18 12:31:32 +03:00
Yuri S
66991d3653 Support for :credentials on Rails v5.2.x. (#60) 2018-01-17 10:40:24 +03:00
Max Melentiev
5407c5028f Fix specs for rails 4 2018-01-09 12:00:49 +03:00
Max Melentiev
b41032eee9 Update rubocop 2018-01-05 14:21:08 +03:00
Max Melentiev
707e532963 Specs for instrumentation 2018-01-05 14:20:19 +03:00
Max Melentiev
45e8a7283f rescue_from feature 2018-01-05 13:19:44 +03:00
Max Melentiev
70a1ed8a8f Compact requires 2018-01-05 12:52:46 +03:00
Max Melentiev
384d203b59 Fix spec helpers for callback queries 2017-12-04 09:47:17 +03:00
30 изменённых файлов: 508 добавлений и 128 удалений

8
.codeclimate.yml Обычный файл
Просмотреть файл

@@ -0,0 +1,8 @@
checks:
method-complexity:
config:
threshold: 6 # should be just fine
plugins:
rubocop:
enabled: true

Просмотреть файл

@@ -1,3 +1,6 @@
# Rubocop does not support 2.0
Gemspec/RequiredRubyVersion: {Enabled: false}
Rails: {Enabled: true}
Layout/AlignParameters:

Просмотреть файл

@@ -1,10 +1,17 @@
language: ruby
cache: bundler
rvm:
- 2.2.3
- 2.5
- 2.4
- 2.3
gemfile:
- gemfiles/rails_42.gemfile
- gemfiles/rails_50.gemfile
- gemfiles/rails_52.gemfile
- gemfiles/rails_51.gemfile
- gemfiles/rails_50.gemfile
- gemfiles/rails_42.gemfile
notifications:
email: false
# for 2.5.0 until 2.5.1 is released: https://github.com/travis-ci/travis-ci/issues/8978
before_install:
- gem update --system

Просмотреть файл

@@ -1,3 +1,8 @@
appraise 'rails-52' do
gem 'actionpack', '~> 5.2.0.beta2'
gem 'railties', '~> 5.2.0.beta2'
end
appraise 'rails-51' do
gem 'actionpack', '~> 5.1'
gem 'railties', '~> 5.1'

Просмотреть файл

@@ -1,6 +1,17 @@
# Unreleased
# 0.12.2
# 0.13.0
- `rescue_from`.
- Support for `credentials` store in Rails 5.2.
- Deprecate `telegram_webhooks` in favor of `telegram_webhook`.
It was too complicated and such routes looked ugly.
# 0.12.4
- Fix spec helpers for callback queries.
# 0.12.3
- New methods from Bot API v3.5
- Collect all api helper-methods in Client::ApiHelper module.

Просмотреть файл

@@ -14,7 +14,7 @@ group :development do
gem 'rspec-its', '~> 1.1.0'
gem 'rspec-rails', '~> 3.5.0'
gem 'rubocop', '~> 0.51.0'
gem 'rubocop', '~> 0.52.1'
gem 'coveralls', '~> 0.8.2', require: false
end

Просмотреть файл

@@ -130,6 +130,9 @@ can represent actions as separate methods keeping source much more readable and
New instance of controller is instantiated for each update.
This way every update is processed in isolation from others.
Bot controllers like usual rails controllers provides features like callbacks,
`rescue_from` and instrumentation.
```ruby
class Telegram::WebhookController < Telegram::Bot::UpdatesController
# use callbacks like in any other controllers
@@ -325,29 +328,37 @@ Callback queries without prefix stay untouched.
# This one handles `set_value:%{something}`.
def set_value_callback_query(new_value = nil, *)
save_this(value)
answer_callback_query('Saved!)
answer_callback_query('Saved!')
end
# And this one is for `make_cool:%{something}`
def make_cool_callback_query(thing = nil, *)
do_it(thing)
answer_callback_query("#{thing} is cool now! Like a callback query context.")
answer_callback_query("#{thing} is cool now! Like a callback query context.")
end
```
### Routes in Rails app
There is `telegram_webhooks` helper for rails app to define routes for webhooks.
There is `telegram_webhook` helper for rails app to define routes for webhooks.
It defines routes at `telegram/#{bot.token}` and connects bots with controller.
For more options see [examples in wiki](https://github.com/telegram-bot-rb/telegram-bot/wiki/Routes-helpers-in-details).
```ruby
# Create routes for all Telegram.bots using single controller:
telegram_webhooks TelegramController
# Most off apps would require
telegram_webhook TelegramController
# which is same as
telegram_webhook TelegramController, :default
# Use different controllers for each bot:
telegram_webhooks chat: TelegramChatController,
auction: TelegramAuctionController
telegram_webhook TelegramChatController, :chat
telegram_webhook TelegramAuctionController, :auction
# Defined route is named and its name depends on `Telegram.bots`.
# For single bot it will use 'telegram_webhook',
# for multiple bots it uses bot's key in the `Telegram.bots` as prefix
# (eg. `chat_telegram_webhook`).
# You can override this with `as` option:
telegram_webhook TelegramController, as: :custom_telegram_webhook
```
#### Processesing updates
@@ -360,7 +371,7 @@ There are several options to run it automatically:
- Use poller (described in the next section).
To run action without update (ex., send notifications from jobs),
you can call `#process` directly. In this case controller can be initialized
you can call `#process` directly. In this case controller can be initialized
with `:from` and/or `:chat` options instead of `update` object:
```ruby
@@ -374,7 +385,7 @@ Use `rake telegram:bot:poller` to run poller in rails app. It automatically load
changes without restart in development env.
Optionally pass bot id in `BOT` envvar (`BOT=chat`) to specify bot to run poller for.
This task requires `telegram_webhooks` helper to be used as it connects bots with controller.
This task requires `telegram_webhook` helper to be used as it connects bots with controller.
To run poller in other cases use:
```ruby
@@ -523,7 +534,7 @@ Yes, it's threadsafe too.
## Development
After checking out the repo, run `bin/setup` to install dependencies.
After checking out the repo, run `bin/setup` to install dependencies and git hooks.
Then, run `appraisal rake spec` to run the tests.
You can also run `bin/console` for an interactive prompt that will allow you to experiment.

Просмотреть файл

@@ -14,7 +14,7 @@ group :development do
gem "rspec", "~> 3.5.0"
gem "rspec-its", "~> 1.1.0"
gem "rspec-rails", "~> 3.5.0"
gem "rubocop", "~> 0.51.0"
gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false
end

Просмотреть файл

@@ -14,7 +14,7 @@ group :development do
gem "rspec", "~> 3.5.0"
gem "rspec-its", "~> 1.1.0"
gem "rspec-rails", "~> 3.5.0"
gem "rubocop", "~> 0.51.0"
gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false
end

Просмотреть файл

@@ -14,7 +14,7 @@ group :development do
gem "rspec", "~> 3.5.0"
gem "rspec-its", "~> 1.1.0"
gem "rspec-rails", "~> 3.5.0"
gem "rubocop", "~> 0.51.0"
gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false
end

21
gemfiles/rails_52.gemfile Обычный файл
Просмотреть файл

@@ -0,0 +1,21 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "actionpack", "~> 5.2.0.beta2"
gem "railties", "~> 5.2.0.beta2"
group :development do
gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1"
gem "pry-byebug", "~> 3.2.0"
gem "sdoc", "~> 0.4.1"
gem "telegram-bot-types", "~> 0.3.0"
gem "rspec", "~> 3.5.0"
gem "rspec-its", "~> 1.1.0"
gem "rspec-rails", "~> 3.5.0"
gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false
end
gemspec path: "../"

Просмотреть файл

@@ -5,9 +5,22 @@ module Telegram
module Bot
class Error < StandardError; end
class NotFound < Error; end
# Raised for valid telegram response with 403 status code.
class Forbidden < Error; end
# Raised for valid telegram response with 404 status code.
class NotFound < Error; end
module_function
def deprecation_0_14
@deprecation ||= begin
require 'active_support/deprecation'
ActiveSupport::Deprecation.new('0.14', 'Telegram::Bot')
end
end
autoload :Async, 'telegram/bot/async'
autoload :Botan, 'telegram/bot/botan'
autoload :Client, 'telegram/bot/client'

Просмотреть файл

@@ -4,7 +4,7 @@ require 'httpclient'
module Telegram
module Bot
class Client
URL_TEMPLATE = 'https://api.telegram.org/bot%s/'.freeze
URL_TEMPLATE = 'https://api.telegram.org/bot%<token>s/'.freeze
autoload :TypedResponse, 'telegram/bot/client/typed_response'
extend Initializers
@@ -36,6 +36,18 @@ module Telegram
def prepare_async_args(action, body = {})
[action.to_s, Async.prepare_hash(prepare_body(body))]
end
def error_for_response(response)
result = JSON.parse(response.body) rescue nil # rubocop:disable RescueModifier
return Error.new(response.reason) unless result
message = result['description'] || '-'
# This errors are raised only for valid responses from Telegram
case response.status
when 403 then Forbidden.new(message)
when 404 then NotFound.new(message)
else Error.new("#{response.reason}: #{message}")
end
end
end
attr_reader :client, :token, :username, :base_uri
@@ -44,23 +56,13 @@ module Telegram
@client = HTTPClient.new
@token = token || options[:token]
@username = username || options[:username]
@base_uri = format URL_TEMPLATE, self.token
@base_uri = format(URL_TEMPLATE, token: self.token)
end
def request(action, body = {})
res = http_request("#{base_uri}#{action}", self.class.prepare_body(body))
status = res.status
return JSON.parse(res.body) if status < 300
result = JSON.parse(res.body) rescue nil # rubocop:disable RescueModifier
err_msg = result && result['description'] || '-'
if result
# This errors are raised only for valid responses from Telegram
case status
when 403 then raise Forbidden, err_msg
when 404 then raise NotFound, err_msg
end
end
raise Error, "#{res.reason}: #{err_msg}"
response = http_request("#{base_uri}#{action}", self.class.prepare_body(body))
raise self.class.error_for_response(response) if response.status >= 300
JSON.parse(response.body)
end
# Endpoint for low-level request. For easy host highjacking & instrumentation.

Просмотреть файл

@@ -53,8 +53,9 @@ module Telegram
def bots_config
@bots_config ||=
if defined?(Rails.application)
secrets = Rails.application.secrets.
fetch(:telegram, {}).with_indifferent_access
app = Rails.application
store = app.respond_to?(:credentials) ? app.credentials : app.secrets
secrets = store.fetch(:telegram, {}).with_indifferent_access
secrets.fetch(:bots, {}).symbolize_keys.tap do |config|
default = secrets[:bot]
config[:default] = default if default

Просмотреть файл

@@ -28,40 +28,49 @@ module Telegram
# telegram_webhooks TelegramController
#
# # Or pass custom bots usin any of supported config options:
# telegram_webhooks TelegramController,
# bot,
# {token: token, username: username},
# other_bot_token
#
# # Use different controllers for each bot:
# telegram_webhooks bot => TelegramChatController,
# other_bot => TelegramAuctionController
#
# # telegram_webhooks creates named routes. See
# # RoutesHelper.route_name_for_bot for more info.
# # You can override this options or specify others:
# telegram_webhooks TelegramController, as: :my_webhook
# telegram_webhooks bot => [TelegramChatController, as: :chat_webhook],
# other_bot => TelegramAuctionController,
# admin_chat: TelegramAdminChatController
#
# telegram_webhooks TelegramController, [
# bot,
# {token: token, username: username},
# other_bot_token,
# ]
def telegram_webhooks(controllers, bots = nil, **options)
Bot.deprecation_0_14.deprecation_warning(:telegram_webhooks, <<-TXT.strip_heredoc)
It brings unnecessary complexity and encourages writeng less readable code.
Please use telegram_webhook method instead.
It's signature `telegram_webhook(controller, bot = :default, **options)`.
Multiple-bot environments now requires calling this method in a loop
or using statement for each bot.
TXT
unless controllers.is_a?(Hash)
bots = bots ? Array.wrap(bots) : Telegram.bots.values
controllers = Hash[bots.map { |x| [x, controllers] }]
end
controllers.each do |bot, controller|
bot = Client.wrap(bot)
controller, bot_options = controller if controller.is_a?(Array)
params = {
to: Middleware.new(bot, controller),
as: RoutesHelper.route_name_for_bot(bot),
format: false,
}.merge!(options).merge!(bot_options || {})
post("telegram/#{RoutesHelper.escape_token bot.token}", params)
UpdatesPoller.add(bot, controller) if Telegram.bot_poller_mode?
telegram_webhook(controller, bot, options.merge(bot_options || {}))
end
end
# Define route which processes requests using given controller and bot.
#
# telegram_webhook TelegramController, bot
#
# telegram_webhook TelegramController
# # same as:
# telegram_webhook TelegramController, :default
#
# # pass additional options
# telegram_webhook TelegramController, :default, as: :custom_route_name
def telegram_webhook(controller, bot = :default, **options)
bot = Client.wrap(bot)
params = {
to: Middleware.new(bot, controller),
as: RoutesHelper.route_name_for_bot(bot),
format: false,
}.merge!(options)
post("telegram/#{RoutesHelper.escape_token bot.token}", params)
UpdatesPoller.add(bot, controller) if Telegram.bot_poller_mode?
end
end
end
end

Просмотреть файл

@@ -1,8 +1,10 @@
RSpec.shared_context 'telegram/bot/integration' do
let(:bot) { Telegram.bot }
let(:default_message_options) { {from: from, chat: chat} }
let(:from) { {id: from_id} }
let(:from_id) { 123 }
let(:chat) { {id: chat_id} }
let(:chat_id) { 456 }
let(:default_message_options) { {from: {id: from_id}, chat: {id: chat_id}} }
let(:controller_path) do
route_name = Telegram::Bot::RoutesHelper.route_name_for_bot(bot)
Rails.application.routes.url_helpers.public_send("#{route_name}_path")

Просмотреть файл

@@ -53,12 +53,19 @@ module Telegram
class UpdatesController < AbstractController::Base # rubocop:disable ClassLength
abstract!
require 'telegram/bot/updates_controller/session'
require 'telegram/bot/updates_controller/log_subscriber'
require 'telegram/bot/updates_controller/instrumentation'
require 'telegram/bot/updates_controller/reply_helpers'
autoload :CallbackQueryContext, 'telegram/bot/updates_controller/callback_query_context'
autoload :MessageContext, 'telegram/bot/updates_controller/message_context'
%w[
instrumentation
log_subscriber
reply_helpers
rescue
session
].each { |file| require "telegram/bot/updates_controller/#{file}" }
%w[
CallbackQueryContext
MessageContext
TypedUpdate
].each { |mod| autoload mod, "telegram/bot/updates_controller/#{mod.underscore}" }
include AbstractController::Callbacks
# Redefine callbacks with default terminator.
@@ -72,11 +79,11 @@ module Telegram
end
include AbstractController::Translation
include Rescue
include ReplyHelpers
prepend Instrumentation
extend Session::ConfigMethods
include Instrumentation
autoload :TypedUpdate, 'telegram/bot/updates_controller/typed_update'
extend Session::ConfigMethods
PAYLOAD_TYPES = %w[
message
@@ -113,10 +120,17 @@ module Telegram
# any commands.
def command_from_text(text, username = nil)
return unless text
match = text.match CMD_REGEX
match = text.match(CMD_REGEX)
return unless match
return if match[3] && username != true && match[3] != username
[match[1], text.split.drop(1)]
mention = match[3]
[match[1], text.split.drop(1)] if username == true || !mention || mention == username
end
def payload_from_update(update)
update && PAYLOAD_TYPES.find do |type|
item = update[type]
return [item, type] if item
end
end
end
@@ -135,13 +149,7 @@ module Telegram
@_update = update
@_bot = bot
@_chat, @_from = options && options.values_at(:chat, :from)
payload_data = nil
update && PAYLOAD_TYPES.find do |type|
item = update[type]
payload_data = [item, type] if item
end
@_payload, @_payload_type = payload_data
@_payload, @_payload_type = self.class.payload_from_update(update)
end
# Accessor to `'chat'` field of payload. Also tries `'chat'` in `'message'`

Просмотреть файл

@@ -4,12 +4,13 @@ module Telegram
# Most methods are taken from ActionController::Instrumentation,
# some are slightly modified.
module Instrumentation
class << self
def prepended(base)
base.send :config_accessor, :logger
base.extend ClassMethods
end
extend ActiveSupport::Concern
included do
config_accessor :logger
end
class << self
def instrument(action, *args, &block)
ActiveSupport::Notifications.instrument(
"#{action}.updates_controller.bot.telegram",

Просмотреть файл

@@ -0,0 +1,20 @@
require 'active_support/rescuable'
module Telegram
module Bot
class UpdatesController
module Rescue
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
private
def process_action(*)
super
rescue Exception => exception # rubocop:disable RescueException
rescue_with_handler(exception) || raise
end
end
end
end
end

Просмотреть файл

@@ -41,40 +41,54 @@ module Telegram
def start
return if running
@running = true
log { 'Started bot poller.' }
while running
begin
fetch_updates do |update|
controller.dispatch(bot, update)
end
rescue Interrupt
@running = false
rescue StandardError => e
logger.error { ([e.message] + e.backtrace).join("\n") } if logger
end
begin
@running = true
log { 'Started bot poller.' }
run
rescue Interrupt
nil # noop
ensure
@running = false
end
log { 'Stop polling bot updates.' }
log { 'Stoped polling bot updates.' }
end
def run
while running
updates = fetch_updates
process_updates(updates) if updates && updates.any?
end
end
# Method to stop poller from other thread.
def stop
return unless running
log { 'Killing polling thread.' }
log { 'Stopping polling bot updates.' }
@running = false
end
def fetch_updates
def fetch_updates(offset = self.offset)
response = bot.async(false) { bot.get_updates(offset: offset, timeout: timeout) }
updates = response.is_a?(Array) ? response : response['result']
return unless updates && updates.any?
response.is_a?(Array) ? response : response['result']
rescue Timeout::Error
log { 'Fetch timeout' }
nil
end
def process_updates(updates)
reload! do
updates.each do |update|
@offset = update['update_id'] + 1
yield update
process_update(update)
end
end
rescue Timeout::Error
log { 'Fetch timeout' }
rescue StandardError => e
logger.error { ([e.message] + e.backtrace).join("\n") } if logger
end
# Override this method to setup custom error collector.
def process_update(update)
controller.dispatch(bot, update)
end
def reload!

Просмотреть файл

@@ -1,6 +1,6 @@
module Telegram
module Bot
VERSION = '0.12.3'.freeze
VERSION = '0.13.0'.freeze
def self.gem_version
Gem::Version.new VERSION

Просмотреть файл

@@ -15,14 +15,21 @@ class TestApplication < Rails::Application
config.eager_load = false
config.log_level = :debug
config.action_dispatch.show_exceptions = false
secrets[:secret_key_base] = 'test'
secrets[:telegram] = {
telegram_config = {
bot: 'default_token',
bots: {
other: {token: 'other_token'},
named: {token: 'named_token', username: 'TestBot'},
},
}
if respond_to?(:credentials)
credentials.config[:telegram] = telegram_config
else
secrets[:secret_key_base] = 'test'
secrets[:telegram] = telegram_config
end
end
Rails.application.initialize!
@@ -68,9 +75,9 @@ RSpec.configure do |config|
require 'telegram/bot/routes_helper'
extend Telegram::Bot::RoutesHelper
telegram_webhooks default: DefaultBotController,
other: OtherBotController,
named: NamedBotController
telegram_webhook DefaultBotController, :default
telegram_webhook OtherBotController, :other
telegram_webhook NamedBotController, :named
end
end
end

Просмотреть файл

@@ -16,7 +16,7 @@ $LOAD_PATH.unshift GEM_ROOT.join('lib')
require 'telegram/bot'
require 'telegram/bot/updates_controller/rspec_helpers'
require 'telegram/bot/types'
require 'active_support/core_ext/object/json'
require 'active_support/json'
Dir[GEM_ROOT.join('spec/support/**/*.rb')].each { |f| require f }

Просмотреть файл

@@ -52,7 +52,7 @@ RSpec.describe Telegram::Bot::Client do
describe '#request' do
subject { -> { instance.request(action, request_body) } }
let(:action) { :some_action }
let(:url) { "#{format described_class::URL_TEMPLATE, token}#{action}" }
let(:url) { "#{format(described_class::URL_TEMPLATE, token: token)}#{action}" }
let(:request_body) { double(:body) }
let(:prepared_body) { double(:prepared_body) }
let(:response) { HTTP::Message.new_response(body).tap { |x| x.status = status } }

Просмотреть файл

@@ -40,6 +40,62 @@ RSpec.describe Telegram::Bot::RoutesHelper do
end
end
describe '#telegram_webhook' do
subject { mapper.telegram_webhook(*input) }
let(:mapper) { double(:mapper).tap { |x| x.extend described_class } }
let(:bots) { {default: bot, other: other_bot} }
let(:controller) { double(:controller, name: :controller) }
let(:other_controller) { double(:other_controller, name: :other_controller) }
before { allow(Telegram).to receive(:bots) { bots } }
def assert_routes(bot, controller, route_name, options) # rubocop:disable AbcSize
expected_path = options.delete(:path) || "telegram/#{bot.token}"
expect(mapper).to receive(:post) do |path, params|
expect(path).to eq expected_path
middleware = params[:to]
expect(middleware.controller).to eq(controller)
expect(middleware.bot.token).to eq(bot.token)
expect(middleware.bot.username).to eq(bot.username)
expect(params[:as]).to eq route_name
expect(params).to include(options) if options
end
subject
end
context 'when called with controller' do
let(:input) { [controller, option: :val] }
it 'creates routes for default bot and this controller' do
assert_routes bot, controller, 'default_telegram_webhook', option: :val
end
context 'and bot does not have configured token' do
let(:bot) { create_bot(nil) }
it 'creates routes for default bot and this controller' do
assert_routes bot, controller, 'default_telegram_webhook', option: :val
end
end
context 'and bot has colon in token' do
let(:bot) { create_bot('some:token') }
it 'replaces colon with underscore' do
assert_routes bot, controller, 'default_telegram_webhook',
option: :val,
path: 'telegram/some_token'
end
end
end
context 'when called with controller and smth castable to bot' do
let(:input) { [controller, 'custom_token', option: :val] }
it 'creates routes for every created bot and controller' do
assert_routes create_bot('custom_token'), controller, 'telegram_webhook',
option: :val
end
end
end
describe '#telegram_webhooks' do
subject { mapper.telegram_webhooks(*input) }
let(:mapper) { double(:mapper).tap { |x| x.extend described_class } }
@@ -47,6 +103,7 @@ RSpec.describe Telegram::Bot::RoutesHelper do
let(:controller) { double(:controller, name: :controller) }
let(:other_controller) { double(:other_controller, name: :other_controller) }
before { allow(Telegram).to receive(:bots) { bots } }
around { |ex| Telegram::Bot.deprecation_0_14.silence { ex.run } }
def assert_routes(*expected) # rubocop:disable AbcSize
expected.each do |(bot, controller, route_name, options)|

Просмотреть файл

@@ -69,4 +69,44 @@ RSpec.describe 'Integrations helper', :telegram_bot do
it { should respond_with_message "Start: #{args[0...-1].inspect}, option: 1" }
end
end
describe 'callback queries', :callback_query do
let(:controller) do
Class.new(Telegram::Bot::UpdatesController) do
include Telegram::Bot::UpdatesController::CallbackQueryContext
def callback_query(data = nil, *)
answer_callback_query "data: #{data}"
end
def context_callback_query(data = nil, *)
answer_callback_query "data: #{data}", extra: :param
end
def answer_and_edit_callback_query(data = nil, *)
answer_callback_query "data: #{data}"
edit_message :text, text: 'edited-text', extra: :param
end
end
end
describe '#callback_query' do
let(:data) { 'unknown:command' }
it { should answer_callback_query("data: #{data}") }
end
describe '#context_callback_query' do
let(:data) { 'context:test:payload' }
it { should answer_callback_query('data: test:payload', extra: :param) }
it { should_not edit_current_message(:text) }
end
describe '#answer_and_edit_callback_query' do
let(:data) { 'answer_and_edit:test:payload' }
it do
should answer_callback_query(/test:payload/).
and edit_current_message(:text, text: /edited/, extra: :param)
end
end
end
end

Просмотреть файл

@@ -0,0 +1,91 @@
RSpec.describe Telegram::Bot::UpdatesController::Instrumentation do
include_context 'telegram/bot/updates_controller'
subject { -> { dispatch } }
let(:update) do
build_update :message, default_message_options.merge(text: '/start')
end
let(:controller_class) do
Class.new(Telegram::Bot::UpdatesController) do
def start(*)
end
end
end
let(:action_scope) { 'updates_controller.bot.telegram' }
let(:events) { Hash.new { |h, k| h[k] = [] } }
let!(:subscriber) do
ActiveSupport::Notifications.subscribe(/#{action_scope}/) do |action, *args|
events[action] << args
end
end
after { ActiveSupport::Notifications.unsubscribe(subscriber) }
def actions_list(*prefixes)
prefixes.map { |x| action_name(x) }
end
def action_name(prefix)
"#{prefix}.#{action_scope}"
end
describe '#process_action' do
it 'fires start_processing and process_action events' do
should change(events, :keys).by(actions_list(:start_processing, :process_action))
action = action_name(:start_processing)
expect(events[action].size).to eq(1)
expect(events[action][0].last).to include(update: update)
action = action_name(:process_action)
expect(events[action].size).to eq(1)
expect(events[action][0].last).to include(update: update)
end
end
describe '#halted_callback_hook' do
let(:controller_class) do
super().try(:class_eval) do |x|
x.before_action :halter_method
def halter_method
require 'active_support/version'
if ActiveSupport::VERSION::MAJOR >= 5
throw :abort
else
false
end
end
self
end
end
it 'fires halted_callback event' do
should change(events, :keys).
by(actions_list(:start_processing, :halted_callback, :process_action))
action = action_name(:halted_callback)
expect(events[action].size).to eq(1)
expect(events[action][0].last).to include(filter: :halter_method)
end
end
describe '#respond_with' do
before do
def controller.start(*)
respond_with :message, text: 'sample response'
end
end
it 'fires respond_with event' do
should change(events, :keys).
by(actions_list(:start_processing, :respond_with, :process_action))
action = action_name(:respond_with)
expect(events[action].size).to eq(1)
expect(events[action][0].last).to include(type: :message)
end
end
end

Просмотреть файл

@@ -0,0 +1,31 @@
RSpec.describe Telegram::Bot::UpdatesController::Rescue do
include_context 'telegram/bot/updates_controller'
describe '#process_action' do
subject { -> { dispatch_message("/#{command} x y z") } }
let(:controller_class) do
Class.new(Telegram::Bot::UpdatesController) do
rescue_from ArgumentError, with: -> { respond_with :message, text: 'Rescued' }
def rescuable(*)
raise ArgumentError, 'rescuable'
end
def not_rescuable(*)
raise 'not_rescuable'
end
end
end
context 'when exception is rescued' do
let(:command) { :rescuable }
it { should respond_with_message 'Rescued' }
end
context 'when exception is not rescued' do
let(:command) { :not_rescuable }
it { should raise_error('not_rescuable') }
end
end
end

Просмотреть файл

@@ -8,12 +8,32 @@ RSpec.describe Telegram::Bot::UpdatesPoller do
it { should be }
end
describe '#fetch_updates' do
subject { -> { instance.fetch_updates(&block) } }
describe '#process_updates' do
subject { -> { instance.process_updates(updates) } }
let(:block) { ->(x) { expect(x).to eq expected_results.shift } }
let(:results) { [{update_id: 12}, {update_id: 34}] }
let(:expected_results) { results.as_json }
let(:request_result) { {ok: true, result: results}.as_json }
let(:updates) { [{update_id: 12}, {update_id: 34}].as_json }
let(:processed_updates) { [] }
before do
allow(controller).to receive(:dispatch) do |bot, update|
expect(bot).to eq self.bot
processed_updates << update
end
end
it { should change(instance, :offset).to(updates.last['update_id'] + 1) }
it { should change(self, :processed_updates).to(updates) }
context 'with typed response' do
let(:updates) { super().map { |x| Telegram::Bot::Types::Update.new(x) } }
it { should change(instance, :offset).to(updates.last['update_id'] + 1) }
it { should change(self, :processed_updates).to(updates) }
end
end
describe '#fetch_updates' do
subject { instance.fetch_updates }
let(:updates) { [{update_id: 12}, {update_id: 34}] }
let(:request_result) { {ok: true, result: updates}.as_json }
before do
allow(bot).to receive(:get_updates) do
expect(bot.async).to be_falsy
@@ -21,19 +41,17 @@ RSpec.describe Telegram::Bot::UpdatesPoller do
end
end
it { should change(instance, :offset).to(results.last[:update_id] + 1) }
it { should change { expected_results }.to([]) }
it { should eq updates.as_json }
context 'with typed response' do
let(:request_result) { results.as_json.map { |x| Telegram::Bot::Types::Update.new(x) } }
let(:expected_results) { request_result.dup }
it { should change(instance, :offset).to(results.last[:update_id] + 1) }
it { should change { expected_results }.to([]) }
let(:updates) { super().map { |x| Telegram::Bot::Types::Update.new(x.as_json) } }
let(:request_result) { updates }
it { should eq updates }
end
context 'when bot is in async mode' do
let(:bot) { Telegram::Bot::Client.new('token', async: Class.new) }
it { should change { expected_results }.to([]) }
it { should eq updates.as_json }
end
end
end

Просмотреть файл

@@ -23,6 +23,6 @@ Gem::Specification.new do |spec|
spec.add_dependency 'activesupport', '>= 4.0', '< 6.0'
spec.add_dependency 'httpclient', '~> 2.7'
spec.add_development_dependency 'bundler', '~> 1.11'
spec.add_development_dependency 'bundler', '~> 1.16'
spec.add_development_dependency 'rake', '~> 10.0'
end