зеркало из
https://github.com/glebtv/telegram-bot.git
synced 2026-09-03 17:55:52 +03:00
Async requests with ActiveJob
Этот коммит содержится в:
15
README.md
15
README.md
@@ -17,6 +17,7 @@ Package contains:
|
|||||||
- Middleware and routes helpers for production env.
|
- Middleware and routes helpers for production env.
|
||||||
- Poller with automatic source-reloader for development env.
|
- Poller with automatic source-reloader for development env.
|
||||||
- Rake tasks to update webhook urls.
|
- Rake tasks to update webhook urls.
|
||||||
|
- Async requests for Telegram and/or Botan API. Let the queue adapter handle errors!
|
||||||
|
|
||||||
Here is sample [telegram_bot_app](https://github.com/telegram-bot-rb/telegram_bot_app)
|
Here is sample [telegram_bot_app](https://github.com/telegram-bot-rb/telegram_bot_app)
|
||||||
with session, keyboards and inline queries.
|
with session, keyboards and inline queries.
|
||||||
@@ -374,6 +375,20 @@ end
|
|||||||
|
|
||||||
There is no stubbing for botan clients, so don't set botan token in tests.
|
There is no stubbing for botan clients, so don't set botan token in tests.
|
||||||
|
|
||||||
|
### Async mode
|
||||||
|
|
||||||
|
There is built in support for async requests using ActiveJob. Without Rails
|
||||||
|
you can implement your own worker class to handle such requests. This allows:
|
||||||
|
|
||||||
|
- Process updates very fast, without waiting for telegram and botan responses.
|
||||||
|
- Handle and retry network and other errors with queue adapter.
|
||||||
|
- ???
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
After checking out the repo, run `bin/setup` to install dependencies.
|
After checking out the repo, run `bin/setup` to install dependencies.
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ module Telegram
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
autoload :Async, 'telegram/bot/async'
|
||||||
autoload :Botan, 'telegram/bot/botan'
|
autoload :Botan, 'telegram/bot/botan'
|
||||||
autoload :Client, 'telegram/bot/client'
|
autoload :Client, 'telegram/bot/client'
|
||||||
autoload :ClientStub, 'telegram/bot/client_stub'
|
autoload :ClientStub, 'telegram/bot/client_stub'
|
||||||
|
|||||||
138
lib/telegram/bot/async.rb
Обычный файл
138
lib/telegram/bot/async.rb
Обычный файл
@@ -0,0 +1,138 @@
|
|||||||
|
module Telegram
|
||||||
|
module Bot
|
||||||
|
# Telegram & Botan clients can perform requests in async way with
|
||||||
|
# any job adapter (ActiveJob by default). Using Rails you don't need any
|
||||||
|
# additional configuration. However you may want to enable async requests
|
||||||
|
# by default with `async: true` in `secrets.yml`. Botan client doesn't inherit
|
||||||
|
# async setting from client and must be configured separately.
|
||||||
|
#
|
||||||
|
# telegram:
|
||||||
|
# bots:
|
||||||
|
# chat_async:
|
||||||
|
# token: secret
|
||||||
|
# async: true # enable async mode for client
|
||||||
|
# botan: botan_token # in this way botan will not be async
|
||||||
|
# botan: # in this way - it's in async mode
|
||||||
|
# token: botan_token
|
||||||
|
# async: true
|
||||||
|
#
|
||||||
|
# Without Rails To start using async requests
|
||||||
|
# initialize client with `id` kwarg and make sure the client is
|
||||||
|
# accessible via `Teletgram.bots[id]` in job worker. Or just use
|
||||||
|
# `Telegram.bots_config=` for configuration.
|
||||||
|
#
|
||||||
|
# Being in async mode `#request` enqueues job instead to perform
|
||||||
|
# http request instead of performing it directly.
|
||||||
|
# Async behavior is controlled with `#async=` writer
|
||||||
|
# and can be enabled/disabled for the block with `#async`:
|
||||||
|
#
|
||||||
|
# client = Telegram::Bot::Client.new(**config, async: true)
|
||||||
|
# client.send_message(message)
|
||||||
|
# client.async(false) { client.send_message(other_one) }
|
||||||
|
#
|
||||||
|
# It can be set with custom job class or classname. By default it defines
|
||||||
|
# job classes for every client class, inherited from ApplicationRecord, which
|
||||||
|
# can be accessed via `.default_async_job`. You can integrate it with any
|
||||||
|
# other job provider by defining a class with `.perform_later(bot_id, *args)`
|
||||||
|
# method. See Async::Job for implemetation.
|
||||||
|
module Async
|
||||||
|
module Job
|
||||||
|
class << self
|
||||||
|
def included(base)
|
||||||
|
base.singleton_class.send :attr_accessor, :client_class
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(client_id, *args)
|
||||||
|
client = self.class.client_class.wrap(client_id.to_sym)
|
||||||
|
client.async(false) { client.request(*args) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def default_async_job
|
||||||
|
@default_async_job ||= begin
|
||||||
|
begin
|
||||||
|
ApplicationJob
|
||||||
|
rescue NameError
|
||||||
|
raise 'Define ApplicationJob class or setup #async= with custom job class'
|
||||||
|
end
|
||||||
|
klass = Class.new(ApplicationJob) { include Job }
|
||||||
|
klass.client_class = self
|
||||||
|
const_set(:AsyncJob, klass)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This is used in specs.
|
||||||
|
def default_async_job=(val)
|
||||||
|
@default_async_job = val
|
||||||
|
remove_const(:AsyncJob) if const_defined?(:AsyncJob, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Prepares argments for async job. ActiveJob doesn't support
|
||||||
|
# Symbol in argumens. Also we can encode json bodies only once here,
|
||||||
|
# so it would not be unnecessarily serialized-deserialized.
|
||||||
|
#
|
||||||
|
# This is stub method, which returns input. Every client class
|
||||||
|
# must prepare args itself.
|
||||||
|
def prepare_async_args(*args)
|
||||||
|
args
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def prepended(base)
|
||||||
|
base.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Transforms symbols to strings in hash values.
|
||||||
|
def prepare_hash(hash)
|
||||||
|
return hash unless hash.is_a?(Hash)
|
||||||
|
hash = hash.dup
|
||||||
|
hash.each { |key, val| hash[key] = val.to_s if val.is_a?(Symbol) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :id
|
||||||
|
|
||||||
|
def initialize(*, id: nil, async: nil, **options)
|
||||||
|
@id = id
|
||||||
|
self.async = async
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sets `@async` to `self.class.default_async_job` if `true` is given
|
||||||
|
# or uses given value.
|
||||||
|
# Pass custom job class to perform async calls with.
|
||||||
|
def async=(val)
|
||||||
|
@async =
|
||||||
|
case val
|
||||||
|
when true then self.class.default_async_job
|
||||||
|
when String then const_get(val)
|
||||||
|
else val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns value of `@async` if no block is given. Otherwise sets this value
|
||||||
|
# for a block.
|
||||||
|
def async(val = true)
|
||||||
|
return @async unless block_given?
|
||||||
|
begin
|
||||||
|
old_val = @async
|
||||||
|
self.async = val
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
@async = old_val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Uses job if #async is set.
|
||||||
|
def request(*args)
|
||||||
|
job_class = async
|
||||||
|
return super unless job_class
|
||||||
|
raise 'Can not enqueue job without client id' unless id
|
||||||
|
job_class.perform_later(id.to_s, *self.class.prepare_async_args(*args))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -8,12 +8,17 @@ module Telegram
|
|||||||
class Error < Bot::Error; end
|
class Error < Bot::Error; end
|
||||||
|
|
||||||
extend Initializers
|
extend Initializers
|
||||||
|
prepend Async
|
||||||
include DebugClient
|
include DebugClient
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def by_id(id)
|
def by_id(id)
|
||||||
Telegram.botans[id]
|
Telegram.botans[id]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def prepare_async_args(method, uri, query = {}, body = nil)
|
||||||
|
[method.to_s, uri.to_s, Async.prepare_hash(query), body]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :client, :token
|
attr_reader :client, :token
|
||||||
@@ -24,12 +29,11 @@ module Telegram
|
|||||||
end
|
end
|
||||||
|
|
||||||
def track(event, uid, payload = {})
|
def track(event, uid, payload = {})
|
||||||
res = http_request(
|
request(:post, TRACK_URI, {name: event, uid: uid}, payload.to_json)
|
||||||
:post,
|
end
|
||||||
TRACK_URI,
|
|
||||||
{token: token, name: event, uid: uid},
|
def request(method, uri, query = {}, body = nil)
|
||||||
payload.to_json,
|
res = http_request(method, uri, query.merge(token: token), body)
|
||||||
)
|
|
||||||
status = res.status
|
status = res.status
|
||||||
return JSON.parse(res.body) if 300 > status
|
return JSON.parse(res.body) if 300 > status
|
||||||
result = JSON.parse(res.body) rescue nil # rubocop:disable RescueModifier
|
result = JSON.parse(res.body) rescue nil # rubocop:disable RescueModifier
|
||||||
@@ -40,6 +44,10 @@ module Telegram
|
|||||||
def http_request(method, uri, query, body)
|
def http_request(method, uri, query, body)
|
||||||
client.request(method, uri, query, body)
|
client.request(method, uri, query, body)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
"#<#{self.class.name}##{object_id}(#{@id})>"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ module Telegram
|
|||||||
|
|
||||||
def initialize(*, botan: nil, **)
|
def initialize(*, botan: nil, **)
|
||||||
super
|
super
|
||||||
@botan = Botan.wrap(botan) if botan
|
@botan = Botan.wrap(botan, id: id) if botan
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ module Telegram
|
|||||||
|
|
||||||
autoload :TypedResponse, 'telegram/bot/client/typed_response'
|
autoload :TypedResponse, 'telegram/bot/client/typed_response'
|
||||||
extend Initializers
|
extend Initializers
|
||||||
|
prepend Async
|
||||||
prepend Botan::ClientHelpers
|
prepend Botan::ClientHelpers
|
||||||
include DebugClient
|
include DebugClient
|
||||||
|
|
||||||
@@ -30,6 +31,10 @@ module Telegram
|
|||||||
body[k] = val.to_json if val.is_a?(Hash) || val.is_a?(Array)
|
body[k] = val.to_json if val.is_a?(Hash) || val.is_a?(Array)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def prepare_async_args(action, body = {})
|
||||||
|
[action.to_s, Async.prepare_hash(prepare_body(body))]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :client, :token, :username, :base_uri
|
attr_reader :client, :token, :username, :base_uri
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ module Telegram
|
|||||||
|
|
||||||
# Hash of bots made with bots_config.
|
# Hash of bots made with bots_config.
|
||||||
def bots
|
def bots
|
||||||
@bots ||= bots_config.transform_values(&Client.method(:wrap))
|
@bots ||= bots_config.each_with_object({}) do |(id, config), h|
|
||||||
|
h[id] = Client.wrap(config, id: id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Default bot.
|
# Default bot.
|
||||||
@@ -44,12 +46,16 @@ module Telegram
|
|||||||
#
|
#
|
||||||
# Can be overwritten with .bots_config=
|
# Can be overwritten with .bots_config=
|
||||||
def bots_config
|
def bots_config
|
||||||
return @bots_config if @bots_config
|
@bots_config ||=
|
||||||
telegram_config = Rails.application.secrets[:telegram]
|
if defined?(Rails)
|
||||||
(telegram_config['bots'] || {}).symbolize_keys.tap do |config|
|
telegram_config = Rails.application.secrets[:telegram] || {}
|
||||||
default = telegram_config['bot']
|
(telegram_config['bots'] || {}).symbolize_keys.tap do |config|
|
||||||
config[:default] = default if default
|
default = telegram_config['bot']
|
||||||
end
|
config[:default] = default if default
|
||||||
|
end
|
||||||
|
else
|
||||||
|
{}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Resets all cached bots and their configs.
|
# Resets all cached bots and their configs.
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ module Telegram
|
|||||||
end
|
end
|
||||||
|
|
||||||
def fetch_updates
|
def fetch_updates
|
||||||
response = bot.get_updates(offset: offset, timeout: timeout)
|
response = bot.async(false) { bot.get_updates(offset: offset, timeout: timeout) }
|
||||||
return unless response['ok'] && response['result'].any?
|
return unless response['ok'] && response['result'].any?
|
||||||
reload! do
|
reload! do
|
||||||
response['result'].each do |update|
|
response['result'].each do |update|
|
||||||
|
|||||||
89
spec/support/examples/async.rb
Обычный файл
89
spec/support/examples/async.rb
Обычный файл
@@ -0,0 +1,89 @@
|
|||||||
|
RSpec.shared_examples 'async' do |request_args: -> {}|
|
||||||
|
let(:instance) { described_class.new(token: token, id: id, async: async) }
|
||||||
|
let(:id) { :default_bot }
|
||||||
|
let(:async) { true }
|
||||||
|
let!(:application_job_class) do
|
||||||
|
klass = Class.new do
|
||||||
|
def self.perform_later(*)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
klass.tap { |x| stub_const('ApplicationJob', x) }
|
||||||
|
end
|
||||||
|
after { described_class.default_async_job = nil }
|
||||||
|
|
||||||
|
describe '#async' do
|
||||||
|
subject { ->(*args, &block) { instance.async(*args, &block) } }
|
||||||
|
its(:call) { should eq described_class.default_async_job }
|
||||||
|
|
||||||
|
context 'when async is disabled' do
|
||||||
|
let(:async) { false }
|
||||||
|
its(:call) { should eq false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when using with block' do
|
||||||
|
it 'sets value inside block' do
|
||||||
|
expect do
|
||||||
|
expect do
|
||||||
|
subject.call(false) do
|
||||||
|
expect do
|
||||||
|
subject.call(nil) { expect(subject[]).to eq nil }
|
||||||
|
end.to_not change(&subject).from false
|
||||||
|
raise 'TestError'
|
||||||
|
end
|
||||||
|
end.to raise_error(/TestError/)
|
||||||
|
end.to_not change(instance, :async).from(described_class.default_async_job)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.default_async_job' do
|
||||||
|
subject { described_class.default_async_job }
|
||||||
|
its(:superclass) { should eq application_job_class }
|
||||||
|
it { should include Telegram::Bot::Async::Job }
|
||||||
|
its(:client_class) { should eq described_class }
|
||||||
|
|
||||||
|
context 'when ApplicationJob is not defined' do
|
||||||
|
let(:application_job_class) {}
|
||||||
|
it { expect { subject }.to raise_error(/Define ApplicationJob/) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#request' do
|
||||||
|
subject { ->(*args) { instance.request(*(args.empty? ? self.args : args)) } }
|
||||||
|
let(:args, &request_args)
|
||||||
|
let(:result) { double(:result) }
|
||||||
|
|
||||||
|
shared_examples 'enqueues job' do
|
||||||
|
it 'enqueues job' do
|
||||||
|
expect(instance).to_not receive(:http_request)
|
||||||
|
expect(described_class).to receive(:prepare_async_args).with(*args) { args }
|
||||||
|
expect(instance.async).to receive(:perform_later).with(id.to_s, *args) { result }
|
||||||
|
expect(subject.call).to eq result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
include_examples 'enqueues job'
|
||||||
|
|
||||||
|
context 'with custom job class' do
|
||||||
|
let(:async) { double(:job_class) }
|
||||||
|
include_examples 'enqueues job'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when id is not set' do
|
||||||
|
let(:id) {}
|
||||||
|
it { should raise_error(/Can not enqueue/) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when async is disabled' do
|
||||||
|
let(:async) { false }
|
||||||
|
let(:result) { double(status: 200, body: '{"test":"ok"}') }
|
||||||
|
let(:args, &request_args)
|
||||||
|
|
||||||
|
it 'performs request immediately' do
|
||||||
|
expect(instance).to receive(:request).with(*args).and_call_original
|
||||||
|
expect(instance).to receive(:http_request) { result }
|
||||||
|
expect(subject[]).to eq 'test' => 'ok'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
28
spec/telegram/bot/async_spec.rb
Обычный файл
28
spec/telegram/bot/async_spec.rb
Обычный файл
@@ -0,0 +1,28 @@
|
|||||||
|
RSpec.describe Telegram::Bot::Async::Job do
|
||||||
|
let(:job_class) do
|
||||||
|
described_class = self.described_class
|
||||||
|
client_class = self.client_class
|
||||||
|
Class.new do
|
||||||
|
include described_class
|
||||||
|
self.client_class = client_class
|
||||||
|
end
|
||||||
|
end
|
||||||
|
let(:client_class) { Telegram::Bot::Client }
|
||||||
|
let(:instance) { job_class.new }
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
subject { instance.perform(id, *args) }
|
||||||
|
let(:id) { 'bot_id' }
|
||||||
|
let(:args) { [double(:action), {body: :content}] }
|
||||||
|
let(:client) { Telegram::Bot::Client.new(async: custom_job_class) }
|
||||||
|
let(:custom_job_class) { Class.new }
|
||||||
|
let(:result) { double(status: 200, body: '{"test":"ok"}') }
|
||||||
|
|
||||||
|
it 'finds client and performs request' do
|
||||||
|
expect(client_class).to receive(:wrap).with(id.to_sym) { client }
|
||||||
|
expect(client).to receive(:request).with(*args).and_call_original
|
||||||
|
expect(client).to receive(:http_request) { result }
|
||||||
|
should eq 'test' => 'ok'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -13,6 +13,12 @@ RSpec.describe Telegram::Bot::Client do
|
|||||||
let(:client_args) { [token, id: client_id, async: Class.new, botan: botan_token] }
|
let(:client_args) { [token, id: client_id, async: Class.new, botan: botan_token] }
|
||||||
it { should be_instance_of Telegram::Bot::Botan }
|
it { should be_instance_of Telegram::Bot::Botan }
|
||||||
its(:token) { should eq botan_token }
|
its(:token) { should eq botan_token }
|
||||||
|
its(:id) { should eq client_id }
|
||||||
|
|
||||||
|
it 'doesnt inherit async from client' do
|
||||||
|
expect(instance.async).to be
|
||||||
|
expect(subject.async).to_not be
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when botan is configured with hash' do
|
context 'when botan is configured with hash' do
|
||||||
@@ -20,6 +26,8 @@ RSpec.describe Telegram::Bot::Client do
|
|||||||
let(:botan_config) { {token: botan_token, async: Class.new} }
|
let(:botan_config) { {token: botan_token, async: Class.new} }
|
||||||
it { should be_instance_of Telegram::Bot::Botan }
|
it { should be_instance_of Telegram::Bot::Botan }
|
||||||
its(:token) { should eq botan_token }
|
its(:token) { should eq botan_token }
|
||||||
|
its(:id) { should eq client_id }
|
||||||
|
its(:async) { should eq botan_config[:async] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ RSpec.describe Telegram::Bot::Botan do
|
|||||||
let(:token) { 'token' }
|
let(:token) { 'token' }
|
||||||
|
|
||||||
include_examples 'initializers', :botans
|
include_examples 'initializers', :botans
|
||||||
|
include_examples 'async', request_args: -> { [double(:method), double(:url)] }
|
||||||
|
|
||||||
describe '.new' do
|
describe '.new' do
|
||||||
subject { described_class.new(*args) }
|
subject { described_class.new(*args) }
|
||||||
@@ -17,4 +18,10 @@ RSpec.describe Telegram::Bot::Botan do
|
|||||||
its(:token) { should eq args[0][:token] }
|
its(:token) { should eq args[0][:token] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.prepare_async_args' do
|
||||||
|
subject { described_class.prepare_async_args(*input) }
|
||||||
|
let(:input) { [:post, :uri, {a: 1, b: :sym, 'd' => 'str'}, 'body'] }
|
||||||
|
it { should eq ['post', 'uri', {a: 1, b: 'sym', 'd' => 'str'}, 'body'] }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ RSpec.describe Telegram::Bot::Client do
|
|||||||
let(:botan_token) { double(:botan_token) }
|
let(:botan_token) { double(:botan_token) }
|
||||||
|
|
||||||
include_examples 'initializers'
|
include_examples 'initializers'
|
||||||
|
include_examples 'async', request_args: -> { [double(:action), {body: :content}] }
|
||||||
|
|
||||||
describe '.prepare_body' do
|
describe '.prepare_body' do
|
||||||
subject { described_class.prepare_body(input) }
|
subject { described_class.prepare_body(input) }
|
||||||
@@ -24,6 +25,12 @@ RSpec.describe Telegram::Bot::Client do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.prepare_async_args' do
|
||||||
|
subject { described_class.prepare_async_args(*input) }
|
||||||
|
let(:input) { [:action, a: 1, b: :sym, c: [:other], 'd' => 'str'] }
|
||||||
|
it { should eq ['action', a: 1, b: 'sym', c: '["other"]', 'd' => 'str'] }
|
||||||
|
end
|
||||||
|
|
||||||
describe '.new' do
|
describe '.new' do
|
||||||
subject { described_class.new(*args) }
|
subject { described_class.new(*args) }
|
||||||
|
|
||||||
|
|||||||
94
spec/telegram/bot/config_methods_spec.rb
Обычный файл
94
spec/telegram/bot/config_methods_spec.rb
Обычный файл
@@ -0,0 +1,94 @@
|
|||||||
|
RSpec.describe Telegram::Bot::ConfigMethods do
|
||||||
|
let(:registry) do
|
||||||
|
Object.new.tap do |x|
|
||||||
|
x.extend described_class
|
||||||
|
x.bots_config = config
|
||||||
|
end
|
||||||
|
end
|
||||||
|
let(:config) do
|
||||||
|
{
|
||||||
|
default: 'default_token',
|
||||||
|
chat: {
|
||||||
|
token: 'chat_token',
|
||||||
|
username: 'Chat',
|
||||||
|
botan: 'chat_botan_token',
|
||||||
|
},
|
||||||
|
other_chat: {
|
||||||
|
'token' => 'other_chat_token',
|
||||||
|
'username' => 'OtherChat',
|
||||||
|
'botan' => 'other_chat_botan_token',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#bot' do
|
||||||
|
subject { registry.bot }
|
||||||
|
it { should eq registry.bots[:default] }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#bots' do
|
||||||
|
context 'configured by token' do
|
||||||
|
subject { registry.bots[:default] }
|
||||||
|
its(:id) { should eq :default }
|
||||||
|
its(:token) { should eq config[:default] }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'configured by hash' do
|
||||||
|
subject { registry.bots[:chat] }
|
||||||
|
its(:id) { should eq :chat }
|
||||||
|
its(:token) { should eq config[:chat][:token] }
|
||||||
|
its(:username) { should eq config[:chat][:username] }
|
||||||
|
its('botan.token') { should eq config[:chat][:botan] }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'configured by hash with stringified keys' do
|
||||||
|
subject { registry.bots[:other_chat] }
|
||||||
|
its(:id) { should eq :other_chat }
|
||||||
|
its(:token) { should eq config[:other_chat]['token'] }
|
||||||
|
its(:username) { should eq config[:other_chat]['username'] }
|
||||||
|
its('botan.token') { should eq config[:other_chat]['botan'] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#botans' do
|
||||||
|
subject { registry.botans }
|
||||||
|
it do
|
||||||
|
should eq(
|
||||||
|
default: nil,
|
||||||
|
chat: registry.bots[:chat].botan,
|
||||||
|
other_chat: registry.bots[:other_chat].botan,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#bots_config' do
|
||||||
|
subject { registry.bots_config }
|
||||||
|
it { should eq config }
|
||||||
|
|
||||||
|
context 'when not configured' do
|
||||||
|
let(:registry) { Object.new.tap { |x| x.extend described_class } }
|
||||||
|
it { should eq({}) }
|
||||||
|
|
||||||
|
context 'in rails environment' do
|
||||||
|
before { stub_const('Rails', double(application: double(secrets: secrets))) }
|
||||||
|
let(:secrets) { {} }
|
||||||
|
it { should eq({}) }
|
||||||
|
|
||||||
|
context 'when there is telegram section in secrets' do
|
||||||
|
let(:secrets) { {telegram: config.stringify_keys} }
|
||||||
|
let(:config) do
|
||||||
|
{
|
||||||
|
bot: double(:bot_config),
|
||||||
|
bots: {
|
||||||
|
chat: double(:chat_config),
|
||||||
|
other_chat: double(:other_chat_config),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
it { should include default: config[:bot] }
|
||||||
|
it { should include config[:bots] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Ссылка в новой задаче
Block a user