fuzzyworld

template / commit

Adding a makefile and more tests.

763c260d1024d05f50d51aa0c7490667ce8b4278 · Pavel Safronov · 2022-10-05 18:09:20 -0700 · parents 32035957d746

Adding a makefile and more tests.
 .gitignore         |  1 +
 Makefile           | 26 +++++++++++++++
 config.go          | 69 +++-----------------------------------
 config_test.go     | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 postscript.go      |  3 +-
 postscript_test.go | 45 +++++++++++++++++++++++++
 profile.go         | 67 +++++++++++++++++++++++++++++++++++++
 profile_test.go    | 59 ++++++++++++++++++++++++++++++++
 utils_test.go      | 37 +++++++++++++++++++++
 9 files changed, 339 insertions(+), 66 deletions(-)

diff --git a/.gitignore b/.gitignore
index 984b600..4c85f83 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 .git
 template.exe
 template
+test
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..280617a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,26 @@
+SHELL := pwsh.exe
+.SHELLFLAGS := -Command
+
+mkfile_path := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
+BINARY_NAME := template
+
+.PHONY: test
+
+build:
+	go build .
+
+install:
+	./scripts/clean-install.ps1
+
+test:
+	go test -v ./... -coverprofile="./test/coverage.out"
+
+test_coverage:
+	go test -v ./... -coverprofile="./test/coverage.out"
+	go tool cover -html="./test/coverage.out" -o "./test/coverage.html"
+
+test_coverage_html: test_coverage
+	start "./test/coverage.html"
+
+clean:
+	go clean
diff --git a/config.go b/config.go
index 7bc8769..c8a6526 100644
--- a/config.go
+++ b/config.go
@@ -3,49 +3,10 @@ package main
 import (
 	"fmt"
 	"os"
-	"strings"
 
-	"golang.org/x/exp/slices"
 	"gopkg.in/yaml.v3"
 )
 
-type ProfileType string
-
-const (
-	FileType   ProfileType = "file"
-	FolderType ProfileType = "folder"
-	ScriptType ProfileType = "script"
-)
-
-type OSType string
-
-const (
-	Windows OSType = "Windows"
-	Mac     OSType = "Mac"
-	All     OSType = "All"
-)
-
-type Profile struct {
-	IsDefault        bool
-	Name             string
-	Description      string
-	Path             string
-	Type             ProfileType
-	NamePatterns     []string
-	PostScripts      []string
-	ScriptShell      string
-	OS               OSType
-	FileTemplatePath string
-	FileTemplate     string
-
-	// FileTemplatePath string
-}
-
-var forbiddenProfileNames = []string{
-	"list",
-	"get",
-}
-
 type Config struct {
 	Profiles []Profile
 }
@@ -141,6 +102,7 @@ Today's notes
 				Description: "Creates a folder in the current folder in the format YYYY-MM-DD[-ProjectName][-vVersion]",
 				Type:        FolderType,
 				ScriptShell: "pwsh",
+				PostScripts: []string{},
 				NamePatterns: []string{
 					"{{$version := getVersion}}" +
 						"{{.Year}}-{{.Month}}-{{.Day}}" +
@@ -188,9 +150,6 @@ Today's notes
 				Type:        ScriptType,
 				NamePatterns: []string{
 					"{{.Year}}-{{.Month}}-{{.Day}}{{if .ProjectName}}-{{.ProjectName}}{{end}}",
-					"{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}{{if .ProjectName}}-{{.ProjectName}}{{end}}",
-					"{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}{{if .ProjectName}}-{{.ProjectName}}{{end}}",
-					"{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}{{if .ProjectName}}-{{.ProjectName}}{{end}}",
 				},
 				ScriptShell: "pwsh",
 				PostScripts: []string{
@@ -211,35 +170,15 @@ func validateConfig(config Config) error {
 
 	foundDefault := false
 	for _, profile := range config.Profiles {
-		if profile.Name == "" {
-			return fmt.Errorf("Profile name is required")
-		}
-		if slices.Contains(forbiddenProfileNames, profile.Name) {
-			return fmt.Errorf("forbidden profile name '%s'", profile.Name)
-		}
 		if profile.IsDefault {
 			if foundDefault {
 				return fmt.Errorf("multiple default profiles found")
 			}
 			foundDefault = true
 		}
-		if profile.NamePatterns == nil || len(profile.NamePatterns) == 0 {
-			return fmt.Errorf("no name patterns found for profile %s", profile.Name)
-		}
-		if profile.Path != "" && strings.Contains(profile.Path, "\\") {
-			return fmt.Errorf("invalid path for profile %s: %s", profile.Name, profile.Path)
-		}
-		if profile.Type == "" || (profile.Type != FileType && profile.Type != FolderType && profile.Type != ScriptType) {
-			return fmt.Errorf("invalid profile type %s for profile %s", profile.Type, profile.Name)
-		}
-		if profile.Type != FileType && profile.FileTemplatePath != "" {
-			return fmt.Errorf("file template path is not supported for profile %s, which has Type=%s", profile.Name, profile.Type)
-		}
-		if profile.Type != ScriptType && profile.NamePatterns != nil && len(profile.NamePatterns) > 1 {
-			return fmt.Errorf("multiple name patterns are not supported for profile %s, which has Type=%s", profile.Name, profile.Type)
-		}
-		if profile.ScriptShell == "" && profile.PostScripts != nil && len(profile.PostScripts) > 0 {
-			return fmt.Errorf("script shell is required for profile %s", profile.Name)
+		error := validateProfile(profile)
+		if error != nil {
+			return error
 		}
 	}
 	return nil
diff --git a/config_test.go b/config_test.go
new file mode 100644
index 0000000..9b9a208
--- /dev/null
+++ b/config_test.go
@@ -0,0 +1,98 @@
+package main
+
+import (
+	"errors"
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSaveLoadConfig(t *testing.T) {
+	config := defaultConfig()
+	configPath := "./test/test-config.yaml"
+
+	// save config
+	err := saveConfig(configPath, config)
+	assert.Nil(t, err, "Error saving config")
+
+	// load config
+	loadedConfig, err := loadConfig(configPath)
+	assert.Nil(t, err, "Error loading config")
+
+	// test config
+	assert.Equal(t, loadedConfig, config)
+
+	// test load non-existent config
+	_, err = loadConfig("non-existent-file.yaml")
+	assert.NotNil(t, err, "Expected to fail to load config, but succeeded")
+
+	// test load invalid yaml
+	os.WriteFile("./test/invalid-yaml.yaml", []byte("invalid yaml"), 0644)
+	_, err = loadConfig("./test/invalid-yaml.yaml")
+	assert.NotNil(t, err, "Expected to fail to parse config, but succeeded")
+
+	// test saving config to invalid path
+	err = saveConfig("invalid/path/invalid-config.yaml", config)
+	assert.NotNil(t, err, "Expected to fail to save config, but succeeded")
+
+	// remove config
+	os.Remove(configPath)
+
+	// test default config creation
+	_, err = os.Stat(configPath)
+	assert.True(t, errors.Is(err, os.ErrNotExist), "Expected config file to not exist, but it does")
+
+	// get and create config
+	loadedConfig, err = getOrCreateConfig(configPath)
+	assert.Nil(t, err, "Error loading config")
+
+	// test config
+	assert.Equal(t, loadedConfig, config)
+
+	// verify config file exists
+	stat1, err := os.Stat(configPath)
+	assert.Nil(t, err, "Expected config file to exist, but it does not")
+
+	// get and create config
+	loadedConfig, err = getOrCreateConfig(configPath)
+	assert.Nil(t, err, "Error loading config")
+
+	// test config
+	assert.Equal(t, loadedConfig, config)
+
+	// verify config file wasn't updated
+	stat2, err := os.Stat(configPath)
+	assert.Nil(t, err, "Expected config file to exist, but it does not")
+	assert.Equal(t, stat1.ModTime(), stat2.ModTime(), "Expected config file to not be modified, but it was")
+}
+
+func TestValidateDefaultConfig(t *testing.T) {
+	config := defaultConfig()
+	err := validateConfig(config)
+	if err != nil {
+		t.Errorf("Expected to validate config, but failed: %v", err)
+	}
+}
+
+func TestValidateConfig(t *testing.T) {
+	testFailure := func(config Config) {
+		err := validateConfig(config)
+		if err == nil {
+			t.Errorf("Expected to fail to validate config, but succeeded")
+		}
+	}
+
+	testFailure(Config{})
+	testFailure(Config{Profiles: []Profile{}})
+	testFailure(Config{Profiles: []Profile{
+		{
+			Name:      "daily",
+			IsDefault: true,
+		},
+		{
+			Name:      "daily2",
+			IsDefault: true,
+		},
+	}})
+}
diff --git a/postscript.go b/postscript.go
index dfc2024..e8cfcce 100644
--- a/postscript.go
+++ b/postscript.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"fmt"
 	"strings"
 	"text/template"
 )
@@ -15,7 +16,7 @@ func createPostScriptArrays(profile Profile, newName string, fullPath string, ex
 	array := make([]string, 0)
 
 	if profile.PostScripts == nil || len(profile.PostScripts) == 0 {
-		return nil, nil
+		return nil, fmt.Errorf("no post scripts specified")
 	}
 
 	patternData := PostScriptPatternData{
diff --git a/postscript_test.go b/postscript_test.go
new file mode 100644
index 0000000..8ae03f6
--- /dev/null
+++ b/postscript_test.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCreatePostScriptArrays(t *testing.T) {
+	testFailure := func(profile Profile) {
+		_, err := createPostScriptArrays(profile, "test", "test", nil)
+		assert.NotNil(t, err, "Expected to fail to create post script arrays, but succeeded")
+	}
+	testSuccess := func(profile Profile) {
+		extras := map[string]string{
+			"foo": "bar",
+		}
+
+		postscripts, err := createPostScriptArrays(profile, "test", "full-path", extras)
+		assert.Nil(t, err, "Error creating postscript array")
+		assert.Equal(t, postscripts, []string{"echo \"test\""})
+	}
+
+	testSuccess(Profile{
+		PostScripts: []string{
+			"echo \"{{.Name}}\"",
+		},
+	})
+	testFailure(Profile{
+		PostScripts: nil,
+	})
+	testFailure(Profile{
+		PostScripts: []string{},
+	})
+	testFailure(Profile{
+		PostScripts: []string{
+			"echo \"{{.Name\"",
+		},
+	})
+	testFailure(Profile{
+		PostScripts: []string{
+			"echo \"{{.NameUnknown}}\"",
+		},
+	})
+}
diff --git a/profile.go b/profile.go
index 6389435..fd19ac1 100644
--- a/profile.go
+++ b/profile.go
@@ -10,8 +10,47 @@ import (
 	"runtime"
 	"strings"
 	"time"
+
+	"golang.org/x/exp/slices"
+)
+
+type ProfileType string
+
+const (
+	FileType   ProfileType = "file"
+	FolderType ProfileType = "folder"
+	ScriptType ProfileType = "script"
+)
+
+type OSType string
+
+const (
+	Windows OSType = "Windows"
+	Mac     OSType = "Mac"
+	All     OSType = "All"
 )
 
+type Profile struct {
+	IsDefault        bool
+	Name             string
+	Description      string
+	Path             string
+	Type             ProfileType
+	NamePatterns     []string
+	PostScripts      []string
+	ScriptShell      string
+	OS               OSType
+	FileTemplatePath string
+	FileTemplate     string
+
+	// FileTemplatePath string
+}
+
+var forbiddenProfileNames = []string{
+	"list",
+	"get",
+}
+
 func findProfile(config Config, profileName string) (Profile, error) {
 	if profileName == "" {
 		// find default profile
@@ -153,3 +192,31 @@ func processProfile(profile Profile, projectName string, extras map[string]strin
 
 	return nil
 }
+
+func validateProfile(profile Profile) error {
+	if profile.Name == "" {
+		return fmt.Errorf("Profile name is required")
+	}
+	if slices.Contains(forbiddenProfileNames, profile.Name) {
+		return fmt.Errorf("forbidden profile name '%s'", profile.Name)
+	}
+	if profile.NamePatterns == nil || len(profile.NamePatterns) == 0 {
+		return fmt.Errorf("no name patterns found for profile %s", profile.Name)
+	}
+	if profile.Path != "" && strings.Contains(profile.Path, "\\") {
+		return fmt.Errorf("invalid path for profile %s: %s", profile.Name, profile.Path)
+	}
+	if profile.Type == "" || (profile.Type != FileType && profile.Type != FolderType && profile.Type != ScriptType) {
+		return fmt.Errorf("invalid profile type %s for profile %s", profile.Type, profile.Name)
+	}
+	if profile.Type != FileType && profile.FileTemplatePath != "" {
+		return fmt.Errorf("file template path is not supported for profile %s, which has Type=%s", profile.Name, profile.Type)
+	}
+	if profile.Type == ScriptType && profile.NamePatterns != nil && len(profile.NamePatterns) > 1 {
+		return fmt.Errorf("multiple name patterns are not supported for profile %s, which has Type=%s", profile.Name, profile.Type)
+	}
+	if profile.ScriptShell == "" && profile.PostScripts != nil && len(profile.PostScripts) > 0 {
+		return fmt.Errorf("script shell is required for profile %s", profile.Name)
+	}
+	return nil
+}
diff --git a/profile_test.go b/profile_test.go
index b79cf7a..2eb60aa 100644
--- a/profile_test.go
+++ b/profile_test.go
@@ -4,6 +4,65 @@ import (
 	"testing"
 )
 
+func TestValidateProfile(t *testing.T) {
+	testSuccess := func(profile Profile) {
+		err := validateProfile(profile)
+		if err != nil {
+			t.Errorf("Expected to validate profile '%v', but failed: %v", profile.Name, err)
+		}
+	}
+	testFailure := func(profile Profile) {
+		err := validateProfile(profile)
+		if err == nil {
+			t.Errorf("Expected to fail to validate profile '%v', but succeeded", profile.Name)
+		}
+	}
+
+	testSuccess(Profile{
+		Type:         FileType,
+		Name:         "daily",
+		NamePatterns: []string{"{{.Date}}"},
+	})
+
+	// empty name
+	testFailure(Profile{
+		Name: "",
+	})
+	// illegal name
+	testFailure(Profile{
+		Name: "list",
+	})
+	testFailure(Profile{
+		Name: "get",
+	})
+	// file type with no path
+	testFailure(Profile{
+		Type: FileType,
+		Name: "daily",
+	})
+	testFailure(Profile{
+		Type: FileType,
+		Name: "daily",
+		Path: "\\",
+	})
+	// no name patterns
+	testFailure(Profile{
+		Type:         FileType,
+		Name:         "daily",
+		NamePatterns: []string{},
+	})
+	// testFailure(Profile{
+	// 	Type:         FileType,
+	// 	Name:         "daily",
+	// 	NamePatterns: []string{"test"},
+	// 	FileTemplatePath: "",
+	// })
+
+	for _, profile := range defaultConfig().Profiles {
+		testSuccess(profile)
+	}
+}
+
 func TestFindProfile(t *testing.T) {
 	config := Config{
 		Profiles: []Profile{
diff --git a/utils_test.go b/utils_test.go
new file mode 100644
index 0000000..1cd946f
--- /dev/null
+++ b/utils_test.go
@@ -0,0 +1,37 @@
+package main
+
+import (
+	"os/user"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetFullPath(t *testing.T) {
+	testSuccess := func(path string, expected string) {
+		fullPath, err := getFullPath(path)
+		if err != nil {
+			t.Errorf("Expected to get full path for '%v', but failed: %v", path, err)
+		}
+		if fullPath != expected {
+			t.Errorf("Expected to get full path '%v' for '%v', but got '%v'", expected, path, fullPath)
+		}
+	}
+	// testFailure := func(path string) {
+	// 	_, err := getFullPath(path)
+	// 	if err == nil {
+	// 		t.Errorf("Expected to fail to get full path for '%v', but succeeded", path)
+	// 	}
+	// }
+
+	testSuccess("C:\\", "C:\\")
+	testSuccess("C:\\test", "C:\\test")
+	testSuccess("C:\\test\\", "C:\\test\\")
+	testSuccess("C:\\test\\..", "C:\\test\\..") // WRONG
+
+	usr, err := user.Current()
+	assert.NoError(t, err, "Failed to get current user")
+
+	testSuccess("~/", usr.HomeDir)
+	testSuccess("~", usr.HomeDir)
+}