Request-spec controller actions for Twitter API webhook
This is the first of many commits to add missing test coverage for Zammad's Twitter functionality. The testing process begins from the broadest scope possible, and will proceed in future commits to more granular, fine-grained behaviors. (Request specs provide integration testing at the controller level, and webhook controller actions are the main point of entry for incoming Twitter API functionality.)
This commit is contained in:
parent
889dd92aea
commit
9409d17de6
2 changed files with 132 additions and 4 deletions
|
@ -9,10 +9,10 @@ FactoryBot.define do
|
||||||
name { 'twitter' }
|
name { 'twitter' }
|
||||||
|
|
||||||
credentials do
|
credentials do
|
||||||
{ consumer_key: 123,
|
{ consumer_key: '123',
|
||||||
consumer_secret: 123,
|
consumer_secret: '123',
|
||||||
oauth_token: 123,
|
oauth_token: '123',
|
||||||
oauth_token_secret: 123 }
|
oauth_token_secret: '123' }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
128
spec/requests/channels_twitter_spec.rb
Normal file
128
spec/requests/channels_twitter_spec.rb
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Twitter channel API endpoints', type: :request do
|
||||||
|
let!(:twitter_channel) { create(:twitter_channel) }
|
||||||
|
let!(:twitter_credential) { create(:twitter_credential) }
|
||||||
|
|
||||||
|
let(:hash_signature) { %(sha256=#{Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', consumer_secret, payload))}) }
|
||||||
|
let(:consumer_secret) { twitter_credential.credentials[:consumer_secret] }
|
||||||
|
|
||||||
|
# What's this all about? See the "Challenge-Response Checks" section of this article:
|
||||||
|
# https://developer.twitter.com/en/docs/accounts-and-users/subscribe-account-activity/guides/securing-webhooks
|
||||||
|
describe 'GET /api/v1/channels_twitter_webhook' do
|
||||||
|
let(:payload) { params[:crc_token] }
|
||||||
|
let(:params) { { crc_token: 'foo' } }
|
||||||
|
|
||||||
|
context 'with consumer secret and "crc_token" param' do
|
||||||
|
it 'responds with { response_token: <hash_signature> }' do
|
||||||
|
get '/api/v1/channels_twitter_webhook', params: params, as: :json
|
||||||
|
|
||||||
|
expect(json_response).to eq('response_token' => hash_signature)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without valid twitter credentials in the DB' do
|
||||||
|
let!(:twitter_credential) { create(:twitter_credential, credentials: { foo: 'bar' }) }
|
||||||
|
|
||||||
|
it 'responds 422 Unprocessable Entity' do
|
||||||
|
get '/api/v1/channels_twitter_webhook', params: params, as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without "crc_token" param' do
|
||||||
|
let(:params) { {} }
|
||||||
|
|
||||||
|
it 'responds 422 Unprocessable Entity' do
|
||||||
|
get '/api/v1/channels_twitter_webhook', params: params, as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/channels_twitter_webhook' do
|
||||||
|
let(:payload) { params.stringify_keys.to_s.gsub(/=>/, ':').tr(' ', '') }
|
||||||
|
let(:headers) { { 'x-twitter-webhooks-signature': hash_signature } }
|
||||||
|
let(:params) { { foo: 'bar', for_user_id: user_id } }
|
||||||
|
let(:user_id) { twitter_channel.options[:user][:id] }
|
||||||
|
|
||||||
|
# What's this all about? See the "Optional signature header validation" section of this article:
|
||||||
|
# https://developer.twitter.com/en/docs/accounts-and-users/subscribe-account-activity/guides/securing-webhooks
|
||||||
|
describe 'hash signature validation' do
|
||||||
|
context 'with valid params and headers (i.e., not one of the failure cases below)' do
|
||||||
|
it 'responds 200 OK' do
|
||||||
|
post '/api/v1/channels_twitter_webhook', params: params, headers: headers, as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '"x-twitter-webhooks-signature" header' do
|
||||||
|
context 'when absent' do
|
||||||
|
let(:headers) { {} }
|
||||||
|
|
||||||
|
it 'responds 422 Unprocessable Entity' do
|
||||||
|
post '/api/v1/channels_twitter_webhook', params: params, headers: headers, as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when payload doesn’t match' do
|
||||||
|
let(:headers) { { 'x-twitter-webhooks-signature': 'Not actually a signature' } }
|
||||||
|
|
||||||
|
it 'responds 401 Not Authorized' do
|
||||||
|
post '/api/v1/channels_twitter_webhook', params: params, headers: headers, as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '"for_user_id" param' do
|
||||||
|
context 'when absent' do
|
||||||
|
let(:params) { { foo: 'bar' } }
|
||||||
|
|
||||||
|
it 'responds 422 Unprocessable Entity' do
|
||||||
|
post '/api/v1/channels_twitter_webhook', params: params, headers: headers, as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without corresponding Channel' do
|
||||||
|
let(:params) { { foo: 'bar', for_user_id: 'no_such_user' } }
|
||||||
|
|
||||||
|
it 'responds 422 Unprocessable Entity' do
|
||||||
|
post '/api/v1/channels_twitter_webhook', params: params, headers: headers, as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'core behavior' do
|
||||||
|
before do
|
||||||
|
allow(TwitterSync).to receive(:new).and_return(twitter_sync)
|
||||||
|
allow(twitter_sync).to receive(:process_webhook)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:twitter_sync) { instance_double('TwitterSync') }
|
||||||
|
|
||||||
|
it 'delegates to TwitterSync#process_webhook' do
|
||||||
|
post '/api/v1/channels_twitter_webhook', params: params, headers: headers, as: :json
|
||||||
|
|
||||||
|
expect(twitter_sync).to have_received(:process_webhook).with(twitter_channel)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'responds with an empty hash' do
|
||||||
|
post '/api/v1/channels_twitter_webhook', params: params, headers: headers, as: :json
|
||||||
|
|
||||||
|
expect(json_response).to eq({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue