From 0495316fd1dcf64668086124053dfce9a57349e4 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Tue, 5 Aug 2025 15:40:59 +0530 Subject: [PATCH] feat: add the support for legacy wa templates --- .../whatsapp/template_converter_service.rb | 115 ++++++++++ .../whatsapp/template_processor_service.rb | 10 +- .../template_converter_service_spec.rb | 199 ++++++++++++++++++ 3 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 app/services/whatsapp/template_converter_service.rb create mode 100644 spec/services/whatsapp/template_converter_service_spec.rb diff --git a/app/services/whatsapp/template_converter_service.rb b/app/services/whatsapp/template_converter_service.rb new file mode 100644 index 000000000..6d858b3ce --- /dev/null +++ b/app/services/whatsapp/template_converter_service.rb @@ -0,0 +1,115 @@ +# Service to convert legacy WhatsApp template parameter formats to enhanced format +# +# Legacy formats (deprecated): +# - Array: ["John", "Order123"] - positional parameters +# - Flat Hash: {"1": "John", "2": "Order123"} - direct key-value mapping +# +# Enhanced format: +# - Component-based: {"body": {"1": "John", "2": "Order123"}} - structured by template components +# - Supports header, body, footer, and button parameters separately +# +class Whatsapp::TemplateConverterService + def initialize(template_params, template) + @template_params = template_params + @template = template + end + + def normalize_to_enhanced + processed_params = @template_params['processed_params'] + + # Early return if already enhanced format + return @template_params if enhanced_format?(processed_params) + + # Convert legacy formats to enhanced structure + # TODO: Legacy format support will be deprecated and removed after 2-3 releases + enhanced_params = convert_legacy_to_enhanced(processed_params, @template) + + # Replace original params with enhanced structure + @template_params['processed_params'] = enhanced_params + @template_params['format_version'] = 'enhanced' + + @template_params + end + + private + + def enhanced_format?(processed_params) + return false unless processed_params.is_a?(Hash) + + # Enhanced format has component-based structure + component_keys = %w[body header footer buttons] + has_component_structure = processed_params.keys.any? { |k| component_keys.include?(k) } + + # Additional validation for enhanced format + if has_component_structure + validate_enhanced_structure(processed_params) + else + false + end + end + + def validate_enhanced_structure(params) + valid_body?(params['body']) && + valid_header?(params['header']) && + valid_buttons?(params['buttons']) + end + + def valid_body?(body) + body.nil? || body.is_a?(Hash) + end + + def valid_header?(header) + header.nil? || header.is_a?(Hash) + end + + def valid_buttons?(buttons) + return true if buttons.nil? + return false unless buttons.is_a?(Array) + + buttons.all? { |b| b.is_a?(Hash) && b['type'] } + end + + def convert_legacy_to_enhanced(legacy_params, _template) + # Legacy system only supported text-based templates with body parameters + # We only convert the parameter format, not add new features + + enhanced = {} + + case legacy_params + when Array + # Array format: ["John", "Order123"] → {body: {"1": "John", "2": "Order123"}} + body_params = convert_array_to_body_params(legacy_params) + enhanced['body'] = body_params unless body_params.empty? + when Hash + # Hash format: {"1": "John", "name": "Jane"} → {body: {"1": "John", "name": "Jane"}} + body_params = convert_hash_to_body_params(legacy_params) + enhanced['body'] = body_params unless body_params.empty? + else + raise ArgumentError, "Unknown legacy format: #{legacy_params.class}" + end + + enhanced + end + + def convert_array_to_body_params(params_array) + return {} if params_array.empty? + + body_params = {} + params_array.each_with_index do |value, index| + body_params[(index + 1).to_s] = value.to_s + end + + body_params + end + + def convert_hash_to_body_params(params_hash) + return {} if params_hash.empty? + + body_params = {} + params_hash.each do |key, value| + body_params[key.to_s] = value.to_s + end + + body_params + end +end diff --git a/app/services/whatsapp/template_processor_service.rb b/app/services/whatsapp/template_processor_service.rb index 1b0b30c08..1db97e8ea 100644 --- a/app/services/whatsapp/template_processor_service.rb +++ b/app/services/whatsapp/template_processor_service.rb @@ -28,11 +28,15 @@ class Whatsapp::TemplateProcessorService template = find_template return if template.blank? - process_enhanced_template_params(template) + # Convert legacy format to enhanced format before processing + converter = Whatsapp::TemplateConverterService.new(template_params, template) + normalized_params = converter.normalize_to_enhanced + + process_enhanced_template_params(template, normalized_params['processed_params']) end - def process_enhanced_template_params(template) - processed_params = template_params['processed_params'] + def process_enhanced_template_params(template, processed_params = nil) + processed_params ||= template_params['processed_params'] components = [] components.concat(process_header_components(processed_params)) diff --git a/spec/services/whatsapp/template_converter_service_spec.rb b/spec/services/whatsapp/template_converter_service_spec.rb new file mode 100644 index 000000000..a79d94ce9 --- /dev/null +++ b/spec/services/whatsapp/template_converter_service_spec.rb @@ -0,0 +1,199 @@ +require 'rails_helper' + +describe Whatsapp::TemplateConverterService do + let(:template) do + { + 'name' => 'test_template', + 'language' => 'en', + 'components' => [ + { + 'type' => 'BODY', + 'text' => 'Hello {{1}}, your order {{2}} is ready!' + } + ] + } + end + + let(:media_template) do + { + 'name' => 'media_template', + 'language' => 'en', + 'components' => [ + { + 'type' => 'HEADER', + 'format' => 'IMAGE' + }, + { + 'type' => 'BODY', + 'text' => 'Check out {{1}}!' + } + ] + } + end + + let(:button_template) do + { + 'name' => 'button_template', + 'language' => 'en', + 'components' => [ + { + 'type' => 'BODY', + 'text' => 'Visit our website!' + }, + { + 'type' => 'BUTTONS', + 'buttons' => [ + { + 'type' => 'URL', + 'url' => 'https://example.com/{{1}}' + }, + { + 'type' => 'COPY_CODE' + } + ] + } + ] + } + end + + describe '#normalize_to_enhanced' do + context 'when already enhanced format' do + let(:enhanced_params) do + { + 'processed_params' => { + 'body' => { '1' => 'John', '2' => 'Order123' } + } + } + end + + it 'returns unchanged' do + converter = described_class.new(enhanced_params, template) + result = converter.normalize_to_enhanced + expect(result).to eq(enhanced_params) + end + end + + context 'when legacy array format' do + let(:legacy_array_params) do + { + 'processed_params' => %w[John Order123] + } + end + + it 'converts to enhanced format' do + converter = described_class.new(legacy_array_params, template) + result = converter.normalize_to_enhanced + + expect(result['processed_params']).to eq({ + 'body' => { '1' => 'John', '2' => 'Order123' } + }) + expect(result['format_version']).to eq('enhanced') + end + end + + context 'when legacy flat hash format' do + let(:legacy_hash_params) do + { + 'processed_params' => { '1' => 'John', '2' => 'Order123' } + } + end + + it 'converts to enhanced format' do + converter = described_class.new(legacy_hash_params, template) + result = converter.normalize_to_enhanced + + expect(result['processed_params']).to eq({ + 'body' => { '1' => 'John', '2' => 'Order123' } + }) + expect(result['format_version']).to eq('enhanced') + end + end + + context 'when legacy hash with all body parameters' do + let(:legacy_hash_params) do + { + 'processed_params' => { + '1' => 'Product', + 'customer_name' => 'John' + } + } + end + + it 'converts to enhanced format with body only' do + converter = described_class.new(legacy_hash_params, media_template) + result = converter.normalize_to_enhanced + + expect(result['processed_params']).to eq({ + 'body' => { + '1' => 'Product', + 'customer_name' => 'John' + } + }) + expect(result['format_version']).to eq('enhanced') + end + end + + context 'when invalid format' do + let(:invalid_params) do + { + 'processed_params' => 'invalid_string' + } + end + + it 'raises ArgumentError' do + expect do + converter = described_class.new(invalid_params, template) + converter.normalize_to_enhanced + end.to raise_error(ArgumentError, /Unknown legacy format/) + end + end + end + + describe '#enhanced_format?' do + it 'returns true for valid enhanced format' do + enhanced = { 'body' => { '1' => 'test' } } + converter = described_class.new({}, template) + expect(converter.send(:enhanced_format?, enhanced)).to be true + end + + it 'returns false for array' do + converter = described_class.new({}, template) + expect(converter.send(:enhanced_format?, ['test'])).to be false + end + + it 'returns false for flat hash' do + converter = described_class.new({}, template) + expect(converter.send(:enhanced_format?, { '1' => 'test' })).to be false + end + + it 'returns false for invalid structure' do + invalid = { 'body' => 'not_a_hash' } + converter = described_class.new({}, template) + expect(converter.send(:enhanced_format?, invalid)).to be false + end + end + + describe 'simplified conversion methods' do + describe '#convert_array_to_body_params' do + it 'converts empty array' do + converter = described_class.new({}, template) + result = converter.send(:convert_array_to_body_params, []) + expect(result).to eq({}) + end + + it 'converts array to numbered body parameters' do + converter = described_class.new({}, template) + result = converter.send(:convert_array_to_body_params, %w[John Order123]) + expect(result).to eq({ '1' => 'John', '2' => 'Order123' }) + end + end + + describe '#convert_hash_to_body_params' do + it 'converts hash to body parameters' do + converter = described_class.new({}, template) + result = converter.send(:convert_hash_to_body_params, { 'name' => 'John', 'order' => '123' }) + expect(result).to eq({ 'name' => 'John', 'order' => '123' }) + end + end + end +end