mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-28 03:46:14 +00:00
* Support all glob wildcard characters in namespace validation
Expand namespace validation to allow all valid glob pattern characters
(*, ?, {}, [], ,) by replacing them with valid characters during RFC 1123
validation. The actual glob pattern validation is handled separately by
the wildcard package.
Also add validation to reject unsupported characters (|, (), !) that are
not valid in glob patterns, and update terminology from "regex" to "glob"
for clarity since this implementation uses glob patterns, not regex.
Changes:
- Replace all glob wildcard characters in validateNamespaceName
- Add test coverage for valid glob patterns in includes/excludes
- Add test coverage for unsupported characters
- Reject exclamation mark (!) in wildcard patterns
- Clarify comments and error messages about glob vs regex
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
* Changelog
Signed-off-by: Joseph <jvaikath@redhat.com>
* Add documentation: glob patterns are now accepted
Signed-off-by: Joseph <jvaikath@redhat.com>
* Error message fix
Signed-off-by: Joseph <jvaikath@redhat.com>
* Remove negation glob char test
Signed-off-by: Joseph <jvaikath@redhat.com>
* Add bracket pattern validation for namespace glob patterns
Extends wildcard validation to support square bracket patterns [] used in glob character classes. Validates bracket syntax including empty brackets, unclosed brackets, and unmatched brackets. Extracts ValidateNamespaceName as a public function to enable reuse in namespace validation logic.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
* Reduce scope to *, ?, [ and ]
Signed-off-by: Joseph <jvaikath@redhat.com>
* Fix tests
Signed-off-by: Joseph <jvaikath@redhat.com>
* Add namespace glob patterns documentation page
Adds dedicated documentation explaining supported glob patterns
for namespace include/exclude filtering to help users understand
the wildcard syntax.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
* Fix build-image Dockerfile envtest download
Replace inaccessible go.kubebuilder.io URL with setup-envtest and update envtest version to 1.33.0 to match Kubernetes v0.33.3 dependencies.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
* kubebuilder binaries mv
Signed-off-by: Joseph <jvaikath@redhat.com>
* Reject brace patterns and update documentation
Add {, }, and , to unsupported characters list to explicitly reject
brace expansion patterns. Remove { from wildcard detection since these
patterns are not supported in the 1.18 release.
Update all documentation to show supported patterns inline (*, ?, [abc])
with clickable links to the detailed namespace-glob-patterns page.
Simplify YAML comments by removing non-clickable URLs.
Update tests to expect errors when brace patterns are used.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
* Document brace expansion as unsupported
Add {} and , to the unsupported patterns section to clarify that
brace expansion patterns like {a,b,c} are not supported.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
* Update tests to expect brace pattern rejection
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
---------
Signed-off-by: Joseph <jvaikath@redhat.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
623 lines
18 KiB
Go
623 lines
18 KiB
Go
package wildcard
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestShouldExpandWildcards(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
includes []string
|
|
excludes []string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "no wildcards",
|
|
includes: []string{"ns1", "ns2"},
|
|
excludes: []string{"ns3", "ns4"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "includes has star - should not expand",
|
|
includes: []string{"*"},
|
|
excludes: []string{"ns1"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "includes has star after a wildcard pattern - should not expand",
|
|
includes: []string{"ns*", "*"},
|
|
excludes: []string{"ns1"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "includes has wildcard pattern",
|
|
includes: []string{"ns*"},
|
|
excludes: []string{"ns1"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "excludes has wildcard pattern",
|
|
includes: []string{"ns1"},
|
|
excludes: []string{"ns*"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "both have wildcard patterns",
|
|
includes: []string{"app-*"},
|
|
excludes: []string{"test-*"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "includes has star and wildcard - star takes precedence",
|
|
includes: []string{"*", "ns*"},
|
|
excludes: []string{},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "double asterisk should be detected as wildcard",
|
|
includes: []string{"**"},
|
|
excludes: []string{},
|
|
expected: true, // ** is a wildcard pattern (but will error during validation)
|
|
},
|
|
{
|
|
name: "empty slices",
|
|
includes: []string{},
|
|
excludes: []string{},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "complex wildcard patterns",
|
|
includes: []string{"*-prod"},
|
|
excludes: []string{"test-*-staging"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "question mark wildcard",
|
|
includes: []string{"ns?"},
|
|
excludes: []string{},
|
|
expected: true, // question mark is now considered a wildcard
|
|
},
|
|
{
|
|
name: "character class wildcard",
|
|
includes: []string{"ns[abc]"},
|
|
excludes: []string{},
|
|
expected: true, // character class is considered wildcard
|
|
},
|
|
{
|
|
name: "brace alternatives wildcard",
|
|
includes: []string{"ns{prod,staging}"},
|
|
excludes: []string{},
|
|
expected: false, // brace alternatives are not supported
|
|
},
|
|
{
|
|
name: "dot is literal - not wildcard",
|
|
includes: []string{"app.prod"},
|
|
excludes: []string{},
|
|
expected: false, // dot is literal, not wildcard
|
|
},
|
|
{
|
|
name: "plus is literal - not wildcard",
|
|
includes: []string{"app+"},
|
|
excludes: []string{},
|
|
expected: false, // plus is literal, not wildcard
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := ShouldExpandWildcards(tt.includes, tt.excludes)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExpandWildcards(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
activeNamespaces []string
|
|
includes []string
|
|
excludes []string
|
|
expectedIncludes []string
|
|
expectedExcludes []string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "no wildcards",
|
|
activeNamespaces: []string{"ns1", "ns2", "ns3"},
|
|
includes: []string{"ns1", "ns4"},
|
|
excludes: []string{"ns2"},
|
|
expectedIncludes: []string{"ns1", "ns4"},
|
|
expectedExcludes: []string{"ns2"},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "wildcard in includes",
|
|
activeNamespaces: []string{"app-prod", "app-staging", "db-prod", "test-ns"},
|
|
includes: []string{"app-*"},
|
|
excludes: []string{"test-ns"},
|
|
expectedIncludes: []string{"app-prod", "app-staging"},
|
|
expectedExcludes: []string{"test-ns"},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "wildcard in excludes",
|
|
activeNamespaces: []string{"app-prod", "app-staging", "db-prod", "test-ns"},
|
|
includes: []string{"app-prod"},
|
|
excludes: []string{"*-staging"},
|
|
expectedIncludes: []string{"app-prod"},
|
|
expectedExcludes: []string{"app-staging"},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "wildcards in both",
|
|
activeNamespaces: []string{"app-prod", "app-staging", "db-prod", "db-staging", "test-ns"},
|
|
includes: []string{"*-prod"},
|
|
excludes: []string{"*-staging"},
|
|
expectedIncludes: []string{"app-prod", "db-prod"},
|
|
expectedExcludes: []string{"app-staging", "db-staging"},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "star pattern in includes",
|
|
activeNamespaces: []string{"ns1", "ns2", "ns3"},
|
|
includes: []string{"*"},
|
|
excludes: []string{},
|
|
expectedIncludes: []string{"ns1", "ns2", "ns3"},
|
|
expectedExcludes: nil,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "empty active namespaces",
|
|
activeNamespaces: []string{},
|
|
includes: []string{"app-*"},
|
|
excludes: []string{"test-*"},
|
|
expectedIncludes: nil,
|
|
expectedExcludes: nil,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "empty includes and excludes",
|
|
activeNamespaces: []string{"ns1", "ns2"},
|
|
includes: []string{},
|
|
excludes: []string{},
|
|
expectedIncludes: nil,
|
|
expectedExcludes: nil,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "complex patterns",
|
|
activeNamespaces: []string{"my-app-prod", "my-app-staging", "your-app-prod", "system-ns"},
|
|
includes: []string{"*-app-*"},
|
|
excludes: []string{"*-staging"},
|
|
expectedIncludes: []string{"my-app-prod", "my-app-staging", "your-app-prod"},
|
|
expectedExcludes: []string{"my-app-staging"},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "double asterisk should error",
|
|
activeNamespaces: []string{"ns1", "ns2", "ns3"},
|
|
includes: []string{"**"},
|
|
excludes: []string{},
|
|
expectedIncludes: nil,
|
|
expectedExcludes: nil,
|
|
expectError: true, // ** is invalid
|
|
},
|
|
{
|
|
name: "double asterisk in pattern should error",
|
|
activeNamespaces: []string{"ns1", "ns2", "ns3"},
|
|
includes: []string{"app-**"},
|
|
excludes: []string{},
|
|
expectedIncludes: nil,
|
|
expectedExcludes: nil,
|
|
expectError: true, // app-** contains ** which is invalid
|
|
},
|
|
{
|
|
name: "question mark patterns",
|
|
activeNamespaces: []string{"ns1", "ns2", "ns10", "test"},
|
|
includes: []string{"ns?"},
|
|
excludes: []string{},
|
|
expectedIncludes: []string{"ns1", "ns2"}, // ? matches single character
|
|
expectedExcludes: nil,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "character class patterns",
|
|
activeNamespaces: []string{"nsa", "nsb", "nsc", "nsx", "ns1"},
|
|
includes: []string{"ns[abc]"},
|
|
excludes: []string{},
|
|
expectedIncludes: []string{"nsa", "nsb", "nsc"}, // [abc] matches a, b, or c
|
|
expectedExcludes: nil,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "brace alternative patterns",
|
|
activeNamespaces: []string{"app-prod", "app-staging", "app-dev", "db-prod"},
|
|
includes: []string{"app-{prod,staging}"},
|
|
excludes: []string{},
|
|
expectedIncludes: nil,
|
|
expectedExcludes: nil,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "literal dot and plus patterns",
|
|
activeNamespaces: []string{"app.prod", "app-prod", "app+", "app"},
|
|
includes: []string{"app.prod", "app+"},
|
|
excludes: []string{},
|
|
expectedIncludes: []string{"app.prod", "app+"}, // . and + are literal
|
|
expectedExcludes: nil,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "unsupported regex patterns should error",
|
|
activeNamespaces: []string{"ns1", "ns2"},
|
|
includes: []string{"ns(1|2)"},
|
|
excludes: []string{},
|
|
expectedIncludes: nil,
|
|
expectedExcludes: nil,
|
|
expectError: true, // |, (, ) are not supported
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
includes, excludes, err := ExpandWildcards(tt.activeNamespaces, tt.includes, tt.excludes)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.ElementsMatch(t, tt.expectedIncludes, includes)
|
|
assert.ElementsMatch(t, tt.expectedExcludes, excludes)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExpandWildcardsPrivate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
patterns []string
|
|
activeNamespaces []string
|
|
expected []string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "empty patterns",
|
|
patterns: []string{},
|
|
activeNamespaces: []string{"ns1", "ns2"},
|
|
expected: nil,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "non-wildcard patterns",
|
|
patterns: []string{"ns1", "ns3"},
|
|
activeNamespaces: []string{"ns1", "ns2"},
|
|
expected: []string{"ns1", "ns3"}, // includes ns3 even if not in active
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "star pattern",
|
|
patterns: []string{"*"},
|
|
activeNamespaces: []string{"ns1", "ns2", "ns3"},
|
|
expected: []string{"ns1", "ns2", "ns3"},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "simple wildcard",
|
|
patterns: []string{"app-*"},
|
|
activeNamespaces: []string{"app-prod", "app-staging", "db-prod"},
|
|
expected: []string{"app-prod", "app-staging"},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "multiple patterns",
|
|
patterns: []string{"app-*", "db-prod", "*-test"},
|
|
activeNamespaces: []string{"app-prod", "app-staging", "db-prod", "service-test", "other"},
|
|
expected: []string{"app-prod", "app-staging", "db-prod", "service-test"},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "wildcard with no matches",
|
|
patterns: []string{"missing-*"},
|
|
activeNamespaces: []string{"app-prod", "db-staging"},
|
|
expected: []string{}, // returns empty slice, not nil
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "duplicate matches from multiple patterns",
|
|
patterns: []string{"app-*", "*-prod"},
|
|
activeNamespaces: []string{"app-prod", "app-staging", "db-prod"},
|
|
expected: []string{"app-prod", "app-staging", "db-prod"}, // no duplicates
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "question mark pattern - glob wildcard",
|
|
patterns: []string{"ns?"},
|
|
activeNamespaces: []string{"ns1", "ns2", "ns10"},
|
|
expected: []string{"ns1", "ns2"}, // ? is a glob pattern for single character
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "character class patterns",
|
|
patterns: []string{"ns[12]"},
|
|
activeNamespaces: []string{"ns1", "ns2", "ns3", "nsa"},
|
|
expected: []string{"ns1", "ns2"}, // [12] matches 1 or 2
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "character range patterns",
|
|
patterns: []string{"ns[a-c]"},
|
|
activeNamespaces: []string{"nsa", "nsb", "nsc", "nsd", "ns1"},
|
|
expected: []string{"nsa", "nsb", "nsc"}, // [a-c] matches a to c
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "double asterisk should error",
|
|
patterns: []string{"**"},
|
|
activeNamespaces: []string{"app-prod", "app.staging", "db/prod"},
|
|
expected: nil,
|
|
expectError: true, // ** is not allowed
|
|
},
|
|
{
|
|
name: "unsupported regex symbols should error",
|
|
patterns: []string{"ns(1|2)"},
|
|
activeNamespaces: []string{"ns1", "ns2"},
|
|
expected: nil,
|
|
expectError: true, // |, (, ) not supported
|
|
},
|
|
{
|
|
name: "double asterisk should error",
|
|
patterns: []string{"**"},
|
|
activeNamespaces: []string{"ns1", "ns2"},
|
|
expected: nil,
|
|
expectError: true, // ** not allowed
|
|
},
|
|
{
|
|
name: "double asterisk in pattern should error",
|
|
patterns: []string{"app-**-prod"},
|
|
activeNamespaces: []string{"app-prod"},
|
|
expected: nil,
|
|
expectError: true, // ** not allowed anywhere
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := expandWildcards(tt.patterns, tt.activeNamespaces)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
if tt.expected == nil {
|
|
assert.Nil(t, result)
|
|
} else if len(tt.expected) == 0 {
|
|
assert.Empty(t, result)
|
|
} else {
|
|
assert.ElementsMatch(t, tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateBracePatterns(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pattern string
|
|
expectError bool
|
|
errorMsg string
|
|
}{
|
|
// Valid square bracket patterns
|
|
{
|
|
name: "valid square bracket pattern",
|
|
pattern: "ns[abc]",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid square bracket pattern with range",
|
|
pattern: "ns[a-z]",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid square bracket pattern with numbers",
|
|
pattern: "ns[0-9]",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid square bracket pattern with mixed",
|
|
pattern: "ns[a-z0-9]",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid square bracket pattern with single character",
|
|
pattern: "ns[a]",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid square bracket pattern with text before and after",
|
|
pattern: "prefix-[abc]-suffix",
|
|
expectError: false,
|
|
},
|
|
// Unclosed opening brackets
|
|
{
|
|
name: "unclosed opening bracket at end",
|
|
pattern: "ns[abc",
|
|
expectError: true,
|
|
errorMsg: "unclosed bracket",
|
|
},
|
|
{
|
|
name: "unclosed opening bracket at start",
|
|
pattern: "[abc",
|
|
expectError: true,
|
|
errorMsg: "unclosed bracket",
|
|
},
|
|
{
|
|
name: "unclosed opening bracket in middle",
|
|
pattern: "ns[abc-test",
|
|
expectError: true,
|
|
errorMsg: "unclosed bracket",
|
|
},
|
|
|
|
// Unmatched closing brackets
|
|
{
|
|
name: "unmatched closing bracket at end",
|
|
pattern: "ns-abc]",
|
|
expectError: true,
|
|
errorMsg: "unmatched closing bracket",
|
|
},
|
|
{
|
|
name: "unmatched closing bracket at start",
|
|
pattern: "]ns-abc",
|
|
expectError: true,
|
|
errorMsg: "unmatched closing bracket",
|
|
},
|
|
{
|
|
name: "unmatched closing bracket in middle",
|
|
pattern: "ns-]abc",
|
|
expectError: true,
|
|
errorMsg: "unmatched closing bracket",
|
|
},
|
|
{
|
|
name: "extra closing bracket after valid pair",
|
|
pattern: "ns[abc]]",
|
|
expectError: true,
|
|
errorMsg: "unmatched closing bracket",
|
|
},
|
|
|
|
// Empty bracket patterns
|
|
{
|
|
name: "completely empty brackets",
|
|
pattern: "ns[]",
|
|
expectError: true,
|
|
errorMsg: "empty bracket pattern",
|
|
},
|
|
{
|
|
name: "empty brackets at start",
|
|
pattern: "[]ns",
|
|
expectError: true,
|
|
errorMsg: "empty bracket pattern",
|
|
},
|
|
{
|
|
name: "empty brackets standalone",
|
|
pattern: "[]",
|
|
expectError: true,
|
|
errorMsg: "empty bracket pattern",
|
|
},
|
|
|
|
// Edge cases
|
|
{
|
|
name: "empty pattern",
|
|
pattern: "",
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := validateBracePatterns(tt.pattern)
|
|
|
|
if tt.expectError {
|
|
require.Error(t, err, "Expected error for pattern: %s", tt.pattern)
|
|
if tt.errorMsg != "" {
|
|
assert.Contains(t, err.Error(), tt.errorMsg, "Error message should contain: %s", tt.errorMsg)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err, "Expected no error for pattern: %s", tt.pattern)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Edge case tests
|
|
func TestExpandWildcardsEdgeCases(t *testing.T) {
|
|
t.Run("nil inputs", func(t *testing.T) {
|
|
includes, excludes, err := ExpandWildcards(nil, nil, nil)
|
|
require.NoError(t, err)
|
|
assert.Nil(t, includes)
|
|
assert.Nil(t, excludes)
|
|
})
|
|
|
|
t.Run("empty string patterns", func(t *testing.T) {
|
|
activeNamespaces := []string{"ns1", "ns2"}
|
|
result, err := expandWildcards([]string{""}, activeNamespaces)
|
|
require.NoError(t, err)
|
|
assert.ElementsMatch(t, []string{""}, result) // empty string is treated as literal
|
|
})
|
|
|
|
t.Run("whitespace patterns", func(t *testing.T) {
|
|
activeNamespaces := []string{"ns1", " ", "ns2"}
|
|
result, err := expandWildcards([]string{" "}, activeNamespaces)
|
|
require.NoError(t, err)
|
|
assert.ElementsMatch(t, []string{" "}, result)
|
|
})
|
|
|
|
t.Run("special characters in namespace names", func(t *testing.T) {
|
|
activeNamespaces := []string{"ns-1", "ns_2", "ns.3", "ns@4"}
|
|
result, err := expandWildcards([]string{"ns*"}, activeNamespaces)
|
|
require.NoError(t, err)
|
|
assert.ElementsMatch(t, []string{"ns-1", "ns_2", "ns.3", "ns@4"}, result)
|
|
})
|
|
|
|
t.Run("mixed literal and wildcard patterns", func(t *testing.T) {
|
|
activeNamespaces := []string{"app.prod", "app-prod", "app_prod", "test.ns"}
|
|
result, err := expandWildcards([]string{"app.prod", "app?prod"}, activeNamespaces)
|
|
require.NoError(t, err)
|
|
assert.ElementsMatch(t, []string{"app.prod", "app-prod", "app_prod"}, result)
|
|
})
|
|
|
|
t.Run("conservative asterisk validation", func(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pattern string
|
|
shouldError bool
|
|
}{
|
|
{"single asterisk", "*", false},
|
|
{"double asterisk", "**", true},
|
|
{"triple asterisk", "***", true},
|
|
{"quadruple asterisk", "****", true},
|
|
{"mixed with double", "app-**", true},
|
|
{"double in middle", "app-**-prod", true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := expandWildcards([]string{tt.pattern}, []string{"test"})
|
|
if tt.shouldError {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("malformed pattern validation", func(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pattern string
|
|
shouldError bool
|
|
}{
|
|
{"unclosed bracket", "ns[abc", true},
|
|
{"valid bracket", "ns[abc]", false},
|
|
{"empty bracket", "ns[]", true}, // empty brackets are invalid
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := expandWildcards([]string{tt.pattern}, []string{"test"})
|
|
if tt.shouldError {
|
|
assert.Error(t, err, "Expected error for pattern: %s", tt.pattern)
|
|
} else {
|
|
assert.NoError(t, err, "Expected no error for pattern: %s", tt.pattern)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|