mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
Closes https://github.com/coder/internal/issues/732 We now try (up to 5 times) when attempting to create an agent using the workspace folder as the name. It is important to note this flow is only ever ran when attempting to create an agent using the workspace folder as the name. If a deployment uses terraform or the devcontainer customization, we do not fall back to this approach.
359 lines
8.1 KiB
Go
359 lines
8.1 KiB
Go
package agentcontainers
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/coder/coder/v2/provisioner"
|
|
)
|
|
|
|
func TestSafeAgentName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
folderName string
|
|
expected string
|
|
fallback bool
|
|
}{
|
|
// Basic valid names
|
|
{
|
|
folderName: "simple",
|
|
expected: "simple",
|
|
},
|
|
{
|
|
folderName: "with-hyphens",
|
|
expected: "with-hyphens",
|
|
},
|
|
{
|
|
folderName: "123numbers",
|
|
expected: "123numbers",
|
|
},
|
|
{
|
|
folderName: "mixed123",
|
|
expected: "mixed123",
|
|
},
|
|
|
|
// Names that need transformation
|
|
{
|
|
folderName: "With_Underscores",
|
|
expected: "with-underscores",
|
|
},
|
|
{
|
|
folderName: "With Spaces",
|
|
expected: "with-spaces",
|
|
},
|
|
{
|
|
folderName: "UPPERCASE",
|
|
expected: "uppercase",
|
|
},
|
|
{
|
|
folderName: "Mixed_Case-Name",
|
|
expected: "mixed-case-name",
|
|
},
|
|
|
|
// Names with special characters that get replaced
|
|
{
|
|
folderName: "special@#$chars",
|
|
expected: "special-chars",
|
|
},
|
|
{
|
|
folderName: "dots.and.more",
|
|
expected: "dots-and-more",
|
|
},
|
|
{
|
|
folderName: "multiple___underscores",
|
|
expected: "multiple-underscores",
|
|
},
|
|
{
|
|
folderName: "multiple---hyphens",
|
|
expected: "multiple-hyphens",
|
|
},
|
|
|
|
// Edge cases with leading/trailing special chars
|
|
{
|
|
folderName: "-leading-hyphen",
|
|
expected: "leading-hyphen",
|
|
},
|
|
{
|
|
folderName: "trailing-hyphen-",
|
|
expected: "trailing-hyphen",
|
|
},
|
|
{
|
|
folderName: "_leading_underscore",
|
|
expected: "leading-underscore",
|
|
},
|
|
{
|
|
folderName: "trailing_underscore_",
|
|
expected: "trailing-underscore",
|
|
},
|
|
{
|
|
folderName: "---multiple-leading",
|
|
expected: "multiple-leading",
|
|
},
|
|
{
|
|
folderName: "trailing-multiple---",
|
|
expected: "trailing-multiple",
|
|
},
|
|
|
|
// Complex transformation cases
|
|
{
|
|
folderName: "___very---complex@@@name___",
|
|
expected: "very-complex-name",
|
|
},
|
|
{
|
|
folderName: "my.project-folder_v2",
|
|
expected: "my-project-folder-v2",
|
|
},
|
|
|
|
// Empty and fallback cases - now correctly uses friendlyName fallback
|
|
{
|
|
folderName: "",
|
|
expected: "friendly-fallback",
|
|
fallback: true,
|
|
},
|
|
{
|
|
folderName: "---",
|
|
expected: "friendly-fallback",
|
|
fallback: true,
|
|
},
|
|
{
|
|
folderName: "___",
|
|
expected: "friendly-fallback",
|
|
fallback: true,
|
|
},
|
|
{
|
|
folderName: "@#$",
|
|
expected: "friendly-fallback",
|
|
fallback: true,
|
|
},
|
|
|
|
// Additional edge cases
|
|
{
|
|
folderName: "a",
|
|
expected: "a",
|
|
},
|
|
{
|
|
folderName: "1",
|
|
expected: "1",
|
|
},
|
|
{
|
|
folderName: "a1b2c3",
|
|
expected: "a1b2c3",
|
|
},
|
|
{
|
|
folderName: "CamelCase",
|
|
expected: "camelcase",
|
|
},
|
|
{
|
|
folderName: "snake_case_name",
|
|
expected: "snake-case-name",
|
|
},
|
|
{
|
|
folderName: "kebab-case-name",
|
|
expected: "kebab-case-name",
|
|
},
|
|
{
|
|
folderName: "mix3d_C4s3-N4m3",
|
|
expected: "mix3d-c4s3-n4m3",
|
|
},
|
|
{
|
|
folderName: "123-456-789",
|
|
expected: "123-456-789",
|
|
},
|
|
{
|
|
folderName: "abc123def456",
|
|
expected: "abc123def456",
|
|
},
|
|
{
|
|
folderName: " spaces everywhere ",
|
|
expected: "spaces-everywhere",
|
|
},
|
|
{
|
|
folderName: "unicode-café-naïve",
|
|
expected: "unicode-caf-na-ve",
|
|
},
|
|
{
|
|
folderName: "path/with/slashes",
|
|
expected: "path-with-slashes",
|
|
},
|
|
{
|
|
folderName: "file.tar.gz",
|
|
expected: "file-tar-gz",
|
|
},
|
|
{
|
|
folderName: "version-1.2.3-alpha",
|
|
expected: "version-1-2-3-alpha",
|
|
},
|
|
|
|
// Truncation test for names exceeding 64 characters
|
|
{
|
|
folderName: "this-is-a-very-long-folder-name-that-exceeds-sixty-four-characters-limit-and-should-be-truncated",
|
|
expected: "this-is-a-very-long-folder-name-that-exceeds-sixty-four-characte",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.folderName, func(t *testing.T) {
|
|
t.Parallel()
|
|
name, usingWorkspaceFolder := safeAgentName(tt.folderName, "friendly-fallback")
|
|
|
|
assert.Equal(t, tt.expected, name)
|
|
assert.True(t, provisioner.AgentNameRegex.Match([]byte(name)))
|
|
assert.Equal(t, tt.fallback, !usingWorkspaceFolder)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExpandedAgentName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
workspaceFolder string
|
|
friendlyName string
|
|
depth int
|
|
expected string
|
|
fallback bool
|
|
}{
|
|
{
|
|
name: "simple path depth 1",
|
|
workspaceFolder: "/home/coder/project",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 0,
|
|
expected: "project",
|
|
},
|
|
{
|
|
name: "simple path depth 2",
|
|
workspaceFolder: "/home/coder/project",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 1,
|
|
expected: "coder-project",
|
|
},
|
|
{
|
|
name: "simple path depth 3",
|
|
workspaceFolder: "/home/coder/project",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 2,
|
|
expected: "home-coder-project",
|
|
},
|
|
{
|
|
name: "simple path depth exceeds available",
|
|
workspaceFolder: "/home/coder/project",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 9,
|
|
expected: "home-coder-project",
|
|
},
|
|
// Cases with special characters that need sanitization
|
|
{
|
|
name: "path with spaces and special chars",
|
|
workspaceFolder: "/home/coder/My Project_v2",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 1,
|
|
expected: "coder-my-project-v2",
|
|
},
|
|
{
|
|
name: "path with dots and underscores",
|
|
workspaceFolder: "/home/user.name/project_folder.git",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 1,
|
|
expected: "user-name-project-folder-git",
|
|
},
|
|
// Edge cases
|
|
{
|
|
name: "empty path",
|
|
workspaceFolder: "",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 0,
|
|
expected: "friendly-fallback",
|
|
fallback: true,
|
|
},
|
|
{
|
|
name: "root path",
|
|
workspaceFolder: "/",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 0,
|
|
expected: "friendly-fallback",
|
|
fallback: true,
|
|
},
|
|
{
|
|
name: "single component",
|
|
workspaceFolder: "project",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 0,
|
|
expected: "project",
|
|
},
|
|
{
|
|
name: "single component with depth 2",
|
|
workspaceFolder: "project",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 1,
|
|
expected: "project",
|
|
},
|
|
// Collision simulation cases
|
|
{
|
|
name: "foo/project depth 1",
|
|
workspaceFolder: "/home/coder/foo/project",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 0,
|
|
expected: "project",
|
|
},
|
|
{
|
|
name: "foo/project depth 2",
|
|
workspaceFolder: "/home/coder/foo/project",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 1,
|
|
expected: "foo-project",
|
|
},
|
|
{
|
|
name: "bar/project depth 1",
|
|
workspaceFolder: "/home/coder/bar/project",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 0,
|
|
expected: "project",
|
|
},
|
|
{
|
|
name: "bar/project depth 2",
|
|
workspaceFolder: "/home/coder/bar/project",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 1,
|
|
expected: "bar-project",
|
|
},
|
|
// Path with trailing slashes
|
|
{
|
|
name: "path with trailing slash",
|
|
workspaceFolder: "/home/coder/project/",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 1,
|
|
expected: "coder-project",
|
|
},
|
|
{
|
|
name: "path with multiple trailing slashes",
|
|
workspaceFolder: "/home/coder/project///",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 1,
|
|
expected: "coder-project",
|
|
},
|
|
// Path with leading slashes
|
|
{
|
|
name: "path with multiple leading slashes",
|
|
workspaceFolder: "///home/coder/project",
|
|
friendlyName: "friendly-fallback",
|
|
depth: 1,
|
|
expected: "coder-project",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
name, usingWorkspaceFolder := expandedAgentName(tt.workspaceFolder, tt.friendlyName, tt.depth)
|
|
|
|
assert.Equal(t, tt.expected, name)
|
|
assert.True(t, provisioner.AgentNameRegex.Match([]byte(name)))
|
|
assert.Equal(t, tt.fallback, !usingWorkspaceFolder)
|
|
})
|
|
}
|
|
}
|