mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-13 03:13:43 +08:00
361 lines
9.5 KiB
Go
361 lines
9.5 KiB
Go
package device
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"vocat/internal/modem"
|
|
)
|
|
|
|
func TestManagerSendSMSDirectGSM7ReturnsAcceptanceEvidence(t *testing.T) {
|
|
client := &transcriptClient{
|
|
steps: []clientStep{
|
|
{command: "AT+CMGF=1", response: okResponse()},
|
|
{command: `AT+CSCS="GSM"`, response: okResponse()},
|
|
{command: "AT+CSMP=49,167,0,0", response: okResponse()},
|
|
},
|
|
promptSteps: []promptClientStep{{
|
|
command: `AT+CMGS="+12345"`,
|
|
payload: "HELLO",
|
|
response: okResponse("+CMGS: 23"),
|
|
}},
|
|
}
|
|
manager, id := newStartedTestManager(t, client)
|
|
|
|
result, err := manager.SendSMS(
|
|
context.Background(),
|
|
id,
|
|
"+12 345",
|
|
"HELLO",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("SendSMS: %v", err)
|
|
}
|
|
if result.To != "+12345" ||
|
|
result.Encoding != SMSEncodingGSM7Text ||
|
|
!result.AcceptedByModem ||
|
|
!result.ReferenceKnown ||
|
|
result.MessageReference != 23 ||
|
|
result.DeliveryConfirmed ||
|
|
result.DeliveryStatus != "unknown" ||
|
|
result.SubmissionStatus != "accepted_by_modem" ||
|
|
result.PartsTotal != 1 ||
|
|
result.PartsAttempted != 1 ||
|
|
result.PartsAccepted != 1 ||
|
|
!result.AllPartsAccepted ||
|
|
len(result.PartResults) != 1 {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
if len(result.ModemEvidence) != 1 || result.ModemEvidence[0] != "+CMGS: 23" {
|
|
t.Fatalf("evidence = %#v", result.ModemEvidence)
|
|
}
|
|
client.assertDone(t)
|
|
}
|
|
|
|
func TestManagerSendMultipartSMSReturnsEveryPartReference(t *testing.T) {
|
|
var concatReference = -1
|
|
validatePart := func(sequence int, wantText string) func(string) error {
|
|
return func(payload string) error {
|
|
message, err := decodeSMSPDU(payload)
|
|
if err != nil {
|
|
return fmt.Errorf("decode part %d: %w", sequence, err)
|
|
}
|
|
if message.Text != wantText || message.Concat == nil ||
|
|
message.Concat.Total != 2 ||
|
|
message.Concat.Sequence != sequence {
|
|
return fmt.Errorf("part %d decoded as %#v", sequence, message)
|
|
}
|
|
if concatReference < 0 {
|
|
concatReference = message.Concat.Reference
|
|
} else if message.Concat.Reference != concatReference {
|
|
return fmt.Errorf(
|
|
"part %d concat reference %d, want %d",
|
|
sequence,
|
|
message.Concat.Reference,
|
|
concatReference,
|
|
)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
client := &transcriptClient{
|
|
steps: []clientStep{{command: "AT+CMGF=0", response: okResponse()}},
|
|
promptSteps: []promptClientStep{
|
|
{
|
|
command: "AT+CMGS=150",
|
|
validateBody: validatePart(1, strings.Repeat("A", 153)),
|
|
response: okResponse("+CMGS: 31"),
|
|
},
|
|
{
|
|
command: "AT+CMGS=24",
|
|
validateBody: validatePart(2, strings.Repeat("A", 8)),
|
|
response: okResponse("+CMGS: 32"),
|
|
},
|
|
},
|
|
}
|
|
manager, id := newStartedTestManager(t, client)
|
|
|
|
result, err := manager.SendSMS(
|
|
context.Background(),
|
|
id,
|
|
"+12345",
|
|
strings.Repeat("A", 161),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("SendSMS: %v", err)
|
|
}
|
|
if result.PartsTotal != 2 ||
|
|
result.PartsAttempted != 2 ||
|
|
result.PartsAccepted != 2 ||
|
|
!result.AcceptedByModem ||
|
|
!result.AllPartsAccepted ||
|
|
result.ReferenceKnown ||
|
|
result.ConcatReference == nil ||
|
|
*result.ConcatReference != concatReference ||
|
|
len(result.PartResults) != 2 ||
|
|
result.PartResults[0].MessageReference != 31 ||
|
|
result.PartResults[1].MessageReference != 32 ||
|
|
!result.PartResults[0].AcceptedByModem ||
|
|
!result.PartResults[1].AcceptedByModem ||
|
|
result.DeliveryConfirmed {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
client.assertDone(t)
|
|
}
|
|
|
|
func TestManagerSendMultipartSMSStopsAndPreservesPartialEvidence(t *testing.T) {
|
|
validatePart := func(sequence int, wantText string) func(string) error {
|
|
return func(payload string) error {
|
|
message, err := decodeSMSPDU(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if message.Concat == nil ||
|
|
message.Concat.Sequence != sequence ||
|
|
message.Text != wantText {
|
|
return fmt.Errorf("part %d decoded as %#v", sequence, message)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
secondError := &modem.CommandError{
|
|
Command: "AT+CMGS=24",
|
|
Final: "+CMS ERROR: 500",
|
|
}
|
|
client := &transcriptClient{
|
|
steps: []clientStep{{command: "AT+CMGF=0", response: okResponse()}},
|
|
promptSteps: []promptClientStep{
|
|
{
|
|
command: "AT+CMGS=150",
|
|
validateBody: validatePart(1, strings.Repeat("A", 153)),
|
|
response: okResponse("+CMGS: 41"),
|
|
},
|
|
{
|
|
command: "AT+CMGS=24",
|
|
validateBody: validatePart(2, strings.Repeat("A", 8)),
|
|
response: modem.Response{
|
|
Final: "+CMS ERROR: 500",
|
|
},
|
|
err: secondError,
|
|
},
|
|
},
|
|
}
|
|
manager, id := newStartedTestManager(t, client)
|
|
|
|
result, err := manager.SendSMS(
|
|
context.Background(),
|
|
id,
|
|
"+12345",
|
|
strings.Repeat("A", 161),
|
|
)
|
|
var commandErr *modem.CommandError
|
|
if !errors.As(err, &commandErr) {
|
|
t.Fatalf("error = %v", err)
|
|
}
|
|
if result.PartsTotal != 2 ||
|
|
result.PartsAttempted != 2 ||
|
|
result.PartsAccepted != 1 ||
|
|
result.AcceptedByModem ||
|
|
result.AllPartsAccepted ||
|
|
result.SubmissionStatus != "partially_accepted_by_modem" ||
|
|
len(result.PartResults) != 2 ||
|
|
!result.PartResults[0].AcceptedByModem ||
|
|
result.PartResults[0].MessageReference != 41 ||
|
|
result.PartResults[1].AcceptedByModem ||
|
|
result.PartResults[1].SubmissionStatus != "rejected_by_modem" ||
|
|
result.DeliveryConfirmed {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
client.assertDone(t)
|
|
}
|
|
|
|
func TestManagerSendSMSUsesUCS2PDUForChinese(t *testing.T) {
|
|
client := &transcriptClient{
|
|
steps: []clientStep{
|
|
{command: "AT+CMGF=0", response: okResponse()},
|
|
},
|
|
promptSteps: []promptClientStep{{
|
|
command: "AT+CMGS=14",
|
|
payload: "00210005912143F50008044F60597D",
|
|
response: okResponse("+CMGS: 0"),
|
|
}},
|
|
}
|
|
manager, id := newStartedTestManager(t, client)
|
|
|
|
result, err := manager.SendSMS(
|
|
context.Background(),
|
|
id,
|
|
"+12345",
|
|
"你好",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("SendSMS: %v", err)
|
|
}
|
|
if result.Encoding != SMSEncodingUCS2PDU ||
|
|
!result.ReferenceKnown ||
|
|
result.MessageReference != 0 ||
|
|
!result.AllPartsAccepted ||
|
|
result.DeliveryConfirmed {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
client.assertDone(t)
|
|
}
|
|
|
|
func TestManagerSendSMSTimeoutNeverClaimsAcceptanceOrDelivery(t *testing.T) {
|
|
client := &transcriptClient{
|
|
steps: []clientStep{
|
|
{command: "AT+CMGF=1", response: okResponse()},
|
|
{command: `AT+CSCS="GSM"`, response: okResponse()},
|
|
{command: "AT+CSMP=49,167,0,0", response: okResponse()},
|
|
},
|
|
promptSteps: []promptClientStep{{
|
|
command: `AT+CMGS="12345"`,
|
|
payload: "HELLO",
|
|
response: modem.Response{
|
|
Lines: []string{"+CMGS: 77"},
|
|
},
|
|
err: modem.ErrCommandTimeout,
|
|
}},
|
|
}
|
|
manager, id := newStartedTestManager(t, client)
|
|
|
|
result, err := manager.SendSMS(
|
|
context.Background(),
|
|
id,
|
|
"12345",
|
|
"HELLO",
|
|
)
|
|
if !errors.Is(err, modem.ErrCommandTimeout) {
|
|
t.Fatalf("error = %v", err)
|
|
}
|
|
if result.AcceptedByModem || result.DeliveryConfirmed ||
|
|
!result.ReferenceKnown || result.MessageReference != 77 ||
|
|
result.SubmissionStatus != "reference_returned_without_final" ||
|
|
result.DeliveryStatus != "unknown" {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
client.mu.Lock()
|
|
closeCount := client.closeCount
|
|
client.mu.Unlock()
|
|
if closeCount != 1 {
|
|
t.Fatalf("close count = %d, want 1 after uncertain timeout", closeCount)
|
|
}
|
|
client.assertDone(t)
|
|
}
|
|
|
|
func TestManagerListReadAndDeleteSMS(t *testing.T) {
|
|
const gsmPDU = "000405912143F500004210203040500005C82293F904"
|
|
const ucs2PDU = "000405912143F5000842102030405000044F60597D"
|
|
client := &transcriptClient{steps: []clientStep{
|
|
{command: "AT+CMGF=0", response: okResponse()},
|
|
{command: `AT+CPMS="SM"`, response: okResponse()},
|
|
{
|
|
command: "AT+CMGL=4",
|
|
response: okResponse(
|
|
"+CMGL: 7,0,,23",
|
|
gsmPDU,
|
|
"+CMGL: 8,1,,22",
|
|
ucs2PDU,
|
|
),
|
|
},
|
|
{command: `AT+CPMS="ME"`, response: okResponse()},
|
|
{command: "AT+CMGL=4", response: okResponse()},
|
|
{command: "AT+CMGF=0", response: okResponse()},
|
|
{
|
|
command: "AT+CMGR=7",
|
|
response: okResponse(
|
|
"+CMGR: 0,,23",
|
|
gsmPDU,
|
|
),
|
|
},
|
|
{command: "AT+CMGD=7", response: okResponse()},
|
|
}}
|
|
manager, id := newStartedTestManager(t, client)
|
|
|
|
messages, err := manager.ListSMS(context.Background(), id)
|
|
if err != nil {
|
|
t.Fatalf("ListSMS: %v", err)
|
|
}
|
|
if len(messages) != 2 ||
|
|
messages[0].Index != 7 ||
|
|
messages[0].Storage != "SM" ||
|
|
messages[0].StorageStatus != SMSStatusReceivedUnread ||
|
|
messages[0].Text != "HELLO" ||
|
|
messages[1].Index != 8 ||
|
|
messages[1].Storage != "SM" ||
|
|
messages[1].StorageStatus != SMSStatusReceivedRead ||
|
|
messages[1].Text != "你好" {
|
|
t.Fatalf("messages = %#v", messages)
|
|
}
|
|
|
|
message, err := manager.ReadSMS(context.Background(), id, 7)
|
|
if err != nil {
|
|
t.Fatalf("ReadSMS: %v", err)
|
|
}
|
|
if message.Index != 7 ||
|
|
message.StorageStatus != SMSStatusReceivedUnread ||
|
|
message.Text != "HELLO" {
|
|
t.Fatalf("message = %#v", message)
|
|
}
|
|
if err := manager.DeleteSMS(context.Background(), id, 7); err != nil {
|
|
t.Fatalf("DeleteSMS: %v", err)
|
|
}
|
|
client.assertDone(t)
|
|
}
|
|
|
|
func TestManagerSendSMSRequiresMessageReference(t *testing.T) {
|
|
client := &transcriptClient{
|
|
steps: []clientStep{
|
|
{command: "AT+CMGF=1", response: okResponse()},
|
|
{command: `AT+CSCS="GSM"`, response: okResponse()},
|
|
{command: "AT+CSMP=49,167,0,0", response: okResponse()},
|
|
},
|
|
promptSteps: []promptClientStep{{
|
|
command: `AT+CMGS="12345"`,
|
|
payload: "HELLO",
|
|
response: okResponse(),
|
|
}},
|
|
}
|
|
manager, id := newStartedTestManager(t, client)
|
|
|
|
result, err := manager.SendSMS(
|
|
context.Background(),
|
|
id,
|
|
"12345",
|
|
"HELLO",
|
|
)
|
|
if !errors.Is(err, ErrSMSReferenceMissing) {
|
|
t.Fatalf("error = %v", err)
|
|
}
|
|
if result.AcceptedByModem ||
|
|
result.ReferenceKnown ||
|
|
result.DeliveryConfirmed ||
|
|
result.SubmissionStatus != "unconfirmed_without_reference" {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
client.assertDone(t)
|
|
}
|