diff --git a/instruction_test.go b/instruction_test.go index 99f6b69..47e3b0e 100644 --- a/instruction_test.go +++ b/instruction_test.go @@ -315,6 +315,254 @@ func TestLinkInstruction_Undo(t *testing.T) { }) } +func TestGlobPatterns(t *testing.T) { + testDir := createTestDir(t) + defer cleanupTestDir(t, testDir) + + // Ensure we're working within the project directory + ensureInProjectDir(t, testDir) + + // Change to test directory + originalDir, _ := os.Getwd() + defer os.Chdir(originalDir) + os.Chdir(testDir) + + // Create complex directory structure for testing + createGlobTestStructure(t, testDir) + + t.Run("Simple glob pattern", func(t *testing.T) { + yamlContent := `- source: src/*.txt + target: dst` + yamlFile := filepath.Join(testDir, "simple_glob.yaml") + err := os.WriteFile(yamlFile, []byte(yamlContent), 0644) + assert.NoError(t, err) + + instructions, err := ParseYAMLFile(yamlFile, testDir) + assert.NoError(t, err) + assert.Equal(t, 2, len(instructions)) + + // Verify the actual files matched + sources := make([]string, len(instructions)) + for i, inst := range instructions { + sources[i] = filepath.Base(inst.Source) + } + assert.Contains(t, sources, "file1.txt") + assert.Contains(t, sources, "file2.txt") + }) + + t.Run("Double star pattern", func(t *testing.T) { + yamlContent := `- source: src/**/*.txt + target: dst` + yamlFile := filepath.Join(testDir, "double_star.yaml") + err := os.WriteFile(yamlFile, []byte(yamlContent), 0644) + assert.NoError(t, err) + + instructions, err := ParseYAMLFile(yamlFile, testDir) + assert.NoError(t, err) + assert.Equal(t, 5, len(instructions)) + + // Verify the actual files matched + sources := make([]string, len(instructions)) + for i, inst := range instructions { + sources[i] = filepath.Base(inst.Source) + } + assert.Contains(t, sources, "file1.txt") + assert.Contains(t, sources, "file2.txt") + assert.Contains(t, sources, "foobar.txt") // from foobar/foobar.txt + assert.Contains(t, sources, "foobar.txt") // from foobar/nested/foobar.txt + assert.Contains(t, sources, "nested.txt") + }) + + t.Run("Complex nested pattern", func(t *testing.T) { + yamlContent := `- source: src/**/nested/*.txt + target: dst` + yamlFile := filepath.Join(testDir, "nested_glob.yaml") + err := os.WriteFile(yamlFile, []byte(yamlContent), 0644) + assert.NoError(t, err) + + instructions, err := ParseYAMLFile(yamlFile, testDir) + assert.NoError(t, err) + assert.Equal(t, 2, len(instructions)) + + // Verify the actual files matched + sources := make([]string, len(instructions)) + for i, inst := range instructions { + sources[i] = filepath.Base(inst.Source) + } + assert.Contains(t, sources, "nested.txt") + assert.Contains(t, sources, "foobar.txt") + }) + + t.Run("Multiple file extensions", func(t *testing.T) { + yamlContent := `- source: src/**/*.{txt,log,md} + target: dst` + yamlFile := filepath.Join(testDir, "multi_ext.yaml") + err := os.WriteFile(yamlFile, []byte(yamlContent), 0644) + assert.NoError(t, err) + + instructions, err := ParseYAMLFile(yamlFile, testDir) + assert.NoError(t, err) + assert.Equal(t, 7, len(instructions)) + + // Verify the actual files matched + sources := make([]string, len(instructions)) + for i, inst := range instructions { + sources[i] = filepath.Base(inst.Source) + } + assert.Contains(t, sources, "file1.txt") + assert.Contains(t, sources, "file2.txt") + assert.Contains(t, sources, "file3.log") + assert.Contains(t, sources, "readme.md") + assert.Contains(t, sources, "foobar.txt") + assert.Contains(t, sources, "nested.txt") + }) + + t.Run("Crazy complex pattern", func(t *testing.T) { + yamlContent := `- source: src/**/*foobar/**/*.txt + target: dst` + yamlFile := filepath.Join(testDir, "crazy_pattern.yaml") + err := os.WriteFile(yamlFile, []byte(yamlContent), 0644) + assert.NoError(t, err) + + instructions, err := ParseYAMLFile(yamlFile, testDir) + assert.NoError(t, err) + assert.Equal(t, 2, len(instructions)) + + // Verify the actual files matched + sources := make([]string, len(instructions)) + for i, inst := range instructions { + sources[i] = filepath.Base(inst.Source) + } + assert.Contains(t, sources, "foobar.txt") + assert.Contains(t, sources, "foobar.txt") // Should have 2 foobar.txt files + }) + + t.Run("Pattern with no matches", func(t *testing.T) { + yamlContent := `- source: src/**/*.nonexistent + target: dst` + yamlFile := filepath.Join(testDir, "no_matches.yaml") + err := os.WriteFile(yamlFile, []byte(yamlContent), 0644) + assert.NoError(t, err) + + instructions, err := ParseYAMLFile(yamlFile, testDir) + assert.NoError(t, err) + assert.Equal(t, 0, len(instructions)) // No matches + }) + + t.Run("Single file pattern", func(t *testing.T) { + yamlContent := `- source: src/file1.txt + target: dst/single.txt` + yamlFile := filepath.Join(testDir, "single_file.yaml") + err := os.WriteFile(yamlFile, []byte(yamlContent), 0644) + assert.NoError(t, err) + + instructions, err := ParseYAMLFile(yamlFile, testDir) + assert.NoError(t, err) + assert.Equal(t, 1, len(instructions)) + assert.Contains(t, instructions[0].Source, "file1.txt") + }) + + t.Run("Pattern with special characters", func(t *testing.T) { + yamlContent := `- source: src/**/*[0-9]*.txt + target: dst` + yamlFile := filepath.Join(testDir, "special_chars.yaml") + err := os.WriteFile(yamlFile, []byte(yamlContent), 0644) + assert.NoError(t, err) + + instructions, err := ParseYAMLFile(yamlFile, testDir) + assert.NoError(t, err) + assert.Equal(t, 2, len(instructions)) // file1.txt and file2.txt + }) + + t.Run("Multiple glob patterns in one file", func(t *testing.T) { + yamlContent := `- source: src/*.txt + target: dst/txt +- source: src/**/*.log + target: dst/logs +- source: src/**/*.md + target: dst/docs` + yamlFile := filepath.Join(testDir, "multiple_patterns.yaml") + err := os.WriteFile(yamlFile, []byte(yamlContent), 0644) + assert.NoError(t, err) + + instructions, err := ParseYAMLFile(yamlFile, testDir) + assert.NoError(t, err) + assert.Equal(t, 4, len(instructions)) // 2 txt + 1 log + 1 md + }) + + t.Run("Glob with force and delete flags", func(t *testing.T) { + yamlContent := `- source: src/**/*.txt + target: dst + force: true + delete: true` + yamlFile := filepath.Join(testDir, "glob_with_flags.yaml") + err := os.WriteFile(yamlFile, []byte(yamlContent), 0644) + assert.NoError(t, err) + + instructions, err := ParseYAMLFile(yamlFile, testDir) + assert.NoError(t, err) + assert.Equal(t, 5, len(instructions)) + + // Verify the actual files matched + sources := make([]string, len(instructions)) + for i, inst := range instructions { + sources[i] = filepath.Base(inst.Source) + } + assert.Contains(t, sources, "file1.txt") + assert.Contains(t, sources, "file2.txt") + assert.Contains(t, sources, "foobar.txt") + assert.Contains(t, sources, "nested.txt") + + // Check that flags are preserved + for _, instr := range instructions { + assert.True(t, instr.Force) + assert.True(t, instr.Delete) + } + }) +} + +// createGlobTestStructure creates a complex directory structure for glob testing +func createGlobTestStructure(t *testing.T, testDir string) { + // Create main source directory + srcDir := filepath.Join(testDir, "src") + err := os.MkdirAll(srcDir, 0755) + assert.NoError(t, err) + + // Create nested directories + nestedDir := filepath.Join(srcDir, "nested") + err = os.MkdirAll(nestedDir, 0755) + assert.NoError(t, err) + + foobarDir := filepath.Join(srcDir, "foobar") + err = os.MkdirAll(foobarDir, 0755) + assert.NoError(t, err) + + foobarNestedDir := filepath.Join(foobarDir, "nested") + err = os.MkdirAll(foobarNestedDir, 0755) + assert.NoError(t, err) + + // Create various test files + testFiles := []struct { + path string + content string + }{ + {filepath.Join(srcDir, "file1.txt"), "content1"}, + {filepath.Join(srcDir, "file2.txt"), "content2"}, + {filepath.Join(srcDir, "file3.log"), "log content"}, + {filepath.Join(srcDir, "readme.md"), "documentation"}, + {filepath.Join(nestedDir, "nested.txt"), "nested content"}, + {filepath.Join(foobarDir, "foobar.txt"), "foobar content"}, + {filepath.Join(foobarNestedDir, "foobar.txt"), "nested foobar content"}, + {filepath.Join(srcDir, "config.json"), "json content"}, + {filepath.Join(srcDir, "data.csv"), "csv content"}, + } + + for _, tf := range testFiles { + err = os.WriteFile(tf.path, []byte(tf.content), 0644) + assert.NoError(t, err) + } +} + func TestParseYAMLFile(t *testing.T) { testDir := createTestDir(t) defer cleanupTestDir(t, testDir) @@ -2261,7 +2509,7 @@ func TestYAMLConfigFrom(t *testing.T) { // Parse should return error for non-existent file _, err = ParseYAMLFileRecursive(mainConfig, testDir) assert.Error(t, err) - assert.Contains(t, err.Error(), "error parsing referenced file") + assert.Contains(t, err.Error(), "error loading from reference") }) t.Run("ParseYAMLFileRecursive_no_from", func(t *testing.T) {