fuzzyworld

template / commit

Merge branch 'makefile-tests' into 'main'

5246d1d62d65a6bd86de5a3be86133aa44a5fa92 · Pavel S · 2022-10-06 20:24:28 +0000 · parents 32035957d746 05f9e8ef1f0d

Merge branch 'makefile-tests' into 'main'

Makefile and tests

See merge request fuzzy-rp/template!3
 .gitignore            |   1 +
 .vscode/settings.json |   3 ++
 Makefile              |  30 ++++++++++++
 config.go             |  77 +++---------------------------
 config_test.go        | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++
 extras_test.go        |   9 ++++
 file.go               |  13 ++---
 main.go               |  11 ++++-
 name_pattern.go       |   9 ++--
 postscript.go         |   3 +-
 postscript_test.go    |  45 +++++++++++++++++
 profile.go            | 100 ++++++++++++++++++++++++++++++--------
 profile_test.go       | 105 ++++++++++++++++++++++++++++++++++++++++
 utils.go              |   8 +---
 utils_test.go         |  34 +++++++++++++
 15 files changed, 467 insertions(+), 111 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/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..bd1c779
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+    "cSpell.words": ["stretchr"]
+}
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..28a2431
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,30 @@
+ifeq ($(OS),Windows_NT)
+    SHELL := pwsh.exe
+else
+    SHELL := pwsh
+endif
+
+.SHELLFLAGS := -Command
+
+BINARY_NAME := template
+
+.PHONY: test
+
+build:
+	go build .
+
+install: build
+	./scripts/clean-install.ps1
+
+test: build
+	New-Item -ItemType Directory -Force -Path ./test | out-null
+	go test -v ./... -coverprofile="./test/coverage.out"
+
+test_coverage: test
+	go tool cover -html="./test/coverage.out" -o "./test/coverage.html"
+
+test_coverage_html: test_coverage
+	Invoke-Item "./test/coverage.html"
+
+clean:
+	go clean
diff --git a/config.go b/config.go
index 7bc8769..dab0302 100644
--- a/config.go
+++ b/config.go
@@ -3,49 +3,11 @@ package main
 import (
 	"fmt"
 	"os"
-	"strings"
+	"os/user"
 
-	"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 +103,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 +151,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 +171,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 fmt.Errorf("error validating profile %s: %s", profile.Name, error)
 		}
 	}
 	return nil
@@ -275,13 +215,10 @@ func saveConfig(path string, config Config) error {
 	return nil
 }
 
-func getOrCreateConfig(configFilePath string) (Config, error) {
+func getOrCreateConfig(configFilePath string, usr user.User) (Config, error) {
 	emptyConfig := Config{}
 
-	fullConfigPath, err := getFullPath(configFilePath)
-	if err != nil {
-		return emptyConfig, fmt.Errorf("failed to get full path for config file %v: %v", configFilePath, err)
-	}
+	fullConfigPath := getFullPath(configFilePath, usr)
 
 	// try and load config
 	config, err := loadConfig(fullConfigPath)
diff --git a/config_test.go b/config_test.go
new file mode 100644
index 0000000..59a30ff
--- /dev/null
+++ b/config_test.go
@@ -0,0 +1,130 @@
+package main
+
+import (
+	"errors"
+	"os"
+	"os/user"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSaveLoadConfig(t *testing.T) {
+	usr, err := user.Current()
+	assert.Nil(t, err)
+
+	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, *usr)
+	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, *usr)
+	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 TestGetOrCreateConfig(t *testing.T) {
+	usr, err := user.Current()
+	assert.Nil(t, err)
+	testFailure := func(configPath string) {
+		_, err := getOrCreateConfig(configPath, *usr)
+		if err == nil {
+			t.Errorf("Expected to fail to get or create config, but succeeded")
+		}
+	}
+
+	testFailure("invalid/path/invalid-config.yaml")
+}
+
+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, errorText string) {
+		err := validateConfig(config)
+		if err == nil {
+			t.Errorf("Expected to fail to validate config, but succeeded")
+		}
+		assert.Contains(t, err.Error(), errorText)
+	}
+
+	testFailure(Config{}, "no profiles found")
+	testFailure(Config{Profiles: []Profile{}}, "no profiles found")
+	testFailure(Config{Profiles: []Profile{
+		{
+			Name:         "daily",
+			IsDefault:    true,
+			NamePatterns: []string{"daily"},
+			FileTemplate: "foo",
+		},
+	}}, "error validating profile")
+	testFailure(Config{Profiles: []Profile{
+		{
+			Name:         "daily",
+			IsDefault:    true,
+			NamePatterns: []string{"daily"},
+			Type:         FileType,
+			FileTemplate: "foo",
+		},
+		{
+			Name:         "daily2",
+			IsDefault:    true,
+			NamePatterns: []string{"daily"},
+			Type:         FileType,
+			FileTemplate: "foo",
+		},
+	}}, "multiple default profiles found")
+}
diff --git a/extras_test.go b/extras_test.go
index 9a72542..5719ab0 100644
--- a/extras_test.go
+++ b/extras_test.go
@@ -49,4 +49,13 @@ func TestExtras(t *testing.T) {
 		t.Error("Error loading extras: ", error)
 	}
 	assert.Equal(t, extras, loadedExtras)
+
+	// test path errors
+	_, error = loadExtras("non-existent-file.yaml")
+	assert.NotNil(t, error, "Expected to fail to load extras, but succeeded")
+
+	os.WriteFile(file.Name(), []byte("invalid yaml"), 0644)
+
+	_, error = loadExtras(fileName)
+	assert.NotNil(t, error, "Expected to fail to parse extras, but succeeded")
 }
diff --git a/file.go b/file.go
index f604e55..e364f77 100644
--- a/file.go
+++ b/file.go
@@ -3,6 +3,7 @@ package main
 import (
 	"fmt"
 	"os"
+	"os/user"
 	"path/filepath"
 	"strings"
 	"text/template"
@@ -29,19 +30,13 @@ func createFilePatternData(now time.Time, projectName string, name string, extra
 	}
 }
 
-func createTemplateFile(srcPath string, destPath string, projectName string, name string, extras map[string]string) error {
+func createTemplateFile(srcPath string, destPath string, projectName string, name string, extras map[string]string, usr user.User) error {
 	now := time.Now()
 	filePatternData := createFilePatternData(now, projectName, name, extras)
 
 	// standardize paths
-	srcPath, err := getFullPath(srcPath)
-	if err != nil {
-		return err
-	}
-	destPath, err = getFullPath(destPath)
-	if err != nil {
-		return err
-	}
+	srcPath = getFullPath(srcPath, usr)
+	destPath = getFullPath(destPath, usr)
 
 	// parse template
 	srcName := filepath.Base(srcPath) // srcName is required because we are sending files to ParseFiles
diff --git a/main.go b/main.go
index 028a555..c0ea6ab 100644
--- a/main.go
+++ b/main.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"log"
 	"os"
+	"os/user"
 	"strings"
 
 	"github.com/urfave/cli/v2" // imports as package "cli"
@@ -17,6 +18,7 @@ func main() {
 	var extrasFilePath string
 	var useDefaultConfig bool
 	var err error
+	var usr *user.User
 
 	app := &cli.App{
 		Name:                 "template",
@@ -65,12 +67,17 @@ func main() {
 		},
 		Action: func(context *cli.Context) error {
 
+			usr, err = user.Current()
+			if err != nil {
+				log.Fatal(err)
+			}
+
 			// get config
 			var config Config
 			if useDefaultConfig {
 				config = defaultConfig()
 			} else {
-				config, err = getOrCreateConfig(configFilePath)
+				config, err = getOrCreateConfig(configFilePath, *usr)
 				if err != nil {
 					return err
 				}
@@ -136,7 +143,7 @@ func main() {
 			}
 
 			// process profile
-			err = processProfile(profile, projectName, extras)
+			err = processProfile(profile, projectName, extras, *usr)
 			if err != nil {
 				return fmt.Errorf("error processing profile: %v", err)
 			}
diff --git a/name_pattern.go b/name_pattern.go
index c383b63..32fb4d3 100644
--- a/name_pattern.go
+++ b/name_pattern.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"errors"
+	"fmt"
 	"strings"
 	"text/template"
 	"time"
@@ -42,13 +43,13 @@ func createNewNameForProfile(now time.Time, projectName string, profile Profile,
 
 			template, err := template.Parse(namePattern)
 			if err != nil {
-				return "", err
+				return "", fmt.Errorf("error parsing name pattern %d: %w", index, err)
 			}
 
 			sb := new(strings.Builder)
 			err = template.Execute(sb, patternData)
 			if err != nil {
-				return "", err
+				return "", fmt.Errorf("error executing name pattern %d: %w", index, err)
 			}
 
 			newName := sb.String()
@@ -68,11 +69,11 @@ func createNewNameForProfile(now time.Time, projectName string, profile Profile,
 
 			// if getVersion was used, but this isn't the last pattern, report an error
 			if useIndex && !isLastPattern {
-				return "", errors.New("Cannot use function getVersion in a pattern that is not the last pattern")
+				return "", errors.New("cannot use function getVersion in a pattern that is not the last pattern")
 			}
 
 			startingVersion++
 		}
 	}
-	return "", errors.New("Couldn't find a new name")
+	return "", errors.New("couldn't find a new name")
 }
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..888ee70 100644
--- a/profile.go
+++ b/profile.go
@@ -6,12 +6,52 @@ import (
 	"io/ioutil"
 	"os"
 	"os/exec"
+	"os/user"
 	"path/filepath"
 	"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
@@ -20,24 +60,18 @@ func findProfile(config Config, profileName string) (Profile, error) {
 				return p, nil
 			}
 		}
-	} else {
-		// find profile by name
-		for _, p := range config.Profiles {
-			if p.Name == profileName {
-				return p, nil
-			}
+		return Profile{}, errors.New("no default profile found")
+	}
+	// find profile by name
+	for _, p := range config.Profiles {
+		if p.Name == profileName {
+			return p, nil
 		}
-		return Profile{}, fmt.Errorf("unable to find profile '%v'", profileName)
 	}
-	return Profile{}, errors.New("couldn't find profile")
+	return Profile{}, fmt.Errorf("unable to find profile '%v'", profileName)
 }
 
-func processProfile(profile Profile, projectName string, extras map[string]string) error {
-	// validate profile
-	if profile.NamePatterns == nil || len(profile.NamePatterns) == 0 {
-		return errors.New("no name patterns specified")
-	}
-
+func processProfile(profile Profile, projectName string, extras map[string]string, usr user.User) error {
 	// decide if the profile should run on this OS
 	profileOs := profile.OS
 	isWindows := runtime.GOOS == "windows"
@@ -56,10 +90,7 @@ func processProfile(profile Profile, projectName string, extras map[string]strin
 	}
 	if profile.Path != "" {
 		// change cwd to the profile's path, if one is specified
-		path, err := getFullPath(profile.Path)
-		if err != nil {
-			return fmt.Errorf("unable to get full path for '%v': %v", profile.Path, err)
-		}
+		path := getFullPath(profile.Path, usr)
 		err = os.Chdir(path)
 		if err != nil {
 			return fmt.Errorf("unable to change directory to %v: %v", path, err)
@@ -114,7 +145,7 @@ func processProfile(profile Profile, projectName string, extras map[string]strin
 		useTemplate := profile.FileTemplate != ""
 		if useTemplateFile {
 			fmt.Printf("Creating file '%v' from template: %v\n", fullPath, profile.FileTemplatePath)
-			err := createTemplateFile(profile.FileTemplatePath, fullPath, projectName, newName, extras)
+			err := createTemplateFile(profile.FileTemplatePath, fullPath, projectName, newName, extras, usr)
 			if err != nil {
 				return fmt.Errorf("error creating template file: %v", err)
 			}
@@ -153,3 +184,34 @@ 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.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 != FileType && profile.FileTemplate != "" {
+		return fmt.Errorf("file template is not supported for profile %s, which has Type=%s", profile.Name, profile.Type)
+	}
+	if profile.Type == FileType && profile.FileTemplatePath != "" && profile.FileTemplate != "" {
+		return fmt.Errorf("both file template path and file template are specified for profile %s", profile.Name)
+	}
+	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..f2c747f 100644
--- a/profile_test.go
+++ b/profile_test.go
@@ -4,6 +4,107 @@ 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}}"},
+	})
+	testSuccess(Profile{
+		Type:         FileType,
+		Name:         "daily",
+		NamePatterns: []string{"{{.Date}}"},
+		FileTemplate: "foo",
+	})
+	testSuccess(Profile{
+		Type:             FileType,
+		Name:             "daily",
+		NamePatterns:     []string{"{{.Date}}"},
+		FileTemplatePath: "foo",
+	})
+
+	// 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{},
+	})
+	// file template and file template path are set
+	testFailure(Profile{
+		Type:             FileType,
+		Name:             "daily",
+		NamePatterns:     []string{"test"},
+		FileTemplatePath: "foo",
+		FileTemplate:     "bar",
+	})
+	// file template set with wrong type
+	testFailure(Profile{
+		Type:         FolderType,
+		Name:         "daily",
+		NamePatterns: []string{"test"},
+		FileTemplate: "bar",
+	})
+	// file template path set with wrong type
+	testFailure(Profile{
+		Type:             FolderType,
+		Name:             "daily",
+		NamePatterns:     []string{"test"},
+		FileTemplatePath: "bar",
+	})
+	// script type with multiple name patterns
+	testFailure(Profile{
+		Type:         ScriptType,
+		Name:         "daily",
+		NamePatterns: []string{"test", "test2"},
+	})
+	// postscripts with no shell
+	testFailure(Profile{
+		Type:         ScriptType,
+		Name:         "daily",
+		NamePatterns: []string{"test"},
+		PostScripts:  []string{"test"},
+		ScriptShell:  "",
+	})
+
+	for _, profile := range defaultConfig().Profiles {
+		testSuccess(profile)
+	}
+}
+
 func TestFindProfile(t *testing.T) {
 	config := Config{
 		Profiles: []Profile{
@@ -37,4 +138,8 @@ func TestFindProfile(t *testing.T) {
 	testSuccess("notes", "notes")
 
 	testFailure("non-existent")
+
+	config.Profiles[1].IsDefault = false
+	testFailure("")
+	testFailure("non-default")
 }
diff --git a/utils.go b/utils.go
index 4cda077..7a794bb 100644
--- a/utils.go
+++ b/utils.go
@@ -6,11 +6,7 @@ import (
 	"strings"
 )
 
-func getFullPath(path string) (string, error) {
-	usr, error := user.Current()
-	if error != nil {
-		return "", error
-	}
+func getFullPath(path string, usr user.User) string {
 	dir := usr.HomeDir
 	var newPath string
 	if path == "~" {
@@ -20,5 +16,5 @@ func getFullPath(path string) (string, error) {
 	} else {
 		newPath = path
 	}
-	return newPath, nil
+	return newPath
 }
diff --git a/utils_test.go b/utils_test.go
new file mode 100644
index 0000000..851bdca
--- /dev/null
+++ b/utils_test.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+	"os/user"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetFullPath(t *testing.T) {
+	usr, err := user.Current()
+	assert.Nil(t, err)
+
+	testSuccess := func(path string, expected string) {
+		fullPath := getFullPath(path, *usr)
+		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
+
+	testSuccess("~/", usr.HomeDir)
+	testSuccess("~", usr.HomeDir)
+}