server_test.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. package server
  2. import (
  3. "bufio"
  4. "context"
  5. "encoding/json"
  6. "github.com/stretchr/testify/assert"
  7. "heckel.io/ntfy/config"
  8. "net/http"
  9. "net/http/httptest"
  10. "path/filepath"
  11. "strings"
  12. "testing"
  13. "time"
  14. )
  15. func TestServer_PublishAndPoll(t *testing.T) {
  16. s := newTestServer(t, newTestConfig(t))
  17. response1 := request(t, s, "PUT", "/mytopic", "my first message", nil)
  18. msg1 := toMessage(t, response1.Body.String())
  19. assert.NotEmpty(t, msg1.ID)
  20. assert.Equal(t, "my first message", msg1.Message)
  21. response2 := request(t, s, "PUT", "/mytopic", "my second\n\nmessage", nil)
  22. msg2 := toMessage(t, response2.Body.String())
  23. assert.NotEqual(t, msg1.ID, msg2.ID)
  24. assert.NotEmpty(t, msg2.ID)
  25. assert.Equal(t, "my second\n\nmessage", msg2.Message)
  26. response := request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
  27. messages := toMessages(t, response.Body.String())
  28. assert.Equal(t, 2, len(messages))
  29. assert.Equal(t, "my first message", messages[0].Message)
  30. assert.Equal(t, "my second\n\nmessage", messages[1].Message)
  31. response = request(t, s, "GET", "/mytopic/sse?poll=1", "", nil)
  32. lines := strings.Split(strings.TrimSpace(response.Body.String()), "\n")
  33. assert.Equal(t, 3, len(lines))
  34. assert.Equal(t, "my first message", toMessage(t, strings.TrimPrefix(lines[0], "data: ")).Message)
  35. assert.Equal(t, "", lines[1])
  36. assert.Equal(t, "my second\n\nmessage", toMessage(t, strings.TrimPrefix(lines[2], "data: ")).Message)
  37. response = request(t, s, "GET", "/mytopic/raw?poll=1", "", nil)
  38. lines = strings.Split(strings.TrimSpace(response.Body.String()), "\n")
  39. assert.Equal(t, 2, len(lines))
  40. assert.Equal(t, "my first message", lines[0])
  41. assert.Equal(t, "my second message", lines[1]) // \n -> " "
  42. }
  43. func TestServer_SubscribeOpenAndKeepalive(t *testing.T) {
  44. c := newTestConfig(t)
  45. c.KeepaliveInterval = time.Second
  46. s := newTestServer(t, c)
  47. rr := httptest.NewRecorder()
  48. ctx, cancel := context.WithCancel(context.Background())
  49. req, err := http.NewRequestWithContext(ctx, "GET", "/mytopic/json", nil)
  50. if err != nil {
  51. t.Fatal(err)
  52. }
  53. doneChan := make(chan bool)
  54. go func() {
  55. s.handle(rr, req)
  56. doneChan <- true
  57. }()
  58. time.Sleep(1300 * time.Millisecond)
  59. cancel()
  60. <-doneChan
  61. messages := toMessages(t, rr.Body.String())
  62. assert.Equal(t, 2, len(messages))
  63. assert.Equal(t, openEvent, messages[0].Event)
  64. assert.Equal(t, "mytopic", messages[0].Topic)
  65. assert.Equal(t, "", messages[0].Message)
  66. assert.Equal(t, "", messages[0].Title)
  67. assert.Equal(t, 0, messages[0].Priority)
  68. assert.Nil(t, messages[0].Tags)
  69. assert.Equal(t, keepaliveEvent, messages[1].Event)
  70. assert.Equal(t, "mytopic", messages[1].Topic)
  71. assert.Equal(t, "", messages[1].Message)
  72. assert.Equal(t, "", messages[1].Title)
  73. assert.Equal(t, 0, messages[1].Priority)
  74. assert.Nil(t, messages[1].Tags)
  75. }
  76. func TestServer_PublishAndSubscribe(t *testing.T) {
  77. s := newTestServer(t, newTestConfig(t))
  78. subscribeRR := httptest.NewRecorder()
  79. subscribeCancel := subscribe(t, s, "/mytopic/json", subscribeRR)
  80. publishFirstRR := request(t, s, "PUT", "/mytopic", "my first message", nil)
  81. assert.Equal(t, 200, publishFirstRR.Code)
  82. publishSecondRR := request(t, s, "PUT", "/mytopic", "my other message", map[string]string{
  83. "Title": " This is a title ",
  84. "X-Tags": "tag1,tag 2, tag3",
  85. "p": "1",
  86. })
  87. assert.Equal(t, 200, publishSecondRR.Code)
  88. subscribeCancel()
  89. messages := toMessages(t, subscribeRR.Body.String())
  90. assert.Equal(t, 3, len(messages))
  91. assert.Equal(t, openEvent, messages[0].Event)
  92. assert.Equal(t, messageEvent, messages[1].Event)
  93. assert.Equal(t, "mytopic", messages[1].Topic)
  94. assert.Equal(t, "my first message", messages[1].Message)
  95. assert.Equal(t, "", messages[1].Title)
  96. assert.Equal(t, 0, messages[1].Priority)
  97. assert.Nil(t, messages[1].Tags)
  98. assert.Equal(t, messageEvent, messages[2].Event)
  99. assert.Equal(t, "mytopic", messages[2].Topic)
  100. assert.Equal(t, "my other message", messages[2].Message)
  101. assert.Equal(t, "This is a title", messages[2].Title)
  102. assert.Equal(t, 1, messages[2].Priority)
  103. assert.Equal(t, []string{"tag1", "tag 2", "tag3"}, messages[2].Tags)
  104. }
  105. func TestServer_StaticSites(t *testing.T) {
  106. s := newTestServer(t, newTestConfig(t))
  107. rr := request(t, s, "GET", "/", "", nil)
  108. assert.Equal(t, 200, rr.Code)
  109. assert.Contains(t, rr.Body.String(), "</html>")
  110. rr = request(t, s, "HEAD", "/", "", nil)
  111. assert.Equal(t, 200, rr.Code)
  112. rr = request(t, s, "GET", "/does-not-exist.txt", "", nil)
  113. assert.Equal(t, 404, rr.Code)
  114. rr = request(t, s, "GET", "/mytopic", "", nil)
  115. assert.Equal(t, 200, rr.Code)
  116. assert.Contains(t, rr.Body.String(), `<meta name="robots" content="noindex, nofollow" />`)
  117. rr = request(t, s, "GET", "/static/css/app.css", "", nil)
  118. assert.Equal(t, 200, rr.Code)
  119. assert.Contains(t, rr.Body.String(), `html, body {`)
  120. rr = request(t, s, "GET", "/docs", "", nil)
  121. assert.Equal(t, 301, rr.Code)
  122. rr = request(t, s, "GET", "/docs/", "", nil)
  123. assert.Equal(t, 200, rr.Code)
  124. assert.Contains(t, rr.Body.String(), `Made with ❤️ by Philipp C. Heckel`)
  125. assert.Contains(t, rr.Body.String(), `<script src=static/js/extra.js></script>`)
  126. }
  127. func TestServer_PublishNoCache(t *testing.T) {
  128. s := newTestServer(t, newTestConfig(t))
  129. response := request(t, s, "PUT", "/mytopic", "this message is not cached", map[string]string{
  130. "Cache": "no",
  131. })
  132. msg := toMessage(t, response.Body.String())
  133. assert.NotEmpty(t, msg.ID)
  134. assert.Equal(t, "this message is not cached", msg.Message)
  135. response = request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
  136. messages := toMessages(t, response.Body.String())
  137. assert.Empty(t, messages)
  138. }
  139. func TestServer_PublishAndMultiPoll(t *testing.T) {
  140. s := newTestServer(t, newTestConfig(t))
  141. response := request(t, s, "PUT", "/mytopic1", "message 1", nil)
  142. msg := toMessage(t, response.Body.String())
  143. assert.NotEmpty(t, msg.ID)
  144. assert.Equal(t, "mytopic1", msg.Topic)
  145. assert.Equal(t, "message 1", msg.Message)
  146. response = request(t, s, "PUT", "/mytopic2", "message 2", nil)
  147. msg = toMessage(t, response.Body.String())
  148. assert.NotEmpty(t, msg.ID)
  149. assert.Equal(t, "mytopic2", msg.Topic)
  150. assert.Equal(t, "message 2", msg.Message)
  151. response = request(t, s, "GET", "/mytopic1/json?poll=1", "", nil)
  152. messages := toMessages(t, response.Body.String())
  153. assert.Equal(t, 1, len(messages))
  154. assert.Equal(t, "mytopic1", messages[0].Topic)
  155. assert.Equal(t, "message 1", messages[0].Message)
  156. response = request(t, s, "GET", "/mytopic1,mytopic2/json?poll=1", "", nil)
  157. messages = toMessages(t, response.Body.String())
  158. assert.Equal(t, 2, len(messages))
  159. assert.Equal(t, "mytopic1", messages[0].Topic)
  160. assert.Equal(t, "message 1", messages[0].Message)
  161. assert.Equal(t, "mytopic2", messages[1].Topic)
  162. assert.Equal(t, "message 2", messages[1].Message)
  163. }
  164. func TestServer_PublishWithNopCache(t *testing.T) {
  165. c := newTestConfig(t)
  166. c.CacheDuration = 0
  167. s := newTestServer(t, c)
  168. subscribeRR := httptest.NewRecorder()
  169. subscribeCancel := subscribe(t, s, "/mytopic/json", subscribeRR)
  170. publishRR := request(t, s, "PUT", "/mytopic", "my first message", nil)
  171. assert.Equal(t, 200, publishRR.Code)
  172. subscribeCancel()
  173. messages := toMessages(t, subscribeRR.Body.String())
  174. assert.Equal(t, 2, len(messages))
  175. assert.Equal(t, openEvent, messages[0].Event)
  176. assert.Equal(t, messageEvent, messages[1].Event)
  177. assert.Equal(t, "my first message", messages[1].Message)
  178. response := request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
  179. messages = toMessages(t, response.Body.String())
  180. assert.Empty(t, messages)
  181. }
  182. func newTestConfig(t *testing.T) *config.Config {
  183. conf := config.New(":80")
  184. conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
  185. return conf
  186. }
  187. func newTestServer(t *testing.T, config *config.Config) *Server {
  188. server, err := New(config)
  189. if err != nil {
  190. t.Fatal(err)
  191. }
  192. return server
  193. }
  194. func request(t *testing.T, s *Server, method, url, body string, headers map[string]string) *httptest.ResponseRecorder {
  195. rr := httptest.NewRecorder()
  196. req, err := http.NewRequest(method, url, strings.NewReader(body))
  197. if err != nil {
  198. t.Fatal(err)
  199. }
  200. for k, v := range headers {
  201. req.Header.Set(k, v)
  202. }
  203. s.handle(rr, req)
  204. return rr
  205. }
  206. func subscribe(t *testing.T, s *Server, url string, rr *httptest.ResponseRecorder) context.CancelFunc {
  207. ctx, cancel := context.WithCancel(context.Background())
  208. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  209. if err != nil {
  210. t.Fatal(err)
  211. }
  212. done := make(chan bool)
  213. go func() {
  214. s.handle(rr, req)
  215. done <- true
  216. }()
  217. cancelAndWaitForDone := func() {
  218. time.Sleep(100 * time.Millisecond)
  219. cancel()
  220. <-done
  221. }
  222. time.Sleep(100 * time.Millisecond)
  223. return cancelAndWaitForDone
  224. }
  225. func toMessages(t *testing.T, s string) []*message {
  226. messages := make([]*message, 0)
  227. scanner := bufio.NewScanner(strings.NewReader(s))
  228. for scanner.Scan() {
  229. messages = append(messages, toMessage(t, scanner.Text()))
  230. }
  231. return messages
  232. }
  233. func toMessage(t *testing.T, s string) *message {
  234. var m message
  235. assert.Nil(t, json.NewDecoder(strings.NewReader(s)).Decode(&m))
  236. return &m
  237. }