Dyte is sunsetting its existing infrastructure after the Cloudflare acquisition, so this migrates Chatwoot’s video call integration to Cloudflare RealtimeKit. The integration now uses Cloudflare Account ID, RealtimeKit App ID, and a Cloudflare API token with Realtime Admin permissions. Meeting creation and participant token generation now call Cloudflare’s RealtimeKit APIs, while the existing Chatwoot call experience remains unchanged for agents and customers. This also adds setup-time credential validation, so admins get clearer errors when the API token is invalid, the Cloudflare account or permissions are incorrect, or the RealtimeKit App ID does not belong to the selected account. Fixes https://linear.app/chatwoot/issue/PLA-176/migrate-dyte-integration-to-cloudflare-realtimekit **How to test** 1. Go to Settings → Integrations → Cloudflare RealtimeKit. 2. Add a Cloudflare Account ID, RealtimeKit App ID, and API token with Realtime Admin permissions. 3. Confirm the integration saves successfully with valid credentials. 4. Try invalid credentials and confirm the error identifies whether the token, account/permissions, or app ID is wrong. 5. Start a video call from a conversation and confirm the RealtimeKit meeting opens. --------- Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com> Co-authored-by: Sony Mathew <sony@chatwoot.com>
141 lines
5.8 KiB
Ruby
141 lines
5.8 KiB
Ruby
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
|
|
allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
|
|
.and_return(Integrations::Cloudflare::RealtimeKitCredentialsValidator::Result.new(true, nil))
|
|
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.cloudflare.com/client/v4/accounts/account_id/realtime/kit/app_id/meetings')
|
|
.to_return(
|
|
status: 200,
|
|
body: { success: true, data: { id: 'meeting_id' } }.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 = response.parsed_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.cloudflare.com/client/v4/accounts/account_id/realtime/kit/app_id/meetings')
|
|
.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 = response.parsed_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.cloudflare.com/client/v4/accounts/account_id/realtime/kit/app_id/meetings/m_id/participants')
|
|
.to_return(
|
|
status: 200,
|
|
body: { success: true, data: { id: 'random_uuid', token: 'json-web-token' } }.to_json,
|
|
headers: headers
|
|
)
|
|
end
|
|
|
|
it 'returns token' 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 = response.parsed_body
|
|
expect(response_body).to eq(
|
|
{
|
|
'id' => 'random_uuid', 'token' => 'json-web-token'
|
|
}
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|