message_cache_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. package server
  2. import (
  3. "database/sql"
  4. "net/netip"
  5. "os"
  6. "path/filepath"
  7. "testing"
  8. "time"
  9. "github.com/stretchr/testify/require"
  10. )
  11. func TestCache_Messages(t *testing.T) {
  12. runMessageCacheTest(t, func(t *testing.T, c MessageCache) {
  13. m1 := newDefaultMessage("mytopic", "my message")
  14. m1.Time = 1
  15. m2 := newDefaultMessage("mytopic", "my other message")
  16. m2.Time = 2
  17. require.Nil(t, c.AddMessage(m1))
  18. require.Nil(t, c.AddMessage(newDefaultMessage("example", "my example message")))
  19. require.Nil(t, c.AddMessage(m2))
  20. // Adding invalid
  21. require.Equal(t, errUnexpectedMessageType, c.AddMessage(newKeepaliveMessage("mytopic"))) // These should not be added!
  22. require.Equal(t, errUnexpectedMessageType, c.AddMessage(newOpenMessage("example"))) // These should not be added!
  23. // mytopic: count
  24. counts, err := c.MessageCounts()
  25. require.Nil(t, err)
  26. require.Equal(t, 2, counts["mytopic"])
  27. // mytopic: since all
  28. messages, _ := c.Messages("mytopic", sinceAllMessages, false)
  29. require.Equal(t, 2, len(messages))
  30. require.Equal(t, "my message", messages[0].Message)
  31. require.Equal(t, "mytopic", messages[0].Topic)
  32. require.Equal(t, messageEvent, messages[0].Event)
  33. require.Equal(t, "", messages[0].Title)
  34. require.Equal(t, 0, messages[0].Priority)
  35. require.Nil(t, messages[0].Tags)
  36. require.Equal(t, "my other message", messages[1].Message)
  37. // mytopic: since none
  38. messages, _ = c.Messages("mytopic", sinceNoMessages, false)
  39. require.Empty(t, messages)
  40. // mytopic: since m1 (by ID)
  41. messages, _ = c.Messages("mytopic", newSinceID(m1.ID), false)
  42. require.Equal(t, 1, len(messages))
  43. require.Equal(t, m2.ID, messages[0].ID)
  44. require.Equal(t, "my other message", messages[0].Message)
  45. require.Equal(t, "mytopic", messages[0].Topic)
  46. // mytopic: since 2
  47. messages, _ = c.Messages("mytopic", newSinceTime(2), false)
  48. require.Equal(t, 1, len(messages))
  49. require.Equal(t, "my other message", messages[0].Message)
  50. // example: count
  51. counts, err = c.MessageCounts()
  52. require.Nil(t, err)
  53. require.Equal(t, 1, counts["example"])
  54. // example: since all
  55. messages, _ = c.Messages("example", sinceAllMessages, false)
  56. require.Equal(t, "my example message", messages[0].Message)
  57. // non-existing: count
  58. counts, err = c.MessageCounts()
  59. require.Nil(t, err)
  60. require.Equal(t, 0, counts["doesnotexist"])
  61. // non-existing: since all
  62. messages, _ = c.Messages("doesnotexist", sinceAllMessages, false)
  63. require.Empty(t, messages)
  64. })
  65. }
  66. func TestCache_MessagesScheduled(t *testing.T) {
  67. runMessageCacheTest(t, func(t *testing.T, c MessageCache) {
  68. m1 := newDefaultMessage("mytopic", "message 1")
  69. m2 := newDefaultMessage("mytopic", "message 2")
  70. m2.Time = time.Now().Add(time.Hour).Unix()
  71. m3 := newDefaultMessage("mytopic", "message 3")
  72. m3.Time = time.Now().Add(time.Minute).Unix() // earlier than m2!
  73. m4 := newDefaultMessage("mytopic2", "message 4")
  74. m4.Time = time.Now().Add(time.Minute).Unix()
  75. require.Nil(t, c.AddMessage(m1))
  76. require.Nil(t, c.AddMessage(m2))
  77. require.Nil(t, c.AddMessage(m3))
  78. messages, _ := c.Messages("mytopic", sinceAllMessages, false) // exclude scheduled
  79. require.Equal(t, 1, len(messages))
  80. require.Equal(t, "message 1", messages[0].Message)
  81. messages, _ = c.Messages("mytopic", sinceAllMessages, true) // include scheduled
  82. require.Equal(t, 3, len(messages))
  83. require.Equal(t, "message 1", messages[0].Message)
  84. require.Equal(t, "message 3", messages[1].Message) // Order!
  85. require.Equal(t, "message 2", messages[2].Message)
  86. messages, _ = c.MessagesDue()
  87. require.Empty(t, messages)
  88. })
  89. }
  90. func TestCache_Topics(t *testing.T) {
  91. runMessageCacheTest(t, func(t *testing.T, c MessageCache) {
  92. require.Nil(t, c.AddMessage(newDefaultMessage("topic1", "my example message")))
  93. require.Nil(t, c.AddMessage(newDefaultMessage("topic2", "message 1")))
  94. require.Nil(t, c.AddMessage(newDefaultMessage("topic2", "message 2")))
  95. require.Nil(t, c.AddMessage(newDefaultMessage("topic2", "message 3")))
  96. topics, err := c.Topics()
  97. if err != nil {
  98. t.Fatal(err)
  99. }
  100. require.Equal(t, 2, len(topics))
  101. require.Equal(t, "topic1", topics["topic1"].ID)
  102. require.Equal(t, "topic2", topics["topic2"].ID)
  103. })
  104. }
  105. func TestCache_MessagesTagsPrioAndTitle(t *testing.T) {
  106. runMessageCacheTest(t, func(t *testing.T, c MessageCache) {
  107. m := newDefaultMessage("mytopic", "some message")
  108. m.Tags = []string{"tag1", "tag2"}
  109. m.Priority = 5
  110. m.Title = "some title"
  111. require.Nil(t, c.AddMessage(m))
  112. messages, _ := c.Messages("mytopic", sinceAllMessages, false)
  113. require.Equal(t, []string{"tag1", "tag2"}, messages[0].Tags)
  114. require.Equal(t, 5, messages[0].Priority)
  115. require.Equal(t, "some title", messages[0].Title)
  116. })
  117. }
  118. func TestCache_MessagesSinceID(t *testing.T) {
  119. runMessageCacheTest(t, func(t *testing.T, c MessageCache) {
  120. m1 := newDefaultMessage("mytopic", "message 1")
  121. m1.Time = 100
  122. m2 := newDefaultMessage("mytopic", "message 2")
  123. m2.Time = 200
  124. m3 := newDefaultMessage("mytopic", "message 3")
  125. m3.Time = time.Now().Add(time.Hour).Unix() // Scheduled, in the future, later than m7 and m5
  126. m4 := newDefaultMessage("mytopic", "message 4")
  127. m4.Time = 400
  128. m5 := newDefaultMessage("mytopic", "message 5")
  129. m5.Time = time.Now().Add(time.Minute).Unix() // Scheduled, in the future, later than m7
  130. m6 := newDefaultMessage("mytopic", "message 6")
  131. m6.Time = 600
  132. m7 := newDefaultMessage("mytopic", "message 7")
  133. m7.Time = 700
  134. require.Nil(t, c.AddMessage(m1))
  135. require.Nil(t, c.AddMessage(m2))
  136. require.Nil(t, c.AddMessage(m3))
  137. require.Nil(t, c.AddMessage(m4))
  138. require.Nil(t, c.AddMessage(m5))
  139. require.Nil(t, c.AddMessage(m6))
  140. require.Nil(t, c.AddMessage(m7))
  141. // Case 1: Since ID exists, exclude scheduled
  142. messages, _ := c.Messages("mytopic", newSinceID(m2.ID), false)
  143. require.Equal(t, 3, len(messages))
  144. require.Equal(t, "message 4", messages[0].Message)
  145. require.Equal(t, "message 6", messages[1].Message) // Not scheduled m3/m5!
  146. require.Equal(t, "message 7", messages[2].Message)
  147. // Case 2: Since ID exists, include scheduled
  148. messages, _ = c.Messages("mytopic", newSinceID(m2.ID), true)
  149. require.Equal(t, 5, len(messages))
  150. require.Equal(t, "message 4", messages[0].Message)
  151. require.Equal(t, "message 6", messages[1].Message)
  152. require.Equal(t, "message 7", messages[2].Message)
  153. require.Equal(t, "message 5", messages[3].Message) // Order!
  154. require.Equal(t, "message 3", messages[4].Message) // Order!
  155. // Case 3: Since ID does not exist (-> Return all messages), include scheduled
  156. messages, _ = c.Messages("mytopic", newSinceID("doesntexist"), true)
  157. require.Equal(t, 7, len(messages))
  158. require.Equal(t, "message 1", messages[0].Message)
  159. require.Equal(t, "message 2", messages[1].Message)
  160. require.Equal(t, "message 4", messages[2].Message)
  161. require.Equal(t, "message 6", messages[3].Message)
  162. require.Equal(t, "message 7", messages[4].Message)
  163. require.Equal(t, "message 5", messages[5].Message) // Order!
  164. require.Equal(t, "message 3", messages[6].Message) // Order!
  165. // Case 4: Since ID exists and is last message (-> Return no messages), exclude scheduled
  166. messages, _ = c.Messages("mytopic", newSinceID(m7.ID), false)
  167. require.Equal(t, 0, len(messages))
  168. // Case 5: Since ID exists and is last message (-> Return no messages), include scheduled
  169. messages, _ = c.Messages("mytopic", newSinceID(m7.ID), true)
  170. require.Equal(t, 2, len(messages))
  171. require.Equal(t, "message 5", messages[0].Message)
  172. require.Equal(t, "message 3", messages[1].Message)
  173. })
  174. }
  175. func TestCache_Prune(t *testing.T) {
  176. runMessageCacheTest(t, func(t *testing.T, c MessageCache) {
  177. now := time.Now().Unix()
  178. m1 := newDefaultMessage("mytopic", "my message")
  179. m1.Time = now - 10
  180. m1.Expires = now - 5
  181. m2 := newDefaultMessage("mytopic", "my other message")
  182. m2.Time = now - 5
  183. m2.Expires = now + 5 // In the future
  184. m3 := newDefaultMessage("another_topic", "and another one")
  185. m3.Time = now - 12
  186. m3.Expires = now - 2
  187. require.Nil(t, c.AddMessage(m1))
  188. require.Nil(t, c.AddMessage(m2))
  189. require.Nil(t, c.AddMessage(m3))
  190. counts, err := c.MessageCounts()
  191. require.Nil(t, err)
  192. require.Equal(t, 2, counts["mytopic"])
  193. require.Equal(t, 1, counts["another_topic"])
  194. expiredMessageIDs, err := c.MessagesExpired()
  195. require.Nil(t, err)
  196. require.Nil(t, c.DeleteMessages(expiredMessageIDs...))
  197. counts, err = c.MessageCounts()
  198. require.Nil(t, err)
  199. require.Equal(t, 1, counts["mytopic"])
  200. require.Equal(t, 0, counts["another_topic"])
  201. messages, err := c.Messages("mytopic", sinceAllMessages, false)
  202. require.Nil(t, err)
  203. require.Equal(t, 1, len(messages))
  204. require.Equal(t, "my other message", messages[0].Message)
  205. })
  206. }
  207. func TestCache_Attachments(t *testing.T) {
  208. runMessageCacheTest(t, func(t *testing.T, c MessageCache) {
  209. expires1 := time.Now().Add(-4 * time.Hour).Unix() // Expired
  210. m := newDefaultMessage("mytopic", "flower for you")
  211. m.ID = "m1"
  212. m.Sender = netip.MustParseAddr("1.2.3.4")
  213. m.Attachment = &attachment{
  214. Name: "flower.jpg",
  215. Type: "image/jpeg",
  216. Size: 5000,
  217. Expires: expires1,
  218. URL: "https://ntfy.sh/file/AbDeFgJhal.jpg",
  219. }
  220. require.Nil(t, c.AddMessage(m))
  221. expires2 := time.Now().Add(2 * time.Hour).Unix() // Future
  222. m = newDefaultMessage("mytopic", "sending you a car")
  223. m.ID = "m2"
  224. m.Sender = netip.MustParseAddr("1.2.3.4")
  225. m.Attachment = &attachment{
  226. Name: "car.jpg",
  227. Type: "image/jpeg",
  228. Size: 10000,
  229. Expires: expires2,
  230. URL: "https://ntfy.sh/file/aCaRURL.jpg",
  231. }
  232. require.Nil(t, c.AddMessage(m))
  233. expires3 := time.Now().Add(1 * time.Hour).Unix() // Future
  234. m = newDefaultMessage("another-topic", "sending you another car")
  235. m.ID = "m3"
  236. m.User = "u_BAsbaAa"
  237. m.Sender = netip.MustParseAddr("5.6.7.8")
  238. m.Attachment = &attachment{
  239. Name: "another-car.jpg",
  240. Type: "image/jpeg",
  241. Size: 20000,
  242. Expires: expires3,
  243. URL: "https://ntfy.sh/file/zakaDHFW.jpg",
  244. }
  245. require.Nil(t, c.AddMessage(m))
  246. messages, err := c.Messages("mytopic", sinceAllMessages, false)
  247. require.Nil(t, err)
  248. require.Equal(t, 2, len(messages))
  249. require.Equal(t, "flower for you", messages[0].Message)
  250. require.Equal(t, "flower.jpg", messages[0].Attachment.Name)
  251. require.Equal(t, "image/jpeg", messages[0].Attachment.Type)
  252. require.Equal(t, int64(5000), messages[0].Attachment.Size)
  253. require.Equal(t, expires1, messages[0].Attachment.Expires)
  254. require.Equal(t, "https://ntfy.sh/file/AbDeFgJhal.jpg", messages[0].Attachment.URL)
  255. require.Equal(t, "1.2.3.4", messages[0].Sender.String())
  256. require.Equal(t, "sending you a car", messages[1].Message)
  257. require.Equal(t, "car.jpg", messages[1].Attachment.Name)
  258. require.Equal(t, "image/jpeg", messages[1].Attachment.Type)
  259. require.Equal(t, int64(10000), messages[1].Attachment.Size)
  260. require.Equal(t, expires2, messages[1].Attachment.Expires)
  261. require.Equal(t, "https://ntfy.sh/file/aCaRURL.jpg", messages[1].Attachment.URL)
  262. require.Equal(t, "1.2.3.4", messages[1].Sender.String())
  263. size, err := c.AttachmentBytesUsedBySender("1.2.3.4")
  264. require.Nil(t, err)
  265. require.Equal(t, int64(10000), size)
  266. size, err = c.AttachmentBytesUsedBySender("5.6.7.8")
  267. require.Nil(t, err)
  268. require.Equal(t, int64(0), size) // Accounted to the user, not the IP!
  269. size, err = c.AttachmentBytesUsedByUser("u_BAsbaAa")
  270. require.Nil(t, err)
  271. require.Equal(t, int64(20000), size)
  272. })
  273. }
  274. func TestCache_AttachmentsExpired(t *testing.T) {
  275. runMessageCacheTest(t, func(t *testing.T, c MessageCache) {
  276. m := newDefaultMessage("mytopic", "flower for you")
  277. m.ID = "m1"
  278. m.Expires = time.Now().Add(time.Hour).Unix()
  279. require.Nil(t, c.AddMessage(m))
  280. m = newDefaultMessage("mytopic", "message with attachment")
  281. m.ID = "m2"
  282. m.Expires = time.Now().Add(2 * time.Hour).Unix()
  283. m.Attachment = &attachment{
  284. Name: "car.jpg",
  285. Type: "image/jpeg",
  286. Size: 10000,
  287. Expires: time.Now().Add(2 * time.Hour).Unix(),
  288. URL: "https://ntfy.sh/file/aCaRURL.jpg",
  289. }
  290. require.Nil(t, c.AddMessage(m))
  291. m = newDefaultMessage("mytopic", "message with external attachment")
  292. m.ID = "m3"
  293. m.Expires = time.Now().Add(2 * time.Hour).Unix()
  294. m.Attachment = &attachment{
  295. Name: "car.jpg",
  296. Type: "image/jpeg",
  297. Expires: 0, // Unknown!
  298. URL: "https://somedomain.com/car.jpg",
  299. }
  300. require.Nil(t, c.AddMessage(m))
  301. m = newDefaultMessage("mytopic2", "message with expired attachment")
  302. m.ID = "m4"
  303. m.Expires = time.Now().Add(2 * time.Hour).Unix()
  304. m.Attachment = &attachment{
  305. Name: "expired-car.jpg",
  306. Type: "image/jpeg",
  307. Size: 20000,
  308. Expires: time.Now().Add(-1 * time.Hour).Unix(),
  309. URL: "https://ntfy.sh/file/aCaRURL.jpg",
  310. }
  311. require.Nil(t, c.AddMessage(m))
  312. ids, err := c.AttachmentsExpired()
  313. require.Nil(t, err)
  314. require.Equal(t, 1, len(ids))
  315. require.Equal(t, "m4", ids[0])
  316. })
  317. }
  318. func TestCache_Sender(t *testing.T) {
  319. runMessageCacheTest(t, func(t *testing.T, c MessageCache) {
  320. m1 := newDefaultMessage("mytopic", "mymessage")
  321. m1.Sender = netip.MustParseAddr("1.2.3.4")
  322. require.Nil(t, c.AddMessage(m1))
  323. m2 := newDefaultMessage("mytopic", "mymessage without sender")
  324. require.Nil(t, c.AddMessage(m2))
  325. messages, err := c.Messages("mytopic", sinceAllMessages, false)
  326. require.Nil(t, err)
  327. require.Equal(t, 2, len(messages))
  328. require.Equal(t, messages[0].Sender, netip.MustParseAddr("1.2.3.4"))
  329. require.Equal(t, messages[1].Sender, netip.Addr{})
  330. })
  331. }
  332. func newSqliteTestCache(t *testing.T) *sqliteMessageCache {
  333. c, err := newSqliteMessageCache(newSqliteTestCacheFile(t), "", time.Hour, 0, 0, false)
  334. if err != nil {
  335. t.Fatal(err)
  336. }
  337. return c
  338. }
  339. func newSqliteTestCacheFile(t *testing.T) string {
  340. return filepath.Join(t.TempDir(), "cache.db")
  341. }
  342. func newSqliteTestCacheFromFile(t *testing.T, filename, startupQueries string) *sqliteMessageCache {
  343. c, err := newSqliteMessageCache(filename, startupQueries, time.Hour, 0, 0, false)
  344. require.Nil(t, err)
  345. return c
  346. }
  347. func newMemTestCache(t *testing.T) MessageCache {
  348. c, err := newMemCache()
  349. require.Nil(t, err)
  350. return c
  351. }
  352. func newPgTestCache(t *testing.T) MessageCache {
  353. connectionString := os.Getenv("NTFY_TEST_MESSAGES_CACHE_PG_CONNECTION_STRING")
  354. if connectionString == "" {
  355. t.Skip("Skipping test, because NTFY_TEST_MESSAGES_CACHE_PG_CONNECTION_STRING not set")
  356. }
  357. db, err := sql.Open("postgres", connectionString)
  358. require.Nil(t, err)
  359. _, err = db.Exec("DROP TABLE IF EXISTS messages")
  360. require.Nil(t, err)
  361. _, err = db.Exec("DROP TABLE IF EXISTS stats")
  362. require.Nil(t, err)
  363. _, err = db.Exec("DROP TABLE IF EXISTS schemaVersion")
  364. require.Nil(t, err)
  365. require.Nil(t, db.Close())
  366. c, err := newPgMessageCache(connectionString, "", 0, 0)
  367. require.Nil(t, err)
  368. return c
  369. }
  370. func runMessageCacheTest(t *testing.T, f func(t *testing.T, c MessageCache)) {
  371. t.Run(t.Name()+"_sqlite", func(t *testing.T) {
  372. f(t, newSqliteTestCache(t))
  373. })
  374. t.Run(t.Name()+"_mem", func(t *testing.T) {
  375. f(t, newMemTestCache(t))
  376. })
  377. t.Run(t.Name()+"_pg", func(t *testing.T) {
  378. f(t, newPgTestCache(t))
  379. })
  380. }