├── .gitignore ├── Dockerfile ├── util ├── default.go ├── http.go ├── default_test.go └── http_test.go ├── circle.yml ├── app.go ├── Makefile ├── user_interface ├── actions.go ├── default.go ├── actions_test.go └── default_test.go ├── README.md └── dontpad ├── client.go └── client_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Auto generated dir before app compilation 2 | bin 3 | 4 | # Auto generated dir before tests 5 | report 6 | 7 | # Undesired vim swap file 8 | *.swp 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine 2 | 3 | LABEL maintainer="Claudio Netto " 4 | 5 | RUN apk update && apk add git && \ 6 | go get github.com/nettoclaudio/dontpad-cli 7 | -------------------------------------------------------------------------------- /util/default.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | ) 8 | 9 | const ( 10 | StatusCodeSuccess = iota 11 | StatusCodeError 12 | ) 13 | 14 | var ( 15 | exit func(int) 16 | errorChannel io.Writer 17 | ) 18 | 19 | func init() { 20 | exit = func(code int) { os.Exit(code) } 21 | 22 | errorChannel = os.Stderr 23 | } 24 | 25 | func ShowMessageAndExitOnError(err error) { 26 | if err != nil { 27 | fmt.Fprintf(errorChannel, "%s", err) 28 | 29 | exit(StatusCodeError) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | timezone: 3 | America/Sao_Paulo 4 | 5 | test: 6 | pre: 7 | - go get github.com/mattn/goveralls 8 | override: 9 | - make test 10 | post: 11 | - mkdir -p ${CIRCLE_ARTIFACTS} && mv report ${CIRCLE_ARTIFACTS} 12 | - goveralls -coverprofile=${CIRCLE_ARTIFACTS}/report/coverage.out -service=circle-ci -repotoken=${COVERALLS_TOKEN} 13 | 14 | deployment: 15 | dockerhub: 16 | branch: master 17 | commands: 18 | - >- 19 | curl -X POST -H 'Content-Type: application/json' 20 | --data '{"source_type": "Branch", "source_name": "master"}' 21 | "https://registry.hub.docker.com/u/nettoclaudio/dontpad-cli/trigger/${DOCKER_HUB_TOKEN}/" 22 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/nettoclaudio/dontpad-cli/user_interface" 5 | "github.com/nettoclaudio/dontpad-cli/util" 6 | ) 7 | 8 | func main() { 9 | var setup user_interface.SetUp 10 | var err error 11 | 12 | setup, err = user_interface.ProcessCommands() 13 | 14 | util.ShowMessageAndExitOnError(err) 15 | 16 | if setup.ListSubfolders { 17 | err = user_interface.ListSubfolders(setup.RemoteFolder) 18 | 19 | util.ShowMessageAndExitOnError(err) 20 | 21 | return 22 | } 23 | 24 | if user_interface.HasPipedInput() { 25 | inputData := user_interface.GetPipedInputData() 26 | 27 | user_interface.WriteFolder(setup.RemoteFolder, inputData); 28 | } else { 29 | err = user_interface.ShowContentFolder(setup.RemoteFolder) 30 | 31 | util.ShowMessageAndExitOnError(err) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /util/http.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "io" 7 | "io/ioutil" 8 | ) 9 | 10 | var ( 11 | doGetRequest func(string) (*http.Response, error) 12 | readAll func(io.Reader) ([]byte, error) 13 | ) 14 | 15 | func init() { 16 | doGetRequest = func(url string) (*http.Response, error) { return http.Get(url) } 17 | readAll = func(r io.Reader) ([]byte, error) { return ioutil.ReadAll(r) } 18 | } 19 | 20 | func ExtractHttpResponseBodyIfStatusCodeIsOk(url string) ([]byte, error) { 21 | var body []byte 22 | 23 | response, err := doGetRequest(url) 24 | 25 | if err != nil { 26 | return body, err 27 | } 28 | 29 | defer response.Body.Close() 30 | 31 | if response.StatusCode == http.StatusOK { 32 | body, err = readAll(response.Body) 33 | 34 | return body, err 35 | } else { 36 | return body, errors.New("Unexpected status code.") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO ?= go 2 | 3 | goBuildFlags ?= -race -x 4 | goBuildDir ?= bin 5 | goAppName ?= dontpad-cli 6 | 7 | installDir ?= /usr/local/bin 8 | reportDir ?= report 9 | 10 | packages := $(shell $(GO) list ./...) 11 | 12 | .PHONY: all build pre-build clean install uninstall test 13 | 14 | all: build 15 | 16 | build: pre-build 17 | $(GO) build $(goBuildFlags) -o $(goBuildDir)/$(goAppName) app.go 18 | 19 | pre-build: clean 20 | mkdir $(goBuildDir) 21 | 22 | clean: 23 | rm -rf $(goBuildDir) 24 | 25 | install: 26 | ln -s $(shell pwd)/$(goBuildDir)/$(goAppName) $(installDir) 27 | 28 | uninstall: 29 | rm -f $(installDir)/$(goAppName) 30 | 31 | test: pre-test 32 | echo "mode: count" > $(reportDir)/coverage-all.out 33 | $(foreach pkg,$(packages),\ 34 | touch $(reportDir)/coverage.out;\ 35 | $(GO) test -coverprofile=$(reportDir)/coverage.out -covermode=count $(pkg);\ 36 | tail -n +2 $(reportDir)/coverage.out >> $(reportDir)/coverage-all.out;\ 37 | ) 38 | mv -f $(reportDir)/coverage-all.out $(reportDir)/coverage.out 39 | 40 | pre-test: clean-old-reports 41 | mkdir -p $(reportDir) 42 | 43 | clean-old-reports: 44 | rm -rf $(reportDir) 45 | -------------------------------------------------------------------------------- /user_interface/actions.go: -------------------------------------------------------------------------------- 1 | package user_interface 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "github.com/nettoclaudio/dontpad-cli/dontpad" 8 | ) 9 | 10 | var ( 11 | getContentFolder func(string) (dontpad.Response, error) 12 | getSubfolders func(string) ([]string, error) 13 | outputChannel io.Writer 14 | ) 15 | 16 | func init() { 17 | getContentFolder = func(remoteFolder string) (dontpad.Response, error) { return dontpad.GetContentFolder(remoteFolder) } 18 | 19 | getSubfolders = func(remoteFolder string) ([]string, error) { return dontpad.GetSubfolders(remoteFolder) } 20 | 21 | outputChannel = os.Stdout 22 | } 23 | 24 | func ShowContentFolder(remoteFolder string) error { 25 | response, err := getContentFolder(remoteFolder) 26 | 27 | if err == nil { 28 | fmt.Fprintf(outputChannel, "%s", response.Body) 29 | } 30 | 31 | return err 32 | } 33 | 34 | func ListSubfolders(remoteFolder string) error { 35 | subfolders, err := getSubfolders(remoteFolder) 36 | 37 | if err == nil { 38 | for _, subfolder := range subfolders { 39 | fmt.Fprintf(outputChannel, "%s\n", subfolder) 40 | } 41 | } 42 | 43 | return err 44 | } 45 | 46 | func WriteFolder(remoteFolder, data string) { 47 | dontpad.ReplaceContentFolder(remoteFolder, data) 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dontpad-cli 2 | 3 | A minimal tool for Dontpad's users under CLI. 4 | 5 | **Warning**: This app isn't a Dontpad official software. 6 | 7 | [![CircleCI Status](https://circleci.com/gh/nettoclaudio/dontpad-cli.svg?style=svg&circle-token=3bd4b3d74f5089c30aa224545365a5585e6c994d)](https://circleci.com/gh/nettoclaudio/dontpad-cli) 8 | [![Coverage Status](https://coveralls.io/repos/github/nettoclaudio/dontpad-cli/badge.svg?branch=master)](https://coveralls.io/github/nettoclaudio/dontpad-cli?branch=master) 9 | [![MicroBadger Info](https://images.microbadger.com/badges/image/nettoclaudio/dontpad-cli.svg)](https://microbadger.com/images/nettoclaudio/dontpad-cli) 10 | 11 | --- 12 | 13 | ## Usage 14 | 15 | [![asciicast](https://asciinema.org/a/150757.png)](https://asciinema.org/a/150757) 16 | 17 | ## Quick start 18 | 19 | ```bash 20 | go get github.com/nettoclaudio/dontpad-cli 21 | ${GOPATH:-"~/go"}/bin/dontpad-cli /my-first-folder/annotations 22 | ``` 23 | 24 | or (preferred for developers) 25 | 26 | ```bash 27 | mkdir -p ${GOPATH}/src/github.com/nettoclaudio 28 | cd ${GOPATH}/src/github.com/nettoclaudio 29 | git clone https://github.com/nettoclaudio/dontpad-cli.git 30 | cd dontpad-cli 31 | make 32 | ./bin/dontpad-cli /my-first-folder/annotations 33 | 34 | # Optional - Put the executable in your PATH 35 | sudo make install 36 | dontpad-cli /my-first-folder/annotations 37 | ``` 38 | 39 | ## TO DO 40 | 41 | + ~~View a folder~~ 42 | + ~~Edit a folder~~ 43 | + ~~List subfolders~~ 44 | + Backup a folder(and subfolders) 45 | 46 | Made with ~~Dontpad~~ <3 47 | -------------------------------------------------------------------------------- /util/default_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "testing" 7 | ) 8 | 9 | func TestShowMessageAndExitOnError_ErrorNotFalse_MustShowErrorMessage(t *testing.T) { 10 | oldExit := exit 11 | oldErrorChannel := errorChannel 12 | 13 | defer func() { 14 | exit = oldExit 15 | errorChannel = oldErrorChannel 16 | }() 17 | 18 | exit = func(code int) {} 19 | 20 | errorBuffer := &bytes.Buffer{} 21 | errorChannel = errorBuffer 22 | 23 | expectedErrorMessage := "Something wrong occurred." 24 | err := errors.New(expectedErrorMessage) 25 | 26 | ShowMessageAndExitOnError(err) 27 | 28 | actualErrorMessage := errorBuffer.String() 29 | 30 | if actualErrorMessage != expectedErrorMessage { 31 | t.Errorf("Expected [%s] but got [%s].", expectedErrorMessage, actualErrorMessage) 32 | } 33 | } 34 | 35 | func TestShowMessageAndExitOnError_ErrorFalse_MustNotShowAnything(t *testing.T) { 36 | oldExit := exit 37 | oldErrorChannel := errorChannel 38 | 39 | defer func() { 40 | exit = oldExit 41 | errorChannel = oldErrorChannel 42 | }() 43 | 44 | exit = func(code int) {} 45 | 46 | errorBuffer := &bytes.Buffer{} 47 | errorChannel = errorBuffer 48 | 49 | ShowMessageAndExitOnError(nil) 50 | 51 | if errorBuffer.Len() != 0 { 52 | t.Errorf("Expected a false value but it didn't.") 53 | } 54 | } 55 | 56 | func TestShowMessageAndExitOnError_ErrorNotFalse_MustCallExitFunction(t *testing.T) { 57 | var wasExistCalled bool 58 | 59 | oldExit := exit 60 | oldErrorChannel := errorChannel 61 | 62 | defer func() { 63 | errorChannel = oldErrorChannel 64 | exit = oldExit 65 | }() 66 | 67 | exit = func(code int) { wasExistCalled = true } 68 | 69 | errorChannel = &bytes.Buffer{} 70 | 71 | ShowMessageAndExitOnError(errors.New("Error Message.")) 72 | 73 | if ! wasExistCalled { 74 | t.Errorf("Expected true but got false.") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /dontpad/client.go: -------------------------------------------------------------------------------- 1 | package dontpad 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "net/url" 9 | "github.com/nettoclaudio/dontpad-cli/util" 10 | ) 11 | 12 | type Response struct { 13 | Changed bool `json:"changed"` 14 | LastUpdate int `json:"lastUpdate"` 15 | Body string `json:"body"` 16 | } 17 | 18 | var ( 19 | extractHttpResponseBody func(string) ([]byte, error) 20 | 21 | templateURL string = "http://dontpad.com/%s" 22 | templateURLViewFolder string = templateURL + ".body.json?lastUpdate=%d" 23 | templateURLSubfolders string = templateURL + ".menu.json" 24 | ) 25 | 26 | func init() { 27 | extractHttpResponseBody = func(url string) ([]byte, error) { return util.ExtractHttpResponseBodyIfStatusCodeIsOk(url) } 28 | } 29 | 30 | func GetContentFolder(remoteFolder string) (Response, error) { 31 | var response Response 32 | 33 | url := formatTemplateURLViewFolder(remoteFolder, 0) 34 | 35 | body, err := extractHttpResponseBody(url) 36 | 37 | if err == nil { 38 | json.Unmarshal(body, &response) 39 | } 40 | 41 | return response, err 42 | } 43 | 44 | func GetSubfolders(remoteFolder string) ([]string, error) { 45 | var subfolders []string 46 | 47 | url := formatTemplateURLSubfolders(remoteFolder) 48 | 49 | body, err := extractHttpResponseBody(url) 50 | 51 | if err == nil { 52 | json.Unmarshal(body, &subfolders) 53 | } 54 | 55 | return subfolders, err 56 | } 57 | 58 | func ReplaceContentFolder(remoteFolder, data string) { 59 | finalURL := formatTemplateURL(remoteFolder) 60 | 61 | formValues := url.Values{} 62 | formValues.Set("text", data) 63 | 64 | http.PostForm(finalURL, formValues) 65 | } 66 | 67 | func formatTemplateURL(remoteFolder string) string { 68 | buffer := &bytes.Buffer{} 69 | 70 | fmt.Fprintf(buffer, templateURL, remoteFolder) 71 | 72 | return buffer.String() 73 | } 74 | 75 | func formatTemplateURLViewFolder(remoteFolder string, lastUpdate int) string { 76 | buffer := &bytes.Buffer{} 77 | 78 | fmt.Fprintf(buffer, templateURLViewFolder, remoteFolder, 0) 79 | 80 | return buffer.String() 81 | } 82 | 83 | func formatTemplateURLSubfolders(remoteFolder string) string { 84 | buffer := &bytes.Buffer{} 85 | 86 | fmt.Fprintf(buffer, templateURLSubfolders, remoteFolder) 87 | 88 | return buffer.String() 89 | } 90 | -------------------------------------------------------------------------------- /user_interface/default.go: -------------------------------------------------------------------------------- 1 | package user_interface 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | type SetUp struct { 14 | RemoteFolder string 15 | ListSubfolders bool 16 | } 17 | 18 | var ( 19 | getInputFileMode func() os.FileMode 20 | 21 | programName string 22 | outputDefault io.Writer 23 | setup SetUp 24 | 25 | inputReader *bufio.Reader 26 | ) 27 | 28 | func init() { 29 | programName = os.Args[0] 30 | outputDefault = os.Stderr 31 | 32 | getInputFileMode = func() os.FileMode { 33 | inputInfo, _ := os.Stdin.Stat() 34 | 35 | return inputInfo.Mode() 36 | } 37 | 38 | inputReader = bufio.NewReader(os.Stdin) 39 | 40 | flag.Usage = customUsage 41 | 42 | flag.BoolVar(&setup.ListSubfolders, "subfolders", false, "List all subfolders from a remote folder.") 43 | 44 | flag.Parse() 45 | } 46 | 47 | func ProcessCommands() (SetUp, error) { 48 | if ! hasRemoteFolder() { 49 | flag.Usage() 50 | 51 | return setup, errors.New("Remote folder is required.") 52 | } 53 | 54 | setup.RemoteFolder = sanitizeRemoteFolder(flag.Arg(0)) 55 | 56 | if ! isValidRemoteFolder(setup.RemoteFolder) { 57 | return setup, errors.New("[ERROR] The remote folder '" + setup.RemoteFolder +"' is invalid.") 58 | } 59 | 60 | return setup, nil 61 | } 62 | 63 | func HasPipedInput() bool { 64 | 65 | return getInputFileMode() & os.ModeNamedPipe != 0 66 | } 67 | 68 | func GetPipedInputData() string { 69 | var data []rune 70 | 71 | for { 72 | rune, _, err := inputReader.ReadRune() 73 | 74 | if err != nil && err == io.EOF { 75 | break 76 | } 77 | 78 | data = append(data, rune) 79 | } 80 | 81 | return string(data) 82 | } 83 | 84 | func customUsage() { 85 | usageHeader := "Usage: %s \n" 86 | 87 | fmt.Fprintf(outputDefault, usageHeader, programName) 88 | 89 | flag.PrintDefaults() 90 | } 91 | 92 | func hasRemoteFolder() bool { 93 | 94 | numberOfRemainingArgs := flag.NArg() 95 | 96 | if numberOfRemainingArgs > 0 { 97 | return true 98 | } 99 | 100 | return false 101 | } 102 | 103 | func sanitizeRemoteFolder(remoteFolder string) string { 104 | return strings.Trim(remoteFolder, " /") 105 | } 106 | 107 | func isValidRemoteFolder(remoteFolder string) bool { 108 | 109 | sanitizedRemoteFolder := sanitizeRemoteFolder(remoteFolder) 110 | 111 | if strings.HasPrefix(sanitizedRemoteFolder, "static/") || 112 | strings.HasSuffix(sanitizedRemoteFolder, ".zip") || 113 | sanitizedRemoteFolder == "" { 114 | return false 115 | } 116 | 117 | return true 118 | } 119 | -------------------------------------------------------------------------------- /user_interface/actions_test.go: -------------------------------------------------------------------------------- 1 | package user_interface 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "testing" 7 | "github.com/nettoclaudio/dontpad-cli/dontpad" 8 | ) 9 | 10 | func TestShowContentFolder_NoError_MustPrintResponseBodyOnBuffer(t *testing.T) { 11 | oldGetContentFolder := getContentFolder 12 | oldOutputChannel := outputChannel 13 | 14 | defer func() { 15 | getContentFolder = oldGetContentFolder 16 | outputChannel = oldOutputChannel 17 | }() 18 | 19 | expected := "Hey Dude.\n How are you?" 20 | 21 | getContentFolder = func(remoteFolder string) (dontpad.Response, error) { return dontpad.Response{Body: expected}, nil } 22 | 23 | buffer := &bytes.Buffer{} 24 | outputChannel = buffer 25 | 26 | ShowContentFolder("my-folder") 27 | 28 | actual := buffer.String() 29 | 30 | if actual != expected { 31 | t.Errorf("Expected [%s] but got [%s].", expected, actual) 32 | } 33 | } 34 | 35 | func TestShowContentFolder_TimeoutError_MustNotPrintAnything(t *testing.T) { 36 | oldGetContentFolder := getContentFolder 37 | oldOutputChannel := outputChannel 38 | 39 | defer func() { 40 | getContentFolder = oldGetContentFolder 41 | outputChannel = oldOutputChannel 42 | }() 43 | 44 | getContentFolder = func(remoteFolder string) (dontpad.Response, error) { return dontpad.Response{}, errors.New("Timeout") } 45 | buffer := &bytes.Buffer{} 46 | 47 | outputChannel = buffer 48 | 49 | ShowContentFolder("my-folder") 50 | 51 | if buffer.Len() > 0 { 52 | t.Errorf("Should not print on stadard output.") 53 | } 54 | } 55 | 56 | func TestListSubfolders_NoError_MustPrintOneSubfolderPerLine(t *testing.T) { 57 | oldGetSubfolders := getSubfolders 58 | oldOutputChannel := outputChannel 59 | 60 | defer func() { 61 | getSubfolders = oldGetSubfolders 62 | outputChannel = oldOutputChannel 63 | }() 64 | 65 | getSubfolders = func(remoteFolder string) ([]string, error) { return []string{"sub1", "sub2", "sub3"}, nil } 66 | 67 | buffer := &bytes.Buffer{} 68 | outputChannel = buffer 69 | 70 | ListSubfolders("my-folder") 71 | 72 | actual := buffer.String() 73 | 74 | expected := "sub1\nsub2\nsub3\n" 75 | 76 | if actual != expected { 77 | t.Errorf("Expected [%s] but got [%s].", expected, actual) 78 | } 79 | } 80 | 81 | func TestListSubfolders_TimeoutErrors_MustNotPrintAnything(t *testing.T) { 82 | oldGetSubfolders := getSubfolders 83 | oldOutputChannel := outputChannel 84 | 85 | defer func() { 86 | getSubfolders = oldGetSubfolders 87 | outputChannel = oldOutputChannel 88 | }() 89 | 90 | getSubfolders = func(remoteFolder string) ([]string, error) { return []string{}, errors.New("Timeout") } 91 | 92 | buffer := &bytes.Buffer{} 93 | outputChannel = buffer 94 | 95 | ListSubfolders("my-folder") 96 | 97 | if buffer.Len() > 0 { 98 | t.Errorf("Should not print on stadard output.") 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /util/http_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | func TestExtractHttpResponseBodyIfStatusCodeIsOk_ConnectionRefusedError_MustReturnError(t *testing.T) { 13 | oldDoGetRequest := doGetRequest 14 | 15 | defer func() { doGetRequest = oldDoGetRequest }() 16 | 17 | doGetRequest = func(url string) (*http.Response, error) { 18 | return nil, errors.New("Connection refused.") 19 | } 20 | 21 | _, err := ExtractHttpResponseBodyIfStatusCodeIsOk("URL") 22 | 23 | if err == nil { 24 | t.Errorf("Should return an error.") 25 | } 26 | } 27 | 28 | func TestExtractHttpResponseBodyIfStatusCodeIsOk_ResponseIsOk_MustReturnResponseBody(t *testing.T) { 29 | oldDoGetRequest := doGetRequest 30 | 31 | defer func() { doGetRequest = oldDoGetRequest }() 32 | 33 | expectedContent := "It works." 34 | 35 | doGetRequest = func(url string) (*http.Response, error) { 36 | response := &http.Response{ 37 | Status: "200 OK", 38 | StatusCode: http.StatusOK, 39 | Proto: "HTTP/1.1", 40 | ProtoMajor: 1, 41 | ProtoMinor: 1, 42 | Body: ioutil.NopCloser(bytes.NewBufferString(expectedContent)), 43 | ContentLength: int64(len(expectedContent)), 44 | Request: nil, 45 | Header: make(http.Header, 0), 46 | } 47 | 48 | return response, nil 49 | } 50 | 51 | actualContent, err := ExtractHttpResponseBodyIfStatusCodeIsOk("http://fakedlocalhost/") 52 | 53 | if err != nil { 54 | t.Errorf("Should not return an error.") 55 | } 56 | 57 | if string(actualContent) != expectedContent { 58 | t.Errorf("Expected [%s] but got [%s].", expectedContent, string(actualContent)) 59 | } 60 | } 61 | 62 | func TestExtractHttpResponseBodyIfStatusCodeIsOk_PageNotFound_MustReturnAnError(t *testing.T) { 63 | oldDoGetRequest := doGetRequest 64 | 65 | defer func() { doGetRequest = oldDoGetRequest }() 66 | 67 | doGetRequest = func(url string) (*http.Response, error) { 68 | response := &http.Response{ 69 | Status: "404 NotFound", 70 | StatusCode: http.StatusNotFound, 71 | Proto: "HTTP/1.1", 72 | ProtoMajor: 1, 73 | ProtoMinor: 1, 74 | Body: ioutil.NopCloser(bytes.NewBufferString("")), 75 | ContentLength: int64(len("")), 76 | Request: nil, 77 | Header: make(http.Header, 0), 78 | } 79 | 80 | return response, nil 81 | } 82 | 83 | _, err := ExtractHttpResponseBodyIfStatusCodeIsOk("http://fakedlocalhost/") 84 | 85 | if err == nil { 86 | t.Errorf("Should return an error.") 87 | } 88 | } 89 | 90 | func TestExtractHttpResponseBodyIfStatusCodeIsOk_ResponseIsOk_ProblemOnReader_MustReturnAnError(t *testing.T) { 91 | oldDoGetRequest := doGetRequest 92 | oldReadAll := readAll 93 | 94 | defer func() { 95 | doGetRequest = oldDoGetRequest 96 | readAll = oldReadAll 97 | }() 98 | 99 | content := "It works." 100 | 101 | doGetRequest = func(url string) (*http.Response, error) { 102 | response := &http.Response{ 103 | Status: "200 OK", 104 | StatusCode: http.StatusOK, 105 | Proto: "HTTP/1.1", 106 | ProtoMajor: 1, 107 | ProtoMinor: 1, 108 | Body: ioutil.NopCloser(bytes.NewBufferString(content)), 109 | ContentLength: int64(len(content)), 110 | Request: nil, 111 | Header: make(http.Header, 0), 112 | } 113 | 114 | return response, nil 115 | } 116 | 117 | readAll = func(r io.Reader) ([]byte, error) { return []byte{}, errors.New("Problem on EOF") } 118 | 119 | _, err := ExtractHttpResponseBodyIfStatusCodeIsOk("http://fakedlocalhost/") 120 | 121 | if err == nil { 122 | t.Errorf("Should return an error.") 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /dontpad/client_test.go: -------------------------------------------------------------------------------- 1 | package dontpad 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestFormatTemplateURLViewFolder_MustReturnSameStringAsExpected(t *testing.T) { 10 | remoteFolder := "my-folder" 11 | lastUpdate := 0 12 | 13 | expectedURL := "http://dontpad.com/my-folder.body.json?lastUpdate=0" 14 | 15 | actualURL := formatTemplateURLViewFolder(remoteFolder, lastUpdate) 16 | 17 | if actualURL != expectedURL { 18 | t.Errorf("Expected [%s] but got [%s].", expectedURL, actualURL) 19 | } 20 | } 21 | 22 | func TestGetContentFolder_ConnectionTimeout_MustReturnError(t *testing.T) { 23 | oldExtractHttpResponseBody := extractHttpResponseBody 24 | 25 | defer func() { extractHttpResponseBody = oldExtractHttpResponseBody }() 26 | 27 | extractHttpResponseBody = func(url string) ([]byte, error) { 28 | return []byte{}, errors.New("Timeout") 29 | } 30 | 31 | _, err := GetContentFolder("my-remote-folder") 32 | 33 | if err == nil { 34 | t.Errorf("Should return an error.") 35 | } 36 | } 37 | 38 | func TestGetContentFolder_UnexpectedResponse_MustReturnError(t *testing.T) { 39 | oldExtractHttpResponseBody := extractHttpResponseBody 40 | 41 | defer func() { extractHttpResponseBody = oldExtractHttpResponseBody }() 42 | 43 | extractHttpResponseBody = func(url string) ([]byte, error) { 44 | return []byte{}, errors.New("Unexpected status code.") 45 | } 46 | 47 | _, err := GetContentFolder("my-remote-folder") 48 | 49 | if err == nil { 50 | t.Errorf("Should return an error.") 51 | } 52 | } 53 | 54 | func TestGetContentFolder_ResponseOK_MustNotReturnError_MustReturnSameBodyMessage(t *testing.T) { 55 | oldExtractHttpResponseBody := extractHttpResponseBody 56 | 57 | defer func() { extractHttpResponseBody = oldExtractHttpResponseBody }() 58 | 59 | extractHttpResponseBody = func(url string) ([]byte, error) { 60 | return []byte(`{"changed":false,"lastUpdate":0,"body":"Hello, howdy!"}`), nil 61 | } 62 | 63 | resp, err := GetContentFolder("my-remote-folder") 64 | 65 | if err != nil { 66 | t.Errorf("Should not return an error.") 67 | } 68 | 69 | expectedContent := "Hello, howdy!" 70 | 71 | if resp.Body != expectedContent { 72 | t.Errorf("Expected [%s] but got [%s].", expectedContent, resp.Body) 73 | } 74 | } 75 | 76 | func TestFormatTemplateURLSubfolders_MustReturnSameStringAsExpected(t *testing.T) { 77 | remoteFolder := "my-folder" 78 | 79 | expectedURL := "http://dontpad.com/my-folder.menu.json" 80 | 81 | actualURL := formatTemplateURLSubfolders(remoteFolder) 82 | 83 | if actualURL != expectedURL { 84 | t.Errorf("Expected [%s] but got [%s].", expectedURL, actualURL) 85 | } 86 | } 87 | 88 | func TestGetSubfolders_UnexpectedResponse_MustReturnError(t *testing.T) { 89 | oldExtractHttpResponseBody := extractHttpResponseBody 90 | 91 | defer func() { extractHttpResponseBody = oldExtractHttpResponseBody }() 92 | 93 | extractHttpResponseBody = func(url string) ([]byte, error) { 94 | return []byte{}, errors.New("Unexpected status code.") 95 | } 96 | 97 | _, err := GetContentFolder("my-remote-folder") 98 | 99 | if err == nil { 100 | t.Errorf("Should return an error.") 101 | } 102 | } 103 | 104 | func TestGetSubfolders_ResponseOK_MustNotReturnError_MustReturnSameList(t *testing.T) { 105 | oldExtractHttpResponseBody := extractHttpResponseBody 106 | 107 | defer func() { extractHttpResponseBody = oldExtractHttpResponseBody }() 108 | 109 | extractHttpResponseBody = func(url string) ([]byte, error) { 110 | return []byte(`["subfolder1", "subfolder2", "subfolder3"]`), nil 111 | } 112 | 113 | subfolders, err := GetSubfolders("my-remote-folder") 114 | 115 | if err != nil { 116 | t.Errorf("Should not return an error.") 117 | } 118 | 119 | expectedSubfolders := []string{"subfolder1", "subfolder2", "subfolder3"} 120 | 121 | if ! reflect.DeepEqual(subfolders, expectedSubfolders) { 122 | t.Errorf("Expected %v but got %v.", expectedSubfolders, subfolders) 123 | } 124 | } 125 | 126 | func TestGetSubfolders_ResponseOK_NoSubfolders_MustReturnEmptyList(t *testing.T) { 127 | oldExtractHttpResponseBody := extractHttpResponseBody 128 | 129 | defer func() { extractHttpResponseBody = oldExtractHttpResponseBody }() 130 | 131 | extractHttpResponseBody = func(url string) ([]byte, error) { 132 | return []byte(`[]`), nil 133 | } 134 | 135 | subfolders, err := GetSubfolders("my-remote-folder") 136 | 137 | if err != nil { 138 | t.Errorf("Should not return an error.") 139 | } 140 | 141 | expectedSubfolders := []string{} 142 | 143 | if ! reflect.DeepEqual(subfolders, expectedSubfolders) { 144 | t.Errorf("Expected %v but got %v.", expectedSubfolders, subfolders) 145 | } 146 | } 147 | 148 | func TestFormatTemplateURL_MustReturnSameStringAsExpected(t *testing.T) { 149 | remoteFolder := "my-folder" 150 | 151 | expectedURL := "http://dontpad.com/my-folder" 152 | 153 | actualURL := formatTemplateURL(remoteFolder) 154 | 155 | if actualURL != expectedURL { 156 | t.Errorf("Expected [%s] but got [%s].", expectedURL, actualURL) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /user_interface/default_test.go: -------------------------------------------------------------------------------- 1 | package user_interface 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "flag" 7 | "os" 8 | "testing" 9 | ) 10 | 11 | func TestCustomUsage_WriteUsageMessageInBuffer_MustWriteSameUsageMessage(t *testing.T) { 12 | buffer := &bytes.Buffer{} 13 | 14 | outputDefault = buffer 15 | programName = "dontpad-cli.exe" 16 | 17 | customUsage() 18 | 19 | actualHeader := buffer.String() 20 | expectedHeader := "Usage: dontpad-cli.exe \n" 21 | 22 | if actualHeader != expectedHeader { 23 | t.Errorf("Expected [%s] but got [%s].", expectedHeader, actualHeader) 24 | } 25 | } 26 | 27 | func TestHasRemoteFolder_NoArgs_MustReturnFalse(t *testing.T) { 28 | os.Args = []string{"dontpad-cli"} 29 | 30 | flag.Parse() 31 | 32 | if hasRemoteFolder() == true { 33 | t.Errorf("Expected [false] but got [true].") 34 | } 35 | } 36 | 37 | func TestHasRemoteFolder_ContainsRemoteFolderArg_MustReturnTrue(t *testing.T) { 38 | os.Args = []string{"dontpad-cli", "/my-remote-folder/subfolder"} 39 | 40 | flag.Parse() 41 | 42 | if hasRemoteFolder() == false { 43 | t.Errorf("Expected [true] but got [false].") 44 | } 45 | } 46 | 47 | func TestHasRemoteFolder_ContainsHelpFlag_MustReturnFalse(t *testing.T) { 48 | os.Args = []string{"dontpad-cli", "--help"} 49 | 50 | flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) 51 | 52 | flag.Parse() 53 | 54 | if hasRemoteFolder() == true { 55 | t.Errorf("Expected [false] but got [true].") 56 | } 57 | } 58 | 59 | func TestHasRemoteFolder_ContainsHelpFlag_ContainsRemoteFolder_MustReturnTrue(t *testing.T) { 60 | os.Args = []string{"dontpad-cli", "--help", "/my-remote-folder"} 61 | 62 | flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) 63 | 64 | flag.Parse() 65 | 66 | if hasRemoteFolder() == false { 67 | t.Errorf("Expected [true] but got [false].") 68 | } 69 | } 70 | 71 | func TestSanitizeRemoteFolder_RemoteFolderUnsanitized_MustReturnDifferentRemoteFolderFromOriginal(t *testing.T) { 72 | expectedRemoteFolder := "my-folder" 73 | 74 | actualSanitizedRemoteFolder := sanitizeRemoteFolder(" /my-folder ") 75 | 76 | if actualSanitizedRemoteFolder != expectedRemoteFolder { 77 | t.Errorf("Expected [%s] but got [%s].", expectedRemoteFolder, actualSanitizedRemoteFolder) 78 | } 79 | } 80 | 81 | func TestSanitizeRemoteFolder_RemoteFolderSanitized_MustReturnSameRemoteFolderFromOriginal(t *testing.T) { 82 | expectedRemoteFolder := "my-folder/subfolder" 83 | 84 | actualSanitizedRemoteFolder := sanitizeRemoteFolder("my-folder/subfolder") 85 | 86 | if actualSanitizedRemoteFolder != expectedRemoteFolder { 87 | t.Errorf("Expected [%s] but got [%s].", expectedRemoteFolder, actualSanitizedRemoteFolder) 88 | } 89 | } 90 | 91 | func TestIsValidRemoteFolder_InvalidsPrefixes_AllMustReturnFalse(t *testing.T) { 92 | invalidFolders := []string{"", "/", "/folder.zip", "/static/forbidden/folder"} 93 | 94 | for _, folder := range invalidFolders { 95 | if isValidRemoteFolder(folder) { 96 | t.Errorf("Folder [%s] is invalid but got valid.", folder) 97 | } 98 | } 99 | } 100 | 101 | func TestIsValidRemoteFolder_ValidPrefixes_AllMustReturnTrue(t *testing.T) { 102 | validFolders := []string{"my-folder", "/my/subfolder", "test.zipa"} 103 | 104 | for _, folder := range validFolders { 105 | if ! isValidRemoteFolder(folder) { 106 | t.Errorf("Folder [%s] is valid but got invalid.", folder) 107 | } 108 | } 109 | } 110 | 111 | func TestProcessCommands_NoRemoteFolder_MustReturnError(t *testing.T) { 112 | os.Args = []string{"dontpad-cli"} 113 | 114 | flag.Parse() 115 | 116 | _, err := ProcessCommands() 117 | 118 | if err == nil { 119 | t.Errorf("Expected not nil error.") 120 | } 121 | } 122 | 123 | func TestProcessCommands_InvalidRemotePath_MustReturnError(t *testing.T) { 124 | os.Args = []string{"dontpad-cli", "/static/invalid/path"} 125 | 126 | flag.Parse() 127 | 128 | _, err := ProcessCommands() 129 | 130 | if err == nil { 131 | t.Errorf("Expected not nil error.") 132 | } 133 | } 134 | 135 | func TestProcessCommands_MustReturnSetupCorrectly(t *testing.T) { 136 | os.Args = []string{"dontpad-cli", "/my-folder/agenda"} 137 | 138 | flag.Parse() 139 | 140 | setup, err := ProcessCommands() 141 | 142 | if err != nil { 143 | t.Errorf("Expected nil error.") 144 | } 145 | 146 | if setup.RemoteFolder != "my-folder/agenda" { 147 | t.Errorf("Expected [my-folder/agenda] but got [%s]", setup.RemoteFolder) 148 | } 149 | } 150 | 151 | func TestHasPipedInput_ReceivedPipedInput_MustReturnTrue(t *testing.T) { 152 | oldGetInputFileMode := getInputFileMode 153 | 154 | defer func() { getInputFileMode = oldGetInputFileMode }() 155 | 156 | getInputFileMode = func() os.FileMode { 157 | return os.ModeNamedPipe 158 | } 159 | 160 | if ! HasPipedInput() { 161 | t.Errorf("Expected [true] but got [false]") 162 | } 163 | } 164 | 165 | func TestHasPipedInput_NotReceivedPipedInput_MustReturnFalse(t *testing.T) { 166 | oldGetInputFileMode := getInputFileMode 167 | 168 | defer func() { getInputFileMode = oldGetInputFileMode }() 169 | 170 | getInputFileMode = func() os.FileMode { 171 | return os.ModeDir 172 | } 173 | 174 | if HasPipedInput() { 175 | t.Errorf("Expected [false] but got [true]") 176 | } 177 | } 178 | 179 | func TestGetPipedInputData_MustReturnExpectedInput(t *testing.T) { 180 | oldInputReader := inputReader 181 | 182 | defer func() { inputReader = oldInputReader }() 183 | 184 | expectedInput := "Hello world" 185 | 186 | inputReader = bufio.NewReader(bytes.NewBufferString(expectedInput)) 187 | 188 | got := GetPipedInputData() 189 | 190 | if got != expectedInput { 191 | t.Errorf("Expected [%s] but got [%s]", expectedInput, got); 192 | } 193 | } 194 | --------------------------------------------------------------------------------