Tests
This commit is contained in:
		
							parent
							
								
									0080ea5a20
								
							
						
					
					
						commit
						a160da3ad9
					
				
					 4 changed files with 100 additions and 83 deletions
				
			
		| 
						 | 
					@ -47,7 +47,7 @@ var cmdPublish = &cli.Command{
 | 
				
			||||||
	Aliases: []string{"pub", "send", "trigger"},
 | 
						Aliases: []string{"pub", "send", "trigger"},
 | 
				
			||||||
	Usage:   "Send message via a ntfy server",
 | 
						Usage:   "Send message via a ntfy server",
 | 
				
			||||||
	UsageText: `ntfy publish [OPTIONS..] TOPIC [MESSAGE...]
 | 
						UsageText: `ntfy publish [OPTIONS..] TOPIC [MESSAGE...]
 | 
				
			||||||
ntfy publish [OPTIONS..] --wait-cmd -P COMMAND...
 | 
					ntfy publish [OPTIONS..] --wait-cmd COMMAND...
 | 
				
			||||||
NTFY_TOPIC=.. ntfy publish [OPTIONS..] -P [MESSAGE...]`,
 | 
					NTFY_TOPIC=.. ntfy publish [OPTIONS..] -P [MESSAGE...]`,
 | 
				
			||||||
	Action:   execPublish,
 | 
						Action:   execPublish,
 | 
				
			||||||
	Category: categoryClient,
 | 
						Category: categoryClient,
 | 
				
			||||||
| 
						 | 
					@ -156,18 +156,18 @@ func execPublish(c *cli.Context) error {
 | 
				
			||||||
		options = append(options, client.WithBasicAuth(user, pass))
 | 
							options = append(options, client.WithBasicAuth(user, pass))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if pid > 0 {
 | 
						if pid > 0 {
 | 
				
			||||||
		if err := waitForProcess(pid); err != nil {
 | 
							newMessage, err := waitForProcess(pid)
 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if message == "" {
 | 
					 | 
				
			||||||
			message = fmt.Sprintf("process with PID %d exited", pid)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else if len(command) > 0 {
 | 
					 | 
				
			||||||
		cmdResultMessage, err := runAndWaitForCommand(command)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		} else if message == "" {
 | 
							} else if message == "" {
 | 
				
			||||||
			message = cmdResultMessage
 | 
								message = newMessage
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if len(command) > 0 {
 | 
				
			||||||
 | 
							newMessage, err := runAndWaitForCommand(command)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							} else if message == "" {
 | 
				
			||||||
 | 
								message = newMessage
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	var body io.Reader
 | 
						var body io.Reader
 | 
				
			||||||
| 
						 | 
					@ -203,11 +203,14 @@ func execPublish(c *cli.Context) error {
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// parseTopicMessageCommand reads the topic and the remaining arguments from the context.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// There are a few cases to consider:
 | 
				
			||||||
 | 
					//   ntfy publish <topic> [<message>]
 | 
				
			||||||
 | 
					//   ntfy publish --wait-cmd <topic> <command>
 | 
				
			||||||
 | 
					//   NTFY_TOPIC=.. ntfy publish [<message>]
 | 
				
			||||||
 | 
					//   NTFY_TOPIC=.. ntfy publish --wait-cmd <command>
 | 
				
			||||||
func parseTopicMessageCommand(c *cli.Context) (topic string, message string, command []string, err error) {
 | 
					func parseTopicMessageCommand(c *cli.Context) (topic string, message string, command []string, err error) {
 | 
				
			||||||
	// 1. ntfy publish --wait-cmd <topic> <command>
 | 
					 | 
				
			||||||
	// 2. NTFY_TOPIC=.. ntfy publish --wait-cmd <command>
 | 
					 | 
				
			||||||
	// 3. ntfy publish <topic> [<message>]
 | 
					 | 
				
			||||||
	// 4. NTFY_TOPIC=.. ntfy publish [<message>]
 | 
					 | 
				
			||||||
	var args []string
 | 
						var args []string
 | 
				
			||||||
	topic, args, err = parseTopicAndArgs(c)
 | 
						topic, args, err = parseTopicAndArgs(c)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -251,35 +254,39 @@ func remainingArgs(c *cli.Context, fromIndex int) []string {
 | 
				
			||||||
	return []string{}
 | 
						return []string{}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func waitForProcess(pid int) error {
 | 
					func waitForProcess(pid int) (message string, err error) {
 | 
				
			||||||
	if !processExists(pid) {
 | 
						if !processExists(pid) {
 | 
				
			||||||
		return fmt.Errorf("process with PID %d not running", pid)
 | 
							return "", fmt.Errorf("process with PID %d not running", pid)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						start := time.Now()
 | 
				
			||||||
	log.Debug("Waiting for process with PID %d to exit", pid)
 | 
						log.Debug("Waiting for process with PID %d to exit", pid)
 | 
				
			||||||
	for processExists(pid) {
 | 
						for processExists(pid) {
 | 
				
			||||||
		time.Sleep(500 * time.Millisecond)
 | 
							time.Sleep(500 * time.Millisecond)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	log.Debug("Process with PID %d exited", pid)
 | 
						runtime := time.Since(start).Round(time.Millisecond)
 | 
				
			||||||
	return nil
 | 
						log.Debug("Process with PID %d exited after %s", pid, runtime)
 | 
				
			||||||
 | 
						return fmt.Sprintf("Process with PID %d exited after %s", pid, runtime), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func runAndWaitForCommand(command []string) (message string, err error) {
 | 
					func runAndWaitForCommand(command []string) (message string, err error) {
 | 
				
			||||||
	prettyCmd := util.QuoteCommand(command)
 | 
						prettyCmd := util.QuoteCommand(command)
 | 
				
			||||||
	log.Debug("Running command: %s", prettyCmd)
 | 
						log.Debug("Running command: %s", prettyCmd)
 | 
				
			||||||
 | 
						start := time.Now()
 | 
				
			||||||
	cmd := exec.Command(command[0], command[1:]...)
 | 
						cmd := exec.Command(command[0], command[1:]...)
 | 
				
			||||||
	if log.IsTrace() {
 | 
						if log.IsTrace() {
 | 
				
			||||||
		cmd.Stdout = os.Stdout
 | 
							cmd.Stdout = os.Stdout
 | 
				
			||||||
		cmd.Stderr = os.Stderr
 | 
							cmd.Stderr = os.Stderr
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if err := cmd.Run(); err != nil {
 | 
						err = cmd.Run()
 | 
				
			||||||
 | 
						runtime := time.Since(start).Round(time.Millisecond)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
		if exitError, ok := err.(*exec.ExitError); ok {
 | 
							if exitError, ok := err.(*exec.ExitError); ok {
 | 
				
			||||||
			message = fmt.Sprintf("Command failed (exit code %d): %s", exitError.ExitCode(), prettyCmd)
 | 
								log.Debug("Command failed after %s (exit code %d): %s", runtime, exitError.ExitCode(), prettyCmd)
 | 
				
			||||||
		} else {
 | 
								return fmt.Sprintf("Command failed after %s (exit code %d): %s", runtime, exitError.ExitCode(), prettyCmd), nil
 | 
				
			||||||
			message = fmt.Sprintf("Command failed: %s, error: %s", prettyCmd, err.Error())
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
							// Hard fail when command does not exist or could not be properly launched
 | 
				
			||||||
		message = fmt.Sprintf("Command done: %s", prettyCmd)
 | 
							return "", fmt.Errorf("command failed: %s, error: %s", prettyCmd, err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	log.Debug(message)
 | 
						log.Debug("Command succeeded after %s: %s", runtime, prettyCmd)
 | 
				
			||||||
	return message, nil
 | 
						return fmt.Sprintf("Command succeeded after %s: %s", runtime, prettyCmd), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,11 @@ import (
 | 
				
			||||||
	"github.com/stretchr/testify/require"
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
	"heckel.io/ntfy/test"
 | 
						"heckel.io/ntfy/test"
 | 
				
			||||||
	"heckel.io/ntfy/util"
 | 
						"heckel.io/ntfy/util"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCLI_Publish_Subscribe_Poll_Real_Server(t *testing.T) {
 | 
					func TestCLI_Publish_Subscribe_Poll_Real_Server(t *testing.T) {
 | 
				
			||||||
| 
						 | 
					@ -70,3 +74,66 @@ func TestCLI_Publish_All_The_Things(t *testing.T) {
 | 
				
			||||||
	require.Equal(t, int64(0), m.Attachment.Expires)
 | 
						require.Equal(t, int64(0), m.Attachment.Expires)
 | 
				
			||||||
	require.Equal(t, "", m.Attachment.Type)
 | 
						require.Equal(t, "", m.Attachment.Type)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCLI_Publish_Wait_PID_And_Cmd(t *testing.T) {
 | 
				
			||||||
 | 
						s, port := test.StartServer(t)
 | 
				
			||||||
 | 
						defer test.StopServer(t, s, port)
 | 
				
			||||||
 | 
						topic := fmt.Sprintf("http://127.0.0.1:%d/mytopic", port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test: sleep 0.5
 | 
				
			||||||
 | 
						sleep := exec.Command("sleep", "0.5")
 | 
				
			||||||
 | 
						require.Nil(t, sleep.Start())
 | 
				
			||||||
 | 
						go sleep.Wait() // Must be called to release resources
 | 
				
			||||||
 | 
						start := time.Now()
 | 
				
			||||||
 | 
						app, _, stdout, _ := newTestApp()
 | 
				
			||||||
 | 
						require.Nil(t, app.Run([]string{"ntfy", "publish", "--wait-pid", strconv.Itoa(sleep.Process.Pid), topic}))
 | 
				
			||||||
 | 
						m := toMessage(t, stdout.String())
 | 
				
			||||||
 | 
						require.True(t, time.Since(start) >= 500*time.Millisecond)
 | 
				
			||||||
 | 
						require.Regexp(t, `Process with PID \d+ exited after `, m.Message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test: PID does not exist
 | 
				
			||||||
 | 
						app, _, _, _ = newTestApp()
 | 
				
			||||||
 | 
						err := app.Run([]string{"ntfy", "publish", "--wait-pid", "1234567", topic})
 | 
				
			||||||
 | 
						require.Error(t, err)
 | 
				
			||||||
 | 
						require.Equal(t, "process with PID 1234567 not running", err.Error())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test: Successful command (exit 0)
 | 
				
			||||||
 | 
						start = time.Now()
 | 
				
			||||||
 | 
						app, _, stdout, _ = newTestApp()
 | 
				
			||||||
 | 
						require.Nil(t, app.Run([]string{"ntfy", "publish", "--wait-cmd", topic, "sleep", "0.5"}))
 | 
				
			||||||
 | 
						m = toMessage(t, stdout.String())
 | 
				
			||||||
 | 
						require.True(t, time.Since(start) >= 500*time.Millisecond)
 | 
				
			||||||
 | 
						require.Contains(t, m.Message, `Command succeeded after `)
 | 
				
			||||||
 | 
						require.Contains(t, m.Message, `: sleep 0.5`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test: Failing command (exit 1)
 | 
				
			||||||
 | 
						app, _, stdout, _ = newTestApp()
 | 
				
			||||||
 | 
						require.Nil(t, app.Run([]string{"ntfy", "publish", "--wait-cmd", topic, "/bin/false", "false doesn't care about its args"}))
 | 
				
			||||||
 | 
						m = toMessage(t, stdout.String())
 | 
				
			||||||
 | 
						require.Contains(t, m.Message, `Command failed after `)
 | 
				
			||||||
 | 
						require.Contains(t, m.Message, `(exit code 1): /bin/false "false doesn't care about its args"`, m.Message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test: Non-existing command (hard fail!)
 | 
				
			||||||
 | 
						app, _, _, _ = newTestApp()
 | 
				
			||||||
 | 
						err = app.Run([]string{"ntfy", "publish", "--wait-cmd", topic, "does-not-exist-no-really", "really though"})
 | 
				
			||||||
 | 
						require.Error(t, err)
 | 
				
			||||||
 | 
						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())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Tests with NTFY_TOPIC set ////
 | 
				
			||||||
 | 
						require.Nil(t, os.Setenv("NTFY_TOPIC", topic))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test: Successful command with NTFY_TOPIC
 | 
				
			||||||
 | 
						app, _, stdout, _ = newTestApp()
 | 
				
			||||||
 | 
						require.Nil(t, app.Run([]string{"ntfy", "publish", "--env-topic", "--cmd", "echo", "hi there"}))
 | 
				
			||||||
 | 
						m = toMessage(t, stdout.String())
 | 
				
			||||||
 | 
						require.Equal(t, "mytopic", m.Topic)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test: Successful --wait-pid with NTFY_TOPIC
 | 
				
			||||||
 | 
						sleep = exec.Command("sleep", "0.2")
 | 
				
			||||||
 | 
						require.Nil(t, sleep.Start())
 | 
				
			||||||
 | 
						go sleep.Wait() // Must be called to release resources
 | 
				
			||||||
 | 
						app, _, stdout, _ = newTestApp()
 | 
				
			||||||
 | 
						require.Nil(t, app.Run([]string{"ntfy", "publish", "--env-topic", "--wait-pid", strconv.Itoa(sleep.Process.Pid)}))
 | 
				
			||||||
 | 
						m = toMessage(t, stdout.String())
 | 
				
			||||||
 | 
						require.Regexp(t, `Process with PID \d+ exited after .+ms`, m.Message)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										32
									
								
								util/util.go
									
										
									
									
									
								
							
							
						
						
									
										32
									
								
								util/util.go
									
										
									
									
									
								
							| 
						 | 
					@ -121,38 +121,6 @@ func ValidRandomString(s string, length int) bool {
 | 
				
			||||||
	return true
 | 
						return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DurationToHuman converts a duration to a human-readable format
 | 
					 | 
				
			||||||
func DurationToHuman(d time.Duration) (str string) {
 | 
					 | 
				
			||||||
	if d == 0 {
 | 
					 | 
				
			||||||
		return "0"
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	d = d.Round(time.Second)
 | 
					 | 
				
			||||||
	days := d / time.Hour / 24
 | 
					 | 
				
			||||||
	if days > 0 {
 | 
					 | 
				
			||||||
		str += fmt.Sprintf("%dd", days)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	d -= days * time.Hour * 24
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	hours := d / time.Hour
 | 
					 | 
				
			||||||
	if hours > 0 {
 | 
					 | 
				
			||||||
		str += fmt.Sprintf("%dh", hours)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	d -= hours * time.Hour
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	minutes := d / time.Minute
 | 
					 | 
				
			||||||
	if minutes > 0 {
 | 
					 | 
				
			||||||
		str += fmt.Sprintf("%dm", minutes)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	d -= minutes * time.Minute
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	seconds := d / time.Second
 | 
					 | 
				
			||||||
	if seconds > 0 {
 | 
					 | 
				
			||||||
		str += fmt.Sprintf("%ds", seconds)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ParsePriority parses a priority string into its equivalent integer value
 | 
					// ParsePriority parses a priority string into its equivalent integer value
 | 
				
			||||||
func ParsePriority(priority string) (int, error) {
 | 
					func ParsePriority(priority string) (int, error) {
 | 
				
			||||||
	switch strings.TrimSpace(strings.ToLower(priority)) {
 | 
						switch strings.TrimSpace(strings.ToLower(priority)) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,33 +5,8 @@ import (
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestDurationToHuman_SevenDays(t *testing.T) {
 | 
					 | 
				
			||||||
	d := 7 * 24 * time.Hour
 | 
					 | 
				
			||||||
	require.Equal(t, "7d", DurationToHuman(d))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestDurationToHuman_MoreThanOneDay(t *testing.T) {
 | 
					 | 
				
			||||||
	d := 49 * time.Hour
 | 
					 | 
				
			||||||
	require.Equal(t, "2d1h", DurationToHuman(d))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestDurationToHuman_LessThanOneDay(t *testing.T) {
 | 
					 | 
				
			||||||
	d := 17*time.Hour + 15*time.Minute
 | 
					 | 
				
			||||||
	require.Equal(t, "17h15m", DurationToHuman(d))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestDurationToHuman_TenOfThings(t *testing.T) {
 | 
					 | 
				
			||||||
	d := 10*time.Hour + 10*time.Minute + 10*time.Second
 | 
					 | 
				
			||||||
	require.Equal(t, "10h10m10s", DurationToHuman(d))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestDurationToHuman_Zero(t *testing.T) {
 | 
					 | 
				
			||||||
	require.Equal(t, "0", DurationToHuman(0))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestRandomString(t *testing.T) {
 | 
					func TestRandomString(t *testing.T) {
 | 
				
			||||||
	s1 := RandomString(10)
 | 
						s1 := RandomString(10)
 | 
				
			||||||
	s2 := RandomString(10)
 | 
						s2 := RandomString(10)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue