From dffcd8a53704c3ac562b4000dbce0c8e8181c18f Mon Sep 17 00:00:00 2001 From: jolheiser Date: Sun, 2 Jan 2022 15:26:00 -0600 Subject: [PATCH] Add more tests and CI Signed-off-by: jolheiser --- .woodpecker.yml | 54 +++++++++++++++++++++ cmd/cabinet/command.go | 1 - internal/router/rate.go | 5 ++ internal/router/rate_test.go | 38 +++++++++++++++ internal/router/router_test.go | 85 +++++++++++++++++++++++++++------- 5 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 .woodpecker.yml create mode 100644 internal/router/rate_test.go diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..e4f6c5b --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,54 @@ +clone: + git: + image: woodpeckerci/plugin-git:next + +pipeline: + compliance: + image: golang:1.17 + commands: + - go test -race ./... + - go vet ./... + when: + event: pull_request + + build: + image: golang:1.17 + commands: + - GOOS="linux" go build go.jolheiser.com/cabinet/cmd/cabinet + - GOOS="windows" go build go.jolheiser.com/cabinet/cmd/cabinet + + release-main: + image: jolheiser/drone-gitea-main:latest + settings: + base_url: https://git.jojodev.com + api_key: + from_secret: gitea_token + files: + - "cabinet" + - "cabinet.exe" + when: + event: push + branch: main + + release-tag: + image: plugins/gitea-release:1 + settings: + base_url: https://git.jojodev.com + api_key: + from_secret: gitea_token + files: + - "cabinet" + - "cabinet.exe" + when: + event: tag + tag: v* + + prune: + image: jolheiser/drone-gitea-prune + settings: + api_key: + from_secret: gitea_token + base: https://git.jojodev.com + when: + event: tag + tag: v* diff --git a/cmd/cabinet/command.go b/cmd/cabinet/command.go index e1a7720..82abb2c 100644 --- a/cmd/cabinet/command.go +++ b/cmd/cabinet/command.go @@ -12,7 +12,6 @@ import ( workspace2 "go.jolheiser.com/cabinet/internal/workspace" "github.com/AlecAivazis/survey/v2" - "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) diff --git a/internal/router/rate.go b/internal/router/rate.go index f7b7b47..22bd430 100644 --- a/internal/router/rate.go +++ b/internal/router/rate.go @@ -98,5 +98,10 @@ func (l *Limit) hasHitSizeLimit(remoteAddr string, size int64) bool { l.mx.Lock() defer l.mx.Unlock() + if l.request[ip] == nil { + l.request[ip] = rate.NewLimiter(rate.Limit(l.rpm/60), l.rpm) + l.size[ip] = rate.NewLimiter(rate.Limit(l.spm/60), l.burst) + } + return !l.size[ip].AllowN(time.Now(), int(size)) } diff --git a/internal/router/rate_test.go b/internal/router/rate_test.go new file mode 100644 index 0000000..669e99c --- /dev/null +++ b/internal/router/rate_test.go @@ -0,0 +1,38 @@ +package router + +import ( + "testing" + + "github.com/matryer/is" +) + +func TestLimit(t *testing.T) { + localhost := "127.0.0.1:80" + + t.Run("Requests Per Minute", func(t *testing.T) { + assert := is.New(t) + limit := NewLimit(5, 5, 10, 10) + for idx := 0; idx < 5; idx++ { + assert.Equal(limit.hasHitRequestLimit(localhost), false) // Five requests is okay + } + assert.Equal(limit.hasHitRequestLimit(localhost), true) // A sixth requests is not okay + }) + + t.Run("Size Per Minute", func(t *testing.T) { + assert := is.New(t) + limit := NewLimit(5, 5, 5, 10) + for idx := 0; idx < 5; idx++ { + assert.Equal(limit.hasHitSizeLimit(localhost, 1), false) // Five requests is okay + } + assert.Equal(limit.hasHitSizeLimit(localhost, 1), true) // A sixth requests is not okay + }) + + t.Run("Size Per Minute w/Burst", func(t *testing.T) { + assert := is.New(t) + limit := NewLimit(5, 5, 10, 10) + for idx := 0; idx < 10; idx++ { + assert.Equal(limit.hasHitSizeLimit(localhost, 1), false) // Ten requests is okay (with Burst) + } + assert.Equal(limit.hasHitSizeLimit(localhost, 1), true) // An eleventh requests is not okay (with Burst) + }) +} diff --git a/internal/router/router_test.go b/internal/router/router_test.go index e550c66..c384ecf 100644 --- a/internal/router/router_test.go +++ b/internal/router/router_test.go @@ -7,13 +7,16 @@ import ( "strings" "testing" - "github.com/matryer/is" "go.jolheiser.com/cabinet" + "go.jolheiser.com/cabinet/internal/workspace" "go.jolheiser.com/cabinet/internal/workspace/mock" + + "github.com/matryer/is" + "github.com/rs/zerolog" ) func TestRouter(t *testing.T) { - assert := is.New(t) + zerolog.SetGlobalLevel(zerolog.Disabled) m := mock.New() server := httptest.NewUnstartedServer(nil) @@ -24,20 +27,68 @@ func TestRouter(t *testing.T) { c := cabinet.New(server.URL, cabinet.WithHTTPClient(server.Client())) - // Redirect - redir := "https://duckduckgo.com" - u, _, err := c.Redirect(redir) - assert.NoErr(err) // Creating a redirect should succeed - resp, err := http.Get(u) - assert.NoErr(err) - assert.Equal(redir, resp.Request.URL.String()) // The redirect should match what was given + testRouter(t, "No Token", true, true, c) - file := "foobar" - f, _, err := c.File("test.txt", strings.NewReader(file)) - assert.NoErr(err) // Creating a file should succeed - resp, err = http.Get(f) - assert.NoErr(err) - b, err := io.ReadAll(resp.Body) - assert.NoErr(err) - assert.Equal(file, string(b)) // The file should match what was given + // All token + aToken := workspace.Token{Key: "all", Permission: workspace.TokenFile | workspace.TokenRedirect} + _ = m.AddToken(aToken) + testRouter(t, "Incorrect Token", false, false, c) + c.Token = aToken.Key + testRouter(t, "All Token", true, true, c) + + // Redirect token + rToken := workspace.Token{Key: "redirect", Permission: workspace.TokenRedirect} + _ = m.AddToken(rToken) + c.Token = rToken.Key + testRouter(t, "Redirect Token", true, false, c) + + // File token + fToken := workspace.Token{Key: "file", Permission: workspace.TokenFile} + _ = m.AddToken(fToken) + c.Token = fToken.Key + testRouter(t, "File Token", false, true, c) +} + +func testRouter(t *testing.T, name string, rSucceed, fSucceed bool, c *cabinet.Client) { + // Redirect + t.Run(name+" Redirect", func(t *testing.T) { + assert := is.New(t) + redir := "https://duckduckgo.com" + u, res, err := c.Redirect(redir) + switch { + case rSucceed: + assert.NoErr(err) // Creating a redirect should succeed + resp, err := http.Get(u) + assert.NoErr(err) + assert.Equal(redir, resp.Request.URL.String()) // The redirect should match what was given + case c.Token == "": + assert.Equal(res.StatusCode, http.StatusUnauthorized) // Creating a redirect should be unauthorized + case c.Token != "": + assert.Equal(res.StatusCode, http.StatusForbidden) // Creating a redirect should be forbidden + default: + assert.Fail() // Unknown case in test + } + }) + + // File + t.Run(name+" File", func(t *testing.T) { + assert := is.New(t) + file := "foobar" + f, res, err := c.File("test.txt", strings.NewReader(file)) + switch { + case fSucceed: + assert.NoErr(err) // Creating a file should succeed + resp, err := http.Get(f) + assert.NoErr(err) + b, err := io.ReadAll(resp.Body) + assert.NoErr(err) + assert.Equal(file, string(b)) // The file should match what was given + case c.Token == "": + assert.Equal(res.StatusCode, http.StatusUnauthorized) // Creating a redirect should be unauthorized + case c.Token != "": + assert.Equal(res.StatusCode, http.StatusForbidden) // Creating a redirect should be forbidden + default: + assert.Fail() // Unknown case in test + } + }) }