a94637ab13
text extractions tests * increase timeout for slow ci/cd systems * add tests
369 lines
9.5 KiB
Go
369 lines
9.5 KiB
Go
package folder
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestValidateFolderPath_ValidPaths(t *testing.T) {
|
|
validPaths := []string{
|
|
"/",
|
|
"/foo",
|
|
"/foo/bar",
|
|
"/foo/bar/baz",
|
|
"/my-folder",
|
|
"/my_folder",
|
|
"/my folder",
|
|
"/My Folder",
|
|
"/folder.name",
|
|
"/2024",
|
|
"/folder123",
|
|
"/a",
|
|
"/A",
|
|
"/foo bar/baz qux",
|
|
"/documents/2024/Q1",
|
|
"/client-data/reports_2024/monthly.reports",
|
|
}
|
|
|
|
for _, path := range validPaths {
|
|
t.Run(path, func(t *testing.T) {
|
|
err := ValidateFolderPath(path)
|
|
assert.NoError(t, err, "path should be valid: %s", path)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFolderPath_MustStartWithSlash(t *testing.T) {
|
|
invalidPaths := []string{
|
|
"foo",
|
|
"foo/bar",
|
|
"./foo",
|
|
"../foo",
|
|
" /foo",
|
|
}
|
|
|
|
for _, path := range invalidPaths {
|
|
t.Run(path, func(t *testing.T) {
|
|
err := ValidateFolderPath(path)
|
|
require.Error(t, err)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "start_with_slash", pve.Constraint)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFolderPath_PathTraversal(t *testing.T) {
|
|
tests := []struct {
|
|
path string
|
|
wantErr bool
|
|
}{
|
|
{"/foo/bar", false},
|
|
{"/foo/../bar", true},
|
|
{"/../etc/passwd", true},
|
|
{"/foo/./bar", true},
|
|
{"/foo/..", true},
|
|
{"/foo/bar/..", true},
|
|
{"/foo/.", true},
|
|
{"/..foo", true}, // starts with dot, blocked by hidden folder rule
|
|
{"/foo..bar", true}, // contains ..
|
|
{"/foo...bar", true}, // contains ..
|
|
{"/foo/bar..baz", true}, // contains ..
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.path, func(t *testing.T) {
|
|
err := ValidateFolderPath(tt.path)
|
|
if tt.wantErr {
|
|
require.Error(t, err, "path should be invalid: %s", tt.path)
|
|
} else {
|
|
assert.NoError(t, err, "path should be valid: %s", tt.path)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFolderPath_EmptySegments(t *testing.T) {
|
|
invalidPaths := []string{
|
|
"//foo",
|
|
"/foo//bar",
|
|
"/foo/bar//",
|
|
"/foo/",
|
|
"/foo/bar/",
|
|
"///",
|
|
}
|
|
|
|
for _, path := range invalidPaths {
|
|
t.Run(path, func(t *testing.T) {
|
|
err := ValidateFolderPath(path)
|
|
require.Error(t, err, "path should be invalid: %s", path)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "empty_segment", pve.Constraint)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFolderPath_LeadingTrailingWhitespace(t *testing.T) {
|
|
invalidPaths := []string{
|
|
"/ foo",
|
|
"/foo ",
|
|
"/ foo ",
|
|
"/foo/ bar",
|
|
"/foo /bar",
|
|
"/ foo",
|
|
"/foo ",
|
|
}
|
|
|
|
for _, path := range invalidPaths {
|
|
t.Run(path, func(t *testing.T) {
|
|
err := ValidateFolderPath(path)
|
|
require.Error(t, err, "path should be invalid: %s", path)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "whitespace", pve.Constraint)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFolderPath_HiddenFolders(t *testing.T) {
|
|
invalidPaths := []string{
|
|
"/.hidden",
|
|
"/foo/.secret",
|
|
"/.git",
|
|
"/foo/.config/bar",
|
|
}
|
|
|
|
for _, path := range invalidPaths {
|
|
t.Run(path, func(t *testing.T) {
|
|
err := ValidateFolderPath(path)
|
|
require.Error(t, err, "path should be invalid: %s", path)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
// Could be hidden_folder or path_traversal depending on the case
|
|
assert.Contains(t, []string{"hidden_folder", "path_traversal"}, pve.Constraint)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFolderPath_InvalidCharacters(t *testing.T) {
|
|
invalidPaths := []string{
|
|
"/foo@bar",
|
|
"/foo#bar",
|
|
"/foo\\bar",
|
|
"/foo<bar>",
|
|
"/foo\"bar",
|
|
"/foo'bar",
|
|
"/foo:bar",
|
|
"/foo*bar",
|
|
"/foo?bar",
|
|
"/foo|bar",
|
|
"/foo`bar",
|
|
"/foo~bar",
|
|
"/foo!bar",
|
|
"/foo$bar",
|
|
"/foo%bar",
|
|
"/foo^bar",
|
|
"/foo&bar",
|
|
"/foo+bar",
|
|
"/foo=bar",
|
|
"/foo[bar]",
|
|
"/foo{bar}",
|
|
"/foo;bar",
|
|
"/foo,bar",
|
|
}
|
|
|
|
for _, path := range invalidPaths {
|
|
t.Run(path, func(t *testing.T) {
|
|
err := ValidateFolderPath(path)
|
|
require.Error(t, err, "path should be invalid: %s", path)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "invalid_characters", pve.Constraint)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFolderPath_WindowsReservedNames(t *testing.T) {
|
|
reservedNames := []string{
|
|
"CON", "PRN", "AUX", "NUL",
|
|
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
|
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
|
|
"con", "prn", "aux", "nul", // lowercase
|
|
"Con", "Prn", "Aux", "Nul", // mixed case
|
|
}
|
|
|
|
for _, name := range reservedNames {
|
|
t.Run(name, func(t *testing.T) {
|
|
// Test as root segment
|
|
err := ValidateFolderPath("/" + name)
|
|
require.Error(t, err, "reserved name should be invalid: /%s", name)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "reserved_name", pve.Constraint)
|
|
|
|
// Test as nested segment
|
|
err = ValidateFolderPath("/foo/" + name + "/bar")
|
|
require.Error(t, err, "reserved name in path should be invalid: /foo/%s/bar", name)
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "reserved_name", pve.Constraint)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFolderPath_SegmentLength(t *testing.T) {
|
|
// Valid: exactly 255 characters
|
|
validSegment := strings.Repeat("a", 255)
|
|
err := ValidateFolderPath("/" + validSegment)
|
|
assert.NoError(t, err, "255 character segment should be valid")
|
|
|
|
// Invalid: 256 characters
|
|
invalidSegment := strings.Repeat("a", 256)
|
|
err = ValidateFolderPath("/" + invalidSegment)
|
|
require.Error(t, err)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "segment_length", pve.Constraint)
|
|
}
|
|
|
|
func TestValidateFolderPath_PathDepth(t *testing.T) {
|
|
// Valid: exactly 20 levels
|
|
segments := make([]string, 20)
|
|
for i := range segments {
|
|
segments[i] = "a"
|
|
}
|
|
validPath := "/" + strings.Join(segments, "/")
|
|
err := ValidateFolderPath(validPath)
|
|
assert.NoError(t, err, "20 level path should be valid")
|
|
|
|
// Invalid: 21 levels
|
|
segments = make([]string, 21)
|
|
for i := range segments {
|
|
segments[i] = "a"
|
|
}
|
|
invalidPath := "/" + strings.Join(segments, "/")
|
|
err = ValidateFolderPath(invalidPath)
|
|
require.Error(t, err)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "max_depth", pve.Constraint)
|
|
}
|
|
|
|
func TestValidateFolderPath_MaxLength(t *testing.T) {
|
|
// Valid: exactly 1000 characters using multiple segments (each <=255 chars)
|
|
// 4 segments of 249 chars each = /249/249/249/249 = 1 + 249 + 1 + 249 + 1 + 249 + 1 + 249 = 1000
|
|
seg249 := strings.Repeat("a", 249)
|
|
validPath := "/" + seg249 + "/" + seg249 + "/" + seg249 + "/" + seg249
|
|
assert.Equal(t, 1000, len(validPath), "sanity check: path should be 1000 chars")
|
|
err := ValidateFolderPath(validPath)
|
|
assert.NoError(t, err, "1000 character path should be valid")
|
|
|
|
// Invalid: 1001 characters
|
|
invalidPath := validPath + "a"
|
|
err = ValidateFolderPath(invalidPath)
|
|
require.Error(t, err)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "max_length", pve.Constraint)
|
|
}
|
|
|
|
func TestValidateFolderPath_ControlCharacters(t *testing.T) {
|
|
controlChars := []rune{
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
|
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
|
0x7F,
|
|
}
|
|
|
|
for _, char := range controlChars {
|
|
t.Run(string(char), func(t *testing.T) {
|
|
path := "/foo" + string(char) + "bar"
|
|
err := ValidateFolderPath(path)
|
|
require.Error(t, err, "control character 0x%02X should be invalid", char)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "control_characters", pve.Constraint)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFolderPath_SegmentMustStartWithAlphanumeric(t *testing.T) {
|
|
invalidPaths := []string{
|
|
"/-foo",
|
|
"/_foo",
|
|
"/ foo", // leading space
|
|
"/foo/-bar",
|
|
"/foo/_bar",
|
|
}
|
|
|
|
for _, path := range invalidPaths {
|
|
t.Run(path, func(t *testing.T) {
|
|
err := ValidateFolderPath(path)
|
|
require.Error(t, err, "path should be invalid: %s", path)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFolderPathForRename(t *testing.T) {
|
|
t.Run("root folder cannot be renamed", func(t *testing.T) {
|
|
err := ValidateFolderPathForRename("/new-name", true)
|
|
require.Error(t, err)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "root_rename", pve.Constraint)
|
|
})
|
|
|
|
t.Run("cannot rename to root", func(t *testing.T) {
|
|
err := ValidateFolderPathForRename("/", false)
|
|
require.Error(t, err)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "rename_to_root", pve.Constraint)
|
|
})
|
|
|
|
t.Run("valid rename", func(t *testing.T) {
|
|
err := ValidateFolderPathForRename("/new-folder", false)
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestValidateFolderPathForCreate(t *testing.T) {
|
|
t.Run("cannot create root when exists", func(t *testing.T) {
|
|
err := ValidateFolderPathForCreate("/", true)
|
|
require.Error(t, err)
|
|
var pve *PathValidationError
|
|
require.ErrorAs(t, err, &pve)
|
|
assert.Equal(t, "root_exists", pve.Constraint)
|
|
})
|
|
|
|
t.Run("can create root when not exists", func(t *testing.T) {
|
|
err := ValidateFolderPathForCreate("/", false)
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
t.Run("valid create", func(t *testing.T) {
|
|
err := ValidateFolderPathForCreate("/new-folder", true)
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestIsPathValidationError(t *testing.T) {
|
|
t.Run("PathValidationError returns true", func(t *testing.T) {
|
|
err := &PathValidationError{Constraint: "test", Message: "test message"}
|
|
assert.True(t, IsPathValidationError(err))
|
|
})
|
|
|
|
t.Run("other error returns false", func(t *testing.T) {
|
|
err := assert.AnError
|
|
assert.False(t, IsPathValidationError(err))
|
|
})
|
|
|
|
t.Run("nil returns false", func(t *testing.T) {
|
|
assert.False(t, IsPathValidationError(nil))
|
|
})
|
|
}
|