Files
chatwoot/spec/controllers/api/v1/widget/integrations/dyte_controller_spec.rb
T
74db16158d feat: migrate dyte integration to cloudflare realtimekit (#14752)
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>
2026-06-24 11:04:08 +04:00

77 lines
3.3 KiB
Ruby

require 'rails_helper'
RSpec.describe '/api/v1/widget/integrations/dyte', type: :request do
let(:account) { create(:account) }
let(:web_widget) { create(:channel_widget, account: account) }
let(:contact) { create(:contact, account: account, email: nil) }
let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: web_widget.inbox) }
let(:conversation) { create(:conversation, contact: contact, account: account, inbox: web_widget.inbox, contact_inbox: contact_inbox) }
let(:payload) { { source_id: contact_inbox.source_id, inbox_id: web_widget.inbox.id } }
let(:token) { Widget::TokenService.new(payload: payload).generate_token }
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
before do
allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
.and_return(Integrations::Cloudflare::RealtimeKitCredentialsValidator::Result.new(true, nil))
create(:integrations_hook, :dyte, account: account)
end
describe 'POST /api/v1/widget/integrations/dyte/add_participant_to_meeting' do
context 'when token is invalid' do
it 'returns error' do
post add_participant_to_meeting_api_v1_widget_integrations_dyte_url,
params: { website_token: web_widget.website_token },
as: :json
expect(response).to have_http_status(:not_found)
end
end
context 'when token is valid' do
context 'when message is not an integration message' do
it 'returns error' do
post add_participant_to_meeting_api_v1_widget_integrations_dyte_url,
headers: { 'X-Auth-Token' => token },
params: { website_token: web_widget.website_token, message_id: message.id },
as: :json
expect(response).to have_http_status(:unprocessable_entity)
response_body = response.parsed_body
expect(response_body['error']).to eq('Invalid message type. Action not permitted')
end
end
context 'when message is an integration message' 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: { 'Content-Type' => 'application/json' }
)
end
it 'returns token' do
post add_participant_to_meeting_api_v1_widget_integrations_dyte_url,
headers: { 'X-Auth-Token' => token },
params: { website_token: web_widget.website_token, message_id: integration_message.id },
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
end