publish_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. package cmd
  2. import (
  3. "fmt"
  4. "github.com/stretchr/testify/require"
  5. "heckel.io/ntfy/v2/test"
  6. "heckel.io/ntfy/v2/util"
  7. "net/http"
  8. "net/http/httptest"
  9. "os"
  10. "os/exec"
  11. "path/filepath"
  12. "strconv"
  13. "strings"
  14. "testing"
  15. "time"
  16. )
  17. func TestCLI_Publish_Subscribe_Poll_Real_Server(t *testing.T) {
  18. testMessage := util.RandomString(10)
  19. app, _, _, _ := newTestApp()
  20. require.Nil(t, app.Run([]string{"ntfy", "publish", "ntfytest", "ntfy unit test " + testMessage}))
  21. _, err := util.Retry(func() (*int, error) {
  22. app2, _, stdout, _ := newTestApp()
  23. if err := app2.Run([]string{"ntfy", "subscribe", "--poll", "ntfytest"}); err != nil {
  24. return nil, err
  25. }
  26. if !strings.Contains(stdout.String(), testMessage) {
  27. return nil, fmt.Errorf("test message %s not found in topic", testMessage)
  28. }
  29. return util.Int(1), nil
  30. }, time.Second, 2*time.Second, 5*time.Second) // Since #502, ntfy.sh writes messages to the cache asynchronously, after a timeout of ~1.5s
  31. require.Nil(t, err)
  32. }
  33. func TestCLI_Publish_Subscribe_Poll(t *testing.T) {
  34. s, port := test.StartServer(t)
  35. defer test.StopServer(t, s, port)
  36. topic := fmt.Sprintf("http://127.0.0.1:%d/mytopic", port)
  37. app, _, stdout, _ := newTestApp()
  38. require.Nil(t, app.Run([]string{"ntfy", "publish", topic, "some message"}))
  39. m := toMessage(t, stdout.String())
  40. require.Equal(t, "some message", m.Message)
  41. app2, _, stdout, _ := newTestApp()
  42. require.Nil(t, app2.Run([]string{"ntfy", "subscribe", "--poll", topic}))
  43. m = toMessage(t, stdout.String())
  44. require.Equal(t, "some message", m.Message)
  45. }
  46. func TestCLI_Publish_All_The_Things(t *testing.T) {
  47. s, port := test.StartServer(t)
  48. defer test.StopServer(t, s, port)
  49. topic := fmt.Sprintf("http://127.0.0.1:%d/mytopic", port)
  50. app, _, stdout, _ := newTestApp()
  51. require.Nil(t, app.Run([]string{
  52. "ntfy", "publish",
  53. "--title", "this is a title",
  54. "--priority", "high",
  55. "--tags", "tag1,tag2",
  56. // No --delay, --email
  57. "--click", "https://ntfy.sh",
  58. "--icon", "https://ntfy.sh/static/img/ntfy.png",
  59. "--attach", "https://f-droid.org/F-Droid.apk",
  60. "--filename", "fdroid.apk",
  61. "--no-cache",
  62. "--no-firebase",
  63. topic,
  64. "some message",
  65. }))
  66. m := toMessage(t, stdout.String())
  67. require.Equal(t, "message", m.Event)
  68. require.Equal(t, "mytopic", m.Topic)
  69. require.Equal(t, "some message", m.Message)
  70. require.Equal(t, "this is a title", m.Title)
  71. require.Equal(t, 4, m.Priority)
  72. require.Equal(t, []string{"tag1", "tag2"}, m.Tags)
  73. require.Equal(t, "https://ntfy.sh", m.Click)
  74. require.Equal(t, "https://f-droid.org/F-Droid.apk", m.Attachment.URL)
  75. require.Equal(t, "fdroid.apk", m.Attachment.Name)
  76. require.Equal(t, int64(0), m.Attachment.Size)
  77. require.Equal(t, "", m.Attachment.Owner)
  78. require.Equal(t, int64(0), m.Attachment.Expires)
  79. require.Equal(t, "", m.Attachment.Type)
  80. require.Equal(t, "https://ntfy.sh/static/img/ntfy.png", m.Icon)
  81. }
  82. func TestCLI_Publish_Wait_PID_And_Cmd(t *testing.T) {
  83. s, port := test.StartServer(t)
  84. defer test.StopServer(t, s, port)
  85. topic := fmt.Sprintf("http://127.0.0.1:%d/mytopic", port)
  86. // Test: sleep 0.5
  87. sleep := exec.Command("sleep", "0.5")
  88. require.Nil(t, sleep.Start())
  89. go sleep.Wait() // Must be called to release resources
  90. start := time.Now()
  91. app, _, stdout, _ := newTestApp()
  92. require.Nil(t, app.Run([]string{"ntfy", "publish", "--wait-pid", strconv.Itoa(sleep.Process.Pid), topic}))
  93. m := toMessage(t, stdout.String())
  94. require.True(t, time.Since(start) >= 500*time.Millisecond)
  95. require.Regexp(t, `Process with PID \d+ exited after `, m.Message)
  96. // Test: PID does not exist
  97. app, _, _, _ = newTestApp()
  98. err := app.Run([]string{"ntfy", "publish", "--wait-pid", "1234567", topic})
  99. require.Error(t, err)
  100. require.Equal(t, "process with PID 1234567 not running", err.Error())
  101. // Test: Successful command (exit 0)
  102. start = time.Now()
  103. app, _, stdout, _ = newTestApp()
  104. require.Nil(t, app.Run([]string{"ntfy", "publish", "--wait-cmd", topic, "sleep", "0.5"}))
  105. m = toMessage(t, stdout.String())
  106. require.True(t, time.Since(start) >= 500*time.Millisecond)
  107. require.Contains(t, m.Message, `Command succeeded after `)
  108. require.Contains(t, m.Message, `: sleep 0.5`)
  109. // Test: Failing command (exit 1)
  110. app, _, stdout, _ = newTestApp()
  111. require.Nil(t, app.Run([]string{"ntfy", "publish", "--wait-cmd", topic, "/bin/false", "false doesn't care about its args"}))
  112. m = toMessage(t, stdout.String())
  113. require.Contains(t, m.Message, `Command failed after `)
  114. require.Contains(t, m.Message, `(exit code 1): /bin/false "false doesn't care about its args"`, m.Message)
  115. // Test: Non-existing command (hard fail!)
  116. app, _, _, _ = newTestApp()
  117. err = app.Run([]string{"ntfy", "publish", "--wait-cmd", topic, "does-not-exist-no-really", "really though"})
  118. require.Error(t, err)
  119. require.Equal(t, `command failed: does-not-exist-no-really "really though", error: exec: "does-not-exist-no-really": executable file not found in $PATH`, err.Error())
  120. // Tests with NTFY_TOPIC set ////
  121. t.Setenv("NTFY_TOPIC", topic)
  122. // Test: Successful command with NTFY_TOPIC
  123. app, _, stdout, _ = newTestApp()
  124. require.Nil(t, app.Run([]string{"ntfy", "publish", "--cmd", "echo", "hi there"}))
  125. m = toMessage(t, stdout.String())
  126. require.Equal(t, "mytopic", m.Topic)
  127. // Test: Successful --wait-pid with NTFY_TOPIC
  128. sleep = exec.Command("sleep", "0.2")
  129. require.Nil(t, sleep.Start())
  130. go sleep.Wait() // Must be called to release resources
  131. app, _, stdout, _ = newTestApp()
  132. require.Nil(t, app.Run([]string{"ntfy", "publish", "--wait-pid", strconv.Itoa(sleep.Process.Pid)}))
  133. m = toMessage(t, stdout.String())
  134. require.Regexp(t, `Process with PID \d+ exited after .+ms`, m.Message)
  135. }
  136. func TestCLI_Publish_Default_UserPass(t *testing.T) {
  137. message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}`
  138. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  139. require.Equal(t, "/mytopic", r.URL.Path)
  140. require.Equal(t, "Basic cGhpbGlwcDpteXBhc3M=", r.Header.Get("Authorization"))
  141. w.WriteHeader(http.StatusOK)
  142. w.Write([]byte(message))
  143. }))
  144. defer server.Close()
  145. filename := filepath.Join(t.TempDir(), "client.yml")
  146. require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(`
  147. default-host: %s
  148. default-user: philipp
  149. default-password: mypass
  150. `, server.URL)), 0600))
  151. app, _, stdout, _ := newTestApp()
  152. require.Nil(t, app.Run([]string{"ntfy", "publish", "--config=" + filename, "mytopic", "triggered"}))
  153. m := toMessage(t, stdout.String())
  154. require.Equal(t, "triggered", m.Message)
  155. }
  156. func TestCLI_Publish_Default_Token(t *testing.T) {
  157. message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}`
  158. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  159. require.Equal(t, "/mytopic", r.URL.Path)
  160. require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization"))
  161. w.WriteHeader(http.StatusOK)
  162. w.Write([]byte(message))
  163. }))
  164. defer server.Close()
  165. filename := filepath.Join(t.TempDir(), "client.yml")
  166. require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(`
  167. default-host: %s
  168. default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2
  169. `, server.URL)), 0600))
  170. app, _, stdout, _ := newTestApp()
  171. require.Nil(t, app.Run([]string{"ntfy", "publish", "--config=" + filename, "mytopic", "triggered"}))
  172. m := toMessage(t, stdout.String())
  173. require.Equal(t, "triggered", m.Message)
  174. }
  175. func TestCLI_Publish_Default_UserPass_CLI_Token(t *testing.T) {
  176. message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}`
  177. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  178. require.Equal(t, "/mytopic", r.URL.Path)
  179. require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization"))
  180. w.WriteHeader(http.StatusOK)
  181. w.Write([]byte(message))
  182. }))
  183. defer server.Close()
  184. filename := filepath.Join(t.TempDir(), "client.yml")
  185. require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(`
  186. default-host: %s
  187. default-user: philipp
  188. default-password: mypass
  189. `, server.URL)), 0600))
  190. app, _, stdout, _ := newTestApp()
  191. require.Nil(t, app.Run([]string{"ntfy", "publish", "--config=" + filename, "--token", "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", "mytopic", "triggered"}))
  192. m := toMessage(t, stdout.String())
  193. require.Equal(t, "triggered", m.Message)
  194. }
  195. func TestCLI_Publish_Default_Token_CLI_UserPass(t *testing.T) {
  196. message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}`
  197. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  198. require.Equal(t, "/mytopic", r.URL.Path)
  199. require.Equal(t, "Basic cGhpbGlwcDpteXBhc3M=", r.Header.Get("Authorization"))
  200. w.WriteHeader(http.StatusOK)
  201. w.Write([]byte(message))
  202. }))
  203. defer server.Close()
  204. filename := filepath.Join(t.TempDir(), "client.yml")
  205. require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(`
  206. default-host: %s
  207. default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2
  208. `, server.URL)), 0600))
  209. app, _, stdout, _ := newTestApp()
  210. require.Nil(t, app.Run([]string{"ntfy", "publish", "--config=" + filename, "--user", "philipp:mypass", "mytopic", "triggered"}))
  211. m := toMessage(t, stdout.String())
  212. require.Equal(t, "triggered", m.Message)
  213. }
  214. func TestCLI_Publish_Default_Token_CLI_Token(t *testing.T) {
  215. message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}`
  216. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  217. require.Equal(t, "/mytopic", r.URL.Path)
  218. require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization"))
  219. w.WriteHeader(http.StatusOK)
  220. w.Write([]byte(message))
  221. }))
  222. defer server.Close()
  223. filename := filepath.Join(t.TempDir(), "client.yml")
  224. require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(`
  225. default-host: %s
  226. default-token: tk_FAKETOKEN01234567890FAKETOKEN
  227. `, server.URL)), 0600))
  228. app, _, stdout, _ := newTestApp()
  229. require.Nil(t, app.Run([]string{"ntfy", "publish", "--config=" + filename, "--token", "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", "mytopic", "triggered"}))
  230. m := toMessage(t, stdout.String())
  231. require.Equal(t, "triggered", m.Message)
  232. }
  233. func TestCLI_Publish_Default_UserPass_CLI_UserPass(t *testing.T) {
  234. message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}`
  235. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  236. require.Equal(t, "/mytopic", r.URL.Path)
  237. require.Equal(t, "Basic cGhpbGlwcDpteXBhc3M=", r.Header.Get("Authorization"))
  238. w.WriteHeader(http.StatusOK)
  239. w.Write([]byte(message))
  240. }))
  241. defer server.Close()
  242. filename := filepath.Join(t.TempDir(), "client.yml")
  243. require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(`
  244. default-host: %s
  245. default-user: philipp
  246. default-password: fakepass
  247. `, server.URL)), 0600))
  248. app, _, stdout, _ := newTestApp()
  249. require.Nil(t, app.Run([]string{"ntfy", "publish", "--config=" + filename, "--user", "philipp:mypass", "mytopic", "triggered"}))
  250. m := toMessage(t, stdout.String())
  251. require.Equal(t, "triggered", m.Message)
  252. }
  253. func TestCLI_Publish_Token_And_UserPass(t *testing.T) {
  254. app, _, _, _ := newTestApp()
  255. err := app.Run([]string{"ntfy", "publish", "--token", "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", "--user", "philipp:mypass", "mytopic", "triggered"})
  256. require.Error(t, err)
  257. require.Equal(t, "cannot set both --user and --token", err.Error())
  258. }