diff --git a/Gemfile b/Gemfile index f9cc34d..ca27cba 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,8 @@ group :development do gem 'pry', '~> 0.10.1' gem 'pry-byebug', '~> 3.2.0' + gem 'telegram-bot-types', '~> 0.2.0' + gem 'rspec', '~> 3.3.0' gem 'rspec-its', '~> 1.1.0' diff --git a/README.md b/README.md index feee697..8a6ea2f 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ [![Build Status](https://travis-ci.org/printercu/telegram-bot.svg)](https://travis-ci.org/printercu/telegram-bot) Tools for developing bot for Telegram. Best used with Rails, but can be be used in -standalone app. Supposed to be used in webhook-mode in production, and poller mode +standalone app. Supposed to be used in webhook-mode in production, and poller-mode in development, but you can use poller in production if you want. Package contains: -- Ligthweight client to bot API (with fast and thread-safe - [httpclient](https://github.com/nahi/httpclient) is under the hood.) +- Ligthweight client for bot API (with fast and thread-safe + [httpclient](https://github.com/nahi/httpclient) under the hood). - Controller with message parser. Allows to write separate methods for each command. - Middleware and routes helpers for production env. - Poller with automatic source-reloader for development env. @@ -62,17 +62,33 @@ telegram: From now clients will be accessible with `Telegram.bots[:chat]` or `Telegram.bots[:auction]`. Single bot can be accessed with `Telegram.bot` or `Telegram.bots[:default]`. -You can create clients manually with `Telegram::Bot.new(token, username)`. +You can create clients manually with `Telegram::Bot::Client.new(token, username)`. Username is optional and used only to parse commands with mentions. -Client has all available methods in underscored style +There is `request(path_suffix, body)` method to perform any query. +And there are also shortcuts for available queries in underscored style (`answer_inline_query` instead of `answerInlineQuery`). All this methods just post given params to specific URL. ```ruby +bot.request(:getMe) or bot.get_me +bot.request(:getupdates, offset: 1) or bot.get_updates(offset: 1) bot.send_message chat_id: chat_id, text: 'Test' ``` +By default client will return parsed json responses. You can enable +response typecasting to virtus models using `telegram-bot-types` gem: +```ruby +# Add to your gemfile: +gem 'telegram-bot-types', '~> x.x.x' +# Enable typecasting: +Telegram::Bot::Client.typed_response! +# or for single instance: +bot.extend Telegram::Bot::Client::TypedResponse + +bot.get_me.class # => Telegram::Bot::Types::User +``` + ### Controller ```ruby @@ -121,6 +137,19 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController end ``` +You can enable typecasting of `update` with `telegram-bot-types` by including +`Telegram::Bot::UpdatesPoller::TypedUpdate`: + +```ruby +class Telegram::WebhookController < Telegram::Bot::UpdatesController + include Telegram::Bot::UpdatesPoller::TypedUpdate + + def message(message) + message.class # => Telegram::Bot::Types::Message + end +end +``` + ### Routes Use `telegram_webhooks` helper to add routes. It will create routes for bots diff --git a/lib/telegram/bot.rb b/lib/telegram/bot.rb index 71b69e0..1e96985 100644 --- a/lib/telegram/bot.rb +++ b/lib/telegram/bot.rb @@ -1,97 +1,16 @@ -require 'httpclient' -require 'json' -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/array/wrap' -require 'telegram/bottable' +require 'telegram/bot/config_methods' module Telegram - extend Bottable + extend Bot::ConfigMethods - class Bot + module Bot class Error < StandardError; end class NotFound < Error; end + autoload :Client, 'telegram/bot/client' autoload :Middleware, 'telegram/bot/middleware' autoload :UpdatesController, 'telegram/bot/updates_controller' autoload :UpdatesPoller, 'telegram/bot/updates_poller' - - URL_TEMPLATE = 'https://api.telegram.org/bot%s/'.freeze - - class << self - # Accepts different options to initialize bot. - def wrap(input) - case input - when self then input - when Array then input.map(&method(__callee__)) - when Hash then - input = input.stringify_keys - new input['token'], input['username'] - else - new(input) - end - end - end - - attr_reader :client, :token, :username, :base_uri - - def initialize(token, username = nil) - @client = HTTPClient.new - @token = token - @username = username - @base_uri = format URL_TEMPLATE, token - end - - def debug!(dev = STDOUT) - client.debug_dev = dev - end - - def debug_off! - client.debug_dev = nil - end - - def request(action, data = {}) - res = http_request("#{base_uri}#{action}", data) - 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'] || '-'}" - # NotFound is raised only for valid responses from Telegram - raise NotFound, err_msg if 404 == status && result - raise Error, err_msg - end - - %w( - answerInlineQuery - forwardMessage - getFile - getMe - getUpdates - getUserProfilePhotos - sendAudio - sendChatAction - sendDocument - sendLocation - sendMessage - sendPhoto - sendSticker - sendVideo - sendVoice - setWebhook - ).each do |method| - define_method(method.underscore) { |*args| request(method, *args) } - end - - # Endpoint for low-level request. For easy host highjacking & instrumentation. - # Params are not used directly but kept for instrumentation purpose. - # You probably don't want to use this method directly. - def http_request(uri, body) - client.post(uri, body) - end - - def inspect - "#" - end end end diff --git a/lib/telegram/bot/client.rb b/lib/telegram/bot/client.rb new file mode 100644 index 0000000..4f6443f --- /dev/null +++ b/lib/telegram/bot/client.rb @@ -0,0 +1,94 @@ +require 'json' +require 'httpclient' +require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/hash/keys' + +module Telegram + module Bot + class Client + autoload :TypedResponse, 'telegram/bot/client/typed_response' + + URL_TEMPLATE = 'https://api.telegram.org/bot%s/'.freeze + + class << self + # Accepts different options to initialize bot. + def wrap(input) + case input + when self then input + when Array then input.map(&method(__callee__)) + when Hash then + input = input.stringify_keys + new input['token'], input['username'] + else + new(input) + end + end + + # Prepend TypedResponse module. + def typed_response! + prepend TypedResponse + end + end + + attr_reader :client, :token, :username, :base_uri + + def initialize(token, username = nil) + @client = HTTPClient.new + @token = token + @username = username + @base_uri = format URL_TEMPLATE, token + end + + def debug!(dev = STDOUT) + client.debug_dev = dev + end + + def debug_off! + client.debug_dev = nil + end + + def request(action, data = {}) + res = http_request("#{base_uri}#{action}", data) + 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'] || '-'}" + # NotFound is raised only for valid responses from Telegram + raise NotFound, err_msg if 404 == status && result + raise Error, err_msg + end + + %w( + answerInlineQuery + forwardMessage + getFile + getMe + getUpdates + getUserProfilePhotos + sendAudio + sendChatAction + sendDocument + sendLocation + sendMessage + sendPhoto + sendSticker + sendVideo + sendVoice + setWebhook + ).each do |method| + define_method(method.underscore) { |*args| request(method, *args) } + end + + # Endpoint for low-level request. For easy host highjacking & instrumentation. + # Params are not used directly but kept for instrumentation purpose. + # You probably don't want to use this method directly. + def http_request(uri, body) + client.post(uri, body) + end + + def inspect + "#" + end + end + end +end diff --git a/lib/telegram/bot/client/typed_response.rb b/lib/telegram/bot/client/typed_response.rb new file mode 100644 index 0000000..c56dc72 --- /dev/null +++ b/lib/telegram/bot/client/typed_response.rb @@ -0,0 +1,38 @@ +module Telegram + module Bot + class Client + # Actions with type-casted results. Install `telegram-bot-types` gem first. + module TypedResponse + { + getFile: :File, + getMe: :User, + getUpdates: [:Update], + getUserProfilePhotos: :UserProfilePhotos, + + forwardMessage: :Message, + sendAudio: :Message, + sendDocument: :Message, + sendLocation: :Message, + sendMessage: :Message, + sendPhoto: :Message, + sendSticker: :Message, + sendVideo: :Message, + sendVoice: :Message, + }.each do |method, type| + next unless type + if type.is_a?(Array) + type_class = Types.const_get(type.first) + define_method(method.to_s.underscore) do |*args| + request(method, *args)['result'].map { |x| type_class.new(x) } + end + else + type_class = Types.const_get(type) + define_method(method.to_s.underscore) do |*args| + type_class.new request(method, *args)['result'] + end + end + end + end + end + end +end diff --git a/lib/telegram/bot/config_methods.rb b/lib/telegram/bot/config_methods.rb new file mode 100644 index 0000000..3903447 --- /dev/null +++ b/lib/telegram/bot/config_methods.rb @@ -0,0 +1,46 @@ +require 'active_support/core_ext/hash/keys' +require 'active_support/core_ext/hash/transform_values' + +module Telegram + module Bot + module ConfigMethods + # Overwrite config. + attr_writer :bots_config + + # Keep this setting here, so we can avoid loading Bot::UpdatesPoller + # when polling is disabled. + attr_writer :bot_poller_mode + + # It just tells routes helpers whether to add routed bots to + # Bot::UpdatesPoller, so their config will be available by bot key in + # Bot::UpdatesPoller.start. + def bot_poller_mode? + return @bot_poller_mode if defined?(@bot_poller_mode) + Rails.env.development? if defined?(Rails) + end + + # Hash of bots made with bots_config. + def bots + @bots ||= bots_config.transform_values(&Client.method(:wrap)) + end + + # Default bot. + def bot + @bot ||= bots[:default] + end + + # Returns config for .bots method. By default uses `telegram['bots']` section + # from `secrets.yml` merging `telegram['bot']` at `:default` key. + # + # Can be overwritten with .bots_config= + def bots_config + return @bots_config if @bots_config + telegram_config = Rails.application.secrets[:telegram] + (telegram_config['bots'] || {}).symbolize_keys.tap do |config| + default = telegram_config['bot'] + config[:default] = default if default + end + end + end + end +end diff --git a/lib/telegram/bot/middleware.rb b/lib/telegram/bot/middleware.rb index 4b38f4e..e42b9d4 100644 --- a/lib/telegram/bot/middleware.rb +++ b/lib/telegram/bot/middleware.rb @@ -3,7 +3,7 @@ require 'action_dispatch/http/mime_type' require 'action_dispatch/middleware/params_parser' module Telegram - class Bot + module Bot class Middleware attr_reader :bot, :controller diff --git a/lib/telegram/bot/railtie.rb b/lib/telegram/bot/railtie.rb index 09315b0..4c08258 100644 --- a/lib/telegram/bot/railtie.rb +++ b/lib/telegram/bot/railtie.rb @@ -1,7 +1,7 @@ require 'telegram/bot/routes_helper' module Telegram - class Bot + module Bot class Railtie < Rails::Railtie config.telegram_updates_controller = ActiveSupport::OrderedOptions.new diff --git a/lib/telegram/bot/routes_helper.rb b/lib/telegram/bot/routes_helper.rb index 01bdfd0..8f8d493 100644 --- a/lib/telegram/bot/routes_helper.rb +++ b/lib/telegram/bot/routes_helper.rb @@ -1,7 +1,8 @@ require 'telegram/bot' +require 'active_support/core_ext/array/wrap' module Telegram - class Bot + module Bot module RoutesHelper class << self # Returns route name for given bot. Result depends on `Telegram.bots`. @@ -42,7 +43,7 @@ module Telegram controllers = Hash[bots.map { |x| [x, controllers] }] end controllers.each do |bot, controller| - bot = Bot.wrap(bot) + bot = Client.wrap(bot) controller, bot_options = controller if controller.is_a?(Array) params = { to: Middleware.new(bot, controller), diff --git a/lib/telegram/bot/updates_controller.rb b/lib/telegram/bot/updates_controller.rb index f910321..493d97a 100644 --- a/lib/telegram/bot/updates_controller.rb +++ b/lib/telegram/bot/updates_controller.rb @@ -2,8 +2,10 @@ require 'abstract_controller' require 'active_support/callbacks' module Telegram - class Bot + module Bot class UpdatesController < AbstractController::Base + abstract! + include AbstractController::Callbacks include AbstractController::Translation @@ -11,6 +13,8 @@ module Telegram require 'telegram/bot/updates_controller/instrumentation' prepend Instrumentation + autoload :TypedUpdate, 'telegram/bot/updates_controller/typed_update' + PAYLOAD_TYPES = %w( message inline_query @@ -18,7 +22,6 @@ module Telegram ).freeze CMD_REGEX = %r{\A/([a-z\d_]{,31})(@(\S+))?(\s|$)}i CONFLICT_CMD_REGEX = Regexp.new("^(#{PAYLOAD_TYPES.join('|')}|\\d)") - abstract! class << self def dispatch(*args) diff --git a/lib/telegram/bot/updates_controller/instrumentation.rb b/lib/telegram/bot/updates_controller/instrumentation.rb index a0f40c6..f59c96c 100644 --- a/lib/telegram/bot/updates_controller/instrumentation.rb +++ b/lib/telegram/bot/updates_controller/instrumentation.rb @@ -1,5 +1,5 @@ module Telegram - class Bot + module Bot class UpdatesController # Most methods are taken from ActionController::Instrumentation, # some are slightly modified. diff --git a/lib/telegram/bot/updates_controller/log_subscriber.rb b/lib/telegram/bot/updates_controller/log_subscriber.rb index 9ed9d0a..584a838 100644 --- a/lib/telegram/bot/updates_controller/log_subscriber.rb +++ b/lib/telegram/bot/updates_controller/log_subscriber.rb @@ -1,7 +1,7 @@ require 'active_support/log_subscriber' module Telegram - class Bot + module Bot class UpdatesController class LogSubscriber < ActiveSupport::LogSubscriber def start_processing(event) diff --git a/lib/telegram/bot/updates_controller/rspec_helpers.rb b/lib/telegram/bot/updates_controller/rspec_helpers.rb new file mode 100644 index 0000000..ab0ec9c --- /dev/null +++ b/lib/telegram/bot/updates_controller/rspec_helpers.rb @@ -0,0 +1,8 @@ +RSpec.shared_context 'telegram/bot/updates_controller' do + let(:instance) { described_class.new(bot, update) } + let(:update) { {payload_type => payload} } + let(:payload_type) { 'some_type' } + let(:payload) { double(:payload) } + let(:bot) { double(username: bot_name) } + let(:bot_name) { 'bot' } +end diff --git a/lib/telegram/bot/updates_controller/typed_update.rb b/lib/telegram/bot/updates_controller/typed_update.rb new file mode 100644 index 0000000..45f8d6b --- /dev/null +++ b/lib/telegram/bot/updates_controller/typed_update.rb @@ -0,0 +1,14 @@ +module Telegram + module Bot + class UpdatesController + # Include this module to type cast update to Virtus model + # using `telegram-bot-types` gem (install this gem first). + module TypedUpdate + def initialize(bot, update) + update = Types::Update.new(update) if update && !update.is_a?(Types::Update) + super + end + end + end + end +end diff --git a/lib/telegram/bot/updates_poller.rb b/lib/telegram/bot/updates_poller.rb index 2fa1c57..84c61f8 100644 --- a/lib/telegram/bot/updates_poller.rb +++ b/lib/telegram/bot/updates_poller.rb @@ -1,5 +1,5 @@ module Telegram - class Bot + module Bot # Supposed to be used in development environments only. class UpdatesPoller class << self @@ -15,7 +15,7 @@ module Telegram end def start(bot_id, controller = nil) - bot = bot_id.is_a?(Symbol) ? Telegram.bots[bot_id] : Bot.wrap(bot_id) + bot = bot_id.is_a?(Symbol) ? Telegram.bots[bot_id] : Client.wrap(bot_id) instance = controller ? new(bot, controller) : instances[bot] raise "Poller not found for #{bot_id.inspect}" unless instance instance.start diff --git a/lib/telegram/bot/version.rb b/lib/telegram/bot/version.rb index 6846284..528c0d6 100644 --- a/lib/telegram/bot/version.rb +++ b/lib/telegram/bot/version.rb @@ -1,5 +1,5 @@ module Telegram - class Bot + module Bot VERSION = '0.3.0'.freeze def self.gem_version diff --git a/lib/telegram/bottable.rb b/lib/telegram/bottable.rb deleted file mode 100644 index 9f1bb1e..0000000 --- a/lib/telegram/bottable.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Telegram - module Bottable - # Overwrite config. - attr_writer :bots_config - - # Keep this setting here, so we can avoid loading Bot::UpdatesPoller - # when polling is disabled. - attr_writer :bot_poller_mode - - # It just tells routes helpers whether to add routed bots to - # Bot::UpdatesPoller, so their config will be available by bot key in - # Bot::UpdatesPoller.start. - def bot_poller_mode? - return @bot_poller_mode if defined?(@bot_poller_mode) - Rails.env.development? if defined?(Rails) - end - - # Hash of bots made with bots_config. - def bots - @bots ||= bots_config.transform_values(&Bot.method(:wrap)) - end - - # Default bot. - def bot - @bot ||= bots[:default] - end - - # Returns config for .bots method. By default uses `telegram['bots']` section - # from `secrets.yml` merging `telegram['bot']` at `:default` key. - # - # Can be overwritten with .bots_config= - def bots_config - return @bots_config if @bots_config - telegram_config = Rails.application.secrets[:telegram] - (telegram_config['bots'] || {}).symbolize_keys.tap do |config| - default = telegram_config['bot'] - config[:default] = default if default - end - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 75c496c..459e2f2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,6 +14,8 @@ GEM_ROOT = Pathname.new File.expand_path('../..', __FILE__) $LOAD_PATH.unshift GEM_ROOT.join('lib') require 'telegram/bot' +require 'telegram/bot/updates_controller/rspec_helpers' +require 'telegram/bot/types' RSpec.configure do |config| config.expect_with :rspec do |expectations| diff --git a/spec/telegram/bot/client/typed_response_spec.rb b/spec/telegram/bot/client/typed_response_spec.rb new file mode 100644 index 0000000..9e80af6 --- /dev/null +++ b/spec/telegram/bot/client/typed_response_spec.rb @@ -0,0 +1,32 @@ +RSpec.describe Telegram::Bot::Client::TypedResponse do + let(:bot) { Telegram::Bot::Client.new('token').tap { |x| x.extend described_class } } + + describe '#get_me' do + subject { bot.get_me } + before { expect(bot).to receive(:request).with(:getMe) { response } } + let(:response) { {'ok' => true, 'result' => {'id' => user_id}} } + let(:user_id) { 123 } + it { should be_instance_of Telegram::Bot::Types::User } + its(:id) { should eq user_id } + + context 'on error' do + let(:response) { raise Telegram::Bot::Error } + it { expect { subject }.to raise_error Telegram::Bot::Error } + end + end + + describe '#get_updates' do + subject { bot.get_updates } + before { expect(bot).to receive(:request).with(:getUpdates) { response } } + let(:response) { {'ok' => true, 'result' => [{'update_id' => update_id}]} } + let(:update_id) { 123 } + it { should be_instance_of Array } + its(:first) { should be_instance_of Telegram::Bot::Types::Update } + its('first.update_id') { should eq update_id } + + context 'on error' do + let(:response) { raise Telegram::Bot::Error } + it { expect { subject }.to raise_error Telegram::Bot::Error } + end + end +end diff --git a/spec/telegram/bot/client_spec.rb b/spec/telegram/bot/client_spec.rb new file mode 100644 index 0000000..fbad969 --- /dev/null +++ b/spec/telegram/bot/client_spec.rb @@ -0,0 +1,47 @@ +RSpec.describe Telegram::Bot::Client do + describe '.wrap' do + subject { described_class.wrap(input) } + let(:result) { double(:result) } + let(:token) { 'token' } + let(:username) { 'username' } + let(:instance) { described_class.new 'token' } + + context 'when input is a string' do + let(:input) { token } + + it 'treats string as token' do + expect(described_class).to receive(:new).with(token) { result } + should eq result + end + end + + context 'when input is a hash' do + let(:input) { {token: token, username: username, ignore: :ignore} } + + it 'extracts token & username' do + expect(described_class).to receive(:new).with(token, username) { result } + should eq result + end + end + + context 'when input is an instance of described_class' do + let!(:input) { instance } + + it 'returns input' do + expect(described_class).to_not receive(:new) + should eq input + end + end + + context 'when input is an array' do + let!(:input) { ['other_token', instance, token: token, username: username] } + let(:result_2) { double(:result_2) } + + it 'calls wrap for every element' do + expect(described_class).to receive(:new).with('other_token') { result } + expect(described_class).to receive(:new).with(token, username) { result_2 } + should eq [result, instance, result_2] + end + end + end +end diff --git a/spec/telegram/bot/routes_helper_spec.rb b/spec/telegram/bot/routes_helper_spec.rb index 01e4ed2..c4aa1da 100644 --- a/spec/telegram/bot/routes_helper_spec.rb +++ b/spec/telegram/bot/routes_helper_spec.rb @@ -1,10 +1,14 @@ require 'telegram/bot/routes_helper' RSpec.describe Telegram::Bot::RoutesHelper do - let(:bot) { Telegram::Bot.new('bot_token') } - let(:other_bot) { Telegram::Bot.new('other_token') } + let(:bot) { create_bot('bot_token') } + let(:other_bot) { create_bot('other_token') } let(:bots) { {default: bot, other: other_bot} } + def create_bot(*args) + Telegram::Bot::Client.new(*args) + end + describe '.route_name_for_bot' do subject { described_class.route_name_for_bot(input) } before { expect(Telegram).to receive(:bots) { bots } } @@ -82,7 +86,7 @@ RSpec.describe Telegram::Bot::RoutesHelper do it 'creates routes for every bot and its controller' do assert_routes [bot, controller, 'default_telegram_webhook', option: :val], [ - Telegram::Bot.new('custom_token'), + create_bot('custom_token'), other_controller, :custom_route, option: :other_val, @@ -101,12 +105,12 @@ RSpec.describe Telegram::Bot::RoutesHelper do it 'creates routes for every created bot and controller' do assert_routes [ - Telegram::Bot.new('custom_token'), + create_bot('custom_token'), controller, 'telegram_webhook', option: :val, ], [ - Telegram::Bot.new(bot.token, 'new_name'), + create_bot(bot.token, 'new_name'), controller, 'telegram_webhook', option: :val, diff --git a/spec/telegram/bot/updates_controller/typed_update_spec.rb b/spec/telegram/bot/updates_controller/typed_update_spec.rb new file mode 100644 index 0000000..b7f6874 --- /dev/null +++ b/spec/telegram/bot/updates_controller/typed_update_spec.rb @@ -0,0 +1,27 @@ +RSpec.describe Telegram::Bot::UpdatesController::TypedUpdate do + include_context 'telegram/bot/updates_controller' + let(:instance) { controller_class.new(bot, update) } + let(:controller_class) do + described_class = self.described_class + Class.new(Telegram::Bot::UpdatesController) do + include described_class + end + end + + context 'when `update` is a virtus model' do + subject { instance } + %w( + message + inline_query + chosen_inline_result + ).each do |type| + context "with #{type}" do + type_class = Telegram::Bot::Types.const_get(type.camelize) + let(:payload_type) { type } + let(:payload) { {} } + its(:payload_type) { should eq payload_type } + its(:payload) { should be_instance_of type_class } + end + end + end +end diff --git a/spec/telegram/bot/updates_controller_spec.rb b/spec/telegram/bot/updates_controller_spec.rb index c7910ed..e39e761 100644 --- a/spec/telegram/bot/updates_controller_spec.rb +++ b/spec/telegram/bot/updates_controller_spec.rb @@ -1,10 +1,5 @@ RSpec.describe Telegram::Bot::UpdatesController do - let(:instance) { described_class.new(bot, update) } - let(:update) { {payload_type => payload} } - let(:payload_type) { 'some_type' } - let(:payload) { double(:payload) } - let(:bot) { double(username: bot_name) } - let(:bot_name) { 'bot' } + include_context 'telegram/bot/updates_controller' let(:other_bot_name) { 'other_bot' } describe '.action_for_command' do @@ -117,6 +112,24 @@ RSpec.describe Telegram::Bot::UpdatesController do end end + context 'when `update` is a virtus model' do + subject { instance } + let(:update) { Telegram::Bot::Types::Update.new(super()) } + %w( + message + inline_query + chosen_inline_result + ).each do |type| + context "with #{type}" do + type_class = Telegram::Bot::Types.const_get(type.camelize) + let(:payload_type) { type } + let(:payload) { {} } + its(:payload_type) { should eq payload_type } + its(:payload) { should be_instance_of type_class } + end + end + end + describe '#bot_username' do subject { instance.bot_username } diff --git a/spec/telegram/bot_spec.rb b/spec/telegram/bot_spec.rb index 1f17405..19a51f4 100644 --- a/spec/telegram/bot_spec.rb +++ b/spec/telegram/bot_spec.rb @@ -1,51 +1,5 @@ RSpec.describe Telegram::Bot do it 'has a version number' do - expect(Telegram::Bot::VERSION).not_to be nil - end - - describe '.wrap' do - subject { described_class.wrap(input) } - let(:result) { double(:result) } - let(:token) { 'token' } - let(:username) { 'username' } - let(:instance) { described_class.new 'token' } - - context 'when input is a string' do - let(:input) { token } - - it 'treats string as token' do - expect(described_class).to receive(:new).with(token) { result } - should eq result - end - end - - context 'when input is a hash' do - let(:input) { {token: token, username: username, ignore: :ignore} } - - it 'extracts token & username' do - expect(described_class).to receive(:new).with(token, username) { result } - should eq result - end - end - - context 'when input is an instance of described_class' do - let!(:input) { instance } - - it 'returns input' do - expect(described_class).to_not receive(:new) - should eq input - end - end - - context 'when input is an array' do - let!(:input) { ['other_token', instance, token: token, username: username] } - let(:result_2) { double(:result_2) } - - it 'calls wrap for every element' do - expect(described_class).to receive(:new).with('other_token') { result } - expect(described_class).to receive(:new).with(token, username) { result_2 } - should eq [result, instance, result_2] - end - end + expect(described_class::VERSION).not_to be nil end end