Skip to content

Commit d3091f0

Browse files
committed
Add more complex filter unit test
1 parent 7821aa4 commit d3091f0

File tree

2 files changed

+208
-20
lines changed

2 files changed

+208
-20
lines changed

coderd/workspaces.go

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -298,26 +298,13 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
298298
return
299299
}
300300

301-
if organization.ID != template.OrganizationID {
302-
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
303-
Message: fmt.Sprintf("Template is not in organization %q.", organization.Name),
304-
})
301+
if !api.Authorize(rw, r, rbac.ActionRead, template) {
305302
return
306303
}
307-
_, err = api.Database.GetOrganizationMemberByUserID(r.Context(), database.GetOrganizationMemberByUserIDParams{
308-
OrganizationID: template.OrganizationID,
309-
UserID: apiKey.UserID,
310-
})
311-
if errors.Is(err, sql.ErrNoRows) {
304+
305+
if organization.ID != template.OrganizationID {
312306
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
313-
Message: "You aren't allowed to access templates in that organization.",
314-
})
315-
return
316-
}
317-
if err != nil {
318-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
319-
Message: "Internal error fetching organization member.",
320-
Detail: err.Error(),
307+
Message: fmt.Sprintf("Template is not in organization %q.", organization.Name),
321308
})
322309
return
323310
}

coderd/workspaces_test.go

Lines changed: 204 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ import (
44
"context"
55
"fmt"
66
"net/http"
7+
"strings"
78
"testing"
89
"time"
910

10-
"github.com/coder/coder/coderd/rbac"
11-
"github.com/coder/coder/coderd/util/ptr"
12-
1311
"github.com/google/uuid"
1412
"github.com/stretchr/testify/require"
1513

1614
"github.com/coder/coder/coderd/autobuild/schedule"
1715
"github.com/coder/coder/coderd/coderdtest"
16+
"github.com/coder/coder/coderd/rbac"
17+
"github.com/coder/coder/coderd/util/ptr"
1818
"github.com/coder/coder/codersdk"
19+
"github.com/coder/coder/cryptorand"
1920
"github.com/coder/coder/provisioner/echo"
2021
"github.com/coder/coder/provisionersdk/proto"
2122
)
@@ -336,8 +337,200 @@ func TestWorkspaceByOwnerAndName(t *testing.T) {
336337
})
337338
}
338339

340+
// TestWorkspaceFilter creates a set of workspaces, users, and organizations
341+
// to run various filters against for testing.
339342
func TestWorkspaceFilter(t *testing.T) {
340343
t.Parallel()
344+
type coderUser struct {
345+
*codersdk.Client
346+
User codersdk.User
347+
Org codersdk.Organization
348+
}
349+
350+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
351+
first := coderdtest.CreateFirstUser(t, client)
352+
353+
users := make([]coderUser, 0)
354+
for i := 0; i < 10; i++ {
355+
userClient := coderdtest.CreateAnotherUser(t, client, first.OrganizationID, rbac.RoleAdmin())
356+
user, err := userClient.User(context.Background(), codersdk.Me)
357+
require.NoError(t, err, "fetch me")
358+
359+
org, err := userClient.CreateOrganization(context.Background(), codersdk.CreateOrganizationRequest{
360+
Name: user.Username + "-org",
361+
})
362+
require.NoError(t, err, "create org")
363+
364+
users = append(users, coderUser{
365+
Client: userClient,
366+
User: user,
367+
Org: org,
368+
})
369+
}
370+
371+
type madeWorkspace struct {
372+
Owner codersdk.User
373+
Workspace codersdk.Workspace
374+
Template codersdk.Template
375+
}
376+
377+
availTemplates := make([]codersdk.Template, 0)
378+
allWorkspaces := make([]madeWorkspace, 0)
379+
380+
// Create some random workspaces
381+
for i, user := range users {
382+
version := coderdtest.CreateTemplateVersion(t, client, user.Org.ID, nil)
383+
384+
// Create a template & workspace in the user's org
385+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
386+
template := coderdtest.CreateTemplate(t, client, user.Org.ID, version.ID, func(request *codersdk.CreateTemplateRequest) {
387+
// Have even templates share the same name for filter complexity.
388+
if i%2 == 0 {
389+
request.Name = "even-template"
390+
}
391+
})
392+
availTemplates = append(availTemplates, template)
393+
workspace := coderdtest.CreateWorkspace(t, user.Client, template.OrganizationID, template.ID)
394+
allWorkspaces = append(allWorkspaces, madeWorkspace{
395+
Workspace: workspace,
396+
Template: template,
397+
Owner: user.User,
398+
})
399+
400+
// Make a workspace with a random template
401+
idx, _ := cryptorand.Intn(len(availTemplates))
402+
randTemplate := availTemplates[idx]
403+
randWorkspace := coderdtest.CreateWorkspace(t, user.Client, randTemplate.OrganizationID, randTemplate.ID)
404+
allWorkspaces = append(allWorkspaces, madeWorkspace{
405+
Workspace: randWorkspace,
406+
Template: randTemplate,
407+
Owner: user.User,
408+
})
409+
}
410+
411+
// Make sure all workspaces are done. Do it after all are made
412+
for i, w := range allWorkspaces {
413+
latest := coderdtest.AwaitWorkspaceBuildJob(t, client, w.Workspace.LatestBuild.ID)
414+
allWorkspaces[i].Workspace.LatestBuild = latest
415+
}
416+
417+
// --- Setup done ---
418+
testCases := []struct {
419+
Name string
420+
Filter codersdk.WorkspaceFilter
421+
// If FilterF is true, we include it in the expected results
422+
FilterF func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool
423+
}{
424+
{
425+
Name: "All",
426+
Filter: codersdk.WorkspaceFilter{},
427+
FilterF: func(_ codersdk.WorkspaceFilter, _ madeWorkspace) bool {
428+
return true
429+
},
430+
},
431+
{
432+
Name: "Owner",
433+
Filter: codersdk.WorkspaceFilter{
434+
Owner: users[must(cryptorand.Intn(len(users)))].User.Username,
435+
},
436+
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
437+
return workspace.Owner.Username == f.Owner
438+
},
439+
},
440+
{
441+
Name: "OrgID",
442+
Filter: codersdk.WorkspaceFilter{
443+
OrganizationID: users[must(cryptorand.Intn(len(users)))].Org.ID,
444+
},
445+
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
446+
return workspace.Template.OrganizationID == f.OrganizationID
447+
},
448+
},
449+
{
450+
Name: "TemplateName",
451+
Filter: codersdk.WorkspaceFilter{
452+
Template: "even-template",
453+
},
454+
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
455+
return workspace.Template.Name == f.Template
456+
},
457+
},
458+
{
459+
Name: "Template&Name",
460+
Filter: codersdk.WorkspaceFilter{
461+
Template: "even-template",
462+
// Use a common letter... one has to have this letter in it
463+
Name: "a",
464+
},
465+
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
466+
return workspace.Template.Name == f.Template && strings.Contains(workspace.Workspace.Name, f.Name)
467+
},
468+
},
469+
{
470+
Name: "Q-Owner/Name",
471+
Filter: codersdk.WorkspaceFilter{
472+
FilterQuery: allWorkspaces[5].Owner.Username + "/" + allWorkspaces[5].Workspace.Name,
473+
},
474+
FilterF: func(_ codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
475+
return workspace.Workspace.ID == allWorkspaces[5].Workspace.ID
476+
},
477+
},
478+
{
479+
Name: "Org&Owner",
480+
Filter: codersdk.WorkspaceFilter{
481+
OrganizationID: users[2].Org.ID,
482+
Owner: users[2].User.Username,
483+
},
484+
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
485+
return workspace.Owner.Username == f.Owner && workspace.Template.OrganizationID == f.OrganizationID
486+
},
487+
},
488+
{
489+
Name: "Org&Owner",
490+
Filter: codersdk.WorkspaceFilter{
491+
OrganizationID: users[2].Org.ID,
492+
Owner: users[2].User.Username,
493+
},
494+
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
495+
return workspace.Owner.Username == f.Owner && workspace.Template.OrganizationID == f.OrganizationID
496+
},
497+
},
498+
{
499+
Name: "Many filters",
500+
Filter: codersdk.WorkspaceFilter{
501+
OrganizationID: allWorkspaces[3].Template.OrganizationID,
502+
Owner: allWorkspaces[3].Owner.Username,
503+
Template: allWorkspaces[3].Template.Name,
504+
Name: allWorkspaces[3].Workspace.Name,
505+
},
506+
FilterF: func(f codersdk.WorkspaceFilter, workspace madeWorkspace) bool {
507+
return workspace.Workspace.ID == allWorkspaces[3].Workspace.ID
508+
},
509+
},
510+
}
511+
512+
for _, c := range testCases {
513+
c := c
514+
t.Run(c.Name, func(t *testing.T) {
515+
t.Parallel()
516+
workspaces, err := client.Workspaces(context.Background(), c.Filter)
517+
require.NoError(t, err, "fetch workspaces")
518+
519+
exp := make([]codersdk.Workspace, 0)
520+
for _, made := range allWorkspaces {
521+
if c.FilterF(c.Filter, made) {
522+
exp = append(exp, made.Workspace)
523+
}
524+
}
525+
require.ElementsMatch(t, exp, workspaces, "expected workspaces returned")
526+
})
527+
}
528+
}
529+
530+
// TestWorkspaceFilterManual runs some specific setups with basic checks.
531+
func TestWorkspaceFilterManual(t *testing.T) {
532+
t.Parallel()
533+
341534
t.Run("Name", func(t *testing.T) {
342535
t.Parallel()
343536
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
@@ -910,3 +1103,11 @@ func mustLocation(t *testing.T, location string) *time.Location {
9101103

9111104
return loc
9121105
}
1106+
1107+
func must[T any](s T, err error) T {
1108+
if err != nil {
1109+
panic(err)
1110+
}
1111+
1112+
return s
1113+
}

0 commit comments

Comments
 (0)