From c953aba6fc51003c460e087c656a3632634752f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Andr=C3=A9=20Galv=C3=A3o=20Da=20Silva?= Date: Sat, 25 Apr 2026 21:40:41 -0300 Subject: [PATCH 1/2] feat: options to format the output --- cmd/cmd_test.go | 105 +++++++++++++++++++++++++++++++++++++++++ cmd/configmaps.go | 7 ++- cmd/pods.go | 7 ++- cmd/resources.go | 11 +++-- cmd/secrets.go | 7 ++- cmd/serviceaccounts.go | 7 ++- cmd/utils.go | 30 ++++++++++++ 7 files changed, 162 insertions(+), 12 deletions(-) diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index 2ae7622..f03b343 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/hbelmiro/kgrep/internal/resource" "github.com/spf13/cobra" ) @@ -13,27 +14,33 @@ func resetFlags() { resourcesNamespace = "" resourcesPattern = "" resourcesAllNamespaces = false + resourcesOutputFormat = "" podsNamespace = "" podsPattern = "" podsAllNamespaces = false + podsOutputFormat = "" configmapsNamespace = "" configmapsPattern = "" configmapsAllNamespaces = false + configmapsOutputFormat = "" secretsNamespace = "" secretsPattern = "" secretsAllNamespaces = false + secretsOutputFormat = "" serviceaccountsNamespace = "" serviceaccountsPattern = "" serviceaccountsAllNamespaces = false + serviceaccountsOutputFormat = "" logsNamespace = "" logsResource = "" logsPattern = "" logsSortBy = "" + outputFormat = "" } func executeCommand(root *cobra.Command, args ...string) (output string, err error) { @@ -386,3 +393,101 @@ func TestAllNamespacesFlagAccepted_Pods(t *testing.T) { t.Logf("Expected error for kubeconfig/connectivity issues: %v", err) } } + +// Test output format flag is accepted +func TestOutputFormatFlag_ConfigMaps(t *testing.T) { + output, err := executeCommand(rootCmd, "configmaps", "--pattern", "test", "--output", "name-only") + + // We don't expect a flag validation error, though the command may fail for other reasons (like kubeconfig) + if err != nil && strings.Contains(err.Error(), "unknown flag") { + t.Errorf("Unexpected flag validation error for --output flag: %v, output: %s", err, output) + } + + // Any other errors are acceptable (like kubeconfig issues) + t.Logf("Command output: %s", output) +} + +func TestOutputFormatFlag_Secrets(t *testing.T) { + output, err := executeCommand(rootCmd, "secrets", "--pattern", "test", "--output", "name-only") + + if err != nil && strings.Contains(err.Error(), "unknown flag") { + t.Errorf("Unexpected flag validation error for --output flag: %v, output: %s", err, output) + } + + t.Logf("Command output: %s", output) +} + +func TestOutputFormatFlag_Pods(t *testing.T) { + output, err := executeCommand(rootCmd, "pods", "--pattern", "test", "--output", "name-only") + + if err != nil && strings.Contains(err.Error(), "unknown flag") { + t.Errorf("Unexpected flag validation error for --output flag: %v, output: %s", err, output) + } + + t.Logf("Command output: %s", output) +} + +func TestOutputFormatFlag_ServiceAccounts(t *testing.T) { + output, err := executeCommand(rootCmd, "serviceaccounts", "--pattern", "test", "--output", "name-only") + + if err != nil && strings.Contains(err.Error(), "unknown flag") { + t.Errorf("Unexpected flag validation error for --output flag: %v, output: %s", err, output) + } + + t.Logf("Command output: %s", output) +} + +func TestOutputFormatFlag_Resources(t *testing.T) { + output, err := executeCommand(rootCmd, "resources", "--kind", "Pod", "--pattern", "test", "--output", "name-only") + + if err != nil && strings.Contains(err.Error(), "unknown flag") { + t.Errorf("Unexpected flag validation error for --output flag: %v, output: %s", err, output) + } + + t.Logf("Command output: %s", output) +} + +// Test printResourceOccurrences with name-only output format +func TestPrintResourceOccurrences_NameOnly(t *testing.T) { + // Set output format to name-only + outputFormat = "name-only" + defer func() { outputFormat = "" }() // Reset after test + + occurrences := []resource.Occurrence{ + {Resource: "secret1", Namespace: "ns1", Line: 10, Content: "some content"}, + {Resource: "secret1", Namespace: "ns1", Line: 20, Content: "more content"}, + {Resource: "secret2", Namespace: "ns1", Line: 5, Content: "other content"}, + {Resource: "secret3", Namespace: "ns2", Line: 1, Content: "data"}, + } + + // Capture output + old := new(bytes.Buffer) + // Redirect stdout temporarily + // Note: In real tests, this would need proper stdout redirection + // For now, we just test the function doesn't panic + printResourceOccurrences(occurrences, "test") + + _ = old // silence unused variable warning +} + +// Test printResourceOccurrences with default output format +func TestPrintResourceOccurrences_Default(t *testing.T) { + // Ensure output format is default (empty) + outputFormat = "" + + occurrences := []resource.Occurrence{ + {Resource: "configmap1", Namespace: "ns1", Line: 10, Content: "some content"}, + } + + // Just test the function doesn't panic + printResourceOccurrences(occurrences, "test") +} + +// Test printResourceOccurrences with no occurrences +func TestPrintResourceOccurrences_NoOccurrences(t *testing.T) { + outputFormat = "" + occurrences := []resource.Occurrence{} + + // Just test the function doesn't panic + printResourceOccurrences(occurrences, "test") +} diff --git a/cmd/configmaps.go b/cmd/configmaps.go index a2a9ab2..b068804 100644 --- a/cmd/configmaps.go +++ b/cmd/configmaps.go @@ -8,9 +8,10 @@ import ( ) var ( - configmapsNamespace string - configmapsPattern string + configmapsNamespace string + configmapsPattern string configmapsAllNamespaces bool + configmapsOutputFormat string ) var configmapsCmd = &cobra.Command{ @@ -51,6 +52,7 @@ var configmapsCmd = &cobra.Command{ } } + outputFormat = configmapsOutputFormat printResourceOccurrences(occurrences, configmapsPattern) return nil @@ -63,6 +65,7 @@ func init() { configmapsCmd.Flags().StringVarP(&configmapsNamespace, "namespace", "n", "", "The Kubernetes namespace") configmapsCmd.Flags().StringVarP(&configmapsPattern, "pattern", "p", "", "grep search pattern") configmapsCmd.Flags().BoolVarP(&configmapsAllNamespaces, "all-namespaces", "A", false, "If present, list the requested object(s) across all namespaces") + configmapsCmd.Flags().StringVarP(&configmapsOutputFormat, "output", "o", "", "Output format: '' (default) or 'name-only'") if err := configmapsCmd.MarkFlagRequired("pattern"); err != nil { panic(fmt.Sprintf("failed to mark pattern flag as required: %v", err)) diff --git a/cmd/pods.go b/cmd/pods.go index c372782..972f3d3 100644 --- a/cmd/pods.go +++ b/cmd/pods.go @@ -8,9 +8,10 @@ import ( ) var ( - podsNamespace string - podsPattern string + podsNamespace string + podsPattern string podsAllNamespaces bool + podsOutputFormat string ) var podsCmd = &cobra.Command{ @@ -51,6 +52,7 @@ var podsCmd = &cobra.Command{ } } + outputFormat = podsOutputFormat printResourceOccurrences(occurrences, podsPattern) return nil @@ -63,6 +65,7 @@ func init() { podsCmd.Flags().StringVarP(&podsNamespace, "namespace", "n", "", "The Kubernetes namespace") podsCmd.Flags().StringVarP(&podsPattern, "pattern", "p", "", "grep search pattern") podsCmd.Flags().BoolVarP(&podsAllNamespaces, "all-namespaces", "A", false, "If present, list the requested object(s) across all namespaces") + podsCmd.Flags().StringVarP(&podsOutputFormat, "output", "o", "", "Output format: '' (default) or 'name-only'") if err := podsCmd.MarkFlagRequired("pattern"); err != nil { panic(fmt.Sprintf("failed to mark pattern flag as required: %v", err)) diff --git a/cmd/resources.go b/cmd/resources.go index 1d29293..f488303 100644 --- a/cmd/resources.go +++ b/cmd/resources.go @@ -8,11 +8,12 @@ import ( ) var ( - resourcesNamespace string - resourcesPattern string - resourcesAPIVersion string - resourcesKind string + resourcesNamespace string + resourcesPattern string + resourcesAPIVersion string + resourcesKind string resourcesAllNamespaces bool + resourcesOutputFormat string ) var resourcesCmd = &cobra.Command{ @@ -60,6 +61,7 @@ var resourcesCmd = &cobra.Command{ } } + outputFormat = resourcesOutputFormat printResourceOccurrences(occurrences, resourcesPattern) return nil @@ -74,6 +76,7 @@ func init() { resourcesCmd.Flags().StringVar(&resourcesAPIVersion, "api-version", "", "API version (e.g., v1, apps/v1). If not provided, will be auto-discovered.") resourcesCmd.Flags().StringVarP(&resourcesKind, "kind", "k", "", "Resource kind (e.g., Pod, Deployment)") resourcesCmd.Flags().BoolVarP(&resourcesAllNamespaces, "all-namespaces", "A", false, "If present, list the requested object(s) across all namespaces") + resourcesCmd.Flags().StringVarP(&resourcesOutputFormat, "output", "o", "", "Output format: '' (default) or 'name-only'") if err := resourcesCmd.MarkFlagRequired("pattern"); err != nil { panic(fmt.Sprintf("failed to mark pattern flag as required: %v", err)) diff --git a/cmd/secrets.go b/cmd/secrets.go index b67071c..300bb52 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -9,9 +9,10 @@ import ( ) var ( - secretsNamespace string - secretsPattern string + secretsNamespace string + secretsPattern string secretsAllNamespaces bool + secretsOutputFormat string ) var secretsCmd = &cobra.Command{ @@ -54,6 +55,7 @@ var secretsCmd = &cobra.Command{ } } + outputFormat = secretsOutputFormat printResourceOccurrences(occurrences, secretsPattern) return nil @@ -66,6 +68,7 @@ func init() { secretsCmd.Flags().StringVarP(&secretsNamespace, "namespace", "n", "", "The Kubernetes namespace") secretsCmd.Flags().StringVarP(&secretsPattern, "pattern", "p", "", "grep search pattern") secretsCmd.Flags().BoolVarP(&secretsAllNamespaces, "all-namespaces", "A", false, "If present, list the requested object(s) across all namespaces") + secretsCmd.Flags().StringVarP(&secretsOutputFormat, "output", "o", "", "Output format: '' (default) or 'name-only'") if err := secretsCmd.MarkFlagRequired("pattern"); err != nil { panic(fmt.Sprintf("failed to mark pattern flag as required: %v", err)) diff --git a/cmd/serviceaccounts.go b/cmd/serviceaccounts.go index f25d42e..749d507 100644 --- a/cmd/serviceaccounts.go +++ b/cmd/serviceaccounts.go @@ -8,9 +8,10 @@ import ( ) var ( - serviceaccountsNamespace string - serviceaccountsPattern string + serviceaccountsNamespace string + serviceaccountsPattern string serviceaccountsAllNamespaces bool + serviceaccountsOutputFormat string ) var serviceaccountsCmd = &cobra.Command{ @@ -51,6 +52,7 @@ var serviceaccountsCmd = &cobra.Command{ } } + outputFormat = serviceaccountsOutputFormat printResourceOccurrences(occurrences, serviceaccountsPattern) return nil @@ -63,6 +65,7 @@ func init() { serviceaccountsCmd.Flags().StringVarP(&serviceaccountsNamespace, "namespace", "n", "", "The Kubernetes namespace") serviceaccountsCmd.Flags().StringVarP(&serviceaccountsPattern, "pattern", "p", "", "grep search pattern") serviceaccountsCmd.Flags().BoolVarP(&serviceaccountsAllNamespaces, "all-namespaces", "A", false, "If present, list the requested object(s) across all namespaces") + serviceaccountsCmd.Flags().StringVarP(&serviceaccountsOutputFormat, "output", "o", "", "Output format: '' (default) or 'name-only'") if err := serviceaccountsCmd.MarkFlagRequired("pattern"); err != nil { panic(fmt.Sprintf("failed to mark pattern flag as required: %v", err)) diff --git a/cmd/utils.go b/cmd/utils.go index 03db08a..b6cd877 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -8,12 +8,22 @@ import ( "github.com/hbelmiro/kgrep/internal/resource" ) +// outputFormat controls how the output is displayed +var outputFormat string + +// printResourceOccurrences prints occurrences based on the output format func printResourceOccurrences(occurrences []resource.Occurrence, pattern string) { if len(occurrences) == 0 { fmt.Printf("No occurrences of '%s' found.\n", pattern) return } + if outputFormat == "name-only" { + printResourceNamesOnly(occurrences) + return + } + + // Default format fmt.Printf("Found %d occurrence(s) of '%s':\n\n", len(occurrences), pattern) for _, occurrence := range occurrences { @@ -31,3 +41,23 @@ func printResourceOccurrences(occurrences []resource.Occurrence, pattern string) fmt.Printf("%s %s\n", prefix, highlightedContent) } } + +// printResourceNamesOnly prints only the unique resource names +func printResourceNamesOnly(occurrences []resource.Occurrence) { + // Use a map to deduplicate resource names + resourceNames := make(map[string]bool) + for _, occurrence := range occurrences { + var resourceKey string + if occurrence.Namespace != "" { + resourceKey = fmt.Sprintf("%s/%s", occurrence.Namespace, occurrence.Resource) + } else { + resourceKey = occurrence.Resource + } + resourceNames[resourceKey] = true + } + + // Print unique resource names + for name := range resourceNames { + fmt.Println(name) + } +} From 1f8bdafa24b380f6b434191ee76ac6381b912e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Andr=C3=A9=20Galv=C3=A3o=20Da=20Silva?= Date: Sun, 26 Apr 2026 00:24:55 -0300 Subject: [PATCH 2/2] feat: pattern around quotes --- cmd/cmd_test.go | 146 +++++++++++++++++++- cmd/utils.go | 18 ++- go.mod | 1 + go.sum | 2 + internal/resource/resource_searcher_test.go | 76 ++++++++++ 5 files changed, 235 insertions(+), 8 deletions(-) diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index f03b343..405b960 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/brianvoe/gofakeit/v6" "github.com/hbelmiro/kgrep/internal/resource" "github.com/spf13/cobra" ) @@ -453,11 +454,13 @@ func TestPrintResourceOccurrences_NameOnly(t *testing.T) { outputFormat = "name-only" defer func() { outputFormat = "" }() // Reset after test + // Use gofakeit to generate test data + gofakeit.Seed(0) occurrences := []resource.Occurrence{ - {Resource: "secret1", Namespace: "ns1", Line: 10, Content: "some content"}, - {Resource: "secret1", Namespace: "ns1", Line: 20, Content: "more content"}, - {Resource: "secret2", Namespace: "ns1", Line: 5, Content: "other content"}, - {Resource: "secret3", Namespace: "ns2", Line: 1, Content: "data"}, + {Resource: gofakeit.Word(), Namespace: gofakeit.Word(), Line: gofakeit.Number(1, 100), Content: gofakeit.Sentence(5)}, + {Resource: gofakeit.Word(), Namespace: gofakeit.Word(), Line: gofakeit.Number(1, 100), Content: gofakeit.Sentence(5)}, + {Resource: gofakeit.Word(), Namespace: gofakeit.Word(), Line: gofakeit.Number(1, 100), Content: gofakeit.Sentence(5)}, + {Resource: gofakeit.Word(), Namespace: gofakeit.Word(), Line: gofakeit.Number(1, 100), Content: gofakeit.Sentence(5)}, } // Capture output @@ -470,13 +473,20 @@ func TestPrintResourceOccurrences_NameOnly(t *testing.T) { _ = old // silence unused variable warning } -// Test printResourceOccurrences with default output format +// Test printResourceOccurrences with default output format using gofakeit func TestPrintResourceOccurrences_Default(t *testing.T) { // Ensure output format is default (empty) outputFormat = "" + // Use gofakeit to generate realistic test data + gofakeit.Seed(0) occurrences := []resource.Occurrence{ - {Resource: "configmap1", Namespace: "ns1", Line: 10, Content: "some content"}, + { + Resource: gofakeit.Word(), + Namespace: gofakeit.Word(), + Line: gofakeit.Number(1, 100), + Content: gofakeit.Sentence(10), + }, } // Just test the function doesn't panic @@ -491,3 +501,127 @@ func TestPrintResourceOccurrences_NoOccurrences(t *testing.T) { // Just test the function doesn't panic printResourceOccurrences(occurrences, "test") } + +// Test formatPatternForDisplay function +func TestFormatPatternForDisplay(t *testing.T) { + tests := []struct { + input string + expected string + }{ + // Patterns without special characters should be wrapped in single quotes + {"error", "'error'"}, + {"test", "'test'"}, + {"hello world", "'hello world'"}, + {"config-map", "'config-map'"}, + + // Patterns with special shell characters should also be wrapped in single quotes + {"[Error]", "'[Error]'"}, + {"test*", "'test*'"}, + {"file?", "'file?'"}, + {"hello|world", "'hello|world'"}, + {"test&debug", "'test&debug'"}, + {"a;b", "'a;b'"}, + {"helloworld", "'hello>world'"}, + {"echo`date`", "'echo`date`'"}, + {"hello$world", "'hello$world'"}, + {"test()", "'test()'"}, + {"{a,b}", "'{a,b}'"}, + {"pattern[0-9]", "'pattern[0-9]'"}, + + // Already quoted patterns should remain unchanged + {"'hello'", "'hello'"}, + {`"hello"`, `"hello"`}, + {"'[Error]'", "'[Error]'"}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result := formatPatternForDisplay(tt.input) + if result != tt.expected { + t.Errorf("formatPatternForDisplay(%q) = %q, want %q", tt.input, result, tt.expected) + } + }) + } +} + +// Test formatPatternForDisplay with randomly generated patterns using gofakeit +func TestFormatPatternForDisplay_RandomPatterns(t *testing.T) { + gofakeit.Seed(0) + + // Generate random patterns and verify they are handled correctly + for i := 0; i < 10; i++ { + // Generate a simple word (should be wrapped in single quotes) + simplePattern := gofakeit.Word() + result := formatPatternForDisplay(simplePattern) + expected := "'" + simplePattern + "'" + if result != expected { + t.Errorf("Simple pattern %q should be wrapped as %q, got %q", simplePattern, expected, result) + } + } + + // Test patterns with special characters generated by gofakeit + specialPatterns := []string{ + gofakeit.Sentence(3) + "[error]", // Contains brackets + gofakeit.Sentence(3) + "*", // Contains asterisk + gofakeit.Sentence(3) + "?", // Contains question mark + "{" + gofakeit.Word() + ",}", // Contains braces + } + + for _, pattern := range specialPatterns { + result := formatPatternForDisplay(pattern) + // Patterns with special chars should be wrapped in single quotes + if !strings.HasPrefix(result, "'") || !strings.HasSuffix(result, "'") { + t.Errorf("Pattern %q with special chars should be wrapped in single quotes, got %q", pattern, result) + } + } +} + +// Test that printResourceOccurrences uses formatPatternForDisplay for output messages +func TestPrintResourceOccurrences_SpecialCharacters(t *testing.T) { + outputFormat = "" + defer func() { outputFormat = "" }() + + // Use gofakeit to generate test data + gofakeit.Seed(0) + + // Test that pattern with special characters is displayed with quotes in the header + occurrences := []resource.Occurrence{ + { + Resource: gofakeit.Word(), + Namespace: gofakeit.Word(), + Line: gofakeit.Number(1, 100), + Content: "[Error] " + gofakeit.Sentence(5), + }, + } + + // Just test the function doesn't panic + printResourceOccurrences(occurrences, "[Error]") +} + +// Test printResourceOccurrences with gofakeit generated bulk data +func TestPrintResourceOccurrences_BulkData(t *testing.T) { + outputFormat = "" + defer func() { outputFormat = "" }() + + gofakeit.Seed(0) + + // Generate bulk test data + occurrences := make([]resource.Occurrence, 100) + for i := 0; i < 100; i++ { + occurrences[i] = resource.Occurrence{ + Resource: gofakeit.Word(), + Namespace: gofakeit.Word(), + Line: gofakeit.Number(1, 1000), + Content: gofakeit.Sentence(gofakeit.Number(5, 20)), + } + } + + // Test default output + printResourceOccurrences(occurrences, gofakeit.Word()) + + // Test name-only output + outputFormat = "name-only" + printResourceOccurrences(occurrences, gofakeit.Word()) + outputFormat = "" +} diff --git a/cmd/utils.go b/cmd/utils.go index b6cd877..a2fb900 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -11,10 +11,24 @@ import ( // outputFormat controls how the output is displayed var outputFormat string +// formatPatternForDisplay wraps the pattern in single quotes for display +// If the pattern is already wrapped in quotes, it returns it as-is +func formatPatternForDisplay(pattern string) string { + // If already wrapped in single or double quotes, return as-is + if (strings.HasPrefix(pattern, "'") && strings.HasSuffix(pattern, "'")) || + (strings.HasPrefix(pattern, "\"") && strings.HasSuffix(pattern, "\"")) { + return pattern + } + + return fmt.Sprintf("'%s'", pattern) +} + // printResourceOccurrences prints occurrences based on the output format func printResourceOccurrences(occurrences []resource.Occurrence, pattern string) { + displayPattern := formatPatternForDisplay(pattern) + if len(occurrences) == 0 { - fmt.Printf("No occurrences of '%s' found.\n", pattern) + fmt.Printf("No occurrences of %s found.\n", displayPattern) return } @@ -24,7 +38,7 @@ func printResourceOccurrences(occurrences []resource.Occurrence, pattern string) } // Default format - fmt.Printf("Found %d occurrence(s) of '%s':\n\n", len(occurrences), pattern) + fmt.Printf("Found %d occurrence(s) of %s:\n\n", len(occurrences), displayPattern) for _, occurrence := range occurrences { boldRed := color.New(color.FgRed).Add(color.Bold) diff --git a/go.mod b/go.mod index 788353b..84ca60b 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( ) require ( + github.com/brianvoe/gofakeit/v6 v6.28.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect diff --git a/go.sum b/go.sum index 9d260c6..5305860 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/internal/resource/resource_searcher_test.go b/internal/resource/resource_searcher_test.go index 6f7417d..4f5dbeb 100644 --- a/internal/resource/resource_searcher_test.go +++ b/internal/resource/resource_searcher_test.go @@ -4,6 +4,7 @@ import ( "strings" "testing" + "github.com/brianvoe/gofakeit/v6" "github.com/stretchr/testify/assert" "k8s.io/client-go/kubernetes/fake" ) @@ -365,3 +366,78 @@ func TestSearcher_APIVersionAndKindPrecedence(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "kubeGet client not available") } + +// TestOccurrence_WithFakeData tests the Occurrence struct with gofakeit generated data +func TestOccurrence_WithFakeData(t *testing.T) { + gofakeit.Seed(0) + + // Generate random occurrence data + occurrence := Occurrence{ + Resource: gofakeit.Word(), + Namespace: gofakeit.Word(), + Line: gofakeit.Number(1, 1000), + Content: gofakeit.Sentence(10), + } + + // Verify the occurrence has valid data + assert.NotEmpty(t, occurrence.Resource) + assert.NotEmpty(t, occurrence.Namespace) + assert.Greater(t, occurrence.Line, 0) + assert.NotEmpty(t, occurrence.Content) + + t.Logf("Generated occurrence: Resource=%s, Namespace=%s, Line=%d, Content=%s", + occurrence.Resource, occurrence.Namespace, occurrence.Line, occurrence.Content) +} + +// TestOccurrence_BulkGeneration tests generating multiple occurrences with gofakeit +func TestOccurrence_BulkGeneration(t *testing.T) { + gofakeit.Seed(0) + + // Generate multiple occurrences + occurrences := make([]Occurrence, 50) + for i := 0; i < 50; i++ { + occurrences[i] = Occurrence{ + Resource: gofakeit.Word(), + Namespace: gofakeit.Word(), + Line: gofakeit.Number(1, 1000), + Content: gofakeit.Sentence(gofakeit.Number(5, 20)), + } + } + + // Verify all occurrences have unique resources + resourceMap := make(map[string]bool) + for _, occ := range occurrences { + resourceMap[occ.Resource] = true + } + + // With 50 random words, we should have mostly unique resources + assert.GreaterOrEqual(t, len(resourceMap), 40, "Expected mostly unique resource names") + + t.Logf("Generated %d occurrences with %d unique resources", len(occurrences), len(resourceMap)) +} + +// TestOccurrence_DifferentPatterns tests occurrences with various pattern types +func TestOccurrence_DifferentPatterns(t *testing.T) { + gofakeit.Seed(0) + + patterns := []string{ + "[Error]", + "test*", + "debug?", + "INFO|WARN", + "{json}", + "hello;world", + } + + for _, pattern := range patterns { + occ := Occurrence{ + Resource: gofakeit.Word(), + Namespace: gofakeit.Word(), + Line: gofakeit.Number(1, 100), + Content: pattern + " " + gofakeit.Sentence(5), + } + + assert.Contains(t, occ.Content, pattern) + t.Logf("Pattern '%s' found in content: %s", pattern, occ.Content) + } +}