Skip to content

Commit 49ce682

Browse files
authored
Merge pull request #31 from rockstaedt/26-update-subcommand
Resolve "Provide update functionality"
2 parents 6eed75e + 7b013f8 commit 49ce682

File tree

6 files changed

+286
-0
lines changed

6 files changed

+286
-0
lines changed

cmd/update.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package cmd
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"log"
8+
"net/http"
9+
"os"
10+
"rockstaedt/commit-message-check/internal/model"
11+
"runtime"
12+
)
13+
14+
type responseData struct {
15+
TagName string `json:"tag_name"`
16+
}
17+
18+
func Update(config *model.UpdateConfig) int {
19+
config.LatestVersion = getLatestTag(config.TagUrl)
20+
if config.LatestVersion == "" {
21+
log.Println("Error at retrieving latest version.")
22+
return 1
23+
}
24+
25+
if config.Version == config.LatestVersion {
26+
log.Println("Current version is latest version.")
27+
return 0
28+
}
29+
30+
return downloadScript(config)
31+
}
32+
33+
func getLatestTag(url string) string {
34+
res, err := http.Get(url)
35+
if err != nil {
36+
return ""
37+
}
38+
39+
if res.StatusCode != 200 {
40+
return ""
41+
}
42+
43+
var data responseData
44+
err = json.NewDecoder(res.Body).Decode(&data)
45+
if err != nil {
46+
return ""
47+
}
48+
49+
return data.TagName
50+
}
51+
52+
func downloadScript(config *model.UpdateConfig) int {
53+
file, err := os.Create(config.DownloadPath + "/commit-message-check")
54+
if err != nil {
55+
return 1
56+
}
57+
58+
res, err := http.Get(getBinaryUrl(config))
59+
if err != nil {
60+
return 2
61+
}
62+
63+
if res.StatusCode != 200 {
64+
return 3
65+
}
66+
67+
_, _ = io.Copy(file, res.Body)
68+
69+
return 0
70+
}
71+
72+
func getBinaryUrl(config *model.UpdateConfig) string {
73+
return fmt.Sprintf(
74+
"%s/commit-message-check-%s-%s-%s",
75+
config.BinaryBaseUrl,
76+
config.LatestVersion,
77+
runtime.GOOS,
78+
runtime.GOARCH,
79+
)
80+
}

cmd/update_test.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"github.com/stretchr/testify/assert"
7+
"log"
8+
"net/http"
9+
"net/http/httptest"
10+
"os"
11+
"rockstaedt/commit-message-check/internal/model"
12+
"runtime"
13+
"testing"
14+
)
15+
16+
func TestUpdate(t *testing.T) {
17+
buffer := &bytes.Buffer{}
18+
log.SetOutput(buffer)
19+
20+
t.Run("returns 0 and", func(t *testing.T) {
21+
22+
t.Run("logs a message when local version is latest", func(t *testing.T) {
23+
buffer.Reset()
24+
ts := httptest.NewServer(getHandlerFor(`{"tag_name":"v1.0.0"}`))
25+
defer ts.Close()
26+
config := &model.UpdateConfig{Version: "v1.0.0", TagUrl: ts.URL}
27+
28+
status := Update(config)
29+
30+
assert.Equal(t, 0, status)
31+
assert.Contains(t, buffer.String(), "Current version is latest version.")
32+
})
33+
34+
t.Run("downloads install script if newer version available", func(t *testing.T) {
35+
ts := httptest.NewServer(getHandlerFor(`{"tag_name":"v1.1.0"}`))
36+
defer ts.Close()
37+
tempDir := t.TempDir()
38+
config := &model.UpdateConfig{Version: "v1.0.0", TagUrl: ts.URL, DownloadPath: tempDir}
39+
40+
_ = Update(config)
41+
42+
assert.FileExists(t, tempDir+"/commit-message-check")
43+
})
44+
})
45+
46+
t.Run("returns 1 and message when error at request", func(t *testing.T) {
47+
buffer.Reset()
48+
ts := httptest.NewServer(getHandlerFor("", 500))
49+
defer ts.Close()
50+
config := &model.UpdateConfig{Version: "v1.0.0", TagUrl: ts.URL, DownloadPath: ""}
51+
52+
status := Update(config)
53+
54+
assert.Equal(t, 1, status)
55+
assert.Contains(t, buffer.String(), "Error at retrieving latest version.")
56+
})
57+
}
58+
59+
func TestGetLatestTag(t *testing.T) {
60+
t.Run("returns latest tag when request is successfully", func(t *testing.T) {
61+
ts := httptest.NewServer(getHandlerFor(`{"tag_name":"v1.2.0"}`))
62+
defer ts.Close()
63+
64+
tag := getLatestTag(ts.URL)
65+
66+
assert.Equal(t, "v1.2.0", tag)
67+
})
68+
69+
t.Run("returns empty string when", func(t *testing.T) {
70+
71+
t.Run("HTTP protocol error", func(t *testing.T) {
72+
tag := getLatestTag("xxx")
73+
74+
assert.Empty(t, tag)
75+
})
76+
77+
t.Run("response status code is not 200", func(t *testing.T) {
78+
ts := httptest.NewServer(getHandlerFor("", 500))
79+
defer ts.Close()
80+
81+
tag := getLatestTag(ts.URL)
82+
83+
assert.Empty(t, tag)
84+
})
85+
86+
t.Run("response body is empty", func(t *testing.T) {
87+
ts := httptest.NewServer(getHandlerFor(""))
88+
defer ts.Close()
89+
90+
tag := getLatestTag(ts.URL)
91+
92+
assert.Empty(t, tag)
93+
})
94+
})
95+
}
96+
97+
func getHandlerFor(resBody string, statusCode ...int) http.HandlerFunc {
98+
sc := 200
99+
if len(statusCode) > 0 {
100+
sc = statusCode[0]
101+
}
102+
103+
return func(w http.ResponseWriter, r *http.Request) {
104+
w.WriteHeader(sc)
105+
w.Header().Set("Content-Type", "application/json")
106+
_, _ = w.Write([]byte(resBody))
107+
}
108+
}
109+
110+
func TestDownloadScript(t *testing.T) {
111+
112+
getProtectedPath := func(t *testing.T) string {
113+
tempDir := t.TempDir()
114+
protectedPath := tempDir + "/protected"
115+
err := os.Mkdir(protectedPath, 0000)
116+
assert.Nil(t, err)
117+
118+
return protectedPath
119+
}
120+
121+
t.Run("returns 0 and writes downloaded binary content to file", func(t *testing.T) {
122+
tempDir := t.TempDir()
123+
err := os.WriteFile(tempDir+"/dummy", []byte("i am a go binary"), os.ModePerm)
124+
assert.Nil(t, err)
125+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
126+
targetUrl := fmt.Sprintf("/commit-message-check-v1.1.1-%s-%s", runtime.GOOS, runtime.GOARCH)
127+
if r.URL.String() == targetUrl {
128+
http.ServeFile(w, r, tempDir+"/dummy")
129+
}
130+
}))
131+
defer ts.Close()
132+
config := &model.UpdateConfig{LatestVersion: "v1.1.1", DownloadPath: tempDir, BinaryBaseUrl: ts.URL}
133+
134+
status := downloadScript(config)
135+
136+
assert.Equal(t, 0, status)
137+
contentBytes, err := os.ReadFile(tempDir + "/commit-message-check")
138+
assert.Nil(t, err)
139+
assert.Contains(t, string(contentBytes), "i am a go binary")
140+
})
141+
142+
t.Run("returns 1 when error at creating file", func(t *testing.T) {
143+
config := &model.UpdateConfig{DownloadPath: getProtectedPath(t)}
144+
145+
status := downloadScript(config)
146+
147+
assert.Equal(t, 1, status)
148+
})
149+
150+
t.Run("return 2 when http protocol error", func(t *testing.T) {
151+
tempDir := t.TempDir()
152+
config := &model.UpdateConfig{DownloadPath: tempDir, BinaryBaseUrl: "/xxx"}
153+
154+
status := downloadScript(config)
155+
156+
assert.Equal(t, 2, status)
157+
})
158+
159+
t.Run("return 3 when request not successfully", func(t *testing.T) {
160+
tempDir := t.TempDir()
161+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
162+
w.WriteHeader(500)
163+
}))
164+
defer ts.Close()
165+
config := &model.UpdateConfig{DownloadPath: tempDir, BinaryBaseUrl: ts.URL}
166+
167+
status := downloadScript(config)
168+
169+
assert.Equal(t, 3, status)
170+
})
171+
}

internal/model/update_config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package model
2+
3+
type UpdateConfig struct {
4+
Version string
5+
LatestVersion string
6+
TagUrl string
7+
BinaryBaseUrl string
8+
DownloadPath string
9+
}

main.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"log"
88
"os"
99
"rockstaedt/commit-message-check/cmd"
10+
"rockstaedt/commit-message-check/internal/model"
1011
"rockstaedt/commit-message-check/util"
1112
)
1213

@@ -51,6 +52,21 @@ func main() {
5152
status = cmd.Setup(gitPath)
5253
case "uninstall":
5354
status = cmd.Uninstall(gitPath)
55+
case "update":
56+
config := &model.UpdateConfig{
57+
Version: version,
58+
TagUrl: "https://api.github.com/repos/rockstaedt/commit-message-check/releases/latest",
59+
BinaryBaseUrl: "https://github.com/rockstaedt/commit-message-check/releases/latest/download/",
60+
DownloadPath: cwd,
61+
}
62+
63+
status = cmd.Update(config)
64+
65+
if status > 0 {
66+
log.Println("[ERROR]\t Could not update commit-message-check.")
67+
break
68+
}
69+
log.Printf("[SUCCESS]\t Updated commit-message-check successfully to %s", config.LatestVersion)
5470
case "validate":
5571
commitLines, err := txtreader.GetLinesFromTextFile(os.Args[2])
5672
if err != nil {

util/manual.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ func PrintManual(writer io.Writer) {
1414
_, _ = fmt.Fprint(writer, "- Subcommands:\n")
1515
_, _ = fmt.Fprint(writer, "\tsetup\t\tInstalls the commit-msg script in every hook directory.\n")
1616
_, _ = fmt.Fprint(writer, "\tuninstall\tRemoves all commit-msg scripts.\n")
17+
_, _ = fmt.Fprint(writer, "\tupdate\t\tUpdates the binary to the latest version.\n")
1718
_, _ = fmt.Fprint(writer, "\tvalidate <PATH>\tValidates the commit message written in <PATH>.\n")
1819
}

util/manual_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ func TestPrintManual(t *testing.T) {
6868
assert.Contains(t, buffer.String(), msg)
6969
})
7070

71+
t.Run("update", func(t *testing.T) {
72+
buffer.Reset()
73+
74+
PrintManual(buffer)
75+
76+
msg := "\tupdate\t\tUpdates the binary to the latest version.\n"
77+
assert.Contains(t, buffer.String(), msg)
78+
})
79+
7180
t.Run("validate", func(t *testing.T) {
7281
buffer.Reset()
7382

0 commit comments

Comments
 (0)