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

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

...

39 Коммитов

Автор SHA1 Сообщение Дата
Max Melentiev
6abbe5489d v0.12.0 2017-08-11 15:04:41 +03:00
Max Melentiev
ff7de976d0 Add specs for #edit_message 2017-08-11 15:04:19 +03:00
Max Melentiev
aaba27eb70 New API methods and payload types (up to Bot API 3.2) 2017-08-11 15:04:02 +03:00
printercu
88a4a9aab3 Merge pull request #37 from zuf/master
update rails dependency (<5.2 instead <=5.1)
2017-08-11 13:43:13 +03:00
Egor
9387043ec1 fix rails dependency (<5.2 instead <=5.1) 2017-08-10 18:07:21 +03:00
Max Melentiev
3dca35ba9a callback_query context for RSpec 2017-07-25 10:58:18 +03:00
Max Melentiev
6879618c00 edit_message helper 2017-07-25 10:49:39 +03:00
Max Melentiev
6e4a69b6b1 Fix storing token in ClientStub when config hash is given 2017-05-17 17:45:21 +06:00
printercu
b8db576452 Merge pull request #31 from telegram-bot-rb/integration_test
Integration test with rails app and two bots
2017-05-15 22:20:26 +06:00
Max Melentiev
2fffdeb475 Integration test with rails app and two bots 2017-05-15 22:11:52 +06:00
Max Melentiev
9defa42f40 Add notes on testing multiple bots to readme
Fixes #29
2017-05-11 14:01:42 +06:00
Max Melentiev
0320507abe Telegram.bot raises error when there is no default config 2017-05-11 13:57:29 +06:00
Max Melentiev
cd3d3739a4 v0.11.3 2017-05-05 17:30:49 +06:00
Max Melentiev
a054ac704c Build with rails 5.1 on travis 2017-05-05 17:28:59 +06:00
printercu
23aa92e614 Merge pull request #28 from ironbrains/master
Increase dependencies version
2017-05-05 17:19:05 +06:00
Bulat F
a49dca66fa Increase dependencies version
- Add support `activesupport 5.1`
- Add support `actionpack 5.1`
2017-05-05 10:16:30 +03:00
Max Melentiev
2d5ca981ce Rails 5.1 deep symbolized secrets support
Fixes #26
2017-04-22 12:02:59 +03:00
Max Melentiev
cf9bd87cf2 Add notice about how to require gem to Readme
Fixes #24
2017-03-11 21:33:20 +03:00
Max Melentiev
48ff7a4f43 Fix poller for typed responses
Fixes: #22
2017-03-05 17:56:32 +03:00
Max Melentiev
17260cb5a9 v0.11.0 2017-02-10 09:34:15 +03:00
Max Melentiev
817af95369 Remove Bot::StaleChat in favor of Bot::Forbidden 2017-02-10 09:33:41 +03:00
Max Melentiev
99fbe41b16 Fixed support for *channel_post updates 2016-12-09 13:33:55 +03:00
Max Melentiev
88178113d7 Support (edited_)channel_post updates, add api methods from 2.3, 2.3.1 2016-12-09 13:23:58 +03:00
printercu
1a2ac0dd83 Update README.md 2016-11-16 21:31:02 +03:00
Max Melentiev
fa6e385ca1 v0.10.0 2016-11-16 20:51:41 +03:00
Max Melentiev
1098118826 Fix integration spec for Rails 4 2016-11-16 14:34:42 +03:00
Max Melentiev
0c9a4a5fa0 Inegration context for RSpec
ActionDispatch::Testing::Integration requires smth else and
defines Rails, so had to change `defined?(Rails)` all over the code.
2016-11-16 14:18:45 +03:00
printercu
5c299887bb Merge pull request #18 from bob-frost/ruby2.2.0
Error "can't dup Fixnum (TypeError)"
2016-11-13 19:16:59 +03:00
bob-frost
9f2234c132 Fix "can't dup Fixnum (TypeError)" for Ruby 2.2.0 2016-11-13 09:47:01 +06:00
Max Melentiev
ec1d5bad7d v0.9.0 2016-11-07 20:24:01 +03:00
Max Melentiev
194fc78202 RSpec matchers 2016-11-07 20:17:34 +03:00
Max Melentiev
ececfa660e [Docs] Note about queue adapters 2016-10-19 14:20:39 +03:00
printercu
9e67122465 Merge pull request #2 from telegram-bot-rb/async
Async requests
2016-10-19 14:00:26 +03:00
Max Melentiev
caee9809d0 More docs on async mode 2016-10-19 13:59:54 +03:00
Max Melentiev
4b511f51a7 update changelog 2016-10-19 13:36:03 +03:00
Max Melentiev
d2d6202fa7 API methods from 2016-10-03 update
https://core.telegram.org/bots/api-changelog#october-3-2016
2016-10-17 17:43:13 +03:00
Max Melentiev
dd38cfea93 Take chat from message for callback queries 2016-10-07 12:45:16 +03:00
Max Melentiev
3cb6e930b8 Fix typo in module name: CallbackQueyContext -> CallbackQueryContext 2016-09-22 01:12:19 +03:00
Max Melentiev
e42a293804 edit_message_* methods 2016-09-21 22:53:06 +03:00
38 изменённых файлов: 1006 добавлений и 139 удалений

1
.gitignore поставляемый
Просмотреть файл

@@ -6,4 +6,5 @@
/doc/
/pkg/
/spec/reports/
/log/
/tmp/

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

@@ -15,6 +15,7 @@ Style/DotPosition: {EnforcedStyle: trailing}
Style/FirstParameterIndentation: {EnforcedStyle: consistent}
Style/IfUnlessModifier: {Enabled: false}
Style/ModuleFunction: {Enabled: false}
Style/MultilineMethodCallIndentation: {EnforcedStyle: indented}
Style/MultilineOperationIndentation: {EnforcedStyle: indented}
Style/NestedParenthesizedCalls: {Enabled: false}
Style/PredicateName: {Enabled: false}

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

@@ -5,5 +5,6 @@ rvm:
env:
- RAILS=4
- RAILS=5
- RAILS=5_1
notifications:
email: false

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

@@ -1,6 +1,51 @@
# Unreleased
# 0.12.0
- New API methods and payload types (up to Bot API 3.2).
- Fix rails 5.1.x support.
- RSpec context for callback_query updates.
- `edit_message` helper.
- ClientStub saves and returns token. Fixes testing multiple bots.
- Raise descriptive error when accessing not-configured bot.
# 0.11.3
- Release dependencies for Rails 5.1.
# 0.11.2
- Rails 5.1 deep symbolized secrets support.
# 0.11.1
- Fixed poller for typed response.
# 0.11.0
- Remove Bot::StaleChat in favor of Bot::Forbidden, as Telegram adds more
and more new descriptions.
Please open an issue if you face a problem.
# 0.10.2
- Support `(edited_)channel_post` updates.
- New methods from 2.3, 2.3.1 API updates.
# 0.10.0
- Integration helpers for RSpec.
- Fix for ruby 2.2.0 bug by @bob-frost.
# 0.9.0
- Async API requests.
- One more description for StaleChat error.
- edit_message_* methods.
- API methods from 2016-10-03 update
- Fix typo in module name: CallbackQueyContext -> CallbackQueryContext.
- Take `chat` from `message` for callback queries
- RSpec matchers.
# 0.8.0

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

@@ -1,22 +1,31 @@
source 'https://rubygems.org'
gemspec
case ENV['RAILS']
when '5'
gem 'actionpack', '5.0.0.rc1'
when '4'
gem 'actionpack', '~> 4.2'
end
group :development do
case ENV['RAILS']
when '5_1'
gem 'railties', '5.1.0'
gem 'actionpack', '5.1.0'
when '5'
gem 'railties', '5.0.2'
gem 'actionpack', '5.0.2'
when '4'
gem 'railties', '~> 4.2'
gem 'actionpack', '~> 4.2'
else
gem 'railties'
gem 'actionpack'
end
gem 'sdoc', '~> 0.4.1'
gem 'pry', '~> 0.10.1'
gem 'pry-byebug', '~> 3.2.0'
gem 'telegram-bot-types', '~> 0.2.0'
gem 'telegram-bot-types', '~> 0.3.0'
gem 'rspec', '~> 3.3.0'
gem 'rspec', '~> 3.5.0'
gem 'rspec-its', '~> 1.1.0'
gem 'rspec-rails', '~> 3.5.0'
gem 'rubocop', '~> 0.37.0'

106
README.md
Просмотреть файл

@@ -17,12 +17,18 @@ Package contains:
- Middleware and routes helpers for production env.
- Poller with automatic source-reloader for development env.
- Rake tasks to update webhook urls.
- Async requests for Telegram and/or Botan API. Let the queue adapter handle errors!
- __[Async mode](#async-mode)__ for Telegram and/or Botan API.
Let the queue adapter handle network errors!
Here is sample [telegram_bot_app](https://github.com/telegram-bot-rb/telegram_bot_app)
with session, keyboards and inline queries.
Run it on your local machine in 1 minute!
And here is [app teamplate](https://github.com/telegram-bot-rb/rails_template)
to generate clean app in seconds.
Examples and cookbook in [wiki](https://github.com/telegram-bot-rb/telegram-bot/wiki).
## Installation
Add this line to your application's Gemfile:
@@ -39,6 +45,12 @@ Or install it yourself as:
$ gem install telegram-bot
Require if necessary:
```ruby
require 'telegram/bot'
```
## Usage
### Configuration
@@ -96,7 +108,7 @@ bot.get_me.class # => Telegram::Bot::Types::User
```
Any API request error will raise `Telegram::Bot::Error` with description in its message.
Special `Telegram::Bot::StaleChat` is raised when bot can't post messages to the chat anymore.
Special `Telegram::Bot::Forbidden` is raised when bot can't post messages to the chat anymore.
### Controller
@@ -132,6 +144,7 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
# do_smth_with(data)
# There are `chat` & `from` shortcut methods.
# For callback queries `chat` if taken from `message` when it's available.
response = from ? "Hello #{from['username']}!" : 'Hi there!'
# There is `respond_with` helper to set `chat_id` from received message:
respond_with :message, text: response
@@ -155,6 +168,20 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
end
```
#### Reply helpers
There are helpers to respond for basic actions. They just set chat/message/query
identifiers from update. See `ReplyHelpers` method for more information.
Here are this methods signatures:
```ruby
def respond_with(type, params); end
def reply_with(type, params); end
def answer_inline_query(results, params = {}); end
def answer_callback_query(text, params = {}); end
def edit_message(type, params = {}); end
```
#### Optional typecasting
You can enable typecasting of `update` with `telegram-bot-types` by including
@@ -246,7 +273,7 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
end
```
You can use `CallbackQueyContext` in the similar way to split `#callback_query` into
You can use `CallbackQueryContext` in the similar way to split `#callback_query` into
several specific methods. It doesn't require session support, and takes context from
data. If data has a prefix with colon like this `my_ctx:smth...` it'll call
`my_ctx_callback_query('smth...')` when there is such action method. Otherwise
@@ -321,18 +348,64 @@ To stub all possible clients use `Telegram::Bot::ClientStub.stub_all!` before
initializing clients. Most likely you'll want something like this:
```ruby
# environments/test.rb
# Make sure to run it before defining routes or storing bot to some place in app!
Telegram.reset_bots
Telegram::Bot::ClientStub.stub_all!
# rails_helper.rb
RSpec.configure do |config|
# ...
Telegram.reset_bots
Telegram::Bot::ClientStub.stub_all!
config.after { Telegram.bot.reset }
# or for multiple bots:
config.after { Telegram.bots.each_value(&:reset) }
# ...
end
```
There are also some helpers for controller tests.
Check out `telegram/bot/updates_controller/rspec_helpers` and
`telegram/bot/updates_controller/testing`.
There are integration and controller contexts for RSpec and some built-in matchers:
```ruby
# spec/requests/telegram_webhooks_spec.rb
require 'telegram/bot/rspec/integration'
RSpec.describe TelegramWebhooksController, :telegram_bot do
# for old rspec add:
# include_context 'telegram/bot/integration'
describe '#start' do
subject { -> { dispatch_command :start } }
it { should respond_with_message 'Hi there!' }
end
# There is context for callback queries with related matchers.
describe '#hey_callback_query', :callback_query do
let(:data) { "hey:#{name}" }
let(:name) { 'Joe' }
it { should answer_callback_query('Hey Joe') }
it { should edit_current_message :text, text: 'Done' }
end
# For controller specs use
require 'telegram/bot/updates_controller/rspec_helpers'
RSpec.describe TelegramWebhooksController, type: :telegram_bot_controller do
# for old rspec add:
# include_context 'telegram/bot/updates_controller'
end
# Matchers are available for custom specs:
include Telegram::Bot::RSpec::ClientMatchers
expect(&process_update).to send_telegram_message(bot, /msg regexp/, some: :option)
expect(&process_update).
to make_telegram_request(bot, :sendMessage, hash_including(text: 'msg text'))
```
Place integration tests inside `spec/requests`
when using RSpec's `infer_spec_type_from_file_location!`,
or just add `type: :request` to `describe`.
See sample app for more examples.
### Deploying
@@ -384,11 +457,28 @@ you can implement your own worker class to handle such requests. This allows:
- Handle and retry network and other errors with queue adapter.
- ???
Instead of performing request instantly client serializes it, pushes to queue,
and immediately return control back. The job is then fetched with a worker
and real API request is performed. And this all is absolutely transparent for the app.
To enable this mode add `async: true` to bot's and botan's config.
For more information and custom configuration check out
[docs](http://www.rubydoc.info/github/telegram-bot-rb/telegram-bot/master/Telegram/Bot/Async) or
[source](https://github.com/telegram-bot-rb/telegram-bot/blob/master/lib/telegram/bot/async.rb).
If you want async mode, but don't want to setup queue, know that Rails 5 are shipped
with Async adapter by default, and there is
[Sucker Punch](https://github.com/brandonhilkert/sucker_punch) for Rails 4.
Be aware of some limitations:
- Client will not return API response.
- Sending files is not available in async mode [now],
because them can not be serialized.
To disable async mode for the block of code use `bot.async(false) { bot.send_photo }`.
Yes, it's threadsafe too.
## Development
After checking out the repo, run `bin/setup` to install dependencies.

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

@@ -6,26 +6,7 @@ module Telegram
module Bot
class Error < StandardError; end
class NotFound < Error; end
# Error class for events when chat is not available anymore for bot.
# While Telegram has same error codes for different messages and there is no
# official docs for this error codes it uses `description` to
# check response.
class StaleChat < Error
DESCRIPTIONS = [
'Bot was blocked',
'bot was kicked',
"can't write to",
'group chat is deactivated',
].freeze
class << self
def match_response?(response)
description = response['description'].to_s
DESCRIPTIONS.any? { |x| description[x] }
end
end
end
class Forbidden < Error; end
autoload :Async, 'telegram/bot/async'
autoload :Botan, 'telegram/bot/botan'
@@ -34,6 +15,7 @@ module Telegram
autoload :DebugClient, 'telegram/bot/debug_client'
autoload :Initializers, 'telegram/bot/initializers'
autoload :Middleware, 'telegram/bot/middleware'
autoload :RSpec, 'telegram/bot/rspec'
autoload :UpdatesController, 'telegram/bot/updates_controller'
autoload :UpdatesPoller, 'telegram/bot/updates_poller'
end

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

@@ -116,7 +116,7 @@ module Telegram
attr_reader :id
def initialize(*, id: nil, async: nil, **)
def initialize(*, id: nil, async: nil, **options)
@id = id
self.async = async
super

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

@@ -5,7 +5,7 @@ module Telegram
module ClientHelpers
attr_reader :botan
def initialize(*, botan: nil, **)
def initialize(*, botan: nil, **options)
super
@botan = Botan.wrap(botan, id: id) if botan
end

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

@@ -5,7 +5,7 @@ require 'active_support/core_ext/hash/keys'
module Telegram
module Bot
class Client
class Client # rubocop:disable ClassLength
URL_TEMPLATE = 'https://api.telegram.org/bot%s/'.freeze
autoload :TypedResponse, 'telegram/bot/client/typed_response'
@@ -46,23 +46,32 @@ module Telegram
@base_uri = format URL_TEMPLATE, self.token
end
def request(action, body = {}) # rubocop:disable PerceivedComplexity
def request(action, body = {})
res = http_request("#{base_uri}#{action}", self.class.prepare_body(body))
status = res.status
return JSON.parse(res.body) if 300 > status
result = JSON.parse(res.body) rescue nil # rubocop:disable RescueModifier
err_msg = "#{res.reason}: #{result && result['description'] || '-'}"
err_msg = result && result['description'] || '-'
if result
# NotFound is raised only for valid responses from Telegram
raise NotFound, err_msg if 404 == status
raise StaleChat, err_msg if StaleChat.match_response?(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, err_msg
raise Error, "#{res.reason}: #{err_msg}"
end
# Splited to the sections similar to API docs.
%w(
deleteWebhook
getUpdates
getWebhookInfo
setWebhook
answerCallbackQuery
answerInlineQuery
deleteChatPhoto
exportChatInviteLink
forwardMessage
getChat
getChatAdministrators
@@ -70,10 +79,12 @@ module Telegram
getChatMembersCount
getFile
getMe
getUpdates
getUserProfilePhotos
kickChatMember
leaveChat
pinChatMessage
promoteChatMember
restrictChatMember
sendAudio
sendChatAction
sendContact
@@ -81,12 +92,38 @@ module Telegram
sendLocation
sendMessage
sendPhoto
sendSticker
sendVenue
sendVideo
sendVideoNote
sendVoice
setWebhook
setChatDescription
setChatPhoto
setChatTitle
unbanChatMember
unpinChatMessage
deleteMessage
editMessageCaption
editMessageReplyMarkup
editMessageText
sendSticker
getStickerSet
uploadStickerFile
createNewStickerSet
addStickerToSet
setStickerPositionInSet
deleteStickerFromSet
answerInlineQuery
sendInvoice
answerShippingQuery
answerPreCheckoutQuery
getGameHighScores
sendGame
setGameScore
).each do |method|
define_method(method.underscore) { |*args| request(method, *args) }
end

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

@@ -35,6 +35,7 @@ module Telegram
end
def initialize(token = nil, username = nil, **options)
@token = token || options[:token]
@username = username || options[:username] || token
reset
end
@@ -43,7 +44,7 @@ module Telegram
@requests = Hash.new { |h, k| h[k] = [] }
end
def request(action, body)
def request(action, body = {})
requests[action.to_sym] << body
end
end

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

@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/transform_values'
require 'active_support/core_ext/hash/indifferent_access'
module Telegram
module Bot
@@ -20,7 +21,7 @@ module Telegram
def bot_poller_mode?
return @bot_poller_mode if defined?(@bot_poller_mode)
@bot_poller_mode = ENV.fetch('BOT_POLLER_MODE') do
Rails.env.development? if defined?(Rails)
Rails.env.development? if defined?(Rails.env)
end
end
@@ -33,7 +34,11 @@ module Telegram
# Default bot.
def bot
@bot ||= bots[:default]
@bot ||= bots.fetch(:default) do
raise 'Default bot is not configured.' \
' Add :default to bots_config' \
' or use telegram.bot/telegram.bots.default section in secrets.yml.'
end
end
# Hash of botan clients made from #bots.
@@ -47,10 +52,11 @@ module Telegram
# Can be overwritten with .bots_config=
def bots_config
@bots_config ||=
if defined?(Rails)
telegram_config = Rails.application.secrets[:telegram] || {}
(telegram_config['bots'] || {}).symbolize_keys.tap do |config|
default = telegram_config['bot']
if defined?(Rails.application)
secrets = Rails.application.secrets.
fetch(:telegram, {}).with_indifferent_access
secrets.fetch(:bots, {}).symbolize_keys.tap do |config|
default = secrets[:bot]
config[:default] = default if default
end
else

7
lib/telegram/bot/rspec.rb Обычный файл
Просмотреть файл

@@ -0,0 +1,7 @@
module Telegram
module Bot
module RSpec
autoload :ClientMatchers, 'telegram/bot/rspec/client_matchers'
end
end
end

151
lib/telegram/bot/rspec/client_matchers.rb Обычный файл
Просмотреть файл

@@ -0,0 +1,151 @@
module Telegram
module Bot
module RSpec
# Proxy that uses RSpec::Mocks::ArgListMatcher when it's available.
# Otherwise just performs `#==` match.
#
# Also allows to check argumets with custom block.
class ArgListMatcher
attr_reader :expected, :expected_proc
def initialize(*args, &block)
@expected_proc = block if block_given?
@expected =
if mocks_matcher?
::RSpec::Mocks::ArgumentListMatcher.new(*args)
else
args
end
end
def args_match?(*actual)
if expected_proc
expected_proc[*actual]
true
elsif mocks_matcher?
expected.args_match?(*actual)
else
expected == actual
end
end
def args
mocks_matcher? ? expected.args : expected
end
def mocks_matcher?
defined?(::RSpec::Mocks::ArgumentListMatcher)
end
def to_s
if mocks_matcher?
expected.expected_args.inspect
elsif expected_proc
'(proc matcher)'
else
expected.inspect
end
end
end
# Matchers to test requests to Telegram API.
#
# Complex matchers requires `rspec-mocks` to be installed.
module ClientMatchers
class MakeTelegramRequest < ::RSpec::Matchers::BuiltIn::BaseMatcher
EXPECTATION_TYPES = {
exactly: :==,
at_most: :>=,
at_least: :<=,
}.freeze
attr_reader :performed_requests, :description
def initialize(bot, action, description: nil)
@bot = bot
@action = action
@description = description || "make #{action} telegram request"
exactly(1)
end
def matches?(proc) # rubocop:disable AbcSize
raise ArgumentError, 'matcher only supports block expectations' unless proc.is_a?(Proc)
original_requests_count = bot.requests[action].count
proc.call
@performed_requests = bot.requests[action].drop(original_requests_count)
@matching_requests_count = performed_requests.count do |request|
!arg_list_matcher || arg_list_matcher.args_match?(request)
end
expectation_method = EXPECTATION_TYPES[expectation_type]
expected_number.public_send(expectation_method, matching_requests_count)
end
def with(*args, &block)
@arg_list_matcher = ArgListMatcher.new(*args, &block)
self
end
EXPECTATION_TYPES.each_key do |type|
define_method type do |count|
@expectation_type = type
@expected_number = Integer(count)
self
end
end
def times
self
end
def failure_message
"expected to #{base_message}"
end
def failure_message_when_negated
"expected not to #{base_message}"
end
def supports_block_expectations?
true
end
private
attr_reader :bot, :action, :expectation_type, :expected_number,
:arg_list_matcher, :matching_requests_count
def base_message
"make #{expectation_type.to_s.tr('_', ' ')} #{expected_number} " \
"#{bot.inspect}.#{action} requests,".tap do |msg|
msg << " with #{arg_list_matcher}," if arg_list_matcher
msg << " but made #{matching_requests_count}"
if performed_requests
actual_args = performed_requests.map(&:inspect).join(', ')
msg << ", and #{performed_requests.count} with #{actual_args}"
end
end
end
end
# Check that bot performed request to telegram API:
#
# expect { dispatch_message('Hi!') }.
# to make_telegram_request(bot, :sendMessage).
# with(text: 'Hello!', chat_id: chat_id)
def make_telegram_request(bot, action)
MakeTelegramRequest.new(bot, action)
end
# Helper for asserting message is sent. Note that options are checked
# with `hash_including`. For strict checks use #make_telegram_request.
def send_telegram_message(bot, text = nil, options = {})
description = "send telegram message #{text.inspect}"
text = a_string_matching(text) if text.is_a?(Regexp)
options = options.merge(text: text) if text
MakeTelegramRequest.new(bot, :sendMessage, description: description).
with(hash_including(options))
end
end
end
end
end

83
lib/telegram/bot/rspec/integration.rb Обычный файл
Просмотреть файл

@@ -0,0 +1,83 @@
RSpec.shared_context 'telegram/bot/integration' do
let(:bot) { Telegram.bot }
let(:from_id) { 123 }
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")
end
let(:request_headers) do
{
'ACCEPT' => 'application/json',
'Content-Type' => 'application/json',
}
end
let(:clear_session?) { described_class.respond_to?(:session_store) }
before { described_class.session_store.try!(:clear) if clear_session? }
include Telegram::Bot::RSpec::ClientMatchers
def dispatch(update)
if ActionPack::VERSION::MAJOR >= 5
post(controller_path, params: update.to_json, headers: request_headers)
else
post(controller_path, update.to_json, request_headers)
end
end
def dispatch_message(text, options = {})
dispatch message: default_message_options.merge(options).merge(text: text)
end
def dispatch_command(*args)
options = args.last.is_a?(Hash) ? args.pop : {}
dispatch_message("/#{args.join ' '}", options)
end
# Matcher to check response. Make sure to define `let(:chat_id)`.
def respond_with_message(expected = Regexp.new(''))
raise 'Define chat_id to use respond_with_message' unless defined?(chat_id)
send_telegram_message(bot, expected, chat_id: chat_id)
end
end
RSpec.shared_context 'telegram/bot/callback_query', callback_query: true do
include_context 'telegram/bot/integration'
subject { -> { dispatch callback_query: payload } }
let(:payload) { {id: 11, from: from, message: message, data: data} }
let(:message) { {message_id: 22, chat: chat, text: 'message text'} }
# Matcher to check that origin message got edited.
def edit_current_message(type, options = {})
description = 'edit current message'
options = options.merge(
message_id: message[:message_id],
chat_id: chat_id,
)
Telegram::Bot::RSpec::ClientMatchers::MakeTelegramRequest.new(
bot, :"editMessage#{type.to_s.camelize}", description: description
).with(hash_including(options))
end
# Matcher to check that callback query is answered.
def answer_callback_query(text = Regexp.new(''), options = {})
description = "answer callback query with #{text.inspect}"
text = a_string_matching(text) if text.is_a?(Regexp)
options = options.merge(
callback_query_id: payload[:id],
text: text,
)
Telegram::Bot::RSpec::ClientMatchers::MakeTelegramRequest.new(
bot, :answerCallbackQuery, description: description
).with(hash_including(options))
end
end
RSpec.configure do |config|
if config.respond_to?(:include_context)
config.include_context 'telegram/bot/integration', :telegram_bot
config.include_context 'telegram/bot/callback_query', :telegram_bot, :callback_query
end
end

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

@@ -57,7 +57,7 @@ module Telegram
require 'telegram/bot/updates_controller/log_subscriber'
require 'telegram/bot/updates_controller/instrumentation'
require 'telegram/bot/updates_controller/reply_helpers'
autoload :CallbackQueyContext, 'telegram/bot/updates_controller/callback_query_context'
autoload :CallbackQueryContext, 'telegram/bot/updates_controller/callback_query_context'
autoload :MessageContext, 'telegram/bot/updates_controller/message_context'
include AbstractController::Callbacks
@@ -80,10 +80,14 @@ module Telegram
PAYLOAD_TYPES = %w(
message
edited_message
channel_post
edited_channel_post
inline_query
chosen_inline_result
callback_query
edited_message
shipping_query
pre_checkout_query
).freeze
CMD_REGEX = %r{\A/([a-z\d_]{,31})(@(\S+))?(\s|$)}i
CONFLICT_CMD_REGEX = Regexp.new("^(#{PAYLOAD_TYPES.join('|')}|\\d)")
@@ -140,10 +144,12 @@ module Telegram
@_payload, @_payload_type = payload_data
end
# Accessor to `'chat'` field of payload. Can be overriden with `chat` option
# for #initialize.
# Accessor to `'chat'` field of payload. Also tries `'chat'` in `'message'`
# when there is no such field in payload.
#
# Can be overriden with `chat` option for #initialize.
def chat
@_chat ||= payload && payload['chat']
@_chat ||= payload.try! { |x| x['chat'] || x['message'] && x['message']['chat'] }
end
# Accessor to `'from'` field of payload. Can be overriden with `from` option
@@ -165,25 +171,27 @@ module Telegram
# Returns array `[is_command?, action, args]`.
def action_for_payload
if payload_type
send("action_for_#{payload_type}") || [false, payload_type, [payload]]
send("action_for_#{payload_type}") || action_for_default_payload
else
[false, :unsupported_payload_type, []]
end
end
def action_for_default_payload
[false, payload_type, [payload]]
end
# If payload is a message with command, then returned action is an
# action for this command.
# Separate method, so it can be easily overriden (ex. MessageContext).
#
# This is not used for edited messages/posts. It process them as basic updates.
def action_for_message
cmd, args = self.class.command_from_text(payload['text'], bot_username)
cmd &&= self.class.action_for_command(cmd)
[true, cmd, args] if cmd
end
# It doesn't extract commands from edited messages. Just process
# them as usual ones.
def action_for_edited_message
end
alias_method :action_for_channel_post, :action_for_message
def action_for_inline_query
[false, payload_type, [payload['query'], payload['offset']]]
@@ -202,6 +210,11 @@ module Telegram
def action_missing(*)
end
PAYLOAD_TYPES.each do |type|
method = :"action_for_#{type}"
alias_method method, :action_for_default_payload unless instance_methods.include?(method)
end
ActiveSupport.run_load_hooks('telegram.bot.updates_controller', self)
end
end

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

@@ -3,7 +3,7 @@ module Telegram
class UpdatesController
# Use separate actions for different callback queries.
# It doesn't require session support. Simply add `%{context}:` prefix to data.
module CallbackQueyContext
module CallbackQueryContext
protected
# Uses #context_from_callback_query as context name.
@@ -15,7 +15,6 @@ module Telegram
# because `data` param is controlled by client.
def action_for_callback_query
context, new_data = context_from_callback_query
# binding.pry
if context
action_name = "#{context}_callback_query"
[false, action_name, [new_data]] if action_method?(action_name)

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

@@ -38,6 +38,19 @@ module Telegram
)
bot.answer_callback_query(params)
end
# Edit message from callback query.
def edit_message(type, params = {})
params =
if message_id = payload['inline_message_id'] # rubocop:disable AssignmentInCondition
params.merge(inline_message_id: message_id)
elsif message = payload['message'] # rubocop:disable AssignmentInCondition
params.merge(chat_id: message['chat']['id'], message_id: message['message_id'])
else
raise 'Can not edit message without `inline_message_id` or `message`'
end
bot.public_send("edit_message_#{type}", params)
end
end
end
end

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

@@ -13,13 +13,18 @@ RSpec.shared_context 'telegram/bot/updates_controller' do
let(:bot) { Telegram::Bot::ClientStub.new(bot_name) }
let(:bot_name) { 'bot' }
let(:session) { controller.send(:session) }
let(:from_id) { 123 }
let(:chat_id) { 456 }
let(:default_message_options) { {from: {id: from_id}, chat: {id: chat_id}} }
include Telegram::Bot::RSpec::ClientMatchers
def dispatch(bot = self.bot, update = self.update)
controller.dispatch_again(bot, update)
end
def dispatch_message(text, options = {})
update = build_update :message, options.merge(text: text)
update = build_update :message, default_message_options.merge(options).merge(text: text)
dispatch bot, update
end
@@ -34,4 +39,15 @@ RSpec.shared_context 'telegram/bot/updates_controller' do
else input
end
end
# Matcher to check response. Make sure to define `let(:chat_id)`.
def respond_with_message(expected)
send_telegram_message(bot, expected, chat_id: chat_id)
end
end
RSpec.configure do |config|
if config.respond_to?(:include_context)
config.include_context 'telegram/bot/updates_controller', type: :telegram_bot_controller
end
end

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

@@ -27,12 +27,12 @@ module Telegram
attr_reader :bot, :controller, :timeout, :offset, :logger, :running, :reload
def initialize(bot, controller, **options)
@logger = options.fetch(:logger) { defined?(Rails) && Rails.logger }
@logger = options.fetch(:logger) { defined?(Rails.logger) && Rails.logger }
@bot = bot
@controller = controller
@timeout = options.fetch(:timeout) { DEFAULT_TIMEOUT }
@offset = options[:offset]
@reload = options.fetch(:reload) { defined?(Rails) && Rails.env.development? }
@reload = options.fetch(:reload) { defined?(Rails.env) && Rails.env.development? }
end
def log(&block)
@@ -65,14 +65,16 @@ module Telegram
def fetch_updates
response = bot.async(false) { bot.get_updates(offset: offset, timeout: timeout) }
return unless response['ok'] && response['result'].any?
updates = response.is_a?(Array) ? response : response['result']
return unless updates && updates.any?
reload! do
response['result'].each do |update|
updates.each do |update|
@offset = update['update_id'] + 1
yield update
end
end
rescue Timeout::Error # rubocop:disable HandleExceptions
rescue Timeout::Error
log { 'Fetch timeout' }
end
def reload!
@@ -85,7 +87,7 @@ module Telegram
end
end
if defined?(Rails) && Rails.application.respond_to?(:reloader)
if defined?(Rails.application) && Rails.application.respond_to?(:reloader)
def reloading_code
Rails.application.reloader.wrap do
yield

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

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

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

@@ -0,0 +1,8 @@
require 'integration_helper'
RSpec.describe DefaultBotController, :telegram_bot, type: :request do
describe '#start' do
subject { -> { dispatch_command :start } }
it { should respond_with_message 'from default' }
end
end

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

@@ -0,0 +1,9 @@
require 'integration_helper'
RSpec.describe NamedBotController, :telegram_bot, type: :request do
let(:bot) { Telegram.bots[:named] }
describe '#start' do
subject { -> { dispatch_command :start } }
it { should respond_with_message 'from named' }
end
end

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

@@ -0,0 +1,9 @@
require 'integration_helper'
RSpec.describe OtherBotController, :telegram_bot, type: :request do
let(:bot) { Telegram.bots[:other] }
describe '#start' do
subject { -> { dispatch_command :start } }
it { should respond_with_message 'from other' }
end
end

62
spec/integration_helper.rb Обычный файл
Просмотреть файл

@@ -0,0 +1,62 @@
require 'telegram/bot/rspec/integration'
require 'action_controller'
require 'action_dispatch'
require 'action_dispatch/testing/integration'
require 'rails'
require 'rspec/rails/adapters'
require 'rspec/rails/fixture_support'
require 'rspec/rails/example/rails_example_group'
require 'rspec/rails/example/request_example_group'
ENV['RAILS_ENV'] = 'test'
class TestApplication < Rails::Application
config.eager_load = false
config.log_level = :debug
secrets[:secret_key_base] = 'test'
secrets[:telegram] = {
bot: 'default_token',
bots: {
other: {token: 'other_token'},
named: {token: 'named_token', username: 'TestBot'},
},
}
end
Rails.application.initialize!
# # Controllers
%w(default other named).each do |bot_name|
controller = Class.new(Telegram::Bot::UpdatesController) do
define_method :start do |*|
respond_with :message, text: "from #{bot_name}"
end
end
Object.const_set("#{bot_name}_bot_controller".camelize, controller)
end
RSpec.configure do |config|
config.include RSpec::Rails::RequestExampleGroup, type: :request
config.around type: :request do |ex|
begin
Telegram.reset_bots
Telegram::Bot::ClientStub.stub_all!
ex.run
ensure
Telegram.reset_bots
Telegram::Bot::ClientStub.stub_all!(false)
end
end
config.before type: :request do
# Redefine routes before every example, so it does not depent on order.
Rails.application.routes.draw do
require 'telegram/bot/routes_helper'
extend Telegram::Bot::RoutesHelper
telegram_webhooks default: DefaultBotController,
other: OtherBotController,
named: NamedBotController
end
end
end

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

@@ -16,6 +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'
Dir[GEM_ROOT.join('spec/support/**/*.rb')].each { |f| require f }

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

@@ -3,7 +3,7 @@ RSpec.describe Telegram::Bot::Botan do
let(:token) { 'token' }
include_examples 'initializers', :botans
include_examples 'async', request_args: -> { [double(:method), double(:url)] }
it_behaves_like 'async', request_args: -> { [double(:method), double(:url)] }
describe '.new' do
subject { described_class.new(*args) }

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

@@ -4,7 +4,7 @@ RSpec.describe Telegram::Bot::Client do
let(:botan_token) { double(:botan_token) }
include_examples 'initializers'
include_examples 'async', request_args: -> { [double(:action), {body: :content}] }
it_behaves_like 'async', request_args: -> { [double(:action), {body: :content}] }
describe '.prepare_body' do
subject { described_class.prepare_body(input) }
@@ -48,4 +48,49 @@ RSpec.describe Telegram::Bot::Client do
its(:base_uri) { should include args[0][:token] }
end
end
describe '#request' do
subject { -> { instance.request(action, request_body) } }
let(:action) { :some_action }
let(:url) { "#{format described_class::URL_TEMPLATE, 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 } }
let(:status) { 200 }
let(:body) { body_json.to_json }
let(:body_json) { {'param' => 'val', 'description' => 'some description'} }
before do
expect(described_class).to receive(:prepare_body).with(request_body) { prepared_body }
expect(instance).to receive(:http_request).with(url, prepared_body) { response }
end
around { |ex| Telegram::Bot::ClientStub.stub_all!(false) { ex.run } }
shared_examples 'invalid body' do |error = Telegram::Bot::Error|
context 'when body is not json' do
let(:body) { '{' }
it { should raise_error error }
end
end
its(:call) { should eq body_json }
include_examples 'invalid body', JSON::ParserError
context 'when status is 403' do
let(:status) { 403 }
it { should raise_error Telegram::Bot::Forbidden, body_json['description'] }
include_examples 'invalid body'
end
context 'when status is 404' do
let(:status) { 404 }
it { should raise_error Telegram::Bot::NotFound, body_json['description'] }
include_examples 'invalid body'
end
context 'when status is other' do
let(:status) { 500 }
it { should raise_error Telegram::Bot::Error, /#{body_json['description']}/ }
end
end
end

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

@@ -46,8 +46,14 @@ RSpec.describe Telegram::Bot::ClientStub do
context 'when username and token are given' do
let(:args) { %w(token superbot) }
its(:token) { should eq nil }
its(:token) { should eq args[0] }
its(:username) { should eq args[1] }
end
context 'when hash config is given' do
let(:args) { [token: 'token', username: 'superbot'] }
its(:token) { should eq args[0][:token] }
its(:username) { should eq args[0][:username] }
end
end
end

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

@@ -22,8 +22,13 @@ RSpec.describe Telegram::Bot::ConfigMethods do
end
describe '#bot' do
subject { registry.bot }
it { should eq registry.bots[:default] }
subject { -> { registry.bot } }
its(:call) { should eq registry.bots[:default] }
context 'when bot is not configured' do
let(:config) { super().except(:default) }
it { should raise_error(/bot is not configured/) }
end
end
describe '#bots' do
@@ -67,6 +72,7 @@ RSpec.describe Telegram::Bot::ConfigMethods do
context 'when not configured' do
let(:registry) { Object.new.tap { |x| x.extend described_class } }
before { hide_const('Rails') }
it { should eq({}) }
context 'in rails environment' do
@@ -87,6 +93,12 @@ RSpec.describe Telegram::Bot::ConfigMethods do
end
it { should include default: config[:bot] }
it { should include config[:bots] }
context 'on rails >5.1 (deep symbolized keys)' do
let(:secrets) { {telegram: config.deep_symbolize_keys} }
it { should include default: config[:bot] }
it { should include config[:bots] }
end
end
end
end

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

@@ -0,0 +1,87 @@
RSpec.describe Telegram::Bot::RSpec::ClientMatchers do
let(:bot) { Telegram::Bot::ClientStub.new('TestBot') }
let(:other_bot) { Telegram::Bot::ClientStub.new('OtherTestBot') }
include described_class
describe '#make_telegram_request' do
context 'without args' do
it 'works for matching' do
block = ->(*) { bot.send_message(text: 'test') }
expect(&block).to make_telegram_request(bot, :sendMessage)
expect { 3.times(&block) }.to make_telegram_request(bot, :sendMessage).exactly(3).times
expect { 3.times(&block) }.to make_telegram_request(bot, :sendMessage).at_least(2).times
expect { 3.times(&block) }.to make_telegram_request(bot, :sendMessage).at_most(4).times
end
it 'works for not-matching' do
block = ->(*) { bot.get_me }
expect {}.to_not make_telegram_request(bot, :getMe)
expect { other_bot.get_me }.to_not make_telegram_request(bot, :getMe)
expect { 3.times(&block) }.to_not make_telegram_request(bot, :getMe)
expect { 3.times(&block) }.to_not make_telegram_request(bot, :getMe).exactly(2).times
expect { 3.times(&block) }.to_not make_telegram_request(bot, :getMe).exactly(4).times
expect { 3.times(&block) }.to_not make_telegram_request(bot, :getMe).at_least(4).times
expect { 3.times(&block) }.to_not make_telegram_request(bot, :getMe).at_most(2).times
end
end
context 'with args' do
it 'works for exact matching' do
args = {text: 'test', parseMode: :Markdown}
block = ->(*) { bot.send_message(args) }
expect(&block).to make_telegram_request(bot, :sendMessage).with(args)
expect(&block).to_not make_telegram_request(bot, :sendMessage).with(args.except(:text))
expect { 3.times(&block) }.to make_telegram_request(bot, :sendMessage).
with(args).exactly(3).times
end
it 'works for block matchers' do
args = {text: 'test', parseMode: :Markdown}
block = ->(*) { bot.send_message(args) }
expect(&block).to make_telegram_request(bot, :sendMessage).
with { |actual| expect(actual).to eq(args) }
# It ignores block's result! Custom expectations must be used inside block.
expect(&block).to make_telegram_request(bot, :sendMessage).
with { |actual| actual == {} }
expect do
expect(&block).to_not make_telegram_request(bot, :sendMessage).
with { |actual| expect(actual).to eq({}) }
end.to raise_error RSpec::Expectations::ExpectationNotMetError
n = -1
expect { 3.times { |i| bot.send_message text: i } }.
to make_telegram_request(bot, :sendMessage).exactly(3).times.
with { |actual| expect(actual).to eq(text: n += 1) }
end
it 'works for RSpec::Mocks matchers' do
args = {text: 'test', parseMode: :Markdown}
block = ->(*) { bot.send_message(args) }
expect(&block).to make_telegram_request(bot, :sendMessage).with(hash_including(args))
expect(&block).to make_telegram_request(bot, :sendMessage).with(
text: a_string_matching(/est$/),
parseMode: :Markdown,
)
end
end
end
describe '#send_telegram_message' do
let(:args) { {text: 'test', parseMode: :Markdown} }
let(:block) { ->(*) { bot.send_message(args) } }
it 'works for matching' do
expect(&block).to send_telegram_message(bot, 'test')
expect(&block).to send_telegram_message(bot, /^tes/)
expect(&block).to send_telegram_message(bot, 'test', parseMode: :Markdown)
expect(&block).to send_telegram_message(bot, a_string_including('es'))
end
it 'works for not-matching' do
expect(&block).to_not send_telegram_message(bot, 'test!')
expect(&block).to_not send_telegram_message(bot, /tes$/)
expect(&block).to_not send_telegram_message(bot, 'test', parseMode: :HTML)
expect(&block).to_not send_telegram_message(bot, a_string_including('smth'))
end
end
end

71
spec/telegram/bot/rspec/integration_spec.rb Обычный файл
Просмотреть файл

@@ -0,0 +1,71 @@
require 'telegram/bot/rspec/integration'
require 'action_controller'
require 'action_dispatch'
require 'action_dispatch/testing/integration'
RSpec.describe 'Integrations helper', :telegram_bot do
include ActionDispatch::Integration::Runner
def reset_template_assertion; end
let(:app) do
app = Telegram::Bot::Middleware.new(bot, controller)
if ActionPack::VERSION::MAJOR >= 5
app
else
require 'action_dispatch/middleware/params_parser'
ActionDispatch::ParamsParser.new(app)
end
end
let(:bot) { Telegram::Bot::ClientStub.new('token') }
let(:controller_path) { '/' }
let(:controller) do
Class.new(Telegram::Bot::UpdatesController) do
def start(*args)
respond_with :message, text: "Start: #{args.inspect}, option: #{payload[:option]}"
end
end
end
describe '#default_message_options' do
subject { default_message_options }
it { should eq from: {id: from_id}, chat: {id: chat_id} }
end
describe '#dispatch' do
subject { -> { dispatch message: {text: '/start', **default_message_options} } }
it { should respond_with_message 'Start: [], option: ' }
end
describe '#dispatch_message' do
subject { -> { dispatch_message "/start #{args.join ' '}", options } }
let(:args) { %w(asd qwe) }
let(:options) { {} }
it { should respond_with_message "Start: #{args.inspect}, option: " }
context 'with options' do
let(:options) { {option: 1} }
it { should respond_with_message "Start: #{args.inspect}, option: 1" }
context 'and chat_id is not set' do
let(:options) { super().merge(chat: nil) }
it { should raise_error(/chat is not present/) }
end
end
end
describe '#dispatch_command' do
subject { -> { dispatch_command :start, *args } }
let(:args) { [] }
it { should respond_with_message "Start: #{args.inspect}, option: " }
context 'with args' do
let(:args) { %w(asd qwe) }
it { should respond_with_message "Start: #{args.inspect}, option: " }
end
context 'with options' do
let(:args) { ['asd', 'qwe', option: 1] }
it { should respond_with_message "Start: #{args[0...-1].inspect}, option: 1" }
end
end
end

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

@@ -1,4 +1,4 @@
RSpec.describe Telegram::Bot::UpdatesController::CallbackQueyContext do
RSpec.describe Telegram::Bot::UpdatesController::CallbackQueryContext do
include_context 'telegram/bot/updates_controller'
let(:controller_class) do
described_class = self.described_class

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

@@ -48,4 +48,32 @@ RSpec.describe Telegram::Bot::UpdatesController do
end
end
end
describe '#edit_message' do
subject { controller.edit_message(type, params) }
let(:type) { :reply_markup }
it { expect { subject }.to raise_error(/Can not edit message without/) }
context 'when inline_message_id is present' do
let(:payload) { {inline_message_id: double(:message_id)} }
it 'sets inline_message_id' do
expect(bot).to receive("edit_message_#{type}").with(params.merge(
inline_message_id: payload[:inline_message_id],
)) { result }
should eq result
end
end
context 'when message is present' do
let(:payload) { {message: super().merge(chat: chat)} }
it 'sets chat_id & message_id' do
expect(bot).to receive("edit_message_#{type}").with(params.merge(
message_id: payload[:message][:message_id],
chat_id: payload[:message][:chat]['id'],
)) { result }
should eq result
end
end
end
end

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

@@ -102,41 +102,59 @@ RSpec.describe Telegram::Bot::UpdatesController do
it { should eq [false, payload_type, payload.values_at(:data)] }
end
context 'when payload is edited_message' do
let(:payload_type) { 'edited_message' }
it { should eq [false, payload_type, [payload]] }
end
context 'when payload is not supported' do
let(:payload_type) { '_unsupported_' }
it { should eq [false, :unsupported_payload_type, []] }
end
context 'when payload is message' do
let(:payload_type) { 'message' }
let(:payload) { {'text' => text} }
let(:text) { 'test' }
it { should eq [false, payload_type, [payload]] }
context 'with command' do
let(:text) { "/test#{"@#{mention}" if mention} arg 1 2" }
let(:mention) {}
it { should eq [true, 'test', %w(arg 1 2)] }
context 'with mention' do
let(:mention) { bot.username }
it { should eq [true, 'test', %w(arg 1 2)] }
end
context 'with mention for other bot' do
let(:mention) { other_bot_name }
it { should eq [false, 'message', [payload]] }
end
%w(message channel_post).each do |type|
context "when payload is edited_#{type}" do
let(:payload_type) { "edited_#{type}" }
it { should eq [false, payload_type, [payload]] }
end
context 'without text' do
let(:payload) { {'audio' => {'file_id' => 123}} }
context 'when payload is message' do
let(:payload_type) { type }
let(:payload) { {'text' => text} }
let(:text) { 'test' }
it { should eq [false, payload_type, [payload]] }
context 'with command' do
let(:text) { "/test#{"@#{mention}" if mention} arg 1 2" }
let(:mention) {}
it { should eq [true, 'test', %w(arg 1 2)] }
context 'with mention' do
let(:mention) { bot.username }
it { should eq [true, 'test', %w(arg 1 2)] }
end
context 'with mention for other bot' do
let(:mention) { other_bot_name }
it { should eq [false, payload_type, [payload]] }
end
end
context 'without text' do
let(:payload) { {'audio' => {'file_id' => 123}} }
it { should eq [false, payload_type, [payload]] }
end
end
end
custom_payload_types = %w(
message
edited_message
channel_post
edited_channel_post
inline_query
chosen_inline_result
callback_query
)
(described_class::PAYLOAD_TYPES - custom_payload_types).each do |type|
context "when payload is #{type}" do
let(:payload_type) { type }
it { should eq [false, payload_type, [payload]] }
end
end
@@ -293,4 +311,49 @@ RSpec.describe Telegram::Bot::UpdatesController do
end
end
end
describe '#chat' do
subject { controller.chat }
let(:payload_type) { :message }
let(:payload) { {chat: 'test_value'} }
it { should eq payload[:chat] }
context 'when payload is not set' do
let(:payload) {}
it { should eq nil }
end
context 'when payload has no such field' do
let(:payload) { {smth: 'other'} }
it { should eq nil }
context 'but has `message`' do
let(:payload) { {message: message} }
let(:message) { {text: 'Hello bot!'} }
it { should eq nil }
context 'with `chat` set' do
let(:message) { super().merge(chat: 'test value') }
it { should eq message[:chat] }
end
end
end
end
describe '#from' do
subject { controller.from }
let(:payload_type) { :message }
let(:payload) { {from: 'test_value'} }
it { should eq payload[:from] }
context 'when payload is not set' do
let(:payload) {}
it { should eq nil }
end
context 'when payload has no such field' do
let(:payload) { {smth: 'other'} }
it { should eq nil }
end
end
end

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

@@ -1,9 +1,39 @@
RSpec.describe Telegram::Bot::UpdatesPoller do
describe '#initialize' do
subject { described_class.new bot, controller }
let(:bot) { double }
let(:controller) { double }
let(:instance) { described_class.new(bot, controller) }
let(:bot) { Telegram::Bot::Client.new('token') }
let(:controller) { double(:controller) }
describe '#initialize' do
subject { instance }
it { should be }
end
describe '#fetch_updates' do
subject { -> { instance.fetch_updates(&block) } }
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 }
before do
allow(bot).to receive(:get_updates) do
expect(bot.async).to be_falsy
request_result
end
end
it { should change(instance, :offset).to(results.last[:update_id] + 1) }
it { should change { expected_results }.to([]) }
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([]) }
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([]) }
end
end
end

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

@@ -2,25 +2,4 @@ RSpec.describe Telegram::Bot do
it 'has a version number' do
expect(described_class::VERSION).not_to be nil
end
describe described_class::StaleChat do
describe '.match_response?' do
subject { ->(val) { described_class.match_response?(val) } }
it 'returns true for specific errors' do
expect(subject.call({})).to eq false
expect(subject.call('description' => 'test')).to eq false
expect(subject.call('description' => 'Error: bot was kicked from')).to eq true
expect(subject.call(
'description' => "Forbidden: can't write to private chat with deleted user"
)).to eq true
expect(subject.call(
'description' => 'Bad request: group chat is deactivated'
)).to eq true
expect(subject.call(
'description' => 'Forbidden: Bot was blocked by the user'
)).to eq true
end
end
end
end

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

@@ -20,8 +20,8 @@ Gem::Specification.new do |spec|
spec.required_ruby_version = '~> 2.0'
spec.add_dependency 'activesupport', '>= 4.0', '< 5.1'
spec.add_dependency 'actionpack', '>= 4.0', '< 5.1'
spec.add_dependency 'activesupport', '>= 4.0', '< 5.2'
spec.add_dependency 'actionpack', '>= 4.0', '< 5.2'
spec.add_dependency 'httpclient', '~> 2.7'
spec.add_development_dependency 'bundler', '~> 1.11'
spec.add_development_dependency 'rake', '~> 10.0'