1
0
зеркало из https://github.com/glebtv/telegram-bot.git synced 2026-09-03 17:55:52 +03:00

Merge pull request #84 from telegram-bot-rb/unify_specs

Unify specs
Этот коммит содержится в:
printercu
2018-06-07 12:29:31 +06:00
коммит произвёл GitHub
родитель 2eb2670ed2 fb02c34be9
Коммит 8db3204609
33 изменённых файлов: 493 добавлений и 411 удалений

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

@@ -1,3 +1,16 @@
# Unreleased
- Make integration & controller specs consistent.
__Breaking changes__ for controller specs:
- Changed signature `dispatch(bot, update) => dispatch(update, bot)`.
- `update` helper is symbolized by default.
- `build_update(type, data)` is dropped in favor of `deep_stringify(type => data)`.
- Provide support for integration testing of bots in poller mode and non-Rails apps.
__Breaking changes__:
- Requiring `telegram/bot/rspec/integration` is deprecated in favor of
`telegram/bot/rspec/integration/rails`.
- `:telegram_bot` rspec tag is replaced with `telegram_bot: :rails`.
# 0.13.1
- Extracted typed response mappings to telegram-bot-types gem.

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

@@ -192,8 +192,9 @@ end
#### Reply helpers
There are helpers to respond for basic actions. They just set chat/message/query
identifiers from update. See [`ReplyHelpers`](https://github.com/telegram-bot-rb/telegram-bot/blob/master/lib/telegram/bot/updates_controller/reply_helpers.rb) module for more information.
Here are this methods signatures:
identifiers from update. See
[`ReplyHelpers`](https://github.com/telegram-bot-rb/telegram-bot/blob/master/lib/telegram/bot/updates_controller/reply_helpers.rb)
module for more information. Here are this methods signatures:
```ruby
def respond_with(type, params); end
@@ -394,11 +395,11 @@ Telegram::Bot::UpdatesPoller.start(bot, controller_class)
### Testing
There is `Telegram::Bot::ClientStub` class to stub client for tests.
Instead of performing API requests it stores them in `requests` hash.
There is a `Telegram::Bot::ClientStub` class to stub client for tests.
Instead of performing API requests it stores them in a `requests` hash.
To stub all possible clients use `Telegram::Bot::ClientStub.stub_all!` before
initializing clients. Here is template for RSpec:
initializing clients. Here is a template for RSpec:
```ruby
# environments/test.rb
@@ -416,47 +417,82 @@ RSpec.configure do |config|
end
```
There are integration and controller contexts for RSpec and some built-in matchers:
RSpec contexts and helpers are included automatically for groups and examples with matching
tags. In RSpec < 3.4 it's required to use `include_context` explicitly.
See [list of available helpers](https://github.com/telegram-bot-rb/telegram-bot/tree/master/lib/telegram/bot/rspec)
for details.
There are 3 types of integration tests:
- `:rails` - for testing bot in webhooks-mode in Rails application.
It simulates webhook requests POSTing data to controller's endpoint.
It works on the top of requests specs, so `rspec-rails` gem is required.
- `:rack` - For testing bot in webhooks-mode in non-Rails application.
It uses `rack-test` gem to POST requests to bot's endpoint.
- `:poller` - Calls `.dispatch` directly on controller class.
Pick the appropriate one, then require `telegram/bot/rspec/integration/#{type}`
and mark spec group with tag `telegram_bot: type`. See configuration options
for each type in
[telegram/bot/rspec/integration/](https://github.com/telegram-bot-rb/telegram-bot/tree/master/lib/telegram/bot/rspec/integration).
Here is an example test for a Rails app:
```ruby
# spec/requests/telegram_webhooks_spec.rb
require 'telegram/bot/rspec/integration'
require 'telegram/bot/rspec/integration/rails'
RSpec.describe TelegramWebhooksController, :telegram_bot do
# for old rspec add:
# include_context 'telegram/bot/integration'
RSpec.describe TelegramWebhooksController, telegram_bot: :rails do
# for old RSpec:
# include_context 'telegram/bot/integration/rails'
# Main method is #dispatch(update). Some helpers are:
# dispatch_message(text, options = {})
# dispatch_command(cmd, *args)
# Available matchers can be found in Telegram::Bot::RSpec::ClientMatchers.
it 'shows usage of basic matchers'
# The most basic one is #make_telegram_request(bot, endpoint, params_matcher)
expect { dispatch_command(:start) }.
to make_telegram_request(bot, :sendMessage, hash_including(text: 'msg text'))
# There are some shortcuts for dispatching basic updates and testing responses.
expect { dispatch_message('Hi') }.to send_telegram_message(bot, /msg regexp/, some: :option)
end
describe '#start' do
subject { -> { dispatch_command :start } }
# Using built in matcher for `respond_to`:
it { should respond_with_message 'Hi there!' }
end
# There is context for callback queries with related matchers.
# There is context for callback queries with related matchers,
# use :callback_query tag to include it.
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
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`.
There is a context for testing bot controller in the way similar to Rails controller tests.
It's supposed to be a low-level alternative for integration tests. Among the differences is
that controller tests use a single controller instance for all dispatches in specific exaple,
session is stubbed (does not use configured store engine), and update is not serialized
so it also supports mocks. This can be useful for unit testing, but should not be used as
the default way to test the bot.
```ruby
require 'telegram/bot/updates_controller/rspec_helpers'
RSpec.describe TelegramWebhooksController, type: :telegram_bot_controller do
# for old RSpec:
# include_context 'telegram/bot/updates_controller'
# Same helpers and matchers like dispatch_command, answer_callback_query are available here.
end
```
See sample app for more examples.

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

@@ -14,10 +14,10 @@ module Telegram
module_function
def deprecation_0_14
def deprecation_0_15
@deprecation ||= begin
require 'active_support/deprecation'
ActiveSupport::Deprecation.new('0.14', 'Telegram::Bot')
ActiveSupport::Deprecation.new('0.15', 'Telegram::Bot')
end
end

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

@@ -1,8 +1,7 @@
require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/json'
require 'action_dispatch/http/mime_type'
require 'action_dispatch/http/request'
require 'action_dispatch'
module Telegram
module Bot

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

@@ -24,33 +24,6 @@ module Telegram
end
end
# # Create routes for all Telegram.bots to use same controller:
# telegram_webhooks TelegramController
#
# # Or pass custom bots usin any of supported config options:
# 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|
controller, bot_options = controller if controller.is_a?(Array)
telegram_webhook(controller, bot, options.merge(bot_options || {}))
end
end
# Define route which processes requests using given controller and bot.
#
# telegram_webhook TelegramController, bot

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

@@ -2,6 +2,15 @@ module Telegram
module Bot
module RSpec
autoload :ClientMatchers, 'telegram/bot/rspec/client_matchers'
module_function
# Yelds a block if `include_context` is supported.
def with_include_context
::RSpec.configure do |config|
yield(config) if config.respond_to?(:include_context)
end
end
end
end
end

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

@@ -0,0 +1,39 @@
require 'telegram/bot/rspec'
require 'telegram/bot/rspec/message_helpers'
# Shared helpers for testing callback query updates.
RSpec.shared_context 'telegram/bot/callback_query' do
include_context 'telegram/bot/message_helpers'
subject { -> { dispatch callback_query: payload } }
let(:payload) { {id: callback_query_id, from: from, message: message, data: data} }
let(:callback_query_id) { 11 }
let(:message_id) { 22 }
let(:message) { {message_id: message_id, chat: chat, text: 'message text'} }
let(:data) { raise '`let(:data) { "callback query data here" }` is required' }
# 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

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

@@ -1,85 +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(: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? }
require 'telegram/bot'
Telegram::Bot.deprecation_0_15.warn(
"`require 'telegram/bot/rspec/integration'` is deprecated in favor of " \
"`require 'telegram/bot/rspec/integration/rails'`"
)
require 'telegram/bot/rspec/integration/rails'
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
Telegram::Bot::RSpec.with_include_context do |config|
config.include_context 'telegram/bot/integration/rails', telegram_bot: true
end

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

@@ -0,0 +1,14 @@
require 'telegram/bot/rspec/integration/shared'
RSpec.shared_context 'telegram/bot/integration/poller' do
include_context 'telegram/bot/integration/shared'
let(:controller_class) { described_class }
def dispatch(update)
controller_class.dispatch(bot, update.as_json)
end
end
Telegram::Bot::RSpec.with_include_context do |config|
config.include_context 'telegram/bot/integration/poller', telegram_bot: :poller
end

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

@@ -0,0 +1,24 @@
require 'telegram/bot/rspec/integration/shared'
require 'rack/test'
RSpec.shared_context 'telegram/bot/integration/rack' do
include_context 'telegram/bot/integration/shared'
include Rack::Test::Methods
let(:request_path) { raise '`let(:request_path) { path to bot }` is required' }
let(:app) { raise '`let(:app) { your rack app here }` is required' }
let(:request_headers) do
{
'ACCEPT' => 'application/json',
'CONTENT_TYPE' => 'application/json',
}
end
def dispatch(update)
post request_path, update.to_json, request_headers
end
end
Telegram::Bot::RSpec.with_include_context do |config|
config.include_context 'telegram/bot/integration/rack', telegram_bot: :rack
end

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

@@ -0,0 +1,28 @@
require 'telegram/bot/rspec/integration/shared'
RSpec.shared_context 'telegram/bot/integration/rails', type: :request do
include_context 'telegram/bot/integration/shared'
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
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
end
Telegram::Bot::RSpec.with_include_context do |config|
config.include_context 'telegram/bot/integration/rails', telegram_bot: :rails
end

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

@@ -0,0 +1,14 @@
require 'active_support/json'
require 'telegram/bot'
require 'telegram/bot/rspec/message_helpers'
require 'telegram/bot/rspec/callback_query_helpers'
RSpec.shared_context 'telegram/bot/integration/shared' do
include Telegram::Bot::RSpec::ClientMatchers
include_context 'telegram/bot/message_helpers'
include_context 'telegram/bot/callback_query', :callback_query
let(:bot) { Telegram.bot }
let(:clear_session?) { described_class.respond_to?(:session_store) }
before { described_class.session_store.try!(:clear) if clear_session? }
end

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

@@ -0,0 +1,26 @@
# Shared helpers for testing message updates.
RSpec.shared_context 'telegram/bot/message_helpers' do
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 }
# Shortcut for dispatching messages with default params.
def dispatch_message(text, options = {})
dispatch message: default_message_options.merge(options).merge(text: text)
end
# Dispatch command message.
def dispatch_command(cmd, *args)
options = args.last.is_a?(Hash) ? args.pop : {}
args.unshift("/#{cmd}")
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

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

@@ -1,37 +1,32 @@
require 'telegram/bot/updates_controller/testing'
require 'telegram/bot/rspec/message_helpers'
require 'telegram/bot/rspec/callback_query_helpers'
RSpec.shared_context 'telegram/bot/updates_controller' do
include Telegram::Bot::RSpec::ClientMatchers
include_context 'telegram/bot/message_helpers'
include_context 'telegram/bot/callback_query', :callback_query
let(:controller_class) { described_class }
let(:controller) do
controller_class.new(bot, update).tap do |x|
controller_class.new(*controller_args).tap do |x|
x.extend Telegram::Bot::UpdatesController::Testing
end
end
let(:update) { build_update(payload_type, payload) }
let(:controller_args) { [bot, deep_stringify(update)] }
let(:update) { {payload_type => payload} }
let(:payload_type) { :some_type }
let(:payload) { double(:payload) }
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, default_message_options.merge(options).merge(text: text)
dispatch bot, update
end
def build_update(type, content)
deep_stringify type => content
# Process update.
def dispatch(update = self.update, bot = self.bot)
controller.dispatch_again(bot, deep_stringify(update))
end
# Same as `.as_json` but mocks-friendly.
def deep_stringify(input)
case input
when Array then input.map(&method(__callee__))
@@ -39,15 +34,8 @@ 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
Telegram::Bot::RSpec.with_include_context do |config|
config.include_context 'telegram/bot/updates_controller', type: :telegram_bot_controller
end

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

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

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

@@ -1,6 +1,6 @@
require 'integration_helper'
RSpec.describe NamedBotController, :telegram_bot, type: :request do
RSpec.describe NamedBotController, telegram_bot: :rails do
let(:bot) { Telegram.bots[:named] }
describe '#start' do
subject { -> { dispatch_command :start } }

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

@@ -1,6 +1,6 @@
require 'integration_helper'
RSpec.describe OtherBotController, :telegram_bot, type: :request do
RSpec.describe OtherBotController, telegram_bot: :rails do
let(:bot) { Telegram.bots[:other] }
describe '#start' do
subject { -> { dispatch_command :start } }

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

@@ -1,7 +1,6 @@
require 'telegram/bot/rspec/integration'
require 'telegram/bot/rspec/integration/rails'
require 'action_controller'
require 'action_dispatch'
require 'action_dispatch/testing/integration'
require 'rails'
require 'telegram/bot/railtie'

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

@@ -32,6 +32,14 @@ RSpec.configure do |config|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
# This config option will be enabled by default on RSpec 4,
# but for reasons of backwards compatibility, you have to
# set it on RSpec 3.
#
# It causes the host group and examples to inherit metadata
# from the shared context.
config.shared_context_metadata_behavior = :apply_to_host_groups
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to

24
spec/support/examples/integration.rb Обычный файл
Просмотреть файл

@@ -0,0 +1,24 @@
RSpec.shared_examples 'shared integration examples' do
let(:bot) { Telegram::Bot::ClientStub.new('token') }
let(:controller_class) do
Class.new(Telegram::Bot::UpdatesController) do
def start(data = nil, *)
respond_with :message, text: "Hi #{data}"
end
def callback_query(data, *)
answer_callback_query "pong: #{data}"
end
end
end
describe '#start' do
subject { -> { dispatch_command(:start, :test_data) } }
it { should respond_with_message('Hi test_data') }
end
describe '#callback_query', :callback_query do
let(:data) { :test_data }
it { should answer_callback_query "pong: #{data}" }
end
end

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

@@ -7,7 +7,6 @@ RSpec.describe Telegram::Bot::Middleware do
describe '#call' do
subject { instance.call(env) }
let(:env) { {'action_dispatch.request.request_parameters' => json_body} }
let(:update) { {'message' => {'id' => 1}} }
let(:env) do
Rack::MockRequest.env_for('/',
@@ -21,7 +20,6 @@ RSpec.describe Telegram::Bot::Middleware do
if ActionPack::VERSION::MAJOR < 5
# Before Rails 5, params are parsed in middleware.
# In Rails 5, they are parsed in Request#request_parameters.
require 'action_dispatch/middleware/params_parser'
let(:instance) { ActionDispatch::ParamsParser.new(super()) }
end

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

@@ -95,108 +95,4 @@ RSpec.describe Telegram::Bot::RoutesHelper do
end
end
end
describe '#telegram_webhooks' do
subject { mapper.telegram_webhooks(*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 } }
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)|
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
end
subject
end
context 'when called with controller' do
let(:input) { [controller, option: :val] }
it 'creates routes for every bot and this controller' do
assert_routes [bot, controller, 'default_telegram_webhook', option: :val],
[other_bot, controller, 'other_telegram_webhook', option: :val]
end
context 'and bot does not have configured token' do
let(:bot) { create_bot(nil) }
it 'creates routes for every bot and this controller' do
assert_routes [bot, controller, 'default_telegram_webhook', option: :val],
[other_bot, controller, 'other_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',
], [other_bot, controller, 'other_telegram_webhook', option: :val]
end
end
end
context 'when called with hash' do
let(:input) do
[
{
bot => controller,
'custom_token' => [other_controller, as: :custom_route, option: :other_val],
other: controller,
},
option: :val,
]
end
it 'creates routes for every bot and its controller' do
assert_routes [bot, controller, 'default_telegram_webhook', option: :val],
[
create_bot('custom_token'),
other_controller,
:custom_route,
option: :other_val,
],
[Telegram.bots[:other], controller, 'other_telegram_webhook', option: :val]
end
end
context 'when called with controller and smth castable to bot' do
let(:input) do
[
controller,
['custom_token', token: bot.token, username: 'new_name'],
option: :val,
]
end
it 'creates routes for every created bot and controller' do
assert_routes [
create_bot('custom_token'),
controller,
'telegram_webhook',
option: :val,
], [
create_bot(bot.token, 'new_name'),
controller,
'telegram_webhook',
option: :val,
]
end
end
end
end

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

@@ -0,0 +1,42 @@
require 'telegram/bot/rspec/integration/poller'
RSpec.describe 'Integration spec helpers', telegram_bot: :poller do
let(:bot) { Telegram::Bot::ClientStub.new('token') }
let(:controller_class) 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', :callback_query do
let(:data) { 'unknown:command' }
it { should answer_callback_query("data: #{data}") }
end
describe '#context_callback_query', :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', :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

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

@@ -0,0 +1,5 @@
require 'telegram/bot/rspec/integration/poller'
RSpec.describe 'Poller integration spec', telegram_bot: :poller do
include_examples 'shared integration examples'
end

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

@@ -0,0 +1,20 @@
require 'telegram/bot/rspec/integration/rack'
RSpec.describe 'Rack integration spec', telegram_bot: :rack do
include_examples 'shared integration examples'
let(:request_path) { '/bot' }
let(:app) do
path = request_path
bot_app = Telegram::Bot::Middleware.new(bot, controller_class)
app = Rack::Builder.new do
map(path) { run bot_app }
run ->(env) { raise "Route is not mapped: #{env['PATH_INFO']}" }
end
if ActionPack::VERSION::MAJOR >= 5
app
else
require 'action_dispatch/middleware/params_parser'
ActionDispatch::ParamsParser.new(app)
end
end
end

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

@@ -0,0 +1 @@
# Tested in spec/integration

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

@@ -1,112 +0,0 @@
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
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,115 @@
require 'telegram/bot/rspec/integration/poller'
RSpec.describe 'Integration: message helpers', telegram_bot: :poller do
describe '#default_message_options' do
subject { default_message_options }
it { should eq from: {id: from_id}, chat: {id: chat_id} }
end
describe '#dispatch_message' do
subject { -> { dispatch_message text, options } }
let(:text) { '/start asd qwe' }
let(:options) { {} }
let(:result) { double(:result) }
it 'invokes dispatch' do
expect(self).to receive(:dispatch).with(
message: hash_including(default_message_options.merge(
text: text,
)),
) { result }
expect(subject.call).to eq result
end
context 'with options' do
let(:options) { {option: 1} }
it 'invokes dispatch' do
expect(self).to receive(:dispatch).with(
message: hash_including(default_message_options.merge(
text: text,
).merge(options)),
) { result }
expect(subject.call).to eq result
end
end
end
describe '#dispatch_command' do
subject { -> { dispatch_command :start, *args } }
let(:args) { [] }
let(:result) { double(:result) }
it 'invokes dispatch' do
expect(self).to receive(:dispatch).with(
message: hash_including(default_message_options.merge(
text: '/start',
)),
) { result }
expect(subject.call).to eq result
end
context 'with args & options' do
let(:args) { [*params, options] }
let(:params) { %w[qwe asd] }
let(:options) { {option: 1} }
it 'invokes dispatch' do
expect(self).to receive(:dispatch).with(
message: hash_including(default_message_options.merge(
text: "/start #{params.join(' ')}",
).merge(options)),
) { result }
expect(subject.call).to eq result
end
end
end
end
# Old specs
RSpec.describe 'Integration: message helpers', telegram_bot: :poller do
let(:bot) { Telegram::Bot::ClientStub.new('token') }
let(:controller_class) 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_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

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

@@ -27,7 +27,7 @@ RSpec.describe Telegram::Bot::UpdatesController::CallbackQueryContext do
describe '#dispatch' do
subject { -> { dispatch } }
let(:payload_type) { :callback_query }
let(:payload) { {'data' => data} }
let(:payload) { {data: data} }
let(:data) { text }
let(:text) { 'asd qwe zxc' }

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

@@ -2,9 +2,7 @@ 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(:update) { {message: default_message_options.merge(text: '/start')} }
let(:controller_class) do
Class.new(Telegram::Bot::UpdatesController) do
@@ -36,11 +34,11 @@ RSpec.describe Telegram::Bot::UpdatesController::Instrumentation do
action = action_name(:start_processing)
expect(events[action].size).to eq(1)
expect(events[action][0].last).to include(update: update)
expect(events[action][0].last).to include(update: deep_stringify(update))
action = action_name(:process_action)
expect(events[action].size).to eq(1)
expect(events[action][0].last).to include(update: update)
expect(events[action][0].last).to include(update: deep_stringify(update))
end
end

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

@@ -4,8 +4,8 @@ RSpec.describe Telegram::Bot::UpdatesController do
let(:respond_type) { :photo }
let(:result) { double(:result) }
let(:payload_type) { :message }
let(:payload) { {message_id: double(:message_id)} }
let(:chat) { {'id' => double(:chat_id)} }
let(:payload) { {message_id: double(:message_id), chat: chat} }
let(:chat) { {id: double(:chat_id)} }
shared_examples 'missing chat' do
context 'when chat is missing' do
@@ -19,9 +19,8 @@ RSpec.describe Telegram::Bot::UpdatesController do
include_examples 'missing chat'
it 'sets chat_id & reply_to_message_id' do
expect(controller).to receive(:chat) { chat }
expect(bot).to receive("send_#{respond_type}").
with(params.merge(chat_id: chat['id'])) { result }
with(params.merge(chat_id: chat[:id])) { result }
should eq result
end
end
@@ -31,19 +30,18 @@ RSpec.describe Telegram::Bot::UpdatesController do
include_examples 'missing chat'
it 'sets chat_id & reply_to_message_id' do
expect(controller).to receive(:chat) { chat }
expect(bot).to receive("send_#{respond_type}").with(params.merge(
chat_id: chat['id'],
chat_id: chat[:id],
reply_to_message_id: payload[:message_id],
)) { result }
should eq result
end
context 'when update is not set' do
let(:update) { {chat: chat} }
let(:controller_args) { [bot, chat: deep_stringify(chat)] }
it 'sets chat_id' do
expect(bot).to receive("send_#{respond_type}").
with(params.merge(chat_id: chat['id'])) { result }
with(params.merge(chat_id: chat[:id])) { result }
should eq result
end
end
@@ -70,7 +68,7 @@ RSpec.describe Telegram::Bot::UpdatesController do
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'],
chat_id: payload[:message][:chat][:id],
)) { result }
should eq result
end

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

@@ -36,7 +36,7 @@ RSpec.describe Telegram::Bot::UpdatesController::Session do
end
def build_message(text, from)
{'message' => {'text' => text, 'from' => from.stringify_keys}}
deep_stringify(message: {text: text, from: from})
end
it 'stores session between requests' do

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

@@ -278,11 +278,13 @@ RSpec.describe Telegram::Bot::UpdatesController do
instance_eval(&block)
context 'when re-initialized' do
let(:controller) do
described_class.new(double(:other_bot), build_update(:message,
initial_update = deep_stringify message: {
text: 'original message',
from: double(:original_from),
chat: double(:original_chat),
)).tap { |x| x.send(:initialize, bot, update) }
}
described_class.new(double(:other_bot), initial_update).
tap { |x| x.send(:initialize, *controller_args) }
end
instance_eval(&block)
end
@@ -300,7 +302,7 @@ RSpec.describe Telegram::Bot::UpdatesController do
end
context 'when options hash is given' do
let(:update) { {from: from, chat: chat} }
let(:controller_args) { [bot, from: from, chat: chat] }
with_reinitialize do
its(:bot) { should eq bot }
its(:update) { should eq nil }