message_cache_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. package server
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "github.com/stretchr/testify/assert"
  6. "github.com/stretchr/testify/require"
  7. "path/filepath"
  8. "testing"
  9. "time"
  10. )
  11. func TestSqliteCache_Messages(t *testing.T) {
  12. testCacheMessages(t, newSqliteTestCache(t))
  13. }
  14. func TestMemCache_Messages(t *testing.T) {
  15. testCacheMessages(t, newMemTestCache(t))
  16. }
  17. func testCacheMessages(t *testing.T, c *messageCache) {
  18. m1 := newDefaultMessage("mytopic", "my message")
  19. m1.Time = 1
  20. m2 := newDefaultMessage("mytopic", "my other message")
  21. m2.Time = 2
  22. require.Nil(t, c.AddMessage(m1))
  23. require.Nil(t, c.AddMessage(newDefaultMessage("example", "my example message")))
  24. require.Nil(t, c.AddMessage(m2))
  25. // Adding invalid
  26. require.Equal(t, errUnexpectedMessageType, c.AddMessage(newKeepaliveMessage("mytopic"))) // These should not be added!
  27. require.Equal(t, errUnexpectedMessageType, c.AddMessage(newOpenMessage("example"))) // These should not be added!
  28. // mytopic: count
  29. count, err := c.MessageCount("mytopic")
  30. require.Nil(t, err)
  31. require.Equal(t, 2, count)
  32. // mytopic: since all
  33. messages, _ := c.Messages("mytopic", sinceAllMessages, false)
  34. require.Equal(t, 2, len(messages))
  35. require.Equal(t, "my message", messages[0].Message)
  36. require.Equal(t, "mytopic", messages[0].Topic)
  37. require.Equal(t, messageEvent, messages[0].Event)
  38. require.Equal(t, "", messages[0].Title)
  39. require.Equal(t, 0, messages[0].Priority)
  40. require.Nil(t, messages[0].Tags)
  41. require.Equal(t, "my other message", messages[1].Message)
  42. // mytopic: since none
  43. messages, _ = c.Messages("mytopic", sinceNoMessages, false)
  44. require.Empty(t, messages)
  45. // mytopic: since m1 (by ID)
  46. messages, _ = c.Messages("mytopic", newSinceID(m1.ID), false)
  47. require.Equal(t, 1, len(messages))
  48. require.Equal(t, m2.ID, messages[0].ID)
  49. require.Equal(t, "my other message", messages[0].Message)
  50. require.Equal(t, "mytopic", messages[0].Topic)
  51. // mytopic: since 2
  52. messages, _ = c.Messages("mytopic", newSinceTime(2), false)
  53. require.Equal(t, 1, len(messages))
  54. require.Equal(t, "my other message", messages[0].Message)
  55. // example: count
  56. count, err = c.MessageCount("example")
  57. require.Nil(t, err)
  58. require.Equal(t, 1, count)
  59. // example: since all
  60. messages, _ = c.Messages("example", sinceAllMessages, false)
  61. require.Equal(t, "my example message", messages[0].Message)
  62. // non-existing: count
  63. count, err = c.MessageCount("doesnotexist")
  64. require.Nil(t, err)
  65. require.Equal(t, 0, count)
  66. // non-existing: since all
  67. messages, _ = c.Messages("doesnotexist", sinceAllMessages, false)
  68. require.Empty(t, messages)
  69. }
  70. func TestSqliteCache_MessagesScheduled(t *testing.T) {
  71. testCacheMessagesScheduled(t, newSqliteTestCache(t))
  72. }
  73. func TestMemCache_MessagesScheduled(t *testing.T) {
  74. testCacheMessagesScheduled(t, newMemTestCache(t))
  75. }
  76. func testCacheMessagesScheduled(t *testing.T, c *messageCache) {
  77. m1 := newDefaultMessage("mytopic", "message 1")
  78. m2 := newDefaultMessage("mytopic", "message 2")
  79. m2.Time = time.Now().Add(time.Hour).Unix()
  80. m3 := newDefaultMessage("mytopic", "message 3")
  81. m3.Time = time.Now().Add(time.Minute).Unix() // earlier than m2!
  82. m4 := newDefaultMessage("mytopic2", "message 4")
  83. m4.Time = time.Now().Add(time.Minute).Unix()
  84. require.Nil(t, c.AddMessage(m1))
  85. require.Nil(t, c.AddMessage(m2))
  86. require.Nil(t, c.AddMessage(m3))
  87. messages, _ := c.Messages("mytopic", sinceAllMessages, false) // exclude scheduled
  88. require.Equal(t, 1, len(messages))
  89. require.Equal(t, "message 1", messages[0].Message)
  90. messages, _ = c.Messages("mytopic", sinceAllMessages, true) // include scheduled
  91. require.Equal(t, 3, len(messages))
  92. require.Equal(t, "message 1", messages[0].Message)
  93. require.Equal(t, "message 3", messages[1].Message) // Order!
  94. require.Equal(t, "message 2", messages[2].Message)
  95. messages, _ = c.MessagesDue()
  96. require.Empty(t, messages)
  97. }
  98. func TestSqliteCache_Topics(t *testing.T) {
  99. testCacheTopics(t, newSqliteTestCache(t))
  100. }
  101. func TestMemCache_Topics(t *testing.T) {
  102. testCacheTopics(t, newMemTestCache(t))
  103. }
  104. func testCacheTopics(t *testing.T, c *messageCache) {
  105. require.Nil(t, c.AddMessage(newDefaultMessage("topic1", "my example message")))
  106. require.Nil(t, c.AddMessage(newDefaultMessage("topic2", "message 1")))
  107. require.Nil(t, c.AddMessage(newDefaultMessage("topic2", "message 2")))
  108. require.Nil(t, c.AddMessage(newDefaultMessage("topic2", "message 3")))
  109. topics, err := c.Topics()
  110. if err != nil {
  111. t.Fatal(err)
  112. }
  113. require.Equal(t, 2, len(topics))
  114. require.Equal(t, "topic1", topics["topic1"].ID)
  115. require.Equal(t, "topic2", topics["topic2"].ID)
  116. }
  117. func TestSqliteCache_MessagesTagsPrioAndTitle(t *testing.T) {
  118. testCacheMessagesTagsPrioAndTitle(t, newSqliteTestCache(t))
  119. }
  120. func TestMemCache_MessagesTagsPrioAndTitle(t *testing.T) {
  121. testCacheMessagesTagsPrioAndTitle(t, newMemTestCache(t))
  122. }
  123. func testCacheMessagesTagsPrioAndTitle(t *testing.T, c *messageCache) {
  124. m := newDefaultMessage("mytopic", "some message")
  125. m.Tags = []string{"tag1", "tag2"}
  126. m.Priority = 5
  127. m.Title = "some title"
  128. require.Nil(t, c.AddMessage(m))
  129. messages, _ := c.Messages("mytopic", sinceAllMessages, false)
  130. require.Equal(t, []string{"tag1", "tag2"}, messages[0].Tags)
  131. require.Equal(t, 5, messages[0].Priority)
  132. require.Equal(t, "some title", messages[0].Title)
  133. }
  134. func TestSqliteCache_MessagesSinceID(t *testing.T) {
  135. testCacheMessagesSinceID(t, newSqliteTestCache(t))
  136. }
  137. func TestMemCache_MessagesSinceID(t *testing.T) {
  138. testCacheMessagesSinceID(t, newMemTestCache(t))
  139. }
  140. func testCacheMessagesSinceID(t *testing.T, c *messageCache) {
  141. m1 := newDefaultMessage("mytopic", "message 1")
  142. m1.Time = 100
  143. m2 := newDefaultMessage("mytopic", "message 2")
  144. m2.Time = 200
  145. m3 := newDefaultMessage("mytopic", "message 3")
  146. m3.Time = time.Now().Add(time.Hour).Unix() // Scheduled, in the future, later than m7 and m5
  147. m4 := newDefaultMessage("mytopic", "message 4")
  148. m4.Time = 400
  149. m5 := newDefaultMessage("mytopic", "message 5")
  150. m5.Time = time.Now().Add(time.Minute).Unix() // Scheduled, in the future, later than m7
  151. m6 := newDefaultMessage("mytopic", "message 6")
  152. m6.Time = 600
  153. m7 := newDefaultMessage("mytopic", "message 7")
  154. m7.Time = 700
  155. require.Nil(t, c.AddMessage(m1))
  156. require.Nil(t, c.AddMessage(m2))
  157. require.Nil(t, c.AddMessage(m3))
  158. require.Nil(t, c.AddMessage(m4))
  159. require.Nil(t, c.AddMessage(m5))
  160. require.Nil(t, c.AddMessage(m6))
  161. require.Nil(t, c.AddMessage(m7))
  162. // Case 1: Since ID exists, exclude scheduled
  163. messages, _ := c.Messages("mytopic", newSinceID(m2.ID), false)
  164. require.Equal(t, 3, len(messages))
  165. require.Equal(t, "message 4", messages[0].Message)
  166. require.Equal(t, "message 6", messages[1].Message) // Not scheduled m3/m5!
  167. require.Equal(t, "message 7", messages[2].Message)
  168. // Case 2: Since ID exists, include scheduled
  169. messages, _ = c.Messages("mytopic", newSinceID(m2.ID), true)
  170. require.Equal(t, 5, len(messages))
  171. require.Equal(t, "message 4", messages[0].Message)
  172. require.Equal(t, "message 6", messages[1].Message)
  173. require.Equal(t, "message 7", messages[2].Message)
  174. require.Equal(t, "message 5", messages[3].Message) // Order!
  175. require.Equal(t, "message 3", messages[4].Message) // Order!
  176. // Case 3: Since ID does not exist (-> Return all messages), include scheduled
  177. messages, _ = c.Messages("mytopic", newSinceID("doesntexist"), true)
  178. require.Equal(t, 7, len(messages))
  179. require.Equal(t, "message 1", messages[0].Message)
  180. require.Equal(t, "message 2", messages[1].Message)
  181. require.Equal(t, "message 4", messages[2].Message)
  182. require.Equal(t, "message 6", messages[3].Message)
  183. require.Equal(t, "message 7", messages[4].Message)
  184. require.Equal(t, "message 5", messages[5].Message) // Order!
  185. require.Equal(t, "message 3", messages[6].Message) // Order!
  186. // Case 4: Since ID exists and is last message (-> Return no messages), exclude scheduled
  187. messages, _ = c.Messages("mytopic", newSinceID(m7.ID), false)
  188. require.Equal(t, 0, len(messages))
  189. // Case 5: Since ID exists and is last message (-> Return no messages), include scheduled
  190. messages, _ = c.Messages("mytopic", newSinceID(m7.ID), true)
  191. require.Equal(t, 2, len(messages))
  192. require.Equal(t, "message 5", messages[0].Message)
  193. require.Equal(t, "message 3", messages[1].Message)
  194. }
  195. func TestSqliteCache_Prune(t *testing.T) {
  196. testCachePrune(t, newSqliteTestCache(t))
  197. }
  198. func TestMemCache_Prune(t *testing.T) {
  199. testCachePrune(t, newMemTestCache(t))
  200. }
  201. func testCachePrune(t *testing.T, c *messageCache) {
  202. m1 := newDefaultMessage("mytopic", "my message")
  203. m1.Time = 1
  204. m2 := newDefaultMessage("mytopic", "my other message")
  205. m2.Time = 2
  206. m3 := newDefaultMessage("another_topic", "and another one")
  207. m3.Time = 1
  208. require.Nil(t, c.AddMessage(m1))
  209. require.Nil(t, c.AddMessage(m2))
  210. require.Nil(t, c.AddMessage(m3))
  211. require.Nil(t, c.Prune(time.Unix(2, 0)))
  212. count, err := c.MessageCount("mytopic")
  213. require.Nil(t, err)
  214. require.Equal(t, 1, count)
  215. count, err = c.MessageCount("another_topic")
  216. require.Nil(t, err)
  217. require.Equal(t, 0, count)
  218. messages, err := c.Messages("mytopic", sinceAllMessages, false)
  219. require.Nil(t, err)
  220. require.Equal(t, 1, len(messages))
  221. require.Equal(t, "my other message", messages[0].Message)
  222. }
  223. func TestSqliteCache_Attachments(t *testing.T) {
  224. testCacheAttachments(t, newSqliteTestCache(t))
  225. }
  226. func TestMemCache_Attachments(t *testing.T) {
  227. testCacheAttachments(t, newMemTestCache(t))
  228. }
  229. func testCacheAttachments(t *testing.T, c *messageCache) {
  230. expires1 := time.Now().Add(-4 * time.Hour).Unix()
  231. m := newDefaultMessage("mytopic", "flower for you")
  232. m.ID = "m1"
  233. m.Attachment = &attachment{
  234. Name: "flower.jpg",
  235. Type: "image/jpeg",
  236. Size: 5000,
  237. Expires: expires1,
  238. URL: "https://ntfy.sh/file/AbDeFgJhal.jpg",
  239. Owner: "1.2.3.4",
  240. }
  241. require.Nil(t, c.AddMessage(m))
  242. expires2 := time.Now().Add(2 * time.Hour).Unix() // Future
  243. m = newDefaultMessage("mytopic", "sending you a car")
  244. m.ID = "m2"
  245. m.Attachment = &attachment{
  246. Name: "car.jpg",
  247. Type: "image/jpeg",
  248. Size: 10000,
  249. Expires: expires2,
  250. URL: "https://ntfy.sh/file/aCaRURL.jpg",
  251. Owner: "1.2.3.4",
  252. }
  253. require.Nil(t, c.AddMessage(m))
  254. expires3 := time.Now().Add(1 * time.Hour).Unix() // Future
  255. m = newDefaultMessage("another-topic", "sending you another car")
  256. m.ID = "m3"
  257. m.Attachment = &attachment{
  258. Name: "another-car.jpg",
  259. Type: "image/jpeg",
  260. Size: 20000,
  261. Expires: expires3,
  262. URL: "https://ntfy.sh/file/zakaDHFW.jpg",
  263. Owner: "1.2.3.4",
  264. }
  265. require.Nil(t, c.AddMessage(m))
  266. messages, err := c.Messages("mytopic", sinceAllMessages, false)
  267. require.Nil(t, err)
  268. require.Equal(t, 2, len(messages))
  269. require.Equal(t, "flower for you", messages[0].Message)
  270. require.Equal(t, "flower.jpg", messages[0].Attachment.Name)
  271. require.Equal(t, "image/jpeg", messages[0].Attachment.Type)
  272. require.Equal(t, int64(5000), messages[0].Attachment.Size)
  273. require.Equal(t, expires1, messages[0].Attachment.Expires)
  274. require.Equal(t, "https://ntfy.sh/file/AbDeFgJhal.jpg", messages[0].Attachment.URL)
  275. require.Equal(t, "1.2.3.4", messages[0].Attachment.Owner)
  276. require.Equal(t, "sending you a car", messages[1].Message)
  277. require.Equal(t, "car.jpg", messages[1].Attachment.Name)
  278. require.Equal(t, "image/jpeg", messages[1].Attachment.Type)
  279. require.Equal(t, int64(10000), messages[1].Attachment.Size)
  280. require.Equal(t, expires2, messages[1].Attachment.Expires)
  281. require.Equal(t, "https://ntfy.sh/file/aCaRURL.jpg", messages[1].Attachment.URL)
  282. require.Equal(t, "1.2.3.4", messages[1].Attachment.Owner)
  283. size, err := c.AttachmentBytesUsed("1.2.3.4")
  284. require.Nil(t, err)
  285. require.Equal(t, int64(30000), size)
  286. size, err = c.AttachmentBytesUsed("5.6.7.8")
  287. require.Nil(t, err)
  288. require.Equal(t, int64(0), size)
  289. ids, err := c.AttachmentsExpired()
  290. require.Nil(t, err)
  291. require.Equal(t, []string{"m1"}, ids)
  292. }
  293. func TestSqliteCache_Migration_From0(t *testing.T) {
  294. filename := newSqliteTestCacheFile(t)
  295. db, err := sql.Open("sqlite3", filename)
  296. require.Nil(t, err)
  297. // Create "version 0" schema
  298. _, err = db.Exec(`
  299. BEGIN;
  300. CREATE TABLE IF NOT EXISTS messages (
  301. id VARCHAR(20) PRIMARY KEY,
  302. time INT NOT NULL,
  303. topic VARCHAR(64) NOT NULL,
  304. message VARCHAR(1024) NOT NULL
  305. );
  306. CREATE INDEX IF NOT EXISTS idx_topic ON messages (topic);
  307. COMMIT;
  308. `)
  309. require.Nil(t, err)
  310. // Insert a bunch of messages
  311. for i := 0; i < 10; i++ {
  312. _, err = db.Exec(`INSERT INTO messages (id, time, topic, message) VALUES (?, ?, ?, ?)`,
  313. fmt.Sprintf("abcd%d", i), time.Now().Unix(), "mytopic", fmt.Sprintf("some message %d", i))
  314. require.Nil(t, err)
  315. }
  316. require.Nil(t, db.Close())
  317. // Create cache to trigger migration
  318. c := newSqliteTestCacheFromFile(t, filename)
  319. checkSchemaVersion(t, c.db)
  320. messages, err := c.Messages("mytopic", sinceAllMessages, false)
  321. require.Nil(t, err)
  322. require.Equal(t, 10, len(messages))
  323. require.Equal(t, "some message 5", messages[5].Message)
  324. require.Equal(t, "", messages[5].Title)
  325. require.Nil(t, messages[5].Tags)
  326. require.Equal(t, 0, messages[5].Priority)
  327. }
  328. func TestSqliteCache_Migration_From1(t *testing.T) {
  329. filename := newSqliteTestCacheFile(t)
  330. db, err := sql.Open("sqlite3", filename)
  331. require.Nil(t, err)
  332. // Create "version 1" schema
  333. _, err = db.Exec(`
  334. CREATE TABLE IF NOT EXISTS messages (
  335. id VARCHAR(20) PRIMARY KEY,
  336. time INT NOT NULL,
  337. topic VARCHAR(64) NOT NULL,
  338. message VARCHAR(512) NOT NULL,
  339. title VARCHAR(256) NOT NULL,
  340. priority INT NOT NULL,
  341. tags VARCHAR(256) NOT NULL
  342. );
  343. CREATE INDEX IF NOT EXISTS idx_topic ON messages (topic);
  344. CREATE TABLE IF NOT EXISTS schemaVersion (
  345. id INT PRIMARY KEY,
  346. version INT NOT NULL
  347. );
  348. INSERT INTO schemaVersion (id, version) VALUES (1, 1);
  349. `)
  350. require.Nil(t, err)
  351. // Insert a bunch of messages
  352. for i := 0; i < 10; i++ {
  353. _, err = db.Exec(`INSERT INTO messages (id, time, topic, message, title, priority, tags) VALUES (?, ?, ?, ?, ?, ?, ?)`,
  354. fmt.Sprintf("abcd%d", i), time.Now().Unix(), "mytopic", fmt.Sprintf("some message %d", i), "", 0, "")
  355. require.Nil(t, err)
  356. }
  357. require.Nil(t, db.Close())
  358. // Create cache to trigger migration
  359. c := newSqliteTestCacheFromFile(t, filename)
  360. checkSchemaVersion(t, c.db)
  361. // Add delayed message
  362. delayedMessage := newDefaultMessage("mytopic", "some delayed message")
  363. delayedMessage.Time = time.Now().Add(time.Minute).Unix()
  364. require.Nil(t, c.AddMessage(delayedMessage))
  365. // 10, not 11!
  366. messages, err := c.Messages("mytopic", sinceAllMessages, false)
  367. require.Nil(t, err)
  368. require.Equal(t, 10, len(messages))
  369. // 11!
  370. messages, err = c.Messages("mytopic", sinceAllMessages, true)
  371. require.Nil(t, err)
  372. require.Equal(t, 11, len(messages))
  373. }
  374. func checkSchemaVersion(t *testing.T, db *sql.DB) {
  375. rows, err := db.Query(`SELECT version FROM schemaVersion`)
  376. require.Nil(t, err)
  377. require.True(t, rows.Next())
  378. var schemaVersion int
  379. require.Nil(t, rows.Scan(&schemaVersion))
  380. require.Equal(t, currentSchemaVersion, schemaVersion)
  381. require.Nil(t, rows.Close())
  382. }
  383. func TestMemCache_NopCache(t *testing.T) {
  384. c, _ := newNopCache()
  385. assert.Nil(t, c.AddMessage(newDefaultMessage("mytopic", "my message")))
  386. messages, err := c.Messages("mytopic", sinceAllMessages, false)
  387. assert.Nil(t, err)
  388. assert.Empty(t, messages)
  389. topics, err := c.Topics()
  390. assert.Nil(t, err)
  391. assert.Empty(t, topics)
  392. }
  393. func newSqliteTestCache(t *testing.T) *messageCache {
  394. c, err := newSqliteCache(newSqliteTestCacheFile(t), false)
  395. if err != nil {
  396. t.Fatal(err)
  397. }
  398. return c
  399. }
  400. func newSqliteTestCacheFile(t *testing.T) string {
  401. return filepath.Join(t.TempDir(), "cache.db")
  402. }
  403. func newSqliteTestCacheFromFile(t *testing.T, filename string) *messageCache {
  404. c, err := newSqliteCache(filename, false)
  405. if err != nil {
  406. t.Fatal(err)
  407. }
  408. return c
  409. }
  410. func newMemTestCache(t *testing.T) *messageCache {
  411. c, err := newMemCache()
  412. if err != nil {
  413. t.Fatal(err)
  414. }
  415. return c
  416. }