@@ -14,6 +14,7 @@ import (
1414 httpsms "github.com/NdoleStudio/httpsms-go"
1515 "github.com/stretchr/testify/assert"
1616 "github.com/stretchr/testify/require"
17+ "github.com/xuri/excelize/v2"
1718)
1819
1920func TestSendSMS_Encrypted (t * testing.T ) {
@@ -402,3 +403,155 @@ func TestHeartbeat_StoreAndIndex(t *testing.T) {
402403 assert .True (t , hb .Charging )
403404 assert .False (t , hb .Timestamp .IsZero (), "timestamp should not be zero" )
404405}
406+
407+ func TestBulkSMS_CSV (t * testing.T ) {
408+ ctx := context .Background ()
409+ phone := setupPhone (ctx , t , 60 )
410+
411+ // Build CSV content with 1 message
412+ csvContent := fmt .Sprintf ("FromPhoneNumber,ToPhoneNumber,Content,SendTime(optional)\n %s,%s,CSV bulk test message,\n " ,
413+ phone .PhoneNumber , randomPhoneNumber ())
414+
415+ // Upload CSV
416+ statusCode , respBody := uploadBulkFile (ctx , t , "test.csv" , []byte (csvContent ))
417+ require .Equal (t , http .StatusAccepted , statusCode , "upload failed: %s" , string (respBody ))
418+ t .Logf ("upload response: %s" , string (respBody ))
419+
420+ // Parse the response to verify message count
421+ var uploadResp struct {
422+ Message string `json:"message"`
423+ }
424+ require .NoError (t , json .Unmarshal (respBody , & uploadResp ))
425+ assert .Contains (t , uploadResp .Message , "1 out of 1" )
426+
427+ // Wait a moment for messages to be persisted
428+ time .Sleep (2 * time .Second )
429+
430+ // Search for the bulk message by owner to get message IDs
431+ messages := searchMessages (ctx , t , "" , phone .PhoneNumber )
432+ require .GreaterOrEqual (t , len (messages ), 1 , "expected at least 1 message for phone %s" , phone .PhoneNumber )
433+
434+ // Find the message with bulk- request_id prefix
435+ var bulkMsg * httpsms.Message
436+ for i := range messages {
437+ if messages [i ].RequestID != nil && strings .HasPrefix (* messages [i ].RequestID , "bulk-" ) {
438+ bulkMsg = & messages [i ]
439+ break
440+ }
441+ }
442+ require .NotNil (t , bulkMsg , "no message with bulk- request_id found" )
443+ messageID := bulkMsg .ID .String ()
444+ requestID := * bulkMsg .RequestID
445+ t .Logf ("found bulk message: id=%s, request_id=%s" , messageID , requestID )
446+
447+ // Wait for FCM push
448+ waitForFCMPush (t , messageID , 30 * time .Second )
449+
450+ // Fire SENT event
451+ fireEvent (ctx , t , phone .PhoneAPIKey , messageID , "SENT" )
452+
453+ // Poll until message reaches "sent" status
454+ msg := pollMessageStatus (ctx , t , messageID , "sent" , 15 * time .Second )
455+ assert .Equal (t , "sent" , msg .Status )
456+
457+ // Verify bulk-messages history endpoint
458+ entries := fetchBulkMessages (ctx , t )
459+ entry := findBulkEntry (entries , requestID )
460+ require .NotNil (t , entry , "bulk entry with request_id %s not found in history" , requestID )
461+
462+ assert .Equal (t , 1 , entry .Total )
463+ assert .Equal (t , 1 , entry .SentCount )
464+ assert .Equal (t , 0 , entry .PendingCount )
465+ assert .Equal (t , 0 , entry .FailedCount )
466+ assert .Equal (t , 0 , entry .ExpiredCount )
467+ assert .Equal (t , 0 , entry .DeliveredCount )
468+ assert .Equal (t , 0 , entry .ScheduledCount )
469+ }
470+
471+ func TestBulkSMS_Excel (t * testing.T ) {
472+ ctx := context .Background ()
473+ phone := setupPhone (ctx , t , 60 )
474+
475+ contact1 := randomPhoneNumber ()
476+ contact2 := randomPhoneNumber ()
477+
478+ // Build Excel file with 2 messages
479+ f := excelize .NewFile ()
480+ sheet := f .GetSheetName (0 )
481+ f .SetCellValue (sheet , "A1" , "FromPhoneNumber" )
482+ f .SetCellValue (sheet , "B1" , "ToPhoneNumber" )
483+ f .SetCellValue (sheet , "C1" , "Content" )
484+ f .SetCellValue (sheet , "D1" , "SendTime(optional)" )
485+
486+ f .SetCellValue (sheet , "A2" , phone .PhoneNumber )
487+ f .SetCellValue (sheet , "B2" , contact1 )
488+ f .SetCellValue (sheet , "C2" , "Excel bulk test message 1" )
489+ f .SetCellValue (sheet , "D2" , "" )
490+
491+ f .SetCellValue (sheet , "A3" , phone .PhoneNumber )
492+ f .SetCellValue (sheet , "B3" , contact2 )
493+ f .SetCellValue (sheet , "C3" , "Excel bulk test message 2" )
494+ f .SetCellValue (sheet , "D3" , "" )
495+
496+ var excelBuf bytes.Buffer
497+ require .NoError (t , f .Write (& excelBuf ))
498+
499+ // Upload Excel
500+ statusCode , respBody := uploadBulkFile (ctx , t , "test.xlsx" , excelBuf .Bytes ())
501+ require .Equal (t , http .StatusAccepted , statusCode , "upload failed: %s" , string (respBody ))
502+ t .Logf ("upload response: %s" , string (respBody ))
503+
504+ var uploadResp struct {
505+ Message string `json:"message"`
506+ }
507+ require .NoError (t , json .Unmarshal (respBody , & uploadResp ))
508+ assert .Contains (t , uploadResp .Message , "2 out of 2" )
509+
510+ // Wait for messages to be persisted
511+ time .Sleep (2 * time .Second )
512+
513+ // Search for bulk messages by owner
514+ messages := searchMessages (ctx , t , "" , phone .PhoneNumber )
515+ require .GreaterOrEqual (t , len (messages ), 2 , "expected at least 2 messages for phone %s" , phone .PhoneNumber )
516+
517+ // Find messages with bulk- request_id prefix
518+ var bulkMessages []httpsms.Message
519+ var requestID string
520+ for i := range messages {
521+ if messages [i ].RequestID != nil && strings .HasPrefix (* messages [i ].RequestID , "bulk-" ) {
522+ bulkMessages = append (bulkMessages , messages [i ])
523+ requestID = * messages [i ].RequestID
524+ }
525+ }
526+ require .Len (t , bulkMessages , 2 , "expected 2 messages with bulk- request_id" )
527+ require .NotEmpty (t , requestID )
528+ t .Logf ("found %d bulk messages with request_id=%s" , len (bulkMessages ), requestID )
529+
530+ // Wait for FCM pushes for both messages
531+ msgID1 := bulkMessages [0 ].ID .String ()
532+ msgID2 := bulkMessages [1 ].ID .String ()
533+ waitForFCMPush (t , msgID1 , 30 * time .Second )
534+ waitForFCMPush (t , msgID2 , 30 * time .Second )
535+
536+ // Fire SENT then DELIVERED on message 1, leave message 2 pending
537+ fireEvent (ctx , t , phone .PhoneAPIKey , msgID1 , "SENT" )
538+ time .Sleep (200 * time .Millisecond )
539+ fireEvent (ctx , t , phone .PhoneAPIKey , msgID1 , "DELIVERED" )
540+
541+ // Poll until message 1 reaches "delivered"
542+ msg1 := pollMessageStatus (ctx , t , msgID1 , "delivered" , 15 * time .Second )
543+ assert .Equal (t , "delivered" , msg1 .Status )
544+
545+ // Verify bulk-messages history endpoint
546+ entries := fetchBulkMessages (ctx , t )
547+ entry := findBulkEntry (entries , requestID )
548+ require .NotNil (t , entry , "bulk entry with request_id %s not found in history" , requestID )
549+
550+ assert .Equal (t , 2 , entry .Total )
551+ assert .Equal (t , 1 , entry .DeliveredCount )
552+ assert .Equal (t , 1 , entry .PendingCount )
553+ assert .Equal (t , 0 , entry .SentCount )
554+ assert .Equal (t , 0 , entry .FailedCount )
555+ assert .Equal (t , 0 , entry .ExpiredCount )
556+ assert .Equal (t , 0 , entry .ScheduledCount )
557+ }
0 commit comments