chore: add more comprehensive specs

This commit is contained in:
Muhsin Keloth
2025-07-31 18:27:57 +04:00
parent 84f53f5dd9
commit 77aa9b8f74
2 changed files with 470 additions and 0 deletions
@@ -155,5 +155,214 @@ describe('templateHelper', () => {
},
]);
});
it('should handle templates with no variables', () => {
const emptyTemplate = templates.find(
t => t.name === 'no_variable_template'
);
const result = buildTemplateParameters(emptyTemplate, false);
expect(result).toEqual({});
});
it('should build parameters for templates with multiple component types', () => {
const complexTemplate = {
components: [
{ type: 'HEADER', format: 'IMAGE' },
{ type: 'BODY', text: 'Hi {{1}}, your order {{2}} is ready!' },
{ type: 'FOOTER', text: 'Thank you for your business' },
{
type: 'BUTTONS',
buttons: [{ type: 'URL', url: 'https://example.com/{{3}}' }],
},
],
};
const result = buildTemplateParameters(complexTemplate, true);
expect(result.header).toEqual({
media_url: '',
media_type: 'image',
});
expect(result.body).toEqual({ 1: '', 2: '' });
expect(result.buttons).toEqual([
{
type: 'url',
parameter: '',
url: 'https://example.com/{{3}}',
variables: ['3'],
},
]);
});
it('should handle copy code buttons correctly', () => {
const copyCodeTemplate = templates.find(
t => t.name === 'discount_coupon'
);
const result = buildTemplateParameters(copyCodeTemplate, false);
expect(result.body).toBeDefined();
expect(result.buttons).toEqual([
{
type: 'copy_code',
parameter: '',
},
]);
});
it('should handle templates with document headers', () => {
const documentTemplate = templates.find(
t => t.name === 'purchase_receipt'
);
const result = buildTemplateParameters(documentTemplate, true);
expect(result.header).toEqual({
media_url: '',
media_type: 'document',
});
expect(result.body).toEqual({
1: '',
2: '',
3: '',
});
});
it('should handle video header templates', () => {
const videoTemplate = templates.find(t => t.name === 'training_video');
const result = buildTemplateParameters(videoTemplate, true);
expect(result.header).toEqual({
media_url: '',
media_type: 'video',
});
expect(result.body).toEqual({
name: '',
date: '',
});
});
});
describe('enhanced format validation', () => {
it('should validate enhanced format structure', () => {
const processedParams = {
body: { 1: 'John', 2: 'Order123' },
header: {
media_url: 'https://example.com/image.jpg',
media_type: 'image',
},
buttons: [{ type: 'copy_code', parameter: 'SAVE20' }],
};
// Test that structure is properly formed
expect(processedParams.body).toBeDefined();
expect(typeof processedParams.body).toBe('object');
expect(processedParams.header).toBeDefined();
expect(Array.isArray(processedParams.buttons)).toBe(true);
});
it('should handle empty component sections', () => {
const processedParams = {
body: {},
header: {},
buttons: [],
};
expect(allKeysRequired(processedParams.body)).toBe(true);
expect(allKeysRequired(processedParams.header)).toBe(true);
expect(processedParams.buttons.length).toBe(0);
});
it('should validate parameter completeness', () => {
const incompleteParams = {
body: { 1: 'John', 2: '' },
};
expect(allKeysRequired(incompleteParams.body)).toBe(false);
});
it('should handle edge cases in processVariable', () => {
expect(processVariable('{{')).toBe('');
expect(processVariable('}}')).toBe('');
expect(processVariable('')).toBe('');
expect(processVariable('{{nested{{variable}}}}')).toBe('nestedvariable');
});
it('should handle special characters in template variables', () => {
/* eslint-disable no-template-curly-in-string */
const templateText =
'Welcome {{user_name}}, your order #{{order_id}} costs ${{amount}}';
/* eslint-enable no-template-curly-in-string */
const processedParams = {
body: {
user_name: 'John & Jane',
order_id: '12345',
amount: '99.99',
},
};
const result = replaceTemplateVariables(templateText, processedParams);
expect(result).toBe(
'Welcome John & Jane, your order #12345 costs $99.99'
);
});
it('should handle templates with mixed parameter types', () => {
const mixedTemplate = {
components: [
{ type: 'HEADER', format: 'VIDEO' },
{ type: 'BODY', text: 'Order {{order_id}} status: {{status}}' },
{ type: 'FOOTER', text: 'Thank you' },
{
type: 'BUTTONS',
buttons: [
{ type: 'URL', url: 'https://track.com/{{order_id}}' },
{ type: 'COPY_CODE' },
{ type: 'PHONE_NUMBER', phone_number: '+1234567890' },
],
},
],
};
const result = buildTemplateParameters(mixedTemplate, true);
expect(result.header).toEqual({
media_url: '',
media_type: 'video',
});
expect(result.body).toEqual({
order_id: '',
status: '',
});
expect(result.buttons).toHaveLength(2); // URL and COPY_CODE (PHONE_NUMBER doesn't need parameters)
expect(result.buttons[0].type).toBe('url');
expect(result.buttons[1].type).toBe('copy_code');
});
it('should handle templates with no processable components', () => {
const emptyTemplate = {
components: [
{ type: 'HEADER', format: 'TEXT', text: 'Static Header' },
{ type: 'BODY', text: 'Static body with no variables' },
{ type: 'FOOTER', text: 'Static footer' },
],
};
const result = buildTemplateParameters(emptyTemplate, false);
expect(result).toEqual({});
});
it('should validate that replaceTemplateVariables preserves unreplaced variables', () => {
const templateText = 'Hi {{name}}, order {{order_id}} is {{status}}';
const partialParams = {
body: {
name: 'John',
// order_id missing
status: 'ready',
},
};
const result = replaceTemplateVariables(templateText, partialParams);
expect(result).toBe('Hi John, order {{order_id}} is ready');
expect(result).toContain('{{order_id}}'); // Unreplaced variable preserved
});
});
});
@@ -165,6 +165,267 @@ describe Whatsapp::SendOnWhatsappService do
described_class.new(message: message).perform
expect(message.reload.source_id).to eq('123456789')
end
it 'handles template with header parameters' do
# Test with body parameters only since no media templates in factory
header_template_params = {
name: 'sample_shipping_confirmation',
namespace: '23423423_2342423_324234234_2343224',
language: 'en_US',
category: 'SHIPPING_UPDATE',
processed_params: {
'body' => { '1' => '3' },
'header' => { 'media_url' => 'https://example.com/image.jpg', 'media_type' => 'image' }
}
}
message = create(:message, additional_attributes: { template_params: header_template_params },
conversation: conversation, message_type: :outgoing)
stub_request(:post, 'https://waba.360dialog.io/v1/messages')
.with(
headers: headers,
body: {
to: '123456789',
template: {
name: 'sample_shipping_confirmation',
namespace: '23423423_2342423_324234234_2343224',
language: { 'policy': 'deterministic', 'code': 'en_US' },
components: [
{ type: 'header', parameters: [{ type: 'image', image: { link: 'https://example.com/image.jpg' } }] },
{ type: 'body', parameters: [{ type: 'text', text: '3' }] }
]
},
type: 'template'
}.to_json
).to_return(status: 200, body: success_response, headers: { 'content-type' => 'application/json' })
described_class.new(message: message).perform
expect(message.reload.source_id).to eq('123456789')
end
it 'handles empty processed_params gracefully' do
empty_template_params = {
name: 'sample_shipping_confirmation',
namespace: '23423423_2342423_324234234_2343224',
language: 'en_US',
category: 'SHIPPING_UPDATE',
processed_params: {}
}
message = create(:message, additional_attributes: { template_params: empty_template_params },
conversation: conversation, message_type: :outgoing)
stub_request(:post, 'https://waba.360dialog.io/v1/messages')
.with(
headers: headers,
body: {
to: '123456789',
template: {
name: 'sample_shipping_confirmation',
namespace: '23423423_2342423_324234234_2343224',
language: { 'policy': 'deterministic', 'code': 'en_US' },
components: []
},
type: 'template'
}.to_json
).to_return(status: 200, body: success_response, headers: { 'content-type' => 'application/json' })
described_class.new(message: message).perform
expect(message.reload.source_id).to eq('123456789')
end
it 'handles template with button parameters' do
# Test button processing with existing template - simulate buttons being added
button_template_params = {
name: 'sample_shipping_confirmation',
namespace: '23423423_2342423_324234234_2343224',
language: 'en_US',
category: 'SHIPPING_UPDATE',
processed_params: {
'body' => { '1' => '3' },
'buttons' => [{ 'type' => 'url', 'parameter' => 'https://track.example.com/123' }]
}
}
message = create(:message, additional_attributes: { template_params: button_template_params },
conversation: conversation, message_type: :outgoing)
stub_request(:post, 'https://waba.360dialog.io/v1/messages')
.with(
headers: headers,
body: {
to: '123456789',
template: {
name: 'sample_shipping_confirmation',
namespace: '23423423_2342423_324234234_2343224',
language: { 'policy': 'deterministic', 'code': 'en_US' },
components: [
{ type: 'body', parameters: [{ type: 'text', text: '3' }] },
{ type: 'button', sub_type: 'url', index: 0, parameters: [{ type: 'text', text: 'https://track.example.com/123' }] }
]
},
type: 'template'
}.to_json
).to_return(status: 200, body: success_response, headers: { 'content-type' => 'application/json' })
described_class.new(message: message).perform
expect(message.reload.source_id).to eq('123456789')
end
it 'processes template parameters correctly via integration' do
# Test enhanced format processing through the integration
complex_template_params = {
name: 'sample_shipping_confirmation',
namespace: '23423423_2342423_324234234_2343224',
language: 'en_US',
category: 'SHIPPING_UPDATE',
processed_params: {
'body' => { '1' => '5' },
'footer' => { 'text' => 'Thank you' }
}
}
message = create(:message, additional_attributes: { template_params: complex_template_params },
conversation: conversation, message_type: :outgoing)
stub_request(:post, 'https://waba.360dialog.io/v1/messages')
.with(
headers: headers,
body: {
to: '123456789',
template: {
name: 'sample_shipping_confirmation',
namespace: '23423423_2342423_324234234_2343224',
language: { 'policy': 'deterministic', 'code': 'en_US' },
components: [
{ type: 'body', parameters: [{ type: 'text', text: '5' }] },
{ type: 'footer', parameters: [{ type: 'text', text: 'Thank you' }] }
]
},
type: 'template'
}.to_json
).to_return(status: 200, body: success_response, headers: { 'content-type' => 'application/json' })
# This tests the full integration including TemplateProcessorService
expect { described_class.new(message: message).perform }.not_to raise_error
expect(message.reload.source_id).to eq('123456789')
end
it 'handles edge case with missing template gracefully' do
# Test the service behavior when template is not found
missing_template_params = {
'name' => 'non_existent_template',
'namespace' => 'missing_namespace',
'language' => 'en_US',
'category' => 'UTILITY',
'processed_params' => { 'body' => { '1' => 'test' } }
}
service = Whatsapp::TemplateProcessorService.new(
channel: whatsapp_channel,
template_params: missing_template_params
)
expect { service.call }.not_to raise_error
name, namespace, language, processed_params = service.call
expect(name).to eq('non_existent_template')
expect(namespace).to eq('missing_namespace')
expect(language).to eq('en_US')
expect(processed_params).to be_nil
end
it 'handles template with blank parameter values correctly' do
# Test that blank/empty values are skipped in parameter processing
blank_values_template_params = {
name: 'sample_shipping_confirmation',
namespace: '23423423_2342423_324234234_2343224',
language: 'en_US',
category: 'SHIPPING_UPDATE',
processed_params: {
'body' => { '1' => '', '2' => 'valid_value', '3' => nil },
'header' => { 'media_url' => '', 'media_type' => 'image' }
}
}
message = create(:message, additional_attributes: { template_params: blank_values_template_params },
conversation: conversation, message_type: :outgoing)
stub_request(:post, 'https://waba.360dialog.io/v1/messages')
.with(
headers: headers,
body: {
to: '123456789',
template: {
name: 'sample_shipping_confirmation',
namespace: '23423423_2342423_324234234_2343224',
language: { 'policy': 'deterministic', 'code': 'en_US' },
components: [
{ type: 'body', parameters: [{ type: 'text', text: 'valid_value' }] }
]
},
type: 'template'
}.to_json
).to_return(status: 200, body: success_response, headers: { 'content-type' => 'application/json' })
described_class.new(message: message).perform
expect(message.reload.source_id).to eq('123456789')
end
it 'handles nil template_params gracefully' do
# Test service behavior when template_params is completely nil
message = create(:message, additional_attributes: {},
conversation: conversation, message_type: :outgoing)
# Should send regular message, not template
stub_request(:post, 'https://waba.360dialog.io/v1/messages')
.with(
headers: headers,
body: {
to: '123456789',
text: { body: message.content },
type: 'text'
}.to_json
).to_return(status: 200, body: success_response, headers: { 'content-type' => 'application/json' })
expect { described_class.new(message: message).perform }.not_to raise_error
end
it 'processes template with rich text formatting' do
# Test that rich text formatting is preserved
rich_text_template_params = {
name: 'sample_shipping_confirmation',
namespace: '23423423_2342423_324234234_2343224',
language: 'en_US',
category: 'SHIPPING_UPDATE',
processed_params: {
'body' => { '1' => '*Bold text* and _italic text_' }
}
}
message = create(:message, additional_attributes: { template_params: rich_text_template_params },
conversation: conversation, message_type: :outgoing)
stub_request(:post, 'https://waba.360dialog.io/v1/messages')
.with(
headers: headers,
body: {
to: '123456789',
template: {
name: 'sample_shipping_confirmation',
namespace: '23423423_2342423_324234234_2343224',
language: { 'policy': 'deterministic', 'code': 'en_US' },
components: [
{ type: 'body', parameters: [{ type: 'text', text: '*Bold text* and _italic text_' }] }
]
},
type: 'template'
}.to_json
).to_return(status: 200, body: success_response, headers: { 'content-type' => 'application/json' })
described_class.new(message: message).perform
expect(message.reload.source_id).to eq('123456789')
end
end
end
end