| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- 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)
- }
|