iachat/spec/services/audio/transcode_service_spec.rb
Gabriel Jablonski ce39e54308
feat: add audio transcoding support for WhatsApp Cloud API (#220)
* feat: add audio transcoding support for WhatsApp Cloud API

- Introduced `Audio::TranscodeService` to handle audio transcoding to OGG/Opus format.
- Updated `Messages::MessageBuilder` to transcode audio attachments based on `transcode_audio` parameter.
- Enhanced `WhatsappCloudService` to normalize audio content types and send voice flag for recorded audio in OGG format.
- Added utility functions for audio conversion in JavaScript.
- Updated Dockerfile to include FFmpeg for audio processing.
- Added tests for audio transcoding and WhatsApp Cloud service interactions.

* feat: enhance audio handling with transcoding support and error management

* feat: improve audio transcoding error handling and enhance audio recording features

* feat: enhance audio transcoding process and error handling for better reliability

* feat: update recorded audio handling to support boolean and array formats
2026-02-22 16:21:50 -03:00

107 lines
3.9 KiB
Ruby

require 'rails_helper'
RSpec.describe Audio::TranscodeService do
let(:message) { create(:message) }
let(:attachment) do
attachment = message.attachments.new(account_id: message.account_id, file_type: :audio)
attachment.file.attach(io: Rails.root.join('spec/assets/sample.mp3').open, filename: 'sample.mp3', content_type: 'audio/mpeg')
attachment.save!
attachment
end
describe '#perform' do
context 'with unsupported format' do
it 'raises UnsupportedFormatError' do
error = nil
begin
described_class.new(attachment, 'aac').perform
rescue StandardError => e
error = e
end
expect(error).not_to be_nil
expect(error.class.name).to eq('CustomExceptions::Audio::UnsupportedFormatError')
expect(error.message).to match(/Unsupported transcode format: aac/)
end
end
context 'with opus format' do
it 'skips transcoding when file is already in target format' do
ogg_attachment = message.attachments.new(account_id: message.account_id, file_type: :audio)
ogg_attachment.file.attach(io: StringIO.new('ogg_data'), filename: 'recording.ogg', content_type: 'audio/ogg')
ogg_attachment.save!
allow(FFMPEG::Movie).to receive(:new)
described_class.new(ogg_attachment, 'opus').perform
expect(FFMPEG::Movie).not_to have_received(:new)
expect(ogg_attachment.file.content_type).to eq('audio/ogg')
end
it 'transcodes audio to ogg/opus format' do
mock_movie = instance_double(FFMPEG::Movie, valid?: true)
allow(FFMPEG::Movie).to receive(:new).and_return(mock_movie)
allow(mock_movie).to receive(:transcode) do |output_path, _options|
File.write(output_path, 'fake_opus_data')
end
described_class.new(attachment, 'opus').perform
expect(attachment.file.filename.to_s).to eq('sample.ogg')
expect(attachment.file.content_type).to eq('audio/ogg')
expect(attachment.file_type).to eq('audio')
end
it 'transcodes using source_file when provided' do
uploaded_file = Rack::Test::UploadedFile.new(Rails.root.join('spec/assets/sample.mp3').to_s, 'audio/mpeg')
mock_movie = instance_double(FFMPEG::Movie, valid?: true)
allow(FFMPEG::Movie).to receive(:new).and_return(mock_movie)
allow(mock_movie).to receive(:transcode) do |output_path, _options|
File.write(output_path, 'fake_opus_data')
end
described_class.new(attachment, 'opus', source_file: uploaded_file).perform
expect(attachment.file.filename.to_s).to eq('sample.ogg')
expect(attachment.file.content_type).to eq('audio/ogg')
expect(attachment.file_type).to eq('audio')
end
it 'raises TranscodingError when the audio file is invalid' do
mock_movie = instance_double(FFMPEG::Movie, valid?: false)
allow(FFMPEG::Movie).to receive(:new).and_return(mock_movie)
error = nil
begin
described_class.new(attachment, 'opus').perform
rescue StandardError => e
error = e
end
expect(error).not_to be_nil
expect(error.class.name).to eq('CustomExceptions::Audio::TranscodingError')
expect(error.message).to match(/Invalid or unreadable audio file/)
end
it 'raises TranscodingError when FFmpeg fails' do
mock_movie = instance_double(FFMPEG::Movie, valid?: true)
allow(FFMPEG::Movie).to receive(:new).and_return(mock_movie)
allow(mock_movie).to receive(:transcode).and_raise(FFMPEG::Error, 'encoding failed')
error = nil
begin
described_class.new(attachment, 'opus').perform
rescue StandardError => e
error = e
end
expect(error).not_to be_nil
expect(error.class.name).to eq('CustomExceptions::Audio::TranscodingError')
expect(error.message).to match(/FFmpeg transcoding failed/)
end
end
end
end