sms_api_test.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package server
  2. import (
  3. "context"
  4. "encoding/json"
  5. "net/http"
  6. "net/http/httptest"
  7. "testing"
  8. "time"
  9. "vocat/internal/store"
  10. )
  11. func TestSMSThreadAllDevicesUsesIMSIFilter(t *testing.T) {
  12. ctx := context.Background()
  13. database, err := store.Open(ctx, ":memory:")
  14. if err != nil {
  15. t.Fatal(err)
  16. }
  17. t.Cleanup(func() { _ = database.Close() })
  18. for index, imsi := range []string{"imsi-a", "imsi-b"} {
  19. if _, err := database.SaveSMSMessage(ctx, store.SMSMessage{
  20. MessageID: "message-" + imsi,
  21. DeviceID: "ec20",
  22. IMSI: imsi,
  23. Peer: "VOXI",
  24. Direction: "inbound",
  25. Body: imsi,
  26. Timestamp: time.Unix(1_700_000_000+int64(index), 0),
  27. }); err != nil {
  28. t.Fatal(err)
  29. }
  30. }
  31. server := &Server{store: database}
  32. request := httptest.NewRequest(
  33. http.MethodGet,
  34. "/api/sms/thread?device_id=all&imsi=imsi-a&peer=VOXI",
  35. nil,
  36. )
  37. response := httptest.NewRecorder()
  38. server.handleSMSThread(response, request)
  39. if response.Code != http.StatusOK {
  40. t.Fatalf("status = %d, body = %s", response.Code, response.Body.String())
  41. }
  42. var envelope struct {
  43. Data []map[string]any `json:"data"`
  44. }
  45. if err := json.Unmarshal(response.Body.Bytes(), &envelope); err != nil {
  46. t.Fatal(err)
  47. }
  48. if len(envelope.Data) != 1 || envelope.Data[0]["imsi"] != "imsi-a" {
  49. t.Fatalf("thread data = %#v", envelope.Data)
  50. }
  51. }
  52. func TestNormalizeSMSDeviceFilter(t *testing.T) {
  53. if got := normalizeSMSDeviceFilter(" ALL "); got != "" {
  54. t.Fatalf("all filter = %q", got)
  55. }
  56. if got := normalizeSMSDeviceFilter("EC20"); got != "EC20" {
  57. t.Fatalf("device filter = %q", got)
  58. }
  59. }
  60. func TestSMSSendOutcome(t *testing.T) {
  61. tests := []struct {
  62. name string
  63. all bool
  64. accepted int
  65. total int
  66. delivered bool
  67. want string
  68. }{
  69. {name: "delivered", all: true, accepted: 1, total: 1, delivered: true, want: "delivered"},
  70. {name: "accepted but unconfirmed", all: true, accepted: 2, total: 2, want: "accepted_unconfirmed"},
  71. {name: "partial", accepted: 1, total: 2, want: "partial"},
  72. {name: "failed", total: 1, want: "failed"},
  73. }
  74. for _, test := range tests {
  75. t.Run(test.name, func(t *testing.T) {
  76. if got := smsSendOutcome(test.all, test.accepted, test.total, test.delivered); got != test.want {
  77. t.Fatalf("smsSendOutcome() = %q, want %q", got, test.want)
  78. }
  79. })
  80. }
  81. }
  82. func TestBlockedSMSDestination(t *testing.T) {
  83. tests := []struct {
  84. name string
  85. phone string
  86. block bool
  87. }{
  88. {"e164 china", "+8613800138000", true},
  89. {"no plus china", "8613800138000", true},
  90. {"international prefix china", "008613800138000", true},
  91. {"spaced china", "+86 138 0013 8000", true},
  92. {"dashed china", "+86-138-0013-8000", true},
  93. {"us e164", "+12025550177", false},
  94. {"us no plus", "12025550177", false},
  95. {"uk e164", "+447700900123", false},
  96. {"italy", "+393331234567", false},
  97. {"russia", "+79161234567", false},
  98. {"japan", "+819012345678", false},
  99. }
  100. for _, test := range tests {
  101. t.Run(test.name, func(t *testing.T) {
  102. blocked, _ := blockedSMSDestination(test.phone)
  103. if blocked != test.block {
  104. t.Fatalf("blockedSMSDestination(%q) blocked = %v, want %v", test.phone, blocked, test.block)
  105. }
  106. })
  107. }
  108. }