From a02222c1f002647bd120e21f6b6a16fd76288bc2 Mon Sep 17 00:00:00 2001 From: Todd Knight Date: Tue, 23 Jun 2020 07:04:44 -0700 Subject: [PATCH] Adding List functionality for projects. (#140) --- .../handlers/projects/project_service.go | 35 +++++++- .../handlers/projects/project_service_test.go | 83 +++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/internal/servers/controller/handlers/projects/project_service.go b/internal/servers/controller/handlers/projects/project_service.go index 466f6e583..3aad8fe41 100644 --- a/internal/servers/controller/handlers/projects/project_service.go +++ b/internal/servers/controller/handlers/projects/project_service.go @@ -42,9 +42,16 @@ func NewService(repo common.IamRepoFactory) (Service, error) { var _ pbs.ProjectServiceServer = Service{} -// ListProjects is not yet implemented but will implement the interface pbs.ProjectServiceServer. +// ListProjects implements the interface pbs.ProjectServiceServer. func (s Service) ListProjects(ctx context.Context, req *pbs.ListProjectsRequest) (*pbs.ListProjectsResponse, error) { - return nil, status.Error(codes.Unimplemented, "List not enabled for this resource.") + if err := validateListRequest(req); err != nil { + return nil, err + } + pl, err := s.listFromRepo(ctx, req.GetOrgId()) + if err != nil { + return nil, err + } + return &pbs.ListProjectsResponse{Items: pl}, nil } // GetProjects implements the interface pbs.ProjectServiceServer. @@ -182,6 +189,22 @@ func (s Service) deleteFromRepo(ctx context.Context, projId string) (bool, error return rows > 0, nil } +func (s Service) listFromRepo(ctx context.Context, orgId string) ([]*pb.Project, error) { + repo, err := s.repo() + if err != nil { + return nil, err + } + pl, err := repo.ListProjects(ctx, orgId) + if err != nil { + return nil, err + } + var outPl []*pb.Project + for _, p := range pl { + outPl = append(outPl, toProto(p)) + } + return outPl, nil +} + // toDbUpdateMask converts the wire format's FieldMask into a list of strings containing FieldMask paths used func toDbUpdateMask(paths []string) ([]string, error) { var dbPaths []string @@ -295,6 +318,14 @@ func validateDeleteRequest(req *pbs.DeleteProjectRequest) error { return nil } +func validateListRequest(req *pbs.ListProjectsRequest) error { + badFields := validateAncestors(req) + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields) + } + return nil +} + func validId(id, prefix string) bool { if !strings.HasPrefix(id, prefix) { return false diff --git a/internal/servers/controller/handlers/projects/project_service_test.go b/internal/servers/controller/handlers/projects/project_service_test.go index cc80183df..d7826eb04 100644 --- a/internal/servers/controller/handlers/projects/project_service_test.go +++ b/internal/servers/controller/handlers/projects/project_service_test.go @@ -113,6 +113,89 @@ func TestGet(t *testing.T) { } } +func TestList(t *testing.T) { + assert, require := assert.New(t), require.New(t) + cleanup, conn, _ := db.TestSetup(t, "postgres") + t.Cleanup(func() { + if err := conn.Close(); err != nil { + t.Errorf("Error when closing gorm DB: %v", err) + } + if err := cleanup(); err != nil { + t.Errorf("Error when cleaning up TestSetup: %v", err) + } + }) + rw := db.New(conn) + wrap := db.TestWrapper(t) + repoFn := func() (*iam.Repository, error) { + return iam.NewRepository(rw, rw, wrap) + } + repo, err := repoFn() + require.NoError(err) + + oNoProjects, p1 := iam.TestScopes(t, conn) + _, err = repo.DeleteScope(context.Background(), p1.GetPublicId()) + require.NoError(err) + oWithProjects, p2 := iam.TestScopes(t, conn) + _, err = repo.DeleteScope(context.Background(), p2.GetPublicId()) + require.NoError(err) + + var wantProjects []*pb.Project + for i := 0; i < 10; i++ { + newP, err := iam.NewProject(oWithProjects.GetPublicId()) + require.NoError(err) + p, err := repo.CreateScope(context.Background(), newP) + require.NoError(err) + wantProjects = append(wantProjects, &pb.Project{ + Id: p.GetPublicId(), + CreatedTime: p.GetCreateTime().GetTimestamp(), + UpdatedTime: p.GetUpdateTime().GetTimestamp(), + }) + } + + cases := []struct { + name string + req *pbs.ListProjectsRequest + res *pbs.ListProjectsResponse + errCode codes.Code + }{ + { + name: "List Many Projects", + req: &pbs.ListProjectsRequest{OrgId: oWithProjects.GetPublicId()}, + res: &pbs.ListProjectsResponse{Items: wantProjects}, + errCode: codes.OK, + }, + { + name: "List No Projects", + req: &pbs.ListProjectsRequest{OrgId: oNoProjects.GetPublicId()}, + res: &pbs.ListProjectsResponse{}, + errCode: codes.OK, + }, + { + name: "Invalid Org Id", + req: &pbs.ListProjectsRequest{OrgId: iam.OrganizationScope.Prefix() + "_this is invalid"}, + res: nil, + errCode: codes.InvalidArgument, + }, + // TODO: When an org doesn't exist, we should return a 404 instead of an empty list. + { + name: "Unfound Org", + req: &pbs.ListProjectsRequest{OrgId: iam.OrganizationScope.Prefix() + "_DoesntExis"}, + res: &pbs.ListProjectsResponse{}, + errCode: codes.OK, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + s, err := projects.NewService(repoFn) + require.NoError(err, "Couldn't create new role service.") + + got, gErr := s.ListProjects(context.Background(), tc.req) + assert.Equal(tc.errCode, status.Code(gErr), "ListProjects(%+v) got error %v, wanted %v", tc.req, gErr, tc.errCode) + assert.True(proto.Equal(got, tc.res), "ListProjects(%q) got response %q, wanted %q", tc.req, got, tc.res) + }) + } +} + func TestDelete(t *testing.T) { require := require.New(t) proj, repo := createDefaultProjectAndRepo(t)