feat: Add backend APIs for Dyte integration (#6197)
- The backend changes required for Dyte Integration.
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
class Api::V1::Accounts::Integrations::DyteController < Api::V1::Accounts::BaseController
|
||||||
|
before_action :fetch_conversation, only: [:create_a_meeting]
|
||||||
|
before_action :fetch_message, only: [:add_participant_to_meeting]
|
||||||
|
before_action :authorize_request
|
||||||
|
|
||||||
|
def create_a_meeting
|
||||||
|
render_response(dyte_processor_service.create_a_meeting(Current.user))
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_participant_to_meeting
|
||||||
|
if @message.content_type != 'integrations'
|
||||||
|
return render json: {
|
||||||
|
error: I18n.t('errors.dyte.invalid_message_type')
|
||||||
|
}, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
|
||||||
|
render_response(
|
||||||
|
dyte_processor_service.add_participant_to_meeting(@message.content_attributes['data']['meeting_id'], Current.user)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def authorize_request
|
||||||
|
authorize @conversation.inbox, :show?
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_response(response)
|
||||||
|
render json: response, status: response[:error].blank? ? :ok : :unprocessable_entity
|
||||||
|
end
|
||||||
|
|
||||||
|
def dyte_processor_service
|
||||||
|
Integrations::Dyte::ProcessorService.new(account: Current.account, conversation: @conversation)
|
||||||
|
end
|
||||||
|
|
||||||
|
def permitted_params
|
||||||
|
params.permit(:conversation_id, :message_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_conversation
|
||||||
|
@conversation = Current.account.conversations.find_by!(display_id: permitted_params[:conversation_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_message
|
||||||
|
@message = Current.account.messages.find(permitted_params[:message_id])
|
||||||
|
@conversation = @message.conversation
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -57,7 +57,8 @@ class Message < ApplicationRecord
|
|||||||
form: 6,
|
form: 6,
|
||||||
article: 7,
|
article: 7,
|
||||||
incoming_email: 8,
|
incoming_email: 8,
|
||||||
input_csat: 9
|
input_csat: 9,
|
||||||
|
integrations: 10
|
||||||
}
|
}
|
||||||
enum status: { sent: 0, delivered: 1, read: 2, failed: 3 }
|
enum status: { sent: 0, delivered: 1, read: 2, failed: 3 }
|
||||||
# [:submitted_email, :items, :submitted_values] : Used for bot message types
|
# [:submitted_email, :items, :submitted_values] : Used for bot message types
|
||||||
|
|||||||
@@ -60,20 +60,34 @@ dialogflow:
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
visible_properties: ['project_id']
|
visible_properties: ['project_id']
|
||||||
fullcontact:
|
dyte:
|
||||||
id: fullcontact
|
id: dyte
|
||||||
logo: fullcontact.png
|
logo: dyte.png
|
||||||
i18n_key: fullcontact
|
i18n_key: dyte
|
||||||
action: /fullcontact
|
action: /dyte
|
||||||
hook_type: account
|
hook_type: account
|
||||||
allow_multiple_hooks: false
|
allow_multiple_hooks: false
|
||||||
settings_json_schema:
|
settings_json_schema: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"api_key": { "type": "string" },
|
||||||
|
"organization_id": { "type": "string" },
|
||||||
|
},
|
||||||
|
"required": ["api_key", "organization_id"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
}
|
||||||
|
settings_form_schema: [
|
||||||
{
|
{
|
||||||
'type': 'object',
|
"label": "Organization ID",
|
||||||
'properties': { 'api_key': { 'type': 'string' } },
|
"type": "text",
|
||||||
'required': ['api_key'],
|
"name": "organization_id",
|
||||||
'additionalProperties': false,
|
"validation": "required",
|
||||||
}
|
},
|
||||||
settings_form_schema:
|
{
|
||||||
[{ 'label': 'API Key', 'type': 'text', 'name': 'api_key',"validation": "required", }]
|
"label": "API Key",
|
||||||
visible_properties: ['api_key']
|
"type": "text",
|
||||||
|
"name": "api_key",
|
||||||
|
"validation": "required",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
visible_properties: ["organization_id"]
|
||||||
|
|||||||
@@ -51,13 +51,15 @@ en:
|
|||||||
contacts:
|
contacts:
|
||||||
import:
|
import:
|
||||||
failed: File is blank
|
failed: File is blank
|
||||||
email:
|
email:
|
||||||
invalid: Invalid email
|
invalid: Invalid email
|
||||||
phone_number:
|
phone_number:
|
||||||
invalid: should be in e164 format
|
invalid: should be in e164 format
|
||||||
categories:
|
categories:
|
||||||
locale:
|
locale:
|
||||||
unique: should be unique in the category and portal
|
unique: should be unique in the category and portal
|
||||||
|
dyte:
|
||||||
|
invalid_message_type: "Invalid message type. Action not permitted"
|
||||||
inboxes:
|
inboxes:
|
||||||
imap:
|
imap:
|
||||||
socket_error: Please check the network connection, IMAP address and try again.
|
socket_error: Please check the network connection, IMAP address and try again.
|
||||||
@@ -153,6 +155,10 @@ en:
|
|||||||
online:
|
online:
|
||||||
delete: "%{contact_name} is Online, please try again later"
|
delete: "%{contact_name} is Online, please try again later"
|
||||||
integration_apps:
|
integration_apps:
|
||||||
|
dyte:
|
||||||
|
name: "Dyte"
|
||||||
|
description: "Dyte is tool that helps you to add live audio & video to your application with just a few lines of code. This integration allows you to give an option to your agents to have a video or voice call with your customers from without leaving Chatwoot."
|
||||||
|
meeting_name: "%{agent_name} has started a meeting"
|
||||||
slack:
|
slack:
|
||||||
name: "Slack"
|
name: "Slack"
|
||||||
description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack."
|
description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack."
|
||||||
|
|||||||
@@ -158,6 +158,12 @@ Rails.application.routes.draw do
|
|||||||
resources :apps, only: [:index, :show]
|
resources :apps, only: [:index, :show]
|
||||||
resources :hooks, only: [:create, :update, :destroy]
|
resources :hooks, only: [:create, :update, :destroy]
|
||||||
resource :slack, only: [:create, :update, :destroy], controller: 'slack'
|
resource :slack, only: [:create, :update, :destroy], controller: 'slack'
|
||||||
|
resource :dyte, controller: 'dyte', only: [] do
|
||||||
|
collection do
|
||||||
|
post :create_a_meeting
|
||||||
|
post :add_participant_to_meeting
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
resources :working_hours, only: [:update]
|
resources :working_hours, only: [:update]
|
||||||
|
|
||||||
|
|||||||
58
lib/dyte.rb
Normal file
58
lib/dyte.rb
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
class Dyte
|
||||||
|
BASE_URL = 'https://api.cluster.dyte.in/v1'.freeze
|
||||||
|
API_KEY_HEADER = 'Authorization'.freeze
|
||||||
|
|
||||||
|
def initialize(organization_id, api_key)
|
||||||
|
@api_key = api_key
|
||||||
|
@organization_id = organization_id
|
||||||
|
|
||||||
|
raise ArgumentError, 'Missing Credentials' if @api_key.blank? || @organization_id.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_a_meeting(title)
|
||||||
|
payload = {
|
||||||
|
'title': title,
|
||||||
|
'authorization': {
|
||||||
|
'waitingRoom': false,
|
||||||
|
'closed': false
|
||||||
|
},
|
||||||
|
'recordOnStart': false,
|
||||||
|
'liveStreamOnStart': false
|
||||||
|
}
|
||||||
|
path = "organizations/#{@organization_id}/meeting"
|
||||||
|
response = post(path, payload)
|
||||||
|
process_response(response)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_participant_to_meeting(meeting_id, client_id, name, avatar_url)
|
||||||
|
raise ArgumentError, 'Missing information' if meeting_id.blank? || client_id.blank? || name.blank? || avatar_url.blank?
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'clientSpecificId': client_id.to_s,
|
||||||
|
'userDetails': {
|
||||||
|
'name': name,
|
||||||
|
'picture': avatar_url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path = "organizations/#{@organization_id}/meetings/#{meeting_id}/participant"
|
||||||
|
response = post(path, payload)
|
||||||
|
process_response(response)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def process_response(response)
|
||||||
|
return response.parsed_response['data'].with_indifferent_access if response.success?
|
||||||
|
|
||||||
|
{ error: response.parsed_response, error_code: response.code }
|
||||||
|
end
|
||||||
|
|
||||||
|
def post(path, payload)
|
||||||
|
HTTParty.post(
|
||||||
|
"#{BASE_URL}/#{path}", {
|
||||||
|
headers: { API_KEY_HEADER => @api_key, 'Content-Type' => 'application/json' },
|
||||||
|
body: payload.to_json
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
55
lib/integrations/dyte/processor_service.rb
Normal file
55
lib/integrations/dyte/processor_service.rb
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
class Integrations::Dyte::ProcessorService
|
||||||
|
pattr_initialize [:account!, :conversation!]
|
||||||
|
|
||||||
|
def create_a_meeting(agent)
|
||||||
|
title = I18n.t('integration_apps.dyte.meeting_name', agent_name: agent.available_name)
|
||||||
|
response = dyte_client.create_a_meeting(title)
|
||||||
|
|
||||||
|
return response if response[:error].present?
|
||||||
|
|
||||||
|
meeting = response['meeting']
|
||||||
|
message = create_a_dyte_integration_message(meeting, title, agent)
|
||||||
|
message.push_event_data
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_participant_to_meeting(meeting_id, user)
|
||||||
|
dyte_client.add_participant_to_meeting(meeting_id, user.id, user.name, avatar_url(user))
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_a_dyte_integration_message(meeting, title, agent)
|
||||||
|
@conversation.messages.create!(
|
||||||
|
{
|
||||||
|
account_id: conversation.account_id,
|
||||||
|
inbox_id: conversation.inbox_id,
|
||||||
|
message_type: :outgoing,
|
||||||
|
content_type: :integrations,
|
||||||
|
content: title,
|
||||||
|
content_attributes: {
|
||||||
|
type: 'dyte',
|
||||||
|
data: {
|
||||||
|
meeting_id: meeting['id'],
|
||||||
|
room_name: meeting['roomName']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sender: agent
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def avatar_url(user)
|
||||||
|
return user.avatar_url if user.avatar_url.present?
|
||||||
|
|
||||||
|
"#{ENV.fetch('FRONTEND_URL', nil)}/integrations/slack/user.png"
|
||||||
|
end
|
||||||
|
|
||||||
|
def dyte_hook
|
||||||
|
@dyte_hook ||= account.hooks.find_by!(app_id: 'dyte')
|
||||||
|
end
|
||||||
|
|
||||||
|
def dyte_client
|
||||||
|
credentials = dyte_hook.settings
|
||||||
|
@dyte_client ||= Dyte.new(credentials['organization_id'], credentials['api_key'])
|
||||||
|
end
|
||||||
|
end
|
||||||
BIN
public/dashboard/images/integrations/dyte.png
Normal file
BIN
public/dashboard/images/integrations/dyte.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,138 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Dyte Integration API', type: :request do
|
||||||
|
let(:headers) { { 'Content-Type' => 'application/json' } }
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:inbox) { create(:inbox, account: account) }
|
||||||
|
let(:conversation) { create(:conversation, account: account, status: :pending) }
|
||||||
|
let(:message) { create(:message, conversation: conversation, account: account, inbox: conversation.inbox) }
|
||||||
|
let(:integration_message) do
|
||||||
|
create(:message, content_type: 'integrations',
|
||||||
|
content_attributes: { type: 'dyte', data: { meeting_id: 'm_id' } },
|
||||||
|
conversation: conversation, account: account, inbox: conversation.inbox)
|
||||||
|
end
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
let(:unauthorized_agent) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:integrations_hook, :dyte, account: account)
|
||||||
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/accounts/:account_id/integrations/dyte/create_a_meeting' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post create_a_meeting_api_v1_account_integrations_dyte_url(account)
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the agent does not have access to the inbox' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post create_a_meeting_api_v1_account_integrations_dyte_url(account),
|
||||||
|
params: { conversation_id: conversation.display_id },
|
||||||
|
headers: unauthorized_agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an agent with inbox access and the Dyte API is a success' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meeting')
|
||||||
|
.to_return(
|
||||||
|
status: 200,
|
||||||
|
body: { success: true, data: { meeting: { id: 'meeting_id', roomName: 'room_name' } } }.to_json,
|
||||||
|
headers: headers
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns valid message payload' do
|
||||||
|
post create_a_meeting_api_v1_account_integrations_dyte_url(account),
|
||||||
|
params: { conversation_id: conversation.display_id },
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
response_body = JSON.parse(response.body)
|
||||||
|
last_message = conversation.reload.messages.last
|
||||||
|
expect(conversation.display_id).to eq(response_body['conversation_id'])
|
||||||
|
expect(last_message.id).to eq(response_body['id'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an agent with inbox access and the Dyte API is errored' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meeting')
|
||||||
|
.to_return(
|
||||||
|
status: 422,
|
||||||
|
body: { success: false, data: { message: 'Title is required' } }.to_json,
|
||||||
|
headers: headers
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns error payload' do
|
||||||
|
post create_a_meeting_api_v1_account_integrations_dyte_url(account),
|
||||||
|
params: { conversation_id: conversation.display_id },
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
response_body = JSON.parse(response.body)
|
||||||
|
expect(response_body['error']).to eq({ 'data' => { 'message' => 'Title is required' }, 'success' => false })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/accounts/:account_id/integrations/dyte/add_participant_to_meeting' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post add_participant_to_meeting_api_v1_account_integrations_dyte_url(account)
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the agent does not have access to the inbox' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post add_participant_to_meeting_api_v1_account_integrations_dyte_url(account),
|
||||||
|
params: { message_id: message.id },
|
||||||
|
headers: unauthorized_agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an agent with inbox access and message_type is not integrations' do
|
||||||
|
it 'returns error' do
|
||||||
|
post add_participant_to_meeting_api_v1_account_integrations_dyte_url(account),
|
||||||
|
params: { message_id: message.id },
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an agent with inbox access and message_type is integrations' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meetings/m_id/participant')
|
||||||
|
.to_return(
|
||||||
|
status: 200,
|
||||||
|
body: { success: true, data: { authResponse: { userAdded: true, id: 'random_uuid', auth_token: 'json-web-token' } } }.to_json,
|
||||||
|
headers: headers
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns authResponse' do
|
||||||
|
post add_participant_to_meeting_api_v1_account_integrations_dyte_url(account),
|
||||||
|
params: { message_id: integration_message.id },
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
response_body = JSON.parse(response.body)
|
||||||
|
expect(response_body['authResponse']).to eq(
|
||||||
|
{
|
||||||
|
'userAdded' => true, 'id' => 'random_uuid', 'auth_token' => 'json-web-token'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -11,5 +11,10 @@ FactoryBot.define do
|
|||||||
app_id { 'dialogflow' }
|
app_id { 'dialogflow' }
|
||||||
settings { { project_id: 'test', credentials: {} } }
|
settings { { project_id: 'test', credentials: {} } }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
trait :dyte do
|
||||||
|
app_id { 'dyte' }
|
||||||
|
settings { { api_key: 'api_key', organization_id: 'org_id' } }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
76
spec/lib/dyte_spec.rb
Normal file
76
spec/lib/dyte_spec.rb
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe Dyte do
|
||||||
|
let(:dyte_client) { described_class.new('org_id', 'api_key') }
|
||||||
|
let(:headers) { { 'Content-Type' => 'application/json' } }
|
||||||
|
|
||||||
|
it 'raises an exception if api_key or organization ID is absent' do
|
||||||
|
expect { described_class.new }.to raise_error(StandardError)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when create_a_meeting is called' do
|
||||||
|
context 'when API response is success' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meeting')
|
||||||
|
.to_return(
|
||||||
|
status: 200,
|
||||||
|
body: { success: true, data: { meeting: { id: 'meeting_id' } } }.to_json,
|
||||||
|
headers: headers
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns api response' do
|
||||||
|
response = dyte_client.create_a_meeting('title_of_the_meeting')
|
||||||
|
expect(response).to eq({ 'meeting' => { 'id' => 'meeting_id' } })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when API response is invalid' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meeting')
|
||||||
|
.to_return(status: 422, body: { message: 'Title is required' }.to_json, headers: headers)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns error code with data' do
|
||||||
|
response = dyte_client.create_a_meeting('')
|
||||||
|
expect(response).to eq({ error: { 'message' => 'Title is required' }, error_code: 422 })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when add_participant_to_meeting is called' do
|
||||||
|
context 'when API parameters are missing' do
|
||||||
|
it 'raises an exception' do
|
||||||
|
expect { dyte_client.add_participant_to_meeting }.to raise_error(StandardError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when API response is success' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meetings/m_id/participant')
|
||||||
|
.to_return(
|
||||||
|
status: 200,
|
||||||
|
body: { success: true, data: { authResponse: { userAdded: true, id: 'random_uuid', auth_token: 'json-web-token' } } }.to_json,
|
||||||
|
headers: headers
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns api response' do
|
||||||
|
response = dyte_client.add_participant_to_meeting('m_id', 'c_id', 'name', 'https://avatar.url')
|
||||||
|
expect(response).to eq({ 'authResponse' => { 'userAdded' => true, 'id' => 'random_uuid', 'auth_token' => 'json-web-token' } })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when API response is invalid' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meetings/m_id/participant')
|
||||||
|
.to_return(status: 422, body: { message: 'Meeting ID is invalid' }.to_json, headers: headers)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns error code with data' do
|
||||||
|
response = dyte_client.add_participant_to_meeting('m_id', 'c_id', 'name', 'https://avatar.url')
|
||||||
|
expect(response).to eq({ error: { 'message' => 'Meeting ID is invalid' }, error_code: 422 })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
68
spec/lib/integrations/dyte/processor_service_spec.rb
Normal file
68
spec/lib/integrations/dyte/processor_service_spec.rb
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe Integrations::Dyte::ProcessorService do
|
||||||
|
let(:headers) { { 'Content-Type' => 'application/json' } }
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:inbox) { create(:inbox, account: account) }
|
||||||
|
let(:conversation) { create(:conversation, account: account, status: :pending) }
|
||||||
|
let(:processor) { described_class.new(account: account, conversation: conversation) }
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:integrations_hook, :dyte, account: account)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#create_a_meeting' do
|
||||||
|
context 'when the API response is success' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meeting')
|
||||||
|
.to_return(
|
||||||
|
status: 200,
|
||||||
|
body: { success: true, data: { meeting: { id: 'meeting_id', roomName: 'room_name' } } }.to_json,
|
||||||
|
headers: headers
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates an integration message in the conversation' do
|
||||||
|
response = processor.create_a_meeting(agent)
|
||||||
|
expect(response['content']).to eq("#{agent.available_name} has started a meeting")
|
||||||
|
expect(conversation.reload.messages.last.content_type).to eq('integrations')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the API response is errored' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meeting')
|
||||||
|
.to_return(
|
||||||
|
status: 422,
|
||||||
|
body: { success: false, data: { message: 'Title is required' } }.to_json,
|
||||||
|
headers: headers
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not create an integration message in the conversation' do
|
||||||
|
response = processor.create_a_meeting(agent)
|
||||||
|
expect(response).to eq({ error: { 'data' => { 'message' => 'Title is required' }, 'success' => false }, error_code: 422 })
|
||||||
|
expect(conversation.reload.messages.count).to eq(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#add_participant_to_meeting' do
|
||||||
|
context 'when the API response is success' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, 'https://api.cluster.dyte.in/v1/organizations/org_id/meetings/m_id/participant')
|
||||||
|
.to_return(
|
||||||
|
status: 200,
|
||||||
|
body: { success: true, data: { authResponse: { userAdded: true, id: 'random_uuid', auth_token: 'json-web-token' } } }.to_json,
|
||||||
|
headers: headers
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'return the authResponse' do
|
||||||
|
response = processor.add_participant_to_meeting('m_id', agent)
|
||||||
|
expect(response[:authResponse]).not_to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user