diff --git a/.gitignore b/.gitignore index 91abe37..556eb12 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ artifacts *.dll *.so *.dylib +coverage # Test binary, built with `go test -c` *.test diff --git a/Makefile b/Makefile index 98771f9..a71580b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ # Inspired from https://dustinspecker.com/posts/go-combined-unit-integration-code-coverage/ and https://netdevops.me/2023/test-coverage-for-go-integration-tests/ BIN_DIR = $(CURDIR)/artifacts BINARY = $(BIN_DIR)/gocustomurls +COVERAGE_DIR = $(CURDIR)/coverage CURRENT_DIR = $(shell pwd) CUR_TAG = $(shell git tag | sort -g | tail -1 | cut -c 2-) VERSION_NUMBER ?= 0.0.0 @@ -29,4 +30,25 @@ lint: .PHONY: build build: - go build -o $(BINARY) \ No newline at end of file + go build -o $(BINARY) + +.PHONY: build-debug +build-debug: + mkdir -p $(BIN_DIR) + go build -cover -o $(BINARY) . + +.PHONY: test +test: build-debug + rm -rf $(COVERAGE_DIR) + mkdir -p $(COVERAGE_DIR) + go test -cover ./... -args -test.gocoverdir="$(COVERAGE_DIR)" + +.PHONY: coverage-full +coverage-full: test + go tool covdata textfmt -i=$(COVERAGE_DIR) -o $(COVERAGE_DIR)/coverage.out + go tool cover -func=$(COVERAGE_DIR)/coverage.out + +.PHONY: coverage-html +coverage-html: coverage-full + go tool cover -html=./coverage/coverage.out -o ./coverage/coverage.html + open ./coverage/coverage.html \ No newline at end of file diff --git a/conf.go b/conf.go index 65c0eea..9919890 100644 --- a/conf.go +++ b/conf.go @@ -85,13 +85,20 @@ func getDefaults() (map[string]string, error) { } m["rulesFp"] = filepath.Join(confDir, "gocustomcurls", "rules.json") m["confFp"] = filepath.Join(confDir, "gocustomcurls", "config.json") - m["logfp"] = filepath.Join(homeDir, ".gocustomurls", "logs", "app.log") + m["logFp"] = filepath.Join(homeDir, ".gocustomurls", "logs", "app.log") return m, nil } -func generateDefaultConfigFile() (parsedConf, error) { +func generateDefaultConfigFile(defaultObj map[string]string) (parsedConf, error) { var p parsedConf - defaults, err := getDefaults() + var err error + var defaults map[string]string + if len(defaultObj) == 0 { + defaults, err = getDefaults() + } else { + defaults = defaultObj + } + if err != nil { return p, err } @@ -127,7 +134,7 @@ func checkIfSizeIsConfigured(fsize string) (bool, error) { } } if len(found) == 0 { - return false, fmt.Errorf("%s has the incorrect suffix, Please use one of this suffixes {\"K\", \"KB\",\"M\", \"MB\", \"G\", \"GB\"}", fsize) + return false, fmt.Errorf("%s has the incorrect suffix, Please use one of this suffixes {\"KB\", \"MB\", \"GB\"}", fsize) } return true, nil @@ -141,7 +148,7 @@ func (c *Config) LoadMainConfigFile(fp string) (parsedConf, error) { if !ok { // generate config file errorLog.Println("Warning, generating default config file") - conf, err = generateDefaultConfigFile() + conf, err = generateDefaultConfigFile(map[string]string{}) if err != nil { return conf, err } diff --git a/conf_test.go b/conf_test.go new file mode 100644 index 0000000..b2d83d7 --- /dev/null +++ b/conf_test.go @@ -0,0 +1,126 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLoadMappingFile(t *testing.T) { + + t.Run("load the mapping file correctly - initial load", func(t *testing.T) { + tmpDir := t.TempDir() + + mkDirForTest(t, fmt.Sprintf("%s/tmp", tmpDir)) + rulesJsonFp := "testData/rules.json" + + expected := fmt.Sprintf("%s/tmp/rules.json", tmpDir) + + cpFileForTest(t, rulesJsonFp, expected) + + cfg := &Config{} + + assert.Equal(t, cfg.MappingFilePath, "") + assert.Empty(t, cfg.MappingRules) + err := cfg.LoadMappingFile(expected) + assert.Equal(t, err, nil) + assert.Equal(t, cfg.MappingFilePath, expected) + assert.NotEmpty(t, cfg.MappingRules) + }) + + t.Run("load the mapping file correctly - reload", func(t *testing.T) { + tmpDir := t.TempDir() + + mkDirForTest(t, fmt.Sprintf("%s/tmp", tmpDir)) + rulesJsonFp := "testData/rules.json" + + expected := fmt.Sprintf("%s/tmp/rules.json", tmpDir) + + cpFileForTest(t, rulesJsonFp, expected) + + cfg := &Config{} + + assert.Equal(t, cfg.MappingFilePath, "") + assert.Empty(t, cfg.MappingRules) + err := cfg.LoadMappingFile(expected) + assert.Equal(t, err, nil) + assert.Equal(t, cfg.MappingFilePath, expected) + assert.NotEmpty(t, cfg.MappingRules) + oldRules := cfg.MappingRules + + rulesJsonFp = "testData/rules2.json" + + cpFileForTest(t, rulesJsonFp, expected) + + err = cfg.LoadMappingFile(expected) + assert.Equal(t, err, nil) + assert.Equal(t, cfg.MappingFilePath, expected) + assert.NotEmpty(t, cfg.MappingRules) + + newRules := cfg.MappingRules + + assert.NotEqualValues(t, oldRules, newRules) + }) +} + +func TestGetDefaults(t *testing.T) { + actual, err := getDefaults() + assert.Equal(t, err, nil) + assert.Contains(t, actual, "rulesFp") + assert.Contains(t, actual, "logFp") + assert.Contains(t, actual, "confFp") +} + +func TestSizeIsConfigured(t *testing.T) { + tests := map[string]struct { + input string + want bool + }{ + "wrong input - K": {input: "5677.45K", want: false}, + "wrong input - KiB": {input: "5677.45KiB", want: false}, + "wrong input - M": {input: "9.45M", want: false}, + "wrong input - G": {input: "9.45G", want: false}, + "correct input - KB": {input: "5.45KB", want: true}, + "correct input - MB": {input: "5677.45MB", want: true}, + "correct input - GB": {input: "9.45GB", want: true}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got, _ := checkIfSizeIsConfigured(tc.input) + assert.Equal(t, got, tc.want) + }) + } +} + +func TestGenerateConfigFile(t *testing.T) { + tmpDir := t.TempDir() + + tempLogsDir := fmt.Sprintf("%s/tmp/logs", tmpDir) + tempConfigDir := fmt.Sprintf("%s/tmp/config", tmpDir) + + mkDirForTest(t, tempLogsDir) + mkDirForTest(t, tempConfigDir) + + defaultConfig := map[string]string{ + "rulesFp": fmt.Sprintf("%s/rules.json", tempConfigDir), + "confFp": fmt.Sprintf("%s/config.json", tempConfigDir), + "logFp": fmt.Sprintf("%s/app.log", tempLogsDir), + } + + ok := IsDirEmpty(t, tempConfigDir) + assert.Equal(t, ok, true) + ok = IsDirEmpty(t, tempLogsDir) + assert.Equal(t, ok, true) + + expected, err := generateDefaultConfigFile(defaultConfig) + assert.Equal(t, err, nil) + assert.NotEmpty(t, expected) + + ok = IsDirEmpty(t, tempConfigDir) + assert.Equal(t, ok, false) + ok = IsDirEmpty(t, tempLogsDir) + assert.Equal(t, ok, true) + +} diff --git a/go.mod b/go.mod index 341eca9..ce261a7 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module gocustomurls go 1.20 + +require github.com/stretchr/testify v1.9.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..60ce688 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/testData/app.log b/testData/app.log new file mode 100644 index 0000000..e69de29 diff --git a/testData/config.json b/testData/config.json new file mode 100644 index 0000000..e69de29 diff --git a/testData/rules.json b/testData/rules.json new file mode 100644 index 0000000..2403123 --- /dev/null +++ b/testData/rules.json @@ -0,0 +1,14 @@ +{ + "mappings": [ + { + "vanity_url":"scale.dev/x/migrate", + "protocol":"git", + "real_url":"https://github.com/scale/migrate" + }, + { + "vanity_url":"localhost:7070/x/touche", + "protocol":"git", + "real_url":"https://github.com/mine/touche" + } + ] +} diff --git a/testData/rules2.json b/testData/rules2.json new file mode 100644 index 0000000..aebbb19 --- /dev/null +++ b/testData/rules2.json @@ -0,0 +1,19 @@ +{ + "mappings": [ + { + "vanity_url":"scale.dev/x/migrate", + "protocol":"git", + "real_url":"https://github.com/scale/migrate" + }, + { + "vanity_url":"codeberg.org/woodpecker-plugins/plugin-gitea-release", + "protocol":"git", + "real_url":"https://codeberg.org/woodpecker-plugins/gitea-release" + }, + { + "vanity_url":"localhost:7070/x/touche", + "protocol":"git", + "real_url":"https://github.com/mine/touche" + } + ] +} diff --git a/testUtils_test.go b/testUtils_test.go new file mode 100644 index 0000000..187658a --- /dev/null +++ b/testUtils_test.go @@ -0,0 +1,65 @@ +package main + +import ( + "io" + "os" + "testing" +) + +func mkDirForTest(t *testing.T, fp string) { + err := os.MkdirAll(fp, os.ModePerm) + if err != nil { + t.Fatal(err) + } +} + +func cpFileForTest(t *testing.T, src string, dst string) { + var srcfd *os.File + var dstfd *os.File + var err error + var srcinfo os.FileInfo + srcfd, err = os.Open(src) + if err != nil { + t.Fatal(err) + } + defer srcfd.Close() + dstfd, err = os.Create(dst) + if err != nil { + t.Fatal(err) + } + + defer dstfd.Close() + _, err = io.Copy(dstfd, srcfd) + if err != nil { + t.Fatal(err) + } + srcinfo, err = os.Stat(src) + if err != nil { + t.Fatal(err) + } + err = os.Chmod(dst, srcinfo.Mode()) + if err != nil { + t.Fatal(err) + } +} + +func writeForTest(t *testing.T, fp string, data []byte) { + err := os.WriteFile(fp, data, 0666) + if err != nil { + t.Fatal(err) + } +} + +func IsDirEmpty(t *testing.T, name string) bool { + f, err := os.Open(name) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + // read in ONLY one file + _, err = f.Readdir(1) + + // and if the file is EOF... well, the dir is empty. + return err == io.EOF +}