mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
* chore: create type for unique role names Using `string` was confusing when something should be combined with org context, and when not to. Naming this new name, "RoleIdentifier"
452 lines
11 KiB
Go
452 lines
11 KiB
Go
package searchquery_test
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/searchquery"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
)
|
|
|
|
func TestSearchWorkspace(t *testing.T) {
|
|
t.Parallel()
|
|
testCases := []struct {
|
|
Name string
|
|
Query string
|
|
Expected database.GetWorkspacesParams
|
|
ExpectedErrorContains string
|
|
}{
|
|
{
|
|
Name: "Empty",
|
|
Query: "",
|
|
Expected: database.GetWorkspacesParams{},
|
|
},
|
|
{
|
|
Name: "Owner/Name",
|
|
Query: "Foo/Bar",
|
|
Expected: database.GetWorkspacesParams{
|
|
OwnerUsername: "foo",
|
|
Name: "bar",
|
|
},
|
|
},
|
|
{
|
|
Name: "Owner/NameWithSpaces",
|
|
Query: " Foo/Bar ",
|
|
Expected: database.GetWorkspacesParams{
|
|
OwnerUsername: "foo",
|
|
Name: "bar",
|
|
},
|
|
},
|
|
{
|
|
Name: "Name",
|
|
Query: "workspace-name",
|
|
Expected: database.GetWorkspacesParams{
|
|
Name: "workspace-name",
|
|
},
|
|
},
|
|
{
|
|
Name: "Name+Param",
|
|
Query: "workspace-name TEMPLATE:docker",
|
|
Expected: database.GetWorkspacesParams{
|
|
Name: "workspace-name",
|
|
TemplateName: "docker",
|
|
},
|
|
},
|
|
{
|
|
Name: "OnlyParams",
|
|
Query: "name:workspace-name template:docker OWNER:Alice",
|
|
Expected: database.GetWorkspacesParams{
|
|
Name: "workspace-name",
|
|
TemplateName: "docker",
|
|
OwnerUsername: "alice",
|
|
},
|
|
},
|
|
{
|
|
Name: "QuotedParam",
|
|
Query: `name:workspace-name template:"docker template" owner:alice`,
|
|
Expected: database.GetWorkspacesParams{
|
|
Name: "workspace-name",
|
|
TemplateName: "docker template",
|
|
OwnerUsername: "alice",
|
|
},
|
|
},
|
|
{
|
|
Name: "QuotedKey",
|
|
Query: `"name":baz "template":foo "owner":bar`,
|
|
Expected: database.GetWorkspacesParams{
|
|
Name: "baz",
|
|
TemplateName: "foo",
|
|
OwnerUsername: "bar",
|
|
},
|
|
},
|
|
{
|
|
// Quotes keep elements together
|
|
Name: "QuotedSpecial",
|
|
Query: `name:"workspace:name"`,
|
|
Expected: database.GetWorkspacesParams{
|
|
Name: "workspace:name",
|
|
},
|
|
},
|
|
{
|
|
Name: "QuotedMadness",
|
|
Query: `"name":"foo:bar:baz/baz/zoo:zonk"`,
|
|
Expected: database.GetWorkspacesParams{
|
|
Name: "foo:bar:baz/baz/zoo:zonk",
|
|
},
|
|
},
|
|
{
|
|
Name: "QuotedName",
|
|
Query: `"foo/bar"`,
|
|
Expected: database.GetWorkspacesParams{
|
|
Name: "foo/bar",
|
|
},
|
|
},
|
|
{
|
|
Name: "QuotedOwner/Name",
|
|
Query: `"foo"/"bar"`,
|
|
Expected: database.GetWorkspacesParams{
|
|
Name: "bar",
|
|
OwnerUsername: "foo",
|
|
},
|
|
},
|
|
{
|
|
Name: "Outdated",
|
|
Query: `outdated:true`,
|
|
Expected: database.GetWorkspacesParams{
|
|
UsingActive: sql.NullBool{
|
|
Bool: false,
|
|
Valid: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Updated",
|
|
Query: `outdated:false`,
|
|
Expected: database.GetWorkspacesParams{
|
|
UsingActive: sql.NullBool{
|
|
Bool: true,
|
|
Valid: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "ParamName",
|
|
Query: "param:foo",
|
|
Expected: database.GetWorkspacesParams{
|
|
HasParam: []string{"foo"},
|
|
},
|
|
},
|
|
{
|
|
Name: "MultipleParamNames",
|
|
Query: "param:foo param:bar param:baz",
|
|
Expected: database.GetWorkspacesParams{
|
|
HasParam: []string{"foo", "bar", "baz"},
|
|
},
|
|
},
|
|
{
|
|
Name: "ParamValue",
|
|
Query: "param:foo=bar",
|
|
Expected: database.GetWorkspacesParams{
|
|
ParamNames: []string{"foo"},
|
|
ParamValues: []string{"bar"},
|
|
},
|
|
},
|
|
{
|
|
Name: "QuotedParamValue",
|
|
Query: `param:"image=ghcr.io/coder/coder-preview:main"`,
|
|
Expected: database.GetWorkspacesParams{
|
|
ParamNames: []string{"image"},
|
|
ParamValues: []string{"ghcr.io/coder/coder-preview:main"},
|
|
},
|
|
},
|
|
{
|
|
Name: "MultipleParamValues",
|
|
Query: "param:foo=bar param:fuzz=buzz",
|
|
Expected: database.GetWorkspacesParams{
|
|
ParamNames: []string{"foo", "fuzz"},
|
|
ParamValues: []string{"bar", "buzz"},
|
|
},
|
|
},
|
|
{
|
|
Name: "MixedParams",
|
|
Query: "param:dot param:foo=bar param:fuzz=buzz param:tot",
|
|
Expected: database.GetWorkspacesParams{
|
|
HasParam: []string{"dot", "tot"},
|
|
ParamNames: []string{"foo", "fuzz"},
|
|
ParamValues: []string{"bar", "buzz"},
|
|
},
|
|
},
|
|
{
|
|
Name: "ParamSpaces",
|
|
Query: `param:" dot " param:" foo=bar "`,
|
|
Expected: database.GetWorkspacesParams{
|
|
HasParam: []string{"dot"},
|
|
ParamNames: []string{"foo"},
|
|
ParamValues: []string{"bar"},
|
|
},
|
|
},
|
|
|
|
// Failures
|
|
{
|
|
Name: "ParamExcessValue",
|
|
Query: "param:foo=bar=baz",
|
|
ExpectedErrorContains: "can only contain 1 '='",
|
|
},
|
|
{
|
|
Name: "ParamNoValue",
|
|
Query: "param:foo=",
|
|
ExpectedErrorContains: "omit the '=' to match",
|
|
},
|
|
{
|
|
Name: "NoPrefix",
|
|
Query: `:foo`,
|
|
ExpectedErrorContains: "cannot start or end",
|
|
},
|
|
{
|
|
Name: "Double",
|
|
Query: `name:foo name:bar`,
|
|
ExpectedErrorContains: "provided more than once",
|
|
},
|
|
{
|
|
Name: "ExtraSlashes",
|
|
Query: `foo/bar/baz`,
|
|
ExpectedErrorContains: "can only contain 1 '/'",
|
|
},
|
|
{
|
|
Name: "ExtraColon",
|
|
Query: `owner:name:extra`,
|
|
ExpectedErrorContains: "can only contain 1 ':'",
|
|
},
|
|
{
|
|
Name: "ExtraKeys",
|
|
Query: `foo:bar`,
|
|
ExpectedErrorContains: `"foo" is not a valid query param`,
|
|
},
|
|
{
|
|
Name: "ParamExtraColons",
|
|
Query: "param:foo:value",
|
|
ExpectedErrorContains: "can only contain 1 ':'",
|
|
},
|
|
}
|
|
|
|
for _, c := range testCases {
|
|
c := c
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
values, errs := searchquery.Workspaces(c.Query, codersdk.Pagination{}, 0)
|
|
if c.ExpectedErrorContains != "" {
|
|
assert.True(t, len(errs) > 0, "expect some errors")
|
|
var s strings.Builder
|
|
for _, err := range errs {
|
|
_, _ = s.WriteString(fmt.Sprintf("%s: %s\n", err.Field, err.Detail))
|
|
}
|
|
assert.Contains(t, s.String(), c.ExpectedErrorContains)
|
|
} else {
|
|
if len(c.Expected.WorkspaceIds) == len(values.WorkspaceIds) {
|
|
// nil slice vs 0 len slice is equivalent for our purposes.
|
|
c.Expected.WorkspaceIds = values.WorkspaceIds
|
|
}
|
|
if len(c.Expected.HasParam) == len(values.HasParam) {
|
|
// nil slice vs 0 len slice is equivalent for our purposes.
|
|
c.Expected.HasParam = values.HasParam
|
|
}
|
|
assert.Len(t, errs, 0, "expected no error")
|
|
assert.Equal(t, c.Expected, values, "expected values")
|
|
}
|
|
})
|
|
}
|
|
t.Run("AgentInactiveDisconnectTimeout", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
query := ``
|
|
timeout := 1337 * time.Second
|
|
values, errs := searchquery.Workspaces(query, codersdk.Pagination{}, timeout)
|
|
require.Empty(t, errs)
|
|
require.Equal(t, int64(timeout.Seconds()), values.AgentInactiveDisconnectTimeoutSeconds)
|
|
})
|
|
}
|
|
|
|
func TestSearchAudit(t *testing.T) {
|
|
t.Parallel()
|
|
testCases := []struct {
|
|
Name string
|
|
Query string
|
|
Expected database.GetAuditLogsOffsetParams
|
|
ExpectedErrorContains string
|
|
}{
|
|
{
|
|
Name: "Empty",
|
|
Query: "",
|
|
Expected: database.GetAuditLogsOffsetParams{},
|
|
},
|
|
// Failures
|
|
{
|
|
Name: "ExtraColon",
|
|
Query: `search:name:extra`,
|
|
ExpectedErrorContains: "can only contain 1 ':'",
|
|
},
|
|
{
|
|
Name: "ExtraKeys",
|
|
Query: `foo:bar`,
|
|
ExpectedErrorContains: `"foo" is not a valid query param`,
|
|
},
|
|
{
|
|
Name: "Dates",
|
|
Query: "date_from:2006",
|
|
ExpectedErrorContains: "valid date format",
|
|
},
|
|
{
|
|
Name: "ResourceTarget",
|
|
Query: "resource_target:foo",
|
|
Expected: database.GetAuditLogsOffsetParams{
|
|
ResourceTarget: "foo",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range testCases {
|
|
c := c
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
values, errs := searchquery.AuditLogs(c.Query)
|
|
if c.ExpectedErrorContains != "" {
|
|
require.True(t, len(errs) > 0, "expect some errors")
|
|
var s strings.Builder
|
|
for _, err := range errs {
|
|
_, _ = s.WriteString(fmt.Sprintf("%s: %s\n", err.Field, err.Detail))
|
|
}
|
|
require.Contains(t, s.String(), c.ExpectedErrorContains)
|
|
} else {
|
|
require.Len(t, errs, 0, "expected no error")
|
|
require.Equal(t, c.Expected, values, "expected values")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSearchUsers(t *testing.T) {
|
|
t.Parallel()
|
|
testCases := []struct {
|
|
Name string
|
|
Query string
|
|
Expected database.GetUsersParams
|
|
ExpectedErrorContains string
|
|
}{
|
|
{
|
|
Name: "Empty",
|
|
Query: "",
|
|
Expected: database.GetUsersParams{
|
|
Status: []database.UserStatus{},
|
|
RbacRole: []string{},
|
|
},
|
|
},
|
|
{
|
|
Name: "Username",
|
|
Query: "user-name",
|
|
Expected: database.GetUsersParams{
|
|
Search: "user-name",
|
|
Status: []database.UserStatus{},
|
|
RbacRole: []string{},
|
|
},
|
|
},
|
|
{
|
|
Name: "UsernameWithSpaces",
|
|
Query: " user-name ",
|
|
Expected: database.GetUsersParams{
|
|
Search: "user-name",
|
|
Status: []database.UserStatus{},
|
|
RbacRole: []string{},
|
|
},
|
|
},
|
|
{
|
|
Name: "Username+Param",
|
|
Query: "usEr-name stAtus:actiVe",
|
|
Expected: database.GetUsersParams{
|
|
Search: "user-name",
|
|
Status: []database.UserStatus{database.UserStatusActive},
|
|
RbacRole: []string{},
|
|
},
|
|
},
|
|
{
|
|
Name: "OnlyParams",
|
|
Query: "status:acTIve sEArch:User-Name role:Owner",
|
|
Expected: database.GetUsersParams{
|
|
Search: "user-name",
|
|
Status: []database.UserStatus{database.UserStatusActive},
|
|
RbacRole: []string{codersdk.RoleOwner},
|
|
},
|
|
},
|
|
{
|
|
Name: "QuotedParam",
|
|
Query: `status:SuSpenDeD sEArch:"User Name" role:meMber`,
|
|
Expected: database.GetUsersParams{
|
|
Search: "user name",
|
|
Status: []database.UserStatus{database.UserStatusSuspended},
|
|
RbacRole: []string{codersdk.RoleMember},
|
|
},
|
|
},
|
|
{
|
|
Name: "QuotedKey",
|
|
Query: `"status":acTIve "sEArch":User-Name "role":Owner`,
|
|
Expected: database.GetUsersParams{
|
|
Search: "user-name",
|
|
Status: []database.UserStatus{database.UserStatusActive},
|
|
RbacRole: []string{codersdk.RoleOwner},
|
|
},
|
|
},
|
|
{
|
|
// Quotes keep elements together
|
|
Name: "QuotedSpecial",
|
|
Query: `search:"user:name"`,
|
|
Expected: database.GetUsersParams{
|
|
Search: "user:name",
|
|
Status: []database.UserStatus{},
|
|
RbacRole: []string{},
|
|
},
|
|
},
|
|
|
|
// Failures
|
|
{
|
|
Name: "ExtraColon",
|
|
Query: `search:name:extra`,
|
|
ExpectedErrorContains: "can only contain 1 ':'",
|
|
},
|
|
{
|
|
Name: "InvalidStatus",
|
|
Query: "status:inActive",
|
|
ExpectedErrorContains: "has invalid values",
|
|
},
|
|
{
|
|
Name: "ExtraKeys",
|
|
Query: `foo:bar`,
|
|
ExpectedErrorContains: `"foo" is not a valid query param`,
|
|
},
|
|
}
|
|
|
|
for _, c := range testCases {
|
|
c := c
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
values, errs := searchquery.Users(c.Query)
|
|
if c.ExpectedErrorContains != "" {
|
|
require.True(t, len(errs) > 0, "expect some errors")
|
|
var s strings.Builder
|
|
for _, err := range errs {
|
|
_, _ = s.WriteString(fmt.Sprintf("%s: %s\n", err.Field, err.Detail))
|
|
}
|
|
require.Contains(t, s.String(), c.ExpectedErrorContains)
|
|
} else {
|
|
require.Len(t, errs, 0, "expected no error")
|
|
require.Equal(t, c.Expected, values, "expected values")
|
|
}
|
|
})
|
|
}
|
|
}
|