template / files / internal / core / profile_test.go
profile_test.go
37160b89 2026-05-01 Pavel Safronov | 1 | package core |
c62570e2 2022-09-23 Pavel Safronov | 2 | |
| 3 | import ( | |
ecad2935 2026-05-01 Pavel Safronov | 4 | "bufio" |
| 5 | "fmt" | |
9573ca02 2022-11-16 Pavel Safronov | 6 | "io/fs" |
c3fc7a22 2022-10-30 Pavel Safronov | 7 | "os" |
| 8 | "os/exec" | |
| 9 | "os/user" | |
| 10 | "path/filepath" | |
9573ca02 2022-11-16 Pavel Safronov | 11 | "runtime" |
ecad2935 2026-05-01 Pavel Safronov | 12 | "strings" |
c62570e2 2022-09-23 Pavel Safronov | 13 | "testing" |
6543e947 2022-12-29 Pavel Safronov | 14 | "time" |
c3fc7a22 2022-10-30 Pavel Safronov | 15 | |
| 16 | "github.com/stretchr/testify/assert" | |
c62570e2 2022-09-23 Pavel Safronov | 17 | ) |
| 18 | ||
763c260d 2022-10-06 Pavel Safronov | 19 | func TestValidateProfile(t *testing.T) { |
| 20 | testSuccess := func(profile Profile) { | |
b0586433 2022-10-21 Pavel Safronov | 21 | err := validateProfile(&profile) |
763c260d 2022-10-06 Pavel Safronov | 22 | if err != nil { |
| 23 | t.Errorf("Expected to validate profile '%v', but failed: %v", profile.Name, err) | |
| 24 | } | |
| 25 | } | |
9573ca02 2022-11-16 Pavel Safronov | 26 | testFailure := func(profile Profile, expectedErrStr string) { |
b0586433 2022-10-21 Pavel Safronov | 27 | err := validateProfile(&profile) |
9573ca02 2022-11-16 Pavel Safronov | 28 | assert.NotNil(t, err, "Expected to fail to validate profile '%v', but succeeded") |
| 29 | assert.Contains(t, err.Error(), expectedErrStr, "Expected error to contain '%v', but it didn't", expectedErrStr) | |
763c260d 2022-10-06 Pavel Safronov | 30 | } |
| 31 | ||
| 32 | testSuccess(Profile{ | |
873846b9 2022-12-09 Pavel Safronov | 33 | Type: FileType, |
| 34 | Name: "daily", | |
| 35 | NamePatterns: []ParameterizedText{"{{.Date}}"}, | |
| 36 | TargetDirectory: "~/test/dir", | |
763c260d 2022-10-06 Pavel Safronov | 37 | }) |
05f9e8ef 2022-10-06 Pavel Safronov | 38 | testSuccess(Profile{ |
873846b9 2022-12-09 Pavel Safronov | 39 | Type: FileType, |
| 40 | Name: "daily", | |
| 41 | NamePatterns: []ParameterizedText{"{{.Date}}"}, | |
| 42 | FileTemplate: "foo", | |
| 43 | TargetDirectory: "~/test/dir", | |
05f9e8ef 2022-10-06 Pavel Safronov | 44 | }) |
763c260d 2022-10-06 Pavel Safronov | 45 | |
| 46 | testFailure(Profile{ | |
| 47 | Name: "", | |
873846b9 2022-12-09 Pavel Safronov | 48 | }, "profile name is required") |
763c260d 2022-10-06 Pavel Safronov | 49 | testFailure(Profile{ |
| 50 | Name: "list", | |
9573ca02 2022-11-16 Pavel Safronov | 51 | }, "forbidden profile name 'list'") |
763c260d 2022-10-06 Pavel Safronov | 52 | testFailure(Profile{ |
| 53 | Name: "get", | |
9573ca02 2022-11-16 Pavel Safronov | 54 | }, "forbidden profile name 'get'") |
763c260d 2022-10-06 Pavel Safronov | 55 | testFailure(Profile{ |
| 56 | Type: FileType, | |
| 57 | Name: "daily", | |
9573ca02 2022-11-16 Pavel Safronov | 58 | }, "namePatterns is required") |
763c260d 2022-10-06 Pavel Safronov | 59 | testFailure(Profile{ |
9573ca02 2022-11-16 Pavel Safronov | 60 | Type: FileType, |
| 61 | Name: "daily", | |
| 62 | NamePatterns: []ParameterizedText{"blah"}, | |
873846b9 2022-12-09 Pavel Safronov | 63 | }, "targetDirectory is not specified for profile daily, which has Type=file") |
8c15a714 2022-11-04 Pavel Safronov | 64 | |
9573ca02 2022-11-16 Pavel Safronov | 65 | testFailure(Profile{ |
| 66 | Type: FolderType, | |
| 67 | Name: "daily", | |
| 68 | NamePatterns: []ParameterizedText{"test"}, | |
873846b9 2022-12-09 Pavel Safronov | 69 | }, "targetDirectory is not specified for profile daily, which has Type=folder") |
05f9e8ef 2022-10-06 Pavel Safronov | 70 | testFailure(Profile{ |
| 71 | Type: FolderType, | |
| 72 | Name: "daily", | |
1a02ef4e 2022-10-21 Pavel Safronov | 73 | NamePatterns: []ParameterizedText{"test"}, |
873846b9 2022-12-09 Pavel Safronov | 74 | FileTemplate: "bar", |
| 75 | }, "targetDirectory is not specified for profile daily, which has Type=folder") | |
05f9e8ef 2022-10-06 Pavel Safronov | 76 | testFailure(Profile{ |
| 77 | Type: ScriptType, | |
| 78 | Name: "daily", | |
1a02ef4e 2022-10-21 Pavel Safronov | 79 | NamePatterns: []ParameterizedText{"test", "test2"}, |
9573ca02 2022-11-16 Pavel Safronov | 80 | }, "multiple name patterns are not supported for profile daily, which has Type=script") |
| 81 | testFailure(Profile{ | |
| 82 | Type: FileType, | |
| 83 | Name: "daily", | |
| 84 | NamePatterns: []ParameterizedText{"test", "test2"}, | |
873846b9 2022-12-09 Pavel Safronov | 85 | }, "targetDirectory is not specified for profile daily, which has Type=file") |
9573ca02 2022-11-16 Pavel Safronov | 86 | testFailure(Profile{ |
| 87 | Type: FileType, | |
| 88 | Name: "daily", | |
| 89 | NamePatterns: []ParameterizedText{"test", ""}, | |
| 90 | }, "name pattern is required for pattern index 1 of profile daily") | |
763c260d 2022-10-06 Pavel Safronov | 91 | |
| 92 | for _, profile := range defaultConfig().Profiles { | |
| 93 | testSuccess(profile) | |
| 94 | } | |
| 95 | } | |
| 96 | ||
ae89a42a 2022-11-02 Pavel Safronov | 97 | func TestFindProfileIndex(t *testing.T) { |
c62570e2 2022-09-23 Pavel Safronov | 98 | config := Config{ |
| 99 | Profiles: []Profile{ | |
| 100 | { | |
| 101 | Name: "daily", | |
| 102 | }, | |
| 103 | { | |
| 104 | IsDefault: true, | |
| 105 | Name: "notes", | |
| 106 | }, | |
| 107 | }, | |
| 108 | } | |
| 109 | testSuccess := func(profileName string, expectedProfileName string) { | |
ae89a42a 2022-11-02 Pavel Safronov | 110 | index, err := findProfileIndex(config.Profiles, profileName) |
c62570e2 2022-09-23 Pavel Safronov | 111 | if err != nil { |
| 112 | t.Errorf("Failed to find profile '%v': %v", profileName, err) | |
| 113 | } | |
ae89a42a 2022-11-02 Pavel Safronov | 114 | profile := config.Profiles[index] |
c62570e2 2022-09-23 Pavel Safronov | 115 | if profile.Name != expectedProfileName { |
| 116 | t.Errorf("Asked for profile '%v', expected '%v', got '%v'", profileName, expectedProfileName, profile.Name) | |
| 117 | } | |
| 118 | } | |
| 119 | testFailure := func(profileName string) { | |
ae89a42a 2022-11-02 Pavel Safronov | 120 | _, err := findProfileIndex(config.Profiles, profileName) |
c62570e2 2022-09-23 Pavel Safronov | 121 | if err == nil { |
| 122 | t.Errorf("Expected to fail to find profile '%v', but succeeded", profileName) | |
| 123 | } | |
| 124 | } | |
| 125 | ||
| 126 | testSuccess("daily", "daily") | |
| 127 | testSuccess("notes", "notes") | |
| 128 | ||
ae89a42a 2022-11-02 Pavel Safronov | 129 | testFailure("") |
c62570e2 2022-09-23 Pavel Safronov | 130 | testFailure("non-existent") |
05f9e8ef 2022-10-06 Pavel Safronov | 131 | |
| 132 | config.Profiles[1].IsDefault = false | |
| 133 | testFailure("") | |
| 134 | testFailure("non-default") | |
c62570e2 2022-09-23 Pavel Safronov | 135 | } |
c3fc7a22 2022-10-30 Pavel Safronov | 136 | |
ae89a42a 2022-11-02 Pavel Safronov | 137 | func TestFindDefaultProfile(t *testing.T) { |
| 138 | _, err := findDefaultProfile(defaultConfig().Profiles) | |
| 139 | assert.Nil(t, err, "Expected to find default profile, but failed") | |
| 140 | ||
| 141 | _, err = findDefaultProfile([]Profile{}) | |
| 142 | assert.NotNil(t, err, "Expected to fail to find default profile, but succeeded") | |
| 143 | } | |
| 144 | ||
c3fc7a22 2022-10-30 Pavel Safronov | 145 | func TestCreateEntity(t *testing.T) { |
| 146 | // setup | |
| 147 | now := testNow() | |
| 148 | cwd := "~/test/dir" | |
| 149 | projectName := "project-name" | |
| 150 | extras := map[string]string{ | |
| 151 | "foo": "bar", | |
| 152 | } | |
| 153 | patternData := testPrePatternData() | |
| 154 | ||
| 155 | testFolder := func() { | |
| 156 | monkeyPatchCalls := applyTestMonkeyPatch(t, monkeyPatch{ | |
| 157 | OSMkdir: func(path string, perm os.FileMode) error { return nil }, | |
| 158 | OSChdir: func(dir string) error { return nil }, | |
| 159 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { return nil }, | |
| 160 | }) | |
| 161 | ||
| 162 | profile := Profile{ | |
| 163 | Type: FolderType, | |
| 164 | Files: []FileInFolder{ | |
| 165 | { | |
873846b9 2022-12-09 Pavel Safronov | 166 | Name: "readme.md", |
| 167 | FileTemplate: "blah", | |
c3fc7a22 2022-10-30 Pavel Safronov | 168 | }, |
| 169 | { | |
873846b9 2022-12-09 Pavel Safronov | 170 | Name: "test.txt", |
| 171 | FileTemplate: "foo", | |
c3fc7a22 2022-10-30 Pavel Safronov | 172 | }, |
| 173 | }, | |
| 174 | } | |
| 175 | ||
| 176 | newName := "new-name" | |
| 177 | expectedFullPath := filepath.Clean("~/test/dir/" + newName) | |
bbd011d0 2022-12-10 Pavel Safronov | 178 | fullPath, err := createEntity(now, profile, cwd, newName, projectName, extras, patternData, nil) |
c3fc7a22 2022-10-30 Pavel Safronov | 179 | assert.Nil(t, err) |
| 180 | assert.Equal(t, expectedFullPath, fullPath) | |
| 181 | assert.Equal(t, 1, len(monkeyPatchCalls.OSChdirCalls)) | |
| 182 | assert.Equal(t, expectedFullPath, monkeyPatchCalls.OSChdirCalls[0].dir) | |
5e424f1b 2022-11-12 Pavel Safronov | 183 | assert.Equal(t, 3, len(monkeyPatchCalls.OSMkdirCalls)) |
c3fc7a22 2022-10-30 Pavel Safronov | 184 | assert.Equal(t, expectedFullPath, monkeyPatchCalls.OSMkdirCalls[0].path) |
| 185 | assert.Equal(t, 2, len(monkeyPatchCalls.OSWriteFileCalls)) | |
| 186 | assert.Equal(t, filepath.Clean(expectedFullPath+"/readme.md"), monkeyPatchCalls.OSWriteFileCalls[0].filename) | |
| 187 | assert.Equal(t, []byte("blah"), monkeyPatchCalls.OSWriteFileCalls[0].data) | |
| 188 | assert.Equal(t, filepath.Clean(expectedFullPath+"/test.txt"), monkeyPatchCalls.OSWriteFileCalls[1].filename) | |
| 189 | assert.Equal(t, []byte("foo"), monkeyPatchCalls.OSWriteFileCalls[1].data) | |
| 190 | } | |
| 191 | ||
| 192 | testFile := func() { | |
| 193 | monkeyPatchCalls := applyTestMonkeyPatch(t, monkeyPatch{ | |
| 194 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { return nil }, | |
| 195 | }) | |
| 196 | ||
| 197 | profile := Profile{ | |
873846b9 2022-12-09 Pavel Safronov | 198 | Type: FileType, |
| 199 | FileTemplate: "blah", | |
c3fc7a22 2022-10-30 Pavel Safronov | 200 | } |
| 201 | newName := "new-name.md" | |
| 202 | expectedFullPath := filepath.Clean("~/test/dir/" + newName) | |
bbd011d0 2022-12-10 Pavel Safronov | 203 | fullPath, err := createEntity(now, profile, cwd, newName, projectName, extras, patternData, nil) |
c3fc7a22 2022-10-30 Pavel Safronov | 204 | assert.Nil(t, err) |
| 205 | assert.Equal(t, expectedFullPath, fullPath) | |
| 206 | assert.Equal(t, 1, len(monkeyPatchCalls.OSWriteFileCalls)) | |
| 207 | assert.Equal(t, expectedFullPath, monkeyPatchCalls.OSWriteFileCalls[0].filename) | |
| 208 | assert.Equal(t, []byte("blah"), monkeyPatchCalls.OSWriteFileCalls[0].data) | |
| 209 | } | |
| 210 | ||
| 211 | testScript := func() { | |
| 212 | profile := Profile{ | |
| 213 | Type: ScriptType, | |
| 214 | PostScripts: []ParameterizedText{ | |
| 215 | "{{.ProjectName}}-{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}-{{.Extras.key1}}", | |
| 216 | }, | |
| 217 | } | |
| 218 | newName := "new-name.md" | |
| 219 | expectedFullPath := filepath.Clean("~/test/dir/" + newName) | |
bbd011d0 2022-12-10 Pavel Safronov | 220 | fullPath, err := createEntity(now, profile, cwd, newName, projectName, extras, patternData, nil) |
c3fc7a22 2022-10-30 Pavel Safronov | 221 | assert.Nil(t, err) |
| 222 | assert.Equal(t, expectedFullPath, fullPath) | |
| 223 | } | |
| 224 | ||
| 225 | testFolder() | |
| 226 | ||
| 227 | testFile() | |
| 228 | ||
| 229 | testScript() | |
ecad2935 2026-05-01 Pavel Safronov | 230 | |
| 231 | // OSMkdir failure for folder type | |
| 232 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 233 | OSMkdir: func(path string, perm os.FileMode) error { return fmt.Errorf("mkdir failed") }, | |
| 234 | }) | |
| 235 | _, err := createEntity(now, Profile{Type: FolderType}, cwd, "new-name", projectName, extras, patternData, nil) | |
| 236 | assert.NotNil(t, err) | |
| 237 | assert.Contains(t, err.Error(), "error creating directory") | |
| 238 | ||
| 239 | // OSChdir failure after successful OSMkdir | |
| 240 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 241 | OSMkdir: func(path string, perm os.FileMode) error { return nil }, | |
| 242 | OSChdir: func(dir string) error { return fmt.Errorf("chdir failed") }, | |
| 243 | }) | |
| 244 | _, err = createEntity(now, Profile{Type: FolderType}, cwd, "new-name", projectName, extras, patternData, nil) | |
| 245 | assert.NotNil(t, err) | |
| 246 | assert.Contains(t, err.Error(), "unable to change directory") | |
| 247 | ||
| 248 | // createFile failure inside folder (FolderType) | |
| 249 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 250 | OSMkdir: func(path string, perm os.FileMode) error { return nil }, | |
| 251 | OSChdir: func(dir string) error { return nil }, | |
| 252 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { return fmt.Errorf("write failed") }, | |
| 253 | }) | |
| 254 | _, err = createEntity(now, Profile{ | |
| 255 | Type: FolderType, | |
| 256 | Files: []FileInFolder{{Name: "f.md", FileTemplate: "tmpl"}}, | |
| 257 | }, cwd, "new-name", projectName, extras, patternData, nil) | |
| 258 | assert.NotNil(t, err) | |
| 259 | assert.Contains(t, err.Error(), "error creating file") | |
| 260 | ||
| 261 | // invalid file name pattern inside folder triggers createNewNameForPatterns failure | |
| 262 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 263 | OSMkdir: func(path string, perm os.FileMode) error { return nil }, | |
| 264 | OSChdir: func(dir string) error { return nil }, | |
| 265 | }) | |
| 266 | _, err = createEntity(now, Profile{ | |
| 267 | Type: FolderType, | |
| 268 | Files: []FileInFolder{{Name: ParameterizedText("{{.Bad"), FileTemplate: "tmpl"}}, | |
| 269 | }, cwd, "new-name", projectName, extras, patternData, nil) | |
| 270 | assert.NotNil(t, err) | |
| 271 | assert.Contains(t, err.Error(), "error creating file name for file index") | |
| 272 | ||
| 273 | // subdir OSMkdir failure inside folder | |
| 274 | mkdirCount := 0 | |
| 275 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 276 | OSMkdir: func(path string, perm os.FileMode) error { | |
| 277 | mkdirCount++ | |
| 278 | if mkdirCount > 1 { | |
| 279 | return fmt.Errorf("subdir mkdir failed") | |
| 280 | } | |
| 281 | return nil | |
| 282 | }, | |
| 283 | OSChdir: func(dir string) error { return nil }, | |
| 284 | }) | |
| 285 | _, err = createEntity(now, Profile{ | |
| 286 | Type: FolderType, | |
| 287 | Files: []FileInFolder{{Name: "f.md", FileTemplate: "tmpl"}}, | |
| 288 | }, cwd, "new-name", projectName, extras, patternData, nil) | |
| 289 | assert.NotNil(t, err) | |
| 290 | assert.Contains(t, err.Error(), "error creating sub directory") | |
| 291 | ||
| 292 | // FileType createFile failure | |
| 293 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 294 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { return fmt.Errorf("write failed") }, | |
| 295 | }) | |
| 296 | _, err = createEntity(now, Profile{Type: FileType, FileTemplate: "tmpl"}, cwd, "new-name.md", projectName, extras, patternData, nil) | |
| 297 | assert.NotNil(t, err) | |
| 298 | assert.Contains(t, err.Error(), "error writing file") | |
c3fc7a22 2022-10-30 Pavel Safronov | 299 | } |
| 300 | ||
9573ca02 2022-11-16 Pavel Safronov | 301 | type testFile struct { |
| 302 | name string | |
| 303 | isDir bool | |
| 304 | } | |
| 305 | ||
| 306 | func (f testFile) Name() string { return f.name } | |
| 307 | func (f testFile) IsDir() bool { return f.isDir } | |
| 308 | func (f testFile) Type() fs.FileMode { return 0 } | |
| 309 | func (f testFile) Info() (fs.FileInfo, error) { return nil, nil } | |
6543e947 2022-12-29 Pavel Safronov | 310 | func (f testFile) ModTime() time.Time { return time.Time{} } |
| 311 | func (f testFile) Mode() fs.FileMode { return 0 } | |
| 312 | func (f testFile) Size() int64 { return 0 } | |
| 313 | func (f testFile) Sys() any { return nil } | |
| 314 | ||
| 315 | var _ os.DirEntry = testFile{} | |
| 316 | var _ os.FileInfo = testFile{} | |
| 317 | ||
| 318 | func createDirEntry(name string, isDir bool) testFile { | |
| 319 | entry := testFile{name: name, isDir: isDir} | |
| 320 | return entry | |
| 321 | } | |
9573ca02 2022-11-16 Pavel Safronov | 322 | |
c3fc7a22 2022-10-30 Pavel Safronov | 323 | func TestProcessProfile(t *testing.T) { |
| 324 | cwd := filepath.Clean("~/test/dir") | |
9573ca02 2022-11-16 Pavel Safronov | 325 | var monkeyPatchCalls *monkeyPatchCalls |
| 326 | configureMonkeyPatching := func() { | |
| 327 | monkeyPatchCalls = applyTestMonkeyPatch(t, monkeyPatch{ | |
| 328 | ExecCommand: func(command string, args ...string) *exec.Cmd { | |
748e3056 2022-11-19 Pavel Safronov | 329 | shell, shellArgs := shellCommandAndArgs("echo") |
| 330 | return exec.Command(shell, shellArgs...) | |
9573ca02 2022-11-16 Pavel Safronov | 331 | }, |
bbd011d0 2022-12-10 Pavel Safronov | 332 | OSCreate: func(name string) (file *os.File, err error) { |
| 333 | dir := os.TempDir() | |
| 334 | return os.CreateTemp(dir, "test") | |
| 335 | }, | |
9573ca02 2022-11-16 Pavel Safronov | 336 | OSChdir: func(dir string) error { return nil }, |
| 337 | OSGetwd: func() (dir string, err error) { return cwd, nil }, | |
| 338 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { | |
| 339 | return []os.DirEntry{ | |
| 340 | createDirEntry("dir1", true), | |
| 341 | createDirEntry("dir2", true), | |
| 342 | createDirEntry("file1", false), | |
| 343 | createDirEntry("file2", false), | |
| 344 | }, nil | |
| 345 | }, | |
6543e947 2022-12-29 Pavel Safronov | 346 | OSStat: func(name string) (os.FileInfo, error) { |
| 347 | return createDirEntry("dir1", true), nil | |
| 348 | }, | |
9573ca02 2022-11-16 Pavel Safronov | 349 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { |
| 350 | return nil | |
| 351 | }, | |
| 352 | }) | |
| 353 | } | |
c3fc7a22 2022-10-30 Pavel Safronov | 354 | |
| 355 | projectName := "project-name" | |
| 356 | extras := map[string]string{ | |
| 357 | "foo": "bar", | |
| 358 | } | |
a5ad914b 2022-10-30 Pavel Safronov | 359 | |
9573ca02 2022-11-16 Pavel Safronov | 360 | usr, err := user.Current() |
| 361 | assert.Nil(t, err) | |
| 362 | assert.NotNil(t, usr) | |
a5ad914b 2022-10-30 Pavel Safronov | 363 | |
9573ca02 2022-11-16 Pavel Safronov | 364 | testSuccess := func(profile Profile) { |
| 365 | configureMonkeyPatching() | |
bbd011d0 2022-12-10 Pavel Safronov | 366 | errors := processProfile(profile, projectName, extras, *usr, nil) |
a5ad914b 2022-10-30 Pavel Safronov | 367 | assert.Equal(t, 0, len(errors)) |
9573ca02 2022-11-16 Pavel Safronov | 368 | for _, error := range errors { |
| 369 | assert.Nil(t, error) | |
| 370 | } | |
a5ad914b 2022-10-30 Pavel Safronov | 371 | } |
9573ca02 2022-11-16 Pavel Safronov | 372 | testFailure := func(profile Profile, errorCount int, errorStr string) { |
| 373 | configureMonkeyPatching() | |
bbd011d0 2022-12-10 Pavel Safronov | 374 | errors := processProfile(profile, projectName, extras, *usr, nil) |
9573ca02 2022-11-16 Pavel Safronov | 375 | assert.Equal(t, errorCount, len(errors)) |
| 376 | allErrorsText := "" | |
| 377 | for _, err := range errors { | |
| 378 | allErrorsText += err.Error() | |
a5ad914b 2022-10-30 Pavel Safronov | 379 | } |
9573ca02 2022-11-16 Pavel Safronov | 380 | assert.Contains(t, allErrorsText, errorStr) |
| 381 | } | |
| 382 | testSingleFailure := func(profile Profile, errorStr string) { | |
| 383 | testFailure(profile, 1, errorStr) | |
| 384 | } | |
a5ad914b 2022-10-30 Pavel Safronov | 385 | |
9573ca02 2022-11-16 Pavel Safronov | 386 | // this profile will be built up from failing to eventually passing |
| 387 | currentOS := getCurrentOs() | |
| 388 | profile := Profile{ | |
| 389 | IsDisabled: true, | |
a5ad914b 2022-10-30 Pavel Safronov | 390 | } |
9573ca02 2022-11-16 Pavel Safronov | 391 | testSingleFailure(profile, "Profile is disabled, doing nothing") |
| 392 | profile.IsDisabled = false | |
| 393 | profile.OS = getOtherOs(currentOS) | |
873846b9 2022-12-09 Pavel Safronov | 394 | testSingleFailure(profile, "this profile is not supported on this OS, doing nothing.") |
9573ca02 2022-11-16 Pavel Safronov | 395 | profile.OS = currentOS |
| 396 | // testSingleFailure(profile, "unable to create post scripts: no post scripts specified") | |
a5ad914b 2022-10-30 Pavel Safronov | 397 | |
9573ca02 2022-11-16 Pavel Safronov | 398 | profile.NamePatterns = []ParameterizedText{} |
| 399 | profile.Type = FileType | |
| 400 | testSingleFailure(profile, "couldn't find a good new name") | |
a5ad914b 2022-10-30 Pavel Safronov | 401 | |
9573ca02 2022-11-16 Pavel Safronov | 402 | profile.NamePatterns = []ParameterizedText{"file1"} |
| 403 | profile.RunPostscriptOnFailure = true | |
| 404 | testSingleFailure(profile, "couldn't find a good new name") | |
| 405 | ||
| 406 | profile.NamePatterns = []ParameterizedText{"file3"} | |
| 407 | profile.PostScripts = []ParameterizedText{"echo {{.ProjectName}}-{{.Year}}"} | |
6543e947 2022-12-29 Pavel Safronov | 408 | profile.TargetDirectory = "./test/dir" |
9573ca02 2022-11-16 Pavel Safronov | 409 | testSuccess(profile) |
| 410 | assert.Equal(t, 1, len(monkeyPatchCalls.ExecCommandCalls), "calls to ExecCommand") | |
| 411 | assert.Equal(t, 1, len(monkeyPatchCalls.OSChdirCalls), "calls to OSChdir") | |
| 412 | assert.Equal(t, 1, len(monkeyPatchCalls.OSGetwdCalls), "calls to OSGetwd") | |
| 413 | assert.Equal(t, 1, len(monkeyPatchCalls.OSReadDirCalls), "calls to OSReadDir") | |
| 414 | assert.Equal(t, 1, len(monkeyPatchCalls.OSWriteFileCalls), "calls to OSWriteFile") | |
| 415 | ||
| 416 | profile.PostScripts = []ParameterizedText{"echo ''"} | |
| 417 | testSuccess(profile) | |
| 418 | assert.Equal(t, 1, len(monkeyPatchCalls.ExecCommandCalls), "calls to ExecCommand") | |
| 419 | assert.Equal(t, 1, len(monkeyPatchCalls.OSChdirCalls), "calls to OSChdir") | |
| 420 | assert.Equal(t, 1, len(monkeyPatchCalls.OSGetwdCalls), "calls to OSGetwd") | |
| 421 | assert.Equal(t, 1, len(monkeyPatchCalls.OSReadDirCalls), "calls to OSReadDir") | |
| 422 | assert.Equal(t, 1, len(monkeyPatchCalls.OSWriteFileCalls), "calls to OSWriteFile") | |
ecad2935 2026-05-01 Pavel Safronov | 423 | |
| 424 | // script-type profile runs a post-script and creates no files | |
| 425 | scriptProfile := Profile{ | |
| 426 | Type: ScriptType, | |
| 427 | Name: "my-script", | |
| 428 | PostScripts: []ParameterizedText{"echo hello"}, | |
| 429 | } | |
| 430 | testSuccess(scriptProfile) | |
| 431 | assert.Equal(t, 1, len(monkeyPatchCalls.ExecCommandCalls), "script profile should run one command") | |
| 432 | assert.Equal(t, 0, len(monkeyPatchCalls.OSWriteFileCalls), "script profile should not write files") | |
| 433 | ||
| 434 | // pipe input with a folder-type profile is an error | |
| 435 | folderProfile := Profile{ | |
| 436 | Type: FolderType, | |
| 437 | Name: "folder", | |
| 438 | NamePatterns: []ParameterizedText{"newfolder"}, | |
| 439 | TargetDirectory: "./test/dir", | |
| 440 | } | |
| 441 | scanner := bufio.NewScanner(strings.NewReader("some input")) | |
| 442 | configureMonkeyPatching() | |
| 443 | errs := processProfile(folderProfile, projectName, extras, *usr, scanner) | |
| 444 | assert.Equal(t, 1, len(errs)) | |
| 445 | assert.Contains(t, errs[0].Error(), "cannot use pipe input with non-file type profile") | |
| 446 | ||
| 447 | // OSGetwd failure | |
| 448 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 449 | OSGetwd: func() (string, error) { return "", fmt.Errorf("getwd failed") }, | |
| 450 | }) | |
| 451 | errs = processProfile(Profile{Type: FileType, NamePatterns: []ParameterizedText{"f"}}, projectName, extras, *usr, nil) | |
| 452 | assert.Equal(t, 1, len(errs)) | |
| 453 | assert.Contains(t, errs[0].Error(), "unable to get current working directory") | |
| 454 | ||
| 455 | // OSReadDir failure | |
| 456 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 457 | OSGetwd: func() (string, error) { return cwd, nil }, | |
| 458 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { return nil, fmt.Errorf("readdir failed") }, | |
| 459 | OSStat: func(name string) (os.FileInfo, error) { return createDirEntry("d", true), nil }, | |
| 460 | OSChdir: func(dir string) error { return nil }, | |
| 461 | }) | |
| 462 | errs = processProfile(Profile{Type: FileType, NamePatterns: []ParameterizedText{"f"}, TargetDirectory: "./test/dir"}, projectName, extras, *usr, nil) | |
| 463 | assert.Equal(t, 1, len(errs)) | |
| 464 | assert.Contains(t, errs[0].Error(), "readdir failed") | |
| 465 | ||
| 466 | // OSStat returns ErrNotExist → OSMkdir fails | |
| 467 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 468 | OSGetwd: func() (string, error) { return cwd, nil }, | |
| 469 | OSStat: func(name string) (os.FileInfo, error) { return nil, os.ErrNotExist }, | |
| 470 | OSMkdir: func(path string, perm os.FileMode) error { return fmt.Errorf("mkdir target failed") }, | |
| 471 | }) | |
| 472 | errs = processProfile(Profile{Type: FileType, NamePatterns: []ParameterizedText{"f"}, TargetDirectory: "./test/dir"}, projectName, extras, *usr, nil) | |
| 473 | assert.Equal(t, 1, len(errs)) | |
| 474 | assert.Contains(t, errs[0].Error(), "unable to create target directory") | |
| 475 | ||
| 476 | // OSChdir fails after mkdir succeeds | |
| 477 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 478 | OSGetwd: func() (string, error) { return cwd, nil }, | |
| 479 | OSStat: func(name string) (os.FileInfo, error) { return nil, os.ErrNotExist }, | |
| 480 | OSMkdir: func(path string, perm os.FileMode) error { return nil }, | |
| 481 | OSChdir: func(dir string) error { return fmt.Errorf("chdir target failed") }, | |
| 482 | }) | |
| 483 | errs = processProfile(Profile{Type: FileType, NamePatterns: []ParameterizedText{"f"}, TargetDirectory: "./test/dir"}, projectName, extras, *usr, nil) | |
| 484 | assert.Equal(t, 1, len(errs)) | |
| 485 | assert.Contains(t, errs[0].Error(), "unable to change directory (1)") | |
| 486 | ||
| 487 | // RunPostscriptOnFailure=true, no good name, still runs post script | |
| 488 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 489 | OSGetwd: func() (string, error) { return cwd, nil }, | |
| 490 | OSStat: func(name string) (os.FileInfo, error) { return createDirEntry("d", true), nil }, | |
| 491 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { | |
| 492 | return []os.DirEntry{createDirEntry("file1", false)}, nil | |
| 493 | }, | |
| 494 | ExecCommand: func(command string, args ...string) *exec.Cmd { | |
| 495 | shell, shellArgs := shellCommandAndArgs("echo") | |
| 496 | return exec.Command(shell, shellArgs...) | |
| 497 | }, | |
| 498 | }) | |
| 499 | errs = processProfile(Profile{ | |
| 500 | Type: FileType, | |
| 501 | NamePatterns: []ParameterizedText{"file1"}, | |
| 502 | RunPostscriptOnFailure: true, | |
| 503 | PostScripts: []ParameterizedText{"echo hi"}, | |
| 504 | }, projectName, extras, *usr, nil) | |
| 505 | assert.Equal(t, 0, len(errs)) | |
| 506 | ||
| 507 | // createEntity error without swallow → "unable to create entity" | |
| 508 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 509 | OSGetwd: func() (string, error) { return cwd, nil }, | |
| 510 | OSStat: func(name string) (os.FileInfo, error) { return createDirEntry("d", true), nil }, | |
| 511 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { return []os.DirEntry{}, nil }, | |
| 512 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { | |
| 513 | return fmt.Errorf("write failed") | |
| 514 | }, | |
| 515 | }) | |
| 516 | errs = processProfile(Profile{ | |
| 517 | Type: FileType, | |
| 518 | NamePatterns: []ParameterizedText{"newfile"}, | |
| 519 | }, projectName, extras, *usr, nil) | |
| 520 | assert.Equal(t, 1, len(errs)) | |
| 521 | assert.Contains(t, errs[0].Error(), "unable to create entity") | |
| 522 | ||
| 523 | // createEntity error with swallow → continues to post scripts | |
| 524 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 525 | OSGetwd: func() (string, error) { return cwd, nil }, | |
| 526 | OSStat: func(name string) (os.FileInfo, error) { return createDirEntry("d", true), nil }, | |
| 527 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { return []os.DirEntry{}, nil }, | |
| 528 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { | |
| 529 | return fmt.Errorf("write failed") | |
| 530 | }, | |
| 531 | ExecCommand: func(command string, args ...string) *exec.Cmd { | |
| 532 | shell, shellArgs := shellCommandAndArgs("echo") | |
| 533 | return exec.Command(shell, shellArgs...) | |
| 534 | }, | |
| 535 | }) | |
| 536 | errs = processProfile(Profile{ | |
| 537 | Type: FileType, | |
| 538 | NamePatterns: []ParameterizedText{"newfile"}, | |
| 539 | RunPostscriptOnFailure: true, | |
| 540 | PostScripts: []ParameterizedText{"echo hi"}, | |
| 541 | }, projectName, extras, *usr, nil) | |
| 542 | assert.Equal(t, 0, len(errs)) | |
| 543 | ||
| 544 | // invalid post script template → createPostScriptArrays failure | |
| 545 | configureMonkeyPatching() | |
| 546 | errs = processProfile(Profile{ | |
| 547 | Type: FileType, | |
| 548 | NamePatterns: []ParameterizedText{"file3"}, | |
| 549 | TargetDirectory: "./test/dir", | |
| 550 | PostScripts: []ParameterizedText{"{{.Bad"}, | |
| 551 | }, projectName, extras, *usr, nil) | |
| 552 | assert.Equal(t, 1, len(errs)) | |
| 553 | assert.Contains(t, errs[0].Error(), "unable to create post scripts") | |
| 554 | ||
| 555 | // post script with no output → "Post script had no output" | |
| 556 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 557 | OSGetwd: func() (string, error) { return cwd, nil }, | |
| 558 | OSStat: func(name string) (os.FileInfo, error) { return createDirEntry("d", true), nil }, | |
| 559 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { return []os.DirEntry{}, nil }, | |
| 560 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { return nil }, | |
| 561 | ExecCommand: func(command string, args ...string) *exec.Cmd { | |
| 562 | return exec.Command("sh", "-c", "true") // exits 0, no stdout output | |
| 563 | }, | |
| 564 | }) | |
| 565 | errs = processProfile(Profile{ | |
| 566 | Type: FileType, | |
| 567 | NamePatterns: []ParameterizedText{"newfile"}, | |
| 568 | PostScripts: []ParameterizedText{"true"}, | |
| 569 | }, projectName, extras, *usr, nil) | |
| 570 | assert.Equal(t, 0, len(errs)) | |
| 571 | ||
| 572 | // post script that exits non-zero → logs error but does not fail | |
| 573 | applyTestMonkeyPatch(t, monkeyPatch{ | |
| 574 | OSGetwd: func() (string, error) { return cwd, nil }, | |
| 575 | OSStat: func(name string) (os.FileInfo, error) { return createDirEntry("d", true), nil }, | |
| 576 | OSReadDir: func(dirname string) ([]os.DirEntry, error) { return []os.DirEntry{}, nil }, | |
| 577 | OSWriteFile: func(filename string, data []byte, perm os.FileMode) error { return nil }, | |
| 578 | ExecCommand: func(command string, args ...string) *exec.Cmd { | |
| 579 | return exec.Command("sh", "-c", "false") // exits 1 | |
| 580 | }, | |
| 581 | }) | |
| 582 | errs = processProfile(Profile{ | |
| 583 | Type: FileType, | |
| 584 | NamePatterns: []ParameterizedText{"newfile"}, | |
| 585 | PostScripts: []ParameterizedText{"false"}, | |
| 586 | }, projectName, extras, *usr, nil) | |
| 587 | assert.Equal(t, 0, len(errs)) | |
| 588 | } | |
| 589 | ||
| 590 | func TestCreateNewNameForPatternsGetVersionError(t *testing.T) { | |
| 591 | now := testNow() | |
| 592 | patternData := testPrePatternData() | |
| 593 | // getVersion used in a non-last pattern is an error | |
| 594 | _, err := createNewNameForPatterns(now, patternData, "proj", | |
| 595 | []ParameterizedText{"{{getVersion}}-name", "fallback"}, | |
| 596 | nil, | |
| 597 | func(newName string) bool { return false }, // always reject | |
| 598 | ) | |
| 599 | assert.NotNil(t, err) | |
| 600 | assert.Contains(t, err.Error(), "cannot use function getVersion in a pattern that is not the last pattern") | |
9573ca02 2022-11-16 Pavel Safronov | 601 | } |
| 602 | ||
| 603 | func getCurrentOs() OSType { | |
| 604 | os := runtime.GOOS | |
| 605 | if os == "windows" { | |
| 606 | return "Windows" | |
| 607 | } else { | |
| 608 | return "Mac" | |
| 609 | } | |
| 610 | } | |
| 611 | func getOtherOs(currentOs OSType) OSType { | |
9b0177be 2022-11-19 Pavel Safronov | 612 | if currentOs == "Windows" { |
9573ca02 2022-11-16 Pavel Safronov | 613 | return "Mac" |
| 614 | } | |
| 615 | return "Windows" | |
c3fc7a22 2022-10-30 Pavel Safronov | 616 | } |