server_twilio_test.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. package server
  2. import (
  3. "github.com/stretchr/testify/require"
  4. "heckel.io/ntfy/user"
  5. "heckel.io/ntfy/util"
  6. "io"
  7. "net/http"
  8. "net/http/httptest"
  9. "sync/atomic"
  10. "testing"
  11. )
  12. func TestServer_Twilio_SMS(t *testing.T) {
  13. var called atomic.Bool
  14. twilioServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  15. body, err := io.ReadAll(r.Body)
  16. require.Nil(t, err)
  17. require.Equal(t, "/2010-04-01/Accounts/AC1234567890/Messages.json", r.URL.Path)
  18. require.Equal(t, "Basic QUMxMjM0NTY3ODkwOkFBRUFBMTIzNDU2Nzg5MA==", r.Header.Get("Authorization"))
  19. require.Equal(t, "Body=test%0A%0A--%0AThis+message+was+sent+by+9.9.9.9+via+ntfy.sh%2Fmytopic&From=%2B1234567890&To=%2B11122233344", string(body))
  20. called.Store(true)
  21. }))
  22. defer twilioServer.Close()
  23. c := newTestConfig(t)
  24. c.BaseURL = "https://ntfy.sh"
  25. c.TwilioMessagingBaseURL = twilioServer.URL
  26. c.TwilioAccount = "AC1234567890"
  27. c.TwilioAuthToken = "AAEAA1234567890"
  28. c.TwilioFromNumber = "+1234567890"
  29. c.VisitorSMSDailyLimit = 1
  30. s := newTestServer(t, c)
  31. response := request(t, s, "POST", "/mytopic", "test", map[string]string{
  32. "SMS": "+11122233344",
  33. })
  34. require.Equal(t, "test", toMessage(t, response.Body.String()).Message)
  35. waitFor(t, func() bool {
  36. return called.Load()
  37. })
  38. }
  39. func TestServer_Twilio_SMS_With_User(t *testing.T) {
  40. var called atomic.Bool
  41. twilioServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  42. if called.Load() {
  43. t.Fatal("Should be only called once")
  44. }
  45. body, err := io.ReadAll(r.Body)
  46. require.Nil(t, err)
  47. require.Equal(t, "/2010-04-01/Accounts/AC1234567890/Messages.json", r.URL.Path)
  48. require.Equal(t, "Basic QUMxMjM0NTY3ODkwOkFBRUFBMTIzNDU2Nzg5MA==", r.Header.Get("Authorization"))
  49. require.Equal(t, "Body=test%0A%0A--%0AThis+message+was+sent+by+phil+%289.9.9.9%29+via+ntfy.sh%2Fmytopic&From=%2B1234567890&To=%2B11122233344", string(body))
  50. called.Store(true)
  51. }))
  52. defer twilioServer.Close()
  53. c := newTestConfigWithAuthFile(t)
  54. c.BaseURL = "https://ntfy.sh"
  55. c.TwilioMessagingBaseURL = twilioServer.URL
  56. c.TwilioAccount = "AC1234567890"
  57. c.TwilioAuthToken = "AAEAA1234567890"
  58. c.TwilioFromNumber = "+1234567890"
  59. s := newTestServer(t, c)
  60. // Add tier and user
  61. require.Nil(t, s.userManager.AddTier(&user.Tier{
  62. Code: "pro",
  63. MessageLimit: 10,
  64. SMSLimit: 1,
  65. }))
  66. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  67. require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
  68. // Do request with user
  69. response := request(t, s, "POST", "/mytopic", "test", map[string]string{
  70. "Authorization": util.BasicAuth("phil", "phil"),
  71. "SMS": "+11122233344",
  72. })
  73. require.Equal(t, "test", toMessage(t, response.Body.String()).Message)
  74. waitFor(t, func() bool {
  75. return called.Load()
  76. })
  77. // Second one should fail due to rate limits
  78. response = request(t, s, "POST", "/mytopic", "test", map[string]string{
  79. "Authorization": util.BasicAuth("phil", "phil"),
  80. "SMS": "+11122233344",
  81. })
  82. require.Equal(t, 42910, toHTTPError(t, response.Body.String()).Code)
  83. }
  84. func TestServer_Twilio_Call(t *testing.T) {
  85. var called atomic.Bool
  86. twilioServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  87. body, err := io.ReadAll(r.Body)
  88. require.Nil(t, err)
  89. require.Equal(t, "/2010-04-01/Accounts/AC1234567890/Calls.json", r.URL.Path)
  90. require.Equal(t, "Basic QUMxMjM0NTY3ODkwOkFBRUFBMTIzNDU2Nzg5MA==", r.Header.Get("Authorization"))
  91. require.Equal(t, "From=%2B1234567890&To=%2B11122233344&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay%3EYou+have+a+message+from+notify+on+topic+mytopic.+Message%3A%3C%2FSay%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay%3Ethis+message+has%26%23xA%3Ba+new+line+and+%26lt%3Bbrackets%26gt%3B%21%26%23xA%3Band+%26%2334%3Bquotes+and+other+%26%2339%3Bquotes%3C%2FSay%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay%3EEnd+message.%3C%2FSay%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay%3EThis+message+was+sent+by+9.9.9.9+via+127.0.0.1%3A12345%2Fmytopic%3C%2FSay%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%3C%2FResponse%3E", string(body))
  92. called.Store(true)
  93. }))
  94. defer twilioServer.Close()
  95. c := newTestConfig(t)
  96. c.TwilioMessagingBaseURL = twilioServer.URL
  97. c.TwilioAccount = "AC1234567890"
  98. c.TwilioAuthToken = "AAEAA1234567890"
  99. c.TwilioFromNumber = "+1234567890"
  100. c.VisitorCallDailyLimit = 1
  101. s := newTestServer(t, c)
  102. body := `this message has
  103. a new line and <brackets>!
  104. and "quotes and other 'quotes`
  105. response := request(t, s, "POST", "/mytopic", body, map[string]string{
  106. "x-call": "+11122233344",
  107. })
  108. require.Equal(t, "this message has\na new line and <brackets>!\nand \"quotes and other 'quotes", toMessage(t, response.Body.String()).Message)
  109. waitFor(t, func() bool {
  110. return called.Load()
  111. })
  112. }
  113. func TestServer_Twilio_Call_With_User(t *testing.T) {
  114. var called atomic.Bool
  115. twilioServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  116. if called.Load() {
  117. t.Fatal("Should be only called once")
  118. }
  119. body, err := io.ReadAll(r.Body)
  120. require.Nil(t, err)
  121. require.Equal(t, "/2010-04-01/Accounts/AC1234567890/Calls.json", r.URL.Path)
  122. require.Equal(t, "Basic QUMxMjM0NTY3ODkwOkFBRUFBMTIzNDU2Nzg5MA==", r.Header.Get("Authorization"))
  123. require.Equal(t, "From=%2B1234567890&To=%2B11122233344&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay%3EYou+have+a+message+from+notify+on+topic+mytopic.+Message%3A%3C%2FSay%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay%3Ehi+there%3C%2FSay%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay%3EEnd+message.%3C%2FSay%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay%3EThis+message+was+sent+by+phil+%289.9.9.9%29+via+127.0.0.1%3A12345%2Fmytopic%3C%2FSay%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%3C%2FResponse%3E", string(body))
  124. called.Store(true)
  125. }))
  126. defer twilioServer.Close()
  127. c := newTestConfigWithAuthFile(t)
  128. c.TwilioMessagingBaseURL = twilioServer.URL
  129. c.TwilioAccount = "AC1234567890"
  130. c.TwilioAuthToken = "AAEAA1234567890"
  131. c.TwilioFromNumber = "+1234567890"
  132. s := newTestServer(t, c)
  133. // Add tier and user
  134. require.Nil(t, s.userManager.AddTier(&user.Tier{
  135. Code: "pro",
  136. MessageLimit: 10,
  137. CallLimit: 1,
  138. }))
  139. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  140. require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
  141. // Do the thing
  142. response := request(t, s, "POST", "/mytopic", "hi there", map[string]string{
  143. "authorization": util.BasicAuth("phil", "phil"),
  144. "x-call": "+11122233344",
  145. })
  146. require.Equal(t, "hi there", toMessage(t, response.Body.String()).Message)
  147. waitFor(t, func() bool {
  148. return called.Load()
  149. })
  150. }
  151. func TestServer_Twilio_Call_InvalidNumber(t *testing.T) {
  152. c := newTestConfig(t)
  153. c.TwilioMessagingBaseURL = "https://127.0.0.1"
  154. c.TwilioAccount = "AC1234567890"
  155. c.TwilioAuthToken = "AAEAA1234567890"
  156. c.TwilioFromNumber = "+1234567890"
  157. s := newTestServer(t, c)
  158. response := request(t, s, "POST", "/mytopic", "test", map[string]string{
  159. "x-call": "+invalid",
  160. })
  161. require.Equal(t, 40031, toHTTPError(t, response.Body.String()).Code)
  162. }
  163. func TestServer_Twilio_SMS_InvalidNumber(t *testing.T) {
  164. c := newTestConfig(t)
  165. c.TwilioMessagingBaseURL = "https://127.0.0.1"
  166. c.TwilioAccount = "AC1234567890"
  167. c.TwilioAuthToken = "AAEAA1234567890"
  168. c.TwilioFromNumber = "+1234567890"
  169. s := newTestServer(t, c)
  170. response := request(t, s, "POST", "/mytopic", "test", map[string]string{
  171. "x-sms": "+invalid",
  172. })
  173. require.Equal(t, 40031, toHTTPError(t, response.Body.String()).Code)
  174. }
  175. func TestServer_Twilio_SMS_Unconfigured(t *testing.T) {
  176. s := newTestServer(t, newTestConfig(t))
  177. response := request(t, s, "POST", "/mytopic", "test", map[string]string{
  178. "x-sms": "+1234",
  179. })
  180. require.Equal(t, 40030, toHTTPError(t, response.Body.String()).Code)
  181. }
  182. func TestServer_Twilio_Call_Unconfigured(t *testing.T) {
  183. s := newTestServer(t, newTestConfig(t))
  184. response := request(t, s, "POST", "/mytopic", "test", map[string]string{
  185. "x-call": "+1234",
  186. })
  187. require.Equal(t, 40030, toHTTPError(t, response.Body.String()).Code)
  188. }