Merge pull request #1166 from trheyi/main
Refactor Twilio provider methods to include context support
This commit is contained in:
commit
be8c226800
4 changed files with 1252 additions and 9 deletions
|
|
@ -162,7 +162,7 @@ func (p *Provider) sendSMS(ctx context.Context, message *types.Message) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, to := range message.To {
|
for _, to := range message.To {
|
||||||
err := p.sendSMSToRecipient(to, message)
|
err := p.sendSMSToRecipient(ctx, to, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send SMS to %s: %w", to, err)
|
return fmt.Errorf("failed to send SMS to %s: %w", to, err)
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +171,7 @@ func (p *Provider) sendSMS(ctx context.Context, message *types.Message) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendSMSToRecipient sends SMS to a single recipient
|
// sendSMSToRecipient sends SMS to a single recipient
|
||||||
func (p *Provider) sendSMSToRecipient(to string, message *types.Message) error {
|
func (p *Provider) sendSMSToRecipient(ctx context.Context, to string, message *types.Message) error {
|
||||||
apiURL := fmt.Sprintf("%s/Accounts/%s/Messages.json", p.baseURL, p.accountSID)
|
apiURL := fmt.Sprintf("%s/Accounts/%s/Messages.json", p.baseURL, p.accountSID)
|
||||||
|
|
||||||
// Prepare form data
|
// Prepare form data
|
||||||
|
|
@ -185,7 +185,7 @@ func (p *Provider) sendSMSToRecipient(to string, message *types.Message) error {
|
||||||
data.Set("From", p.fromPhone)
|
data.Set("From", p.fromPhone)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.sendTwilioRequest(apiURL, data)
|
return p.sendTwilioRequest(ctx, apiURL, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendWhatsApp sends a WhatsApp message via Twilio
|
// sendWhatsApp sends a WhatsApp message via Twilio
|
||||||
|
|
@ -195,7 +195,7 @@ func (p *Provider) sendWhatsApp(ctx context.Context, message *types.Message) err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, to := range message.To {
|
for _, to := range message.To {
|
||||||
err := p.sendWhatsAppToRecipient(to, message)
|
err := p.sendWhatsAppToRecipient(ctx, to, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send WhatsApp message to %s: %w", to, err)
|
return fmt.Errorf("failed to send WhatsApp message to %s: %w", to, err)
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +204,7 @@ func (p *Provider) sendWhatsApp(ctx context.Context, message *types.Message) err
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendWhatsAppToRecipient sends WhatsApp message to a single recipient
|
// sendWhatsAppToRecipient sends WhatsApp message to a single recipient
|
||||||
func (p *Provider) sendWhatsAppToRecipient(to string, message *types.Message) error {
|
func (p *Provider) sendWhatsAppToRecipient(ctx context.Context, to string, message *types.Message) error {
|
||||||
apiURL := fmt.Sprintf("%s/Accounts/%s/Messages.json", p.baseURL, p.accountSID)
|
apiURL := fmt.Sprintf("%s/Accounts/%s/Messages.json", p.baseURL, p.accountSID)
|
||||||
|
|
||||||
// Ensure phone numbers have WhatsApp prefix
|
// Ensure phone numbers have WhatsApp prefix
|
||||||
|
|
@ -224,7 +224,7 @@ func (p *Provider) sendWhatsAppToRecipient(to string, message *types.Message) er
|
||||||
data.Set("To", toWhatsApp)
|
data.Set("To", toWhatsApp)
|
||||||
data.Set("Body", message.Body)
|
data.Set("Body", message.Body)
|
||||||
|
|
||||||
return p.sendTwilioRequest(apiURL, data)
|
return p.sendTwilioRequest(ctx, apiURL, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendEmail sends an email via Twilio SendGrid API
|
// sendEmail sends an email via Twilio SendGrid API
|
||||||
|
|
@ -288,7 +288,7 @@ func (p *Provider) sendEmail(ctx context.Context, message *types.Message) error
|
||||||
|
|
||||||
// Send via SendGrid API
|
// Send via SendGrid API
|
||||||
apiURL := "https://api.sendgrid.com/v3/mail/send"
|
apiURL := "https://api.sendgrid.com/v3/mail/send"
|
||||||
req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
|
req, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewBuffer(jsonData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create request: %w", err)
|
return fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -311,9 +311,9 @@ func (p *Provider) sendEmail(ctx context.Context, message *types.Message) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendTwilioRequest sends a request to Twilio API
|
// sendTwilioRequest sends a request to Twilio API
|
||||||
func (p *Provider) sendTwilioRequest(apiURL string, data url.Values) error {
|
func (p *Provider) sendTwilioRequest(ctx context.Context, apiURL string, data url.Values) error {
|
||||||
// Add custom metadata as status callback parameters
|
// Add custom metadata as status callback parameters
|
||||||
req, err := http.NewRequest("POST", apiURL, strings.NewReader(data.Encode()))
|
req, err := http.NewRequestWithContext(ctx, "POST", apiURL, strings.NewReader(data.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create request: %w", err)
|
return fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
316
messenger/providers/twilio/twilio_sms_test.go
Normal file
316
messenger/providers/twilio/twilio_sms_test.go
Normal file
|
|
@ -0,0 +1,316 @@
|
||||||
|
package twilio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/yaoapp/yao/messenger/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test phone numbers for SMS (placeholder for future implementation)
|
||||||
|
const (
|
||||||
|
TestSMSPhoneAgent = "+1234567890" // Placeholder - replace with authorized test numbers
|
||||||
|
TestSMSPhoneX = "+1234567891" // Placeholder - replace with authorized test numbers
|
||||||
|
TestSMSPhoneXiang = "+1234567892" // Placeholder - replace with authorized test numbers
|
||||||
|
)
|
||||||
|
|
||||||
|
// createTestSMSMessage creates a test SMS message
|
||||||
|
func createTestSMSMessage() *types.Message {
|
||||||
|
return &types.Message{
|
||||||
|
Type: types.MessageTypeSMS,
|
||||||
|
To: []string{TestSMSPhoneAgent},
|
||||||
|
Body: "Test SMS from Twilio Provider - This is a test message.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadSMSTestConfig loads configuration optimized for SMS testing
|
||||||
|
func loadSMSTestConfig(t *testing.T) types.ProviderConfig {
|
||||||
|
config := loadTestConfig(t) // Reuse base config loading
|
||||||
|
|
||||||
|
// Ensure SMS-specific options are available
|
||||||
|
// In real implementation, verify TWILIO_FROM_PHONE is configured
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SMS Provider Configuration Tests
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestSMS_ProviderConfig_WithFromPhone(t *testing.T) {
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "sms_test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"account_sid": "test_account_sid",
|
||||||
|
"auth_token": "test_auth_token",
|
||||||
|
"from_phone": "+15551234567", // SMS requires from_phone
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, provider)
|
||||||
|
assert.Equal(t, "+15551234567", provider.fromPhone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSMS_ProviderConfig_WithMessagingService(t *testing.T) {
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "sms_test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"account_sid": "test_account_sid",
|
||||||
|
"auth_token": "test_auth_token",
|
||||||
|
"messaging_service_sid": "MGXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // Alternative to from_phone
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, provider)
|
||||||
|
assert.Equal(t, "MGXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", provider.messagingServiceSID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSMS_ProviderConfig_MissingPhoneAndService(t *testing.T) {
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "sms_test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"account_sid": "test_account_sid",
|
||||||
|
"auth_token": "test_auth_token",
|
||||||
|
// Missing both from_phone and messaging_service_sid
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
smsMessage := createTestSMSMessage()
|
||||||
|
|
||||||
|
err = provider.Send(ctx, smsMessage)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "either from_phone or messaging_service_sid is required for SMS")
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SMS Sending Tests (Future Implementation)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// TODO: Implement real SMS sending tests
|
||||||
|
func TestSend_SMSMessage_RealAPI(t *testing.T) {
|
||||||
|
t.Skip("SMS real API tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// config := loadSMSTestConfig(t)
|
||||||
|
// provider, err := NewTwilioProvider(config)
|
||||||
|
// require.NoError(t, err)
|
||||||
|
//
|
||||||
|
// // Skip if from_phone is not configured
|
||||||
|
// if provider.fromPhone == "" {
|
||||||
|
// t.Skip("TWILIO_FROM_PHONE not configured, skipping real SMS API test")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// smsMessage := createTestSMSMessage()
|
||||||
|
// err = provider.Send(ctx, smsMessage)
|
||||||
|
// if err == nil {
|
||||||
|
// t.Log("Real Twilio SMS API call succeeded")
|
||||||
|
// } else {
|
||||||
|
// t.Logf("Real Twilio SMS API call failed: %v", err)
|
||||||
|
// // Handle expected failures in test environments
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_SMSMessage_WithMessagingService_RealAPI(t *testing.T) {
|
||||||
|
t.Skip("SMS Messaging Service real API tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - SMS sending using Messaging Service SID instead of from_phone
|
||||||
|
// - Service-based features like automatic failover, delivery optimization
|
||||||
|
// - Compliance and opt-out handling
|
||||||
|
// - Alpha sender ID support
|
||||||
|
// - Short code support
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_SMSMessage_ContextTimeout_RealAPI(t *testing.T) {
|
||||||
|
t.Skip("SMS context timeout tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// config := loadSMSTestConfig(t)
|
||||||
|
// provider, err := NewTwilioProvider(config)
|
||||||
|
// require.NoError(t, err)
|
||||||
|
//
|
||||||
|
// // Create a very short timeout context
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// smsMessage := createTestSMSMessage()
|
||||||
|
// err = provider.Send(ctx, smsMessage)
|
||||||
|
// if err != nil {
|
||||||
|
// t.Log("Context timeout working correctly with real SMS API")
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendBatch_SMS_RealAPI(t *testing.T) {
|
||||||
|
t.Skip("SMS batch real API tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// config := loadSMSTestConfig(t)
|
||||||
|
// provider, err := NewTwilioProvider(config)
|
||||||
|
// require.NoError(t, err)
|
||||||
|
//
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// messages := []*types.Message{
|
||||||
|
// {
|
||||||
|
// Type: types.MessageTypeSMS,
|
||||||
|
// To: []string{TestSMSPhoneAgent},
|
||||||
|
// Body: "Batch SMS Test 1",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Type: types.MessageTypeSMS,
|
||||||
|
// To: []string{TestSMSPhoneX},
|
||||||
|
// Body: "Batch SMS Test 2",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// err = provider.SendBatch(ctx, messages)
|
||||||
|
// if err == nil {
|
||||||
|
// t.Log("Real Twilio SMS batch API call succeeded")
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_SMS_MultipleRecipients_RealAPI(t *testing.T) {
|
||||||
|
t.Skip("SMS multiple recipients tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// config := loadSMSTestConfig(t)
|
||||||
|
// provider, err := NewTwilioProvider(config)
|
||||||
|
// require.NoError(t, err)
|
||||||
|
//
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// smsMessage := &types.Message{
|
||||||
|
// Type: types.MessageTypeSMS,
|
||||||
|
// To: []string{TestSMSPhoneAgent, TestSMSPhoneX, TestSMSPhoneXiang},
|
||||||
|
// Body: "Multi-recipient SMS test from Twilio Provider",
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// err = provider.Send(ctx, smsMessage)
|
||||||
|
// if err == nil {
|
||||||
|
// t.Log("Twilio SMS multiple recipients API call succeeded")
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SMS Advanced Features Tests (Future Implementation)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestSend_SMS_WithCustomMetadata(t *testing.T) {
|
||||||
|
t.Skip("SMS metadata tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Custom metadata in status callbacks
|
||||||
|
// - Tracking and analytics integration
|
||||||
|
// - Custom parameters for delivery reporting
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_SMS_WithDeliveryStatus(t *testing.T) {
|
||||||
|
t.Skip("SMS delivery status tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Status callback configuration
|
||||||
|
// - Delivery receipt handling
|
||||||
|
// - Failed message retry logic
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_SMS_PhoneNumberValidation(t *testing.T) {
|
||||||
|
t.Skip("SMS phone validation tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - E.164 format validation
|
||||||
|
// - International number support
|
||||||
|
// - Invalid number error handling
|
||||||
|
// - Carrier lookup integration
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_SMS_RateLimiting(t *testing.T) {
|
||||||
|
t.Skip("SMS rate limiting tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Rate limit handling
|
||||||
|
// - Queue management for high-volume sending
|
||||||
|
// - Backoff strategies
|
||||||
|
// - Error recovery from rate limit exceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_SMS_LongMessages(t *testing.T) {
|
||||||
|
t.Skip("SMS long message tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Automatic message segmentation
|
||||||
|
// - Multi-part SMS handling
|
||||||
|
// - Character encoding (GSM 7-bit vs UCS-2)
|
||||||
|
// - Cost calculation for long messages
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SMS Error Handling Tests (Future Implementation)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestSend_SMS_InvalidPhoneNumber(t *testing.T) {
|
||||||
|
t.Skip("SMS invalid phone tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Invalid phone number format errors
|
||||||
|
// - Undeliverable number handling
|
||||||
|
// - Landline vs mobile detection
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_SMS_InsufficientBalance(t *testing.T) {
|
||||||
|
t.Skip("SMS balance tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Account balance insufficient errors
|
||||||
|
// - Graceful degradation when funds are low
|
||||||
|
// - Balance monitoring and alerts
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_SMS_APIError_Scenarios(t *testing.T) {
|
||||||
|
t.Skip("SMS API error tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Various Twilio API error codes
|
||||||
|
// - Network timeout handling
|
||||||
|
// - Authentication failures
|
||||||
|
// - Service unavailable scenarios
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SMS Benchmark Tests (Future Implementation)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func BenchmarkSend_SMS(b *testing.B) {
|
||||||
|
b.Skip("SMS benchmarks not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will benchmark:
|
||||||
|
// - Single SMS sending performance
|
||||||
|
// - Memory allocation patterns
|
||||||
|
// - Connection reuse efficiency
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSendBatch_SMS(b *testing.B) {
|
||||||
|
b.Skip("SMS batch benchmarks not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will benchmark:
|
||||||
|
// - Batch SMS sending throughput
|
||||||
|
// - Optimal batch sizes
|
||||||
|
// - Resource utilization under load
|
||||||
|
}
|
||||||
547
messenger/providers/twilio/twilio_test.go
Normal file
547
messenger/providers/twilio/twilio_test.go
Normal file
|
|
@ -0,0 +1,547 @@
|
||||||
|
package twilio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/messenger/types"
|
||||||
|
"github.com/yaoapp/yao/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test recipient email addresses - use authorized addresses for real API tests
|
||||||
|
const (
|
||||||
|
TestEmailAgent = "agent@iqka.com"
|
||||||
|
TestEmailX = "x@iqka.com"
|
||||||
|
TestEmailXiang = "xiang@iqka.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Email-focused tests - SMS and WhatsApp tests are in separate files
|
||||||
|
|
||||||
|
// loadTestConfig loads the unified.twilio.yao configuration for testing
|
||||||
|
func loadTestConfig(t *testing.T) types.ProviderConfig {
|
||||||
|
test.Prepare(t, config.Conf, "YAO_TEST_APPLICATION")
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "unified",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"account_sid": os.Getenv("TWILIO_ACCOUNT_SID"),
|
||||||
|
"auth_token": os.Getenv("TWILIO_AUTH_TOKEN"),
|
||||||
|
"from_phone": os.Getenv("TWILIO_FROM_PHONE"),
|
||||||
|
"from_email": os.Getenv("TWILIO_FROM_EMAIL"),
|
||||||
|
"api_sid": os.Getenv("TWILIO_API_SID"),
|
||||||
|
"api_key": os.Getenv("TWILIO_API_KEY"),
|
||||||
|
"sendgrid_api_key": os.Getenv("TWILIO_SENDGRID_API_KEY"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// createTestMessage creates a test message of the specified type
|
||||||
|
func createTestMessage(messageType types.MessageType) *types.Message {
|
||||||
|
switch messageType {
|
||||||
|
case types.MessageTypeEmail:
|
||||||
|
return &types.Message{
|
||||||
|
Type: types.MessageTypeEmail,
|
||||||
|
To: []string{TestEmailAgent},
|
||||||
|
Subject: "Test Email from Twilio Provider",
|
||||||
|
Body: "This is a test email sent via Twilio SendGrid API.",
|
||||||
|
HTML: "<h1>Test Email</h1><p>This is a test email sent via <strong>Twilio SendGrid API</strong>.</p>",
|
||||||
|
}
|
||||||
|
// SMS and WhatsApp message creation moved to separate test files
|
||||||
|
default:
|
||||||
|
return &types.Message{
|
||||||
|
Type: types.MessageTypeEmail,
|
||||||
|
To: []string{TestEmailAgent},
|
||||||
|
Body: "Default test message",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Basic Provider Tests
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestNewTwilioProvider(t *testing.T) {
|
||||||
|
config := loadTestConfig(t)
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, provider)
|
||||||
|
|
||||||
|
// Verify configuration using actual environment variables
|
||||||
|
assert.Equal(t, os.Getenv("TWILIO_ACCOUNT_SID"), provider.accountSID)
|
||||||
|
assert.Equal(t, os.Getenv("TWILIO_AUTH_TOKEN"), provider.authToken)
|
||||||
|
assert.Equal(t, os.Getenv("TWILIO_FROM_PHONE"), provider.fromPhone)
|
||||||
|
assert.Equal(t, os.Getenv("TWILIO_FROM_EMAIL"), provider.fromEmail)
|
||||||
|
assert.Equal(t, os.Getenv("TWILIO_API_SID"), provider.apiSID)
|
||||||
|
assert.Equal(t, os.Getenv("TWILIO_API_KEY"), provider.apiKey)
|
||||||
|
assert.Equal(t, os.Getenv("TWILIO_SENDGRID_API_KEY"), provider.sendGridAPIKey)
|
||||||
|
assert.Equal(t, "unified", provider.config.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTwilioProvider_MissingOptions(t *testing.T) {
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, provider)
|
||||||
|
assert.Contains(t, err.Error(), "Twilio provider requires options")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTwilioProvider_MissingAccountSID(t *testing.T) {
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"auth_token": "test_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, provider)
|
||||||
|
assert.Contains(t, err.Error(), "account_sid")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetType(t *testing.T) {
|
||||||
|
config := loadTestConfig(t)
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "twilio", provider.GetType())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetName(t *testing.T) {
|
||||||
|
config := loadTestConfig(t)
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "unified", provider.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate(t *testing.T) {
|
||||||
|
config := loadTestConfig(t)
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = provider.Validate()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate_MissingAccountSID(t *testing.T) {
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"auth_token": "test_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Nil(t, provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate_MissingAuth(t *testing.T) {
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"account_sid": "test_sid",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = provider.Validate()
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "auth_token")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate_PartialAPIKeys(t *testing.T) {
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"account_sid": "test_sid",
|
||||||
|
"auth_token": "valid_token", // Provide auth_token to pass first check
|
||||||
|
"api_sid": "test_api_sid",
|
||||||
|
// Missing api_key - this should trigger the partial API keys error
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = provider.Validate()
|
||||||
|
assert.Error(t, err)
|
||||||
|
// Should trigger the "both api_sid and api_key must be provided together" error
|
||||||
|
assert.Contains(t, err.Error(), "both 'api_sid' and 'api_key' must be provided together")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClose(t *testing.T) {
|
||||||
|
config := loadTestConfig(t)
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = provider.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Email Sending Tests (via SendGrid API)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestSend_EmailMessage_RealAPI(t *testing.T) {
|
||||||
|
config := loadTestConfig(t)
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Skip if SendGrid API key is not configured
|
||||||
|
if provider.sendGridAPIKey == "" {
|
||||||
|
t.Skip("TWILIO_SENDGRID_API_KEY not configured, skipping real API test")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
emailMessage := createTestMessage(types.MessageTypeEmail)
|
||||||
|
|
||||||
|
err = provider.Send(ctx, emailMessage)
|
||||||
|
if err == nil {
|
||||||
|
t.Log("Real Twilio SendGrid API call succeeded")
|
||||||
|
} else {
|
||||||
|
t.Logf("Real Twilio SendGrid API call failed (expected in some test environments): %v", err)
|
||||||
|
// Don't fail the test if it's just an API configuration issue
|
||||||
|
if !strings.Contains(err.Error(), "SendGrid API error") {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_EmailMessage_APIError(t *testing.T) {
|
||||||
|
// Create a mock HTTP server that returns an error
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"errors": []map[string]interface{}{
|
||||||
|
{"message": "Bad Request", "field": "from.email", "help": "Invalid from email"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"account_sid": "test_sid",
|
||||||
|
"auth_token": "test_token",
|
||||||
|
"from_email": "test@example.com",
|
||||||
|
"sendgrid_api_key": "test_api_key",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Mock the SendGrid API endpoint by temporarily replacing the sendEmail method behavior
|
||||||
|
// For this test, we'll create a custom provider with a modified http client
|
||||||
|
provider.httpClient = &http.Client{
|
||||||
|
Transport: &mockTransport{server: server},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
emailMessage := createTestMessage(types.MessageTypeEmail)
|
||||||
|
|
||||||
|
err = provider.Send(ctx, emailMessage)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "SendGrid API error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_ContextTimeout_Email_RealAPI(t *testing.T) {
|
||||||
|
config := loadTestConfig(t)
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Skip if SendGrid API key is not configured
|
||||||
|
if provider.sendGridAPIKey == "" {
|
||||||
|
t.Skip("TWILIO_SENDGRID_API_KEY not configured, skipping real API test")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a very short timeout context
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
emailMessage := createTestMessage(types.MessageTypeEmail)
|
||||||
|
|
||||||
|
err = provider.Send(ctx, emailMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Log("Context timeout working correctly with real API")
|
||||||
|
// Could be timeout or other error, both are acceptable for this test
|
||||||
|
} else {
|
||||||
|
t.Log("Request completed faster than timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendBatch_Email_RealAPI(t *testing.T) {
|
||||||
|
config := loadTestConfig(t)
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Skip if SendGrid API key is not configured
|
||||||
|
if provider.sendGridAPIKey == "" {
|
||||||
|
t.Skip("TWILIO_SENDGRID_API_KEY not configured, skipping real API test")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
messages := []*types.Message{
|
||||||
|
{
|
||||||
|
Type: types.MessageTypeEmail,
|
||||||
|
To: []string{TestEmailAgent},
|
||||||
|
Subject: "Batch Test Email 1",
|
||||||
|
Body: "This is batch test email 1 via Twilio SendGrid API.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: types.MessageTypeEmail,
|
||||||
|
To: []string{TestEmailX},
|
||||||
|
Subject: "Batch Test Email 2",
|
||||||
|
Body: "This is batch test email 2 via Twilio SendGrid API.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = provider.SendBatch(ctx, messages)
|
||||||
|
if err == nil {
|
||||||
|
t.Log("Real Twilio SendGrid batch API call succeeded")
|
||||||
|
} else {
|
||||||
|
t.Logf("Real Twilio SendGrid batch API call failed (expected in some test environments): %v", err)
|
||||||
|
// Don't fail the test if it's just an API configuration issue
|
||||||
|
if !strings.Contains(err.Error(), "SendGrid API error") {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_EmailMessage_WithCustomFrom(t *testing.T) {
|
||||||
|
// Create a mock HTTP server
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var payload map[string]interface{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&payload)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify custom from address is used
|
||||||
|
from := payload["from"].(map[string]interface{})
|
||||||
|
assert.Equal(t, "custom@example.com", from["email"])
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusAccepted)
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{"message": "success"})
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"account_sid": "test_sid",
|
||||||
|
"auth_token": "test_token",
|
||||||
|
"from_email": "default@example.com",
|
||||||
|
"sendgrid_api_key": "test_api_key",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
provider.httpClient = &http.Client{
|
||||||
|
Transport: &mockTransport{server: server},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
emailMessage := createTestMessage(types.MessageTypeEmail)
|
||||||
|
emailMessage.From = "custom@example.com"
|
||||||
|
|
||||||
|
err = provider.Send(ctx, emailMessage)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_EmailMessage_WithScheduledTime(t *testing.T) {
|
||||||
|
// Create a mock HTTP server
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var payload map[string]interface{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&payload)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify scheduled time is set
|
||||||
|
sendAt, exists := payload["send_at"]
|
||||||
|
assert.True(t, exists)
|
||||||
|
assert.NotNil(t, sendAt)
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusAccepted)
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{"message": "success"})
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"account_sid": "test_sid",
|
||||||
|
"auth_token": "test_token",
|
||||||
|
"from_email": "test@example.com",
|
||||||
|
"sendgrid_api_key": "test_api_key",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
provider.httpClient = &http.Client{
|
||||||
|
Transport: &mockTransport{server: server},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
emailMessage := createTestMessage(types.MessageTypeEmail)
|
||||||
|
scheduledTime := time.Now().Add(1 * time.Hour)
|
||||||
|
emailMessage.ScheduledAt = &scheduledTime
|
||||||
|
|
||||||
|
err = provider.Send(ctx, emailMessage)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_EmailMessage_MultipleRecipients_RealAPI(t *testing.T) {
|
||||||
|
config := loadTestConfig(t)
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Skip if SendGrid API key is not configured
|
||||||
|
if provider.sendGridAPIKey == "" {
|
||||||
|
t.Skip("TWILIO_SENDGRID_API_KEY not configured, skipping real API test")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
emailMessage := createTestMessage(types.MessageTypeEmail)
|
||||||
|
emailMessage.To = []string{TestEmailAgent, TestEmailX, TestEmailXiang}
|
||||||
|
emailMessage.Subject = "Multiple Recipients Test"
|
||||||
|
|
||||||
|
err = provider.Send(ctx, emailMessage)
|
||||||
|
if err == nil {
|
||||||
|
t.Log("Twilio SendGrid multiple recipients API call succeeded")
|
||||||
|
} else {
|
||||||
|
t.Logf("Twilio SendGrid multiple recipients API call failed (expected in some test environments): %v", err)
|
||||||
|
// Don't fail the test if it's just an API configuration issue
|
||||||
|
if !strings.Contains(err.Error(), "SendGrid API error") {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_UnsupportedMessageType(t *testing.T) {
|
||||||
|
config := loadTestConfig(t)
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Test unsupported message type
|
||||||
|
unsupportedMessage := &types.Message{
|
||||||
|
Type: "unsupported_type",
|
||||||
|
To: []string{TestEmailAgent},
|
||||||
|
Body: "This should fail",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = provider.Send(ctx, unsupportedMessage)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "unsupported message type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Note: SMS and WhatsApp tests have been moved to separate files:
|
||||||
|
// - twilio_sms_test.go: SMS-specific tests and functionality
|
||||||
|
// - twilio_whatsapp_test.go: WhatsApp-specific tests and functionality
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Benchmark Tests
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func BenchmarkSend_Email(b *testing.B) {
|
||||||
|
config := loadTestConfig(&testing.T{})
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("Failed to create provider: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if SendGrid API key is not configured
|
||||||
|
if provider.sendGridAPIKey == "" {
|
||||||
|
b.Skip("TWILIO_SENDGRID_API_KEY not configured, skipping benchmark")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
emailMessage := createTestMessage(types.MessageTypeEmail)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = provider.Send(ctx, emailMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSendBatch_Email(b *testing.B) {
|
||||||
|
config := loadTestConfig(&testing.T{})
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("Failed to create provider: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if SendGrid API key is not configured
|
||||||
|
if provider.sendGridAPIKey == "" {
|
||||||
|
b.Skip("TWILIO_SENDGRID_API_KEY not configured, skipping benchmark")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
messages := []*types.Message{
|
||||||
|
createTestMessage(types.MessageTypeEmail),
|
||||||
|
createTestMessage(types.MessageTypeEmail),
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = provider.SendBatch(ctx, messages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Helper Types for Mocking
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// mockTransport is a custom RoundTripper for mocking HTTP requests
|
||||||
|
type mockTransport struct {
|
||||||
|
server *httptest.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
// Redirect all requests to our mock server
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
req.URL.Host = strings.TrimPrefix(t.server.URL, "http://")
|
||||||
|
return http.DefaultTransport.RoundTrip(req)
|
||||||
|
}
|
||||||
380
messenger/providers/twilio/twilio_whatsapp_test.go
Normal file
380
messenger/providers/twilio/twilio_whatsapp_test.go
Normal file
|
|
@ -0,0 +1,380 @@
|
||||||
|
package twilio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/yaoapp/yao/messenger/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test phone numbers for WhatsApp (placeholder for future implementation)
|
||||||
|
const (
|
||||||
|
TestWhatsAppPhoneAgent = "+1234567890" // Placeholder - replace with authorized WhatsApp Business numbers
|
||||||
|
TestWhatsAppPhoneX = "+1234567891" // Placeholder - replace with authorized WhatsApp Business numbers
|
||||||
|
TestWhatsAppPhoneXiang = "+1234567892" // Placeholder - replace with authorized WhatsApp Business numbers
|
||||||
|
)
|
||||||
|
|
||||||
|
// createTestWhatsAppMessage creates a test WhatsApp message
|
||||||
|
func createTestWhatsAppMessage() *types.Message {
|
||||||
|
return &types.Message{
|
||||||
|
Type: types.MessageTypeWhatsApp,
|
||||||
|
To: []string{TestWhatsAppPhoneAgent},
|
||||||
|
Body: "Test WhatsApp message from Twilio Provider - Hello from Yao! 👋",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadWhatsAppTestConfig loads configuration optimized for WhatsApp testing
|
||||||
|
func loadWhatsAppTestConfig(t *testing.T) types.ProviderConfig {
|
||||||
|
config := loadTestConfig(t) // Reuse base config loading
|
||||||
|
|
||||||
|
// Ensure WhatsApp-specific options are available
|
||||||
|
// In real implementation, verify TWILIO_FROM_PHONE is a WhatsApp Business number
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// WhatsApp Provider Configuration Tests
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestWhatsApp_ProviderConfig_WithFromPhone(t *testing.T) {
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "whatsapp_test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"account_sid": "test_account_sid",
|
||||||
|
"auth_token": "test_auth_token",
|
||||||
|
"from_phone": "+15551234567", // WhatsApp requires from_phone (WhatsApp Business number)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, provider)
|
||||||
|
assert.Equal(t, "+15551234567", provider.fromPhone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWhatsApp_ProviderConfig_MissingFromPhone(t *testing.T) {
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "whatsapp_test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"account_sid": "test_account_sid",
|
||||||
|
"auth_token": "test_auth_token",
|
||||||
|
// Missing from_phone - required for WhatsApp
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
whatsappMessage := createTestWhatsAppMessage()
|
||||||
|
|
||||||
|
err = provider.Send(ctx, whatsappMessage)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "from_phone is required for WhatsApp messages")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWhatsApp_PhoneNumberFormatting(t *testing.T) {
|
||||||
|
// Test that phone numbers are properly formatted with whatsapp: prefix
|
||||||
|
// This tests the internal logic without making API calls
|
||||||
|
|
||||||
|
config := types.ProviderConfig{
|
||||||
|
Name: "whatsapp_test",
|
||||||
|
Connector: "twilio",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"account_sid": "test_account_sid",
|
||||||
|
"auth_token": "test_auth_token",
|
||||||
|
"from_phone": "+15551234567", // Will be formatted to whatsapp:+15551234567
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := NewTwilioProvider(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Test internal phone number formatting logic
|
||||||
|
// In real implementation, we'd test the sendWhatsAppToRecipient method
|
||||||
|
// For now, just verify the provider stores the number correctly
|
||||||
|
assert.Equal(t, "+15551234567", provider.fromPhone)
|
||||||
|
assert.False(t, strings.HasPrefix(provider.fromPhone, "whatsapp:"))
|
||||||
|
|
||||||
|
// The whatsapp: prefix should be added during sending, not during configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// WhatsApp Sending Tests (Future Implementation)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// TODO: Implement real WhatsApp sending tests
|
||||||
|
func TestSend_WhatsAppMessage_RealAPI(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp real API tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// config := loadWhatsAppTestConfig(t)
|
||||||
|
// provider, err := NewTwilioProvider(config)
|
||||||
|
// require.NoError(t, err)
|
||||||
|
//
|
||||||
|
// // Skip if from_phone is not configured or not a WhatsApp Business number
|
||||||
|
// if provider.fromPhone == "" {
|
||||||
|
// t.Skip("TWILIO_FROM_PHONE not configured, skipping real WhatsApp API test")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// whatsappMessage := createTestWhatsAppMessage()
|
||||||
|
// err = provider.Send(ctx, whatsappMessage)
|
||||||
|
// if err == nil {
|
||||||
|
// t.Log("Real Twilio WhatsApp API call succeeded")
|
||||||
|
// } else {
|
||||||
|
// t.Logf("Real Twilio WhatsApp API call failed: %v", err)
|
||||||
|
// // Handle expected failures in test environments
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_WhatsAppMessage_PhoneNumberFormatting_RealAPI(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp phone formatting real API tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Automatic "whatsapp:" prefix addition for both from and to numbers
|
||||||
|
// - Handling of numbers that already have the prefix
|
||||||
|
// - International phone number format validation
|
||||||
|
// - E.164 format compliance
|
||||||
|
// - Error handling for invalid WhatsApp numbers
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_WhatsAppMessage_ContextTimeout_RealAPI(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp context timeout tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// config := loadWhatsAppTestConfig(t)
|
||||||
|
// provider, err := NewTwilioProvider(config)
|
||||||
|
// require.NoError(t, err)
|
||||||
|
//
|
||||||
|
// // Create a very short timeout context
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// whatsappMessage := createTestWhatsAppMessage()
|
||||||
|
// err = provider.Send(ctx, whatsappMessage)
|
||||||
|
// if err != nil {
|
||||||
|
// t.Log("Context timeout working correctly with real WhatsApp API")
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendBatch_WhatsApp_RealAPI(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp batch real API tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// config := loadWhatsAppTestConfig(t)
|
||||||
|
// provider, err := NewTwilioProvider(config)
|
||||||
|
// require.NoError(t, err)
|
||||||
|
//
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// messages := []*types.Message{
|
||||||
|
// {
|
||||||
|
// Type: types.MessageTypeWhatsApp,
|
||||||
|
// To: []string{TestWhatsAppPhoneAgent},
|
||||||
|
// Body: "Batch WhatsApp Test 1 - Hello! 👋",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Type: types.MessageTypeWhatsApp,
|
||||||
|
// To: []string{TestWhatsAppPhoneX},
|
||||||
|
// Body: "Batch WhatsApp Test 2 - How are you? 😊",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// err = provider.SendBatch(ctx, messages)
|
||||||
|
// if err == nil {
|
||||||
|
// t.Log("Real Twilio WhatsApp batch API call succeeded")
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_MultipleRecipients_RealAPI(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp multiple recipients tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// config := loadWhatsAppTestConfig(t)
|
||||||
|
// provider, err := NewTwilioProvider(config)
|
||||||
|
// require.NoError(t, err)
|
||||||
|
//
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// whatsappMessage := &types.Message{
|
||||||
|
// Type: types.MessageTypeWhatsApp,
|
||||||
|
// To: []string{TestWhatsAppPhoneAgent, TestWhatsAppPhoneX, TestWhatsAppPhoneXiang},
|
||||||
|
// Body: "Multi-recipient WhatsApp test from Twilio Provider 🚀",
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// err = provider.Send(ctx, whatsappMessage)
|
||||||
|
// if err == nil {
|
||||||
|
// t.Log("Twilio WhatsApp multiple recipients API call succeeded")
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// WhatsApp Advanced Features Tests (Future Implementation)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_WithMediaMessage(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp media message tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Image messages with media URLs
|
||||||
|
// - Document attachments
|
||||||
|
// - Audio messages
|
||||||
|
// - Video messages
|
||||||
|
// - Media size and format validation
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_WithTemplateMessage(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp template message tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - WhatsApp Business template messages
|
||||||
|
// - Template parameter substitution
|
||||||
|
// - Template approval status handling
|
||||||
|
// - Language-specific templates
|
||||||
|
// - Template versioning
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_WithInteractiveMessage(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp interactive message tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Button messages
|
||||||
|
// - List messages
|
||||||
|
// - Quick reply buttons
|
||||||
|
// - Interactive message validation
|
||||||
|
// - Response handling
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_WithLocationMessage(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp location message tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Location sharing messages
|
||||||
|
// - Address and coordinates
|
||||||
|
// - Location name and description
|
||||||
|
// - Venue information
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_WithContactMessage(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp contact message tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Contact card messages
|
||||||
|
// - vCard format support
|
||||||
|
// - Multiple contact sharing
|
||||||
|
// - Contact information validation
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// WhatsApp Business Features Tests (Future Implementation)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_BusinessProfile(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp Business profile tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Business profile information
|
||||||
|
// - Verified business badge
|
||||||
|
// - Business hours and description
|
||||||
|
// - Website and contact info
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_OptInOptOut(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp opt-in/out tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - User opt-in confirmation
|
||||||
|
// - Opt-out request handling
|
||||||
|
// - Compliance with WhatsApp policies
|
||||||
|
// - Subscription management
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_MessageStatus(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp message status tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Message delivery status
|
||||||
|
// - Read receipts
|
||||||
|
// - Failed message handling
|
||||||
|
// - Status webhook configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// WhatsApp Error Handling Tests (Future Implementation)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_InvalidPhoneNumber(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp invalid phone tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Invalid WhatsApp number format errors
|
||||||
|
// - Non-WhatsApp numbers
|
||||||
|
// - Blocked or suspended numbers
|
||||||
|
// - Number verification failures
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_RateLimiting(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp rate limiting tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - WhatsApp Business API rate limits
|
||||||
|
// - 24-hour messaging window
|
||||||
|
// - Template message limits
|
||||||
|
// - Conversation-based pricing
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_PolicyViolation(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp policy tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Content policy violations
|
||||||
|
// - Spam detection and prevention
|
||||||
|
// - Business policy compliance
|
||||||
|
// - Account suspension scenarios
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend_WhatsApp_APIError_Scenarios(t *testing.T) {
|
||||||
|
t.Skip("WhatsApp API error tests not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will test:
|
||||||
|
// - Various WhatsApp API error codes
|
||||||
|
// - Network timeout handling
|
||||||
|
// - Authentication failures
|
||||||
|
// - Service unavailable scenarios
|
||||||
|
// - Webhook delivery failures
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// WhatsApp Benchmark Tests (Future Implementation)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func BenchmarkSend_WhatsApp(b *testing.B) {
|
||||||
|
b.Skip("WhatsApp benchmarks not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will benchmark:
|
||||||
|
// - Single WhatsApp message sending performance
|
||||||
|
// - Memory allocation patterns
|
||||||
|
// - Connection reuse efficiency
|
||||||
|
// - Media message processing time
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSendBatch_WhatsApp(b *testing.B) {
|
||||||
|
b.Skip("WhatsApp batch benchmarks not implemented yet - placeholder for future implementation")
|
||||||
|
|
||||||
|
// Future implementation will benchmark:
|
||||||
|
// - Batch WhatsApp sending throughput
|
||||||
|
// - Optimal batch sizes for WhatsApp
|
||||||
|
// - Resource utilization under load
|
||||||
|
// - Template message processing efficiency
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue