server_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. package server
  2. import (
  3. "bufio"
  4. "context"
  5. "encoding/json"
  6. "github.com/stretchr/testify/require"
  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. require.NotEmpty(t, msg1.ID)
  20. require.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. require.NotEqual(t, msg1.ID, msg2.ID)
  24. require.NotEmpty(t, msg2.ID)
  25. require.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. require.Equal(t, 2, len(messages))
  29. require.Equal(t, "my first message", messages[0].Message)
  30. require.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. require.Equal(t, 3, len(lines))
  34. require.Equal(t, "my first message", toMessage(t, strings.TrimPrefix(lines[0], "data: ")).Message)
  35. require.Equal(t, "", lines[1])
  36. require.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. require.Equal(t, 2, len(lines))
  40. require.Equal(t, "my first message", lines[0])
  41. require.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. require.Equal(t, 2, len(messages))
  63. require.Equal(t, openEvent, messages[0].Event)
  64. require.Equal(t, "mytopic", messages[0].Topic)
  65. require.Equal(t, "", messages[0].Message)
  66. require.Equal(t, "", messages[0].Title)
  67. require.Equal(t, 0, messages[0].Priority)
  68. require.Nil(t, messages[0].Tags)
  69. require.Equal(t, keepaliveEvent, messages[1].Event)
  70. require.Equal(t, "mytopic", messages[1].Topic)
  71. require.Equal(t, "", messages[1].Message)
  72. require.Equal(t, "", messages[1].Title)
  73. require.Equal(t, 0, messages[1].Priority)
  74. require.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. require.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. require.Equal(t, 200, publishSecondRR.Code)
  88. subscribeCancel()
  89. messages := toMessages(t, subscribeRR.Body.String())
  90. require.Equal(t, 3, len(messages))
  91. require.Equal(t, openEvent, messages[0].Event)
  92. require.Equal(t, messageEvent, messages[1].Event)
  93. require.Equal(t, "mytopic", messages[1].Topic)
  94. require.Equal(t, "my first message", messages[1].Message)
  95. require.Equal(t, "", messages[1].Title)
  96. require.Equal(t, 0, messages[1].Priority)
  97. require.Nil(t, messages[1].Tags)
  98. require.Equal(t, messageEvent, messages[2].Event)
  99. require.Equal(t, "mytopic", messages[2].Topic)
  100. require.Equal(t, "my other message", messages[2].Message)
  101. require.Equal(t, "This is a title", messages[2].Title)
  102. require.Equal(t, 1, messages[2].Priority)
  103. require.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. require.Equal(t, 200, rr.Code)
  109. require.Contains(t, rr.Body.String(), "</html>")
  110. rr = request(t, s, "HEAD", "/", "", nil)
  111. require.Equal(t, 200, rr.Code)
  112. rr = request(t, s, "GET", "/does-not-exist.txt", "", nil)
  113. require.Equal(t, 404, rr.Code)
  114. rr = request(t, s, "GET", "/mytopic", "", nil)
  115. require.Equal(t, 200, rr.Code)
  116. require.Contains(t, rr.Body.String(), `<meta name="robots" content="noindex, nofollow" />`)
  117. rr = request(t, s, "GET", "/static/css/app.css", "", nil)
  118. require.Equal(t, 200, rr.Code)
  119. require.Contains(t, rr.Body.String(), `html, body {`)
  120. rr = request(t, s, "GET", "/docs", "", nil)
  121. require.Equal(t, 301, rr.Code)
  122. rr = request(t, s, "GET", "/docs/", "", nil)
  123. require.Equal(t, 200, rr.Code)
  124. require.Contains(t, rr.Body.String(), `Made with ❤️ by Philipp C. Heckel`)
  125. require.Contains(t, rr.Body.String(), `<script src=static/js/extra.js></script>`)
  126. }
  127. func TestServer_PublishLargeMessage(t *testing.T) {
  128. s := newTestServer(t, newTestConfig(t))
  129. body := strings.Repeat("this is a large message", 1000)
  130. truncated := body[0:512]
  131. response := request(t, s, "PUT", "/mytopic", body, nil)
  132. msg := toMessage(t, response.Body.String())
  133. require.NotEmpty(t, msg.ID)
  134. require.Equal(t, truncated, msg.Message)
  135. response = request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
  136. messages := toMessages(t, response.Body.String())
  137. require.Equal(t, 1, len(messages))
  138. require.Equal(t, truncated, messages[0].Message)
  139. }
  140. func TestServer_PublishNoCache(t *testing.T) {
  141. s := newTestServer(t, newTestConfig(t))
  142. response := request(t, s, "PUT", "/mytopic", "this message is not cached", map[string]string{
  143. "Cache": "no",
  144. })
  145. msg := toMessage(t, response.Body.String())
  146. require.NotEmpty(t, msg.ID)
  147. require.Equal(t, "this message is not cached", msg.Message)
  148. response = request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
  149. messages := toMessages(t, response.Body.String())
  150. require.Empty(t, messages)
  151. }
  152. func TestServer_PublishAt(t *testing.T) {
  153. c := newTestConfig(t)
  154. c.MinDelay = time.Second
  155. c.AtSenderInterval = 100 * time.Millisecond
  156. s := newTestServer(t, c)
  157. response := request(t, s, "PUT", "/mytopic", "a message", map[string]string{
  158. "In": "1s",
  159. })
  160. require.Equal(t, 200, response.Code)
  161. response = request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
  162. messages := toMessages(t, response.Body.String())
  163. require.Equal(t, 0, len(messages))
  164. time.Sleep(time.Second)
  165. require.Nil(t, s.sendDelayedMessages())
  166. response = request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
  167. messages = toMessages(t, response.Body.String())
  168. require.Equal(t, 1, len(messages))
  169. require.Equal(t, "a message", messages[0].Message)
  170. }
  171. func TestServer_PublishAtWithCacheError(t *testing.T) {
  172. s := newTestServer(t, newTestConfig(t))
  173. response := request(t, s, "PUT", "/mytopic", "a message", map[string]string{
  174. "Cache": "no",
  175. "In": "30 min",
  176. })
  177. require.Equal(t, 400, response.Code)
  178. }
  179. func TestServer_PublishAtTooShortDelay(t *testing.T) {
  180. s := newTestServer(t, newTestConfig(t))
  181. response := request(t, s, "PUT", "/mytopic", "a message", map[string]string{
  182. "In": "1s",
  183. })
  184. require.Equal(t, 400, response.Code)
  185. }
  186. func TestServer_PublishAtTooLongDelay(t *testing.T) {
  187. s := newTestServer(t, newTestConfig(t))
  188. response := request(t, s, "PUT", "/mytopic", "a message", map[string]string{
  189. "In": "99999999h",
  190. })
  191. require.Equal(t, 400, response.Code)
  192. }
  193. func TestServer_PublishAtAndPrune(t *testing.T) {
  194. s := newTestServer(t, newTestConfig(t))
  195. response := request(t, s, "PUT", "/mytopic", "a message", map[string]string{
  196. "In": "1h",
  197. })
  198. require.Equal(t, 200, response.Code)
  199. s.updateStatsAndPrune() // Fire pruning
  200. response = request(t, s, "GET", "/mytopic/json?poll=1&scheduled=1", "", nil)
  201. messages := toMessages(t, response.Body.String())
  202. require.Equal(t, 1, len(messages)) // Not affected by pruning
  203. require.Equal(t, "a message", messages[0].Message)
  204. }
  205. func TestServer_PublishAndMultiPoll(t *testing.T) {
  206. s := newTestServer(t, newTestConfig(t))
  207. response := request(t, s, "PUT", "/mytopic1", "message 1", nil)
  208. msg := toMessage(t, response.Body.String())
  209. require.NotEmpty(t, msg.ID)
  210. require.Equal(t, "mytopic1", msg.Topic)
  211. require.Equal(t, "message 1", msg.Message)
  212. response = request(t, s, "PUT", "/mytopic2", "message 2", nil)
  213. msg = toMessage(t, response.Body.String())
  214. require.NotEmpty(t, msg.ID)
  215. require.Equal(t, "mytopic2", msg.Topic)
  216. require.Equal(t, "message 2", msg.Message)
  217. response = request(t, s, "GET", "/mytopic1/json?poll=1", "", nil)
  218. messages := toMessages(t, response.Body.String())
  219. require.Equal(t, 1, len(messages))
  220. require.Equal(t, "mytopic1", messages[0].Topic)
  221. require.Equal(t, "message 1", messages[0].Message)
  222. response = request(t, s, "GET", "/mytopic1,mytopic2/json?poll=1", "", nil)
  223. messages = toMessages(t, response.Body.String())
  224. require.Equal(t, 2, len(messages))
  225. require.Equal(t, "mytopic1", messages[0].Topic)
  226. require.Equal(t, "message 1", messages[0].Message)
  227. require.Equal(t, "mytopic2", messages[1].Topic)
  228. require.Equal(t, "message 2", messages[1].Message)
  229. }
  230. func TestServer_PublishWithNopCache(t *testing.T) {
  231. c := newTestConfig(t)
  232. c.CacheDuration = 0
  233. s := newTestServer(t, c)
  234. subscribeRR := httptest.NewRecorder()
  235. subscribeCancel := subscribe(t, s, "/mytopic/json", subscribeRR)
  236. publishRR := request(t, s, "PUT", "/mytopic", "my first message", nil)
  237. require.Equal(t, 200, publishRR.Code)
  238. subscribeCancel()
  239. messages := toMessages(t, subscribeRR.Body.String())
  240. require.Equal(t, 2, len(messages))
  241. require.Equal(t, openEvent, messages[0].Event)
  242. require.Equal(t, messageEvent, messages[1].Event)
  243. require.Equal(t, "my first message", messages[1].Message)
  244. response := request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
  245. messages = toMessages(t, response.Body.String())
  246. require.Empty(t, messages)
  247. }
  248. func newTestConfig(t *testing.T) *config.Config {
  249. conf := config.New(":80")
  250. conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
  251. return conf
  252. }
  253. func newTestServer(t *testing.T, config *config.Config) *Server {
  254. server, err := New(config)
  255. if err != nil {
  256. t.Fatal(err)
  257. }
  258. return server
  259. }
  260. func request(t *testing.T, s *Server, method, url, body string, headers map[string]string) *httptest.ResponseRecorder {
  261. rr := httptest.NewRecorder()
  262. req, err := http.NewRequest(method, url, strings.NewReader(body))
  263. if err != nil {
  264. t.Fatal(err)
  265. }
  266. for k, v := range headers {
  267. req.Header.Set(k, v)
  268. }
  269. s.handle(rr, req)
  270. return rr
  271. }
  272. func subscribe(t *testing.T, s *Server, url string, rr *httptest.ResponseRecorder) context.CancelFunc {
  273. ctx, cancel := context.WithCancel(context.Background())
  274. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  275. if err != nil {
  276. t.Fatal(err)
  277. }
  278. done := make(chan bool)
  279. go func() {
  280. s.handle(rr, req)
  281. done <- true
  282. }()
  283. cancelAndWaitForDone := func() {
  284. time.Sleep(100 * time.Millisecond)
  285. cancel()
  286. <-done
  287. }
  288. time.Sleep(100 * time.Millisecond)
  289. return cancelAndWaitForDone
  290. }
  291. func toMessages(t *testing.T, s string) []*message {
  292. messages := make([]*message, 0)
  293. scanner := bufio.NewScanner(strings.NewReader(s))
  294. for scanner.Scan() {
  295. messages = append(messages, toMessage(t, scanner.Text()))
  296. }
  297. return messages
  298. }
  299. func toMessage(t *testing.T, s string) *message {
  300. var m message
  301. require.Nil(t, json.NewDecoder(strings.NewReader(s)).Decode(&m))
  302. return &m
  303. }