mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
427 lines
12 KiB
Go
427 lines
12 KiB
Go
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
|
|
// Licensed under the GNU Affero General Public License (AGPL).
|
|
// See License-AGPL.txt in the project root for license information.
|
|
|
|
package apiv1
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
connect "github.com/bufbuild/connect-go"
|
|
v1 "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1"
|
|
"github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1/v1connect"
|
|
protocol "github.com/gitpod-io/gitpod/gitpod-protocol"
|
|
"github.com/gitpod-io/gitpod/public-api-server/pkg/auth"
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestProjectsService_CreateProject(t *testing.T) {
|
|
|
|
t.Run("returns invalid argument when request validation fails", func(t *testing.T) {
|
|
for _, s := range []struct {
|
|
Name string
|
|
Spec *v1.Project
|
|
ExpectedError string
|
|
}{
|
|
{
|
|
Name: "name is required",
|
|
Spec: &v1.Project{},
|
|
ExpectedError: "Name is a required argument.",
|
|
},
|
|
{
|
|
Name: "whitespace name is rejected",
|
|
Spec: &v1.Project{
|
|
Name: " ",
|
|
},
|
|
ExpectedError: "Name is a required argument.",
|
|
},
|
|
{
|
|
Name: "slug is required",
|
|
Spec: &v1.Project{
|
|
Name: "name",
|
|
},
|
|
ExpectedError: "Slug is a required argument.",
|
|
},
|
|
{
|
|
Name: "whitespace slug is rejected",
|
|
Spec: &v1.Project{
|
|
Name: "name",
|
|
Slug: " ",
|
|
},
|
|
ExpectedError: "Slug is a required argument.",
|
|
},
|
|
{
|
|
Name: "clone url is required",
|
|
Spec: &v1.Project{
|
|
Name: "name",
|
|
Slug: "slug",
|
|
},
|
|
ExpectedError: "Clone URL is a required argument.",
|
|
},
|
|
{
|
|
Name: "whitespace clone url is rejected",
|
|
Spec: &v1.Project{
|
|
Name: "name",
|
|
Slug: "slug",
|
|
CloneUrl: " ",
|
|
},
|
|
ExpectedError: "Clone URL is a required argument.",
|
|
},
|
|
{
|
|
Name: "user ID must be a valid UUID",
|
|
Spec: &v1.Project{
|
|
Name: "name",
|
|
Slug: "slug",
|
|
CloneUrl: "some.clone.url",
|
|
UserId: "my-user",
|
|
},
|
|
ExpectedError: "User ID is not a valid UUID.",
|
|
},
|
|
{
|
|
Name: "team ID must be a valid UUID",
|
|
Spec: &v1.Project{
|
|
Name: "name",
|
|
Slug: "slug",
|
|
CloneUrl: "some.clone.url",
|
|
TeamId: "my-user",
|
|
},
|
|
ExpectedError: "Team ID is not a valid UUID.",
|
|
},
|
|
{
|
|
Name: "specifying both user and team id is invalid",
|
|
Spec: &v1.Project{
|
|
Name: "name",
|
|
Slug: "slug",
|
|
CloneUrl: "some.clone.url",
|
|
TeamId: uuid.New().String(),
|
|
UserId: uuid.New().String(),
|
|
},
|
|
ExpectedError: "Specifying both User ID and Team ID is not allowed.",
|
|
},
|
|
} {
|
|
t.Run(s.Name, func(t *testing.T) {
|
|
_, client := setupProjectsService(t)
|
|
|
|
_, err := client.CreateProject(context.Background(), connect.NewRequest(&v1.CreateProjectRequest{
|
|
Project: s.Spec,
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
require.Equal(t, s.ExpectedError, err.(*connect.Error).Message())
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("proxies request to server", func(t *testing.T) {
|
|
projectsMock, client := setupProjectsService(t)
|
|
|
|
project := newProject(&protocol.Project{
|
|
Settings: &protocol.ProjectSettings{},
|
|
})
|
|
|
|
projectsMock.EXPECT().CreateProject(gomock.Any(), &protocol.CreateProjectOptions{
|
|
UserID: project.UserID,
|
|
Name: project.Name,
|
|
Slug: project.Slug,
|
|
CloneURL: project.CloneURL,
|
|
AppInstallationID: "undefined",
|
|
}).Return(project, nil)
|
|
|
|
response, err := client.CreateProject(context.Background(), connect.NewRequest(&v1.CreateProjectRequest{
|
|
Project: &v1.Project{
|
|
UserId: project.UserID,
|
|
Name: project.Name,
|
|
Slug: project.Slug,
|
|
CloneUrl: project.CloneURL,
|
|
},
|
|
}))
|
|
require.NoError(t, err)
|
|
requireEqualProto(t, &v1.CreateProjectResponse{
|
|
Project: projectToAPIResponse(project),
|
|
}, response.Msg)
|
|
|
|
})
|
|
}
|
|
|
|
func TestProjectsService_ListProjects(t *testing.T) {
|
|
|
|
t.Run("invalid argument when both team id and project id are missing", func(t *testing.T) {
|
|
_, client := setupProjectsService(t)
|
|
|
|
_, err := client.ListProjects(context.Background(), connect.NewRequest(&v1.ListProjectsRequest{}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("invalid argument when both team id and project id are set", func(t *testing.T) {
|
|
_, client := setupProjectsService(t)
|
|
|
|
_, err := client.ListProjects(context.Background(), connect.NewRequest(&v1.ListProjectsRequest{
|
|
UserId: "user-id",
|
|
TeamId: "team-id",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("invalid argument when user ID is not a valid UUID", func(t *testing.T) {
|
|
_, client := setupProjectsService(t)
|
|
|
|
_, err := client.ListProjects(context.Background(), connect.NewRequest(&v1.ListProjectsRequest{
|
|
UserId: "some-id",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("invalid argument when team ID is not a valid UUID", func(t *testing.T) {
|
|
_, client := setupProjectsService(t)
|
|
|
|
_, err := client.ListProjects(context.Background(), connect.NewRequest(&v1.ListProjectsRequest{
|
|
TeamId: "some-id",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("no projects from server return empty list", func(t *testing.T) {
|
|
serverMock, client := setupProjectsService(t)
|
|
|
|
serverMock.EXPECT().GetUserProjects(gomock.Any()).Return(nil, nil)
|
|
|
|
response, err := client.ListProjects(context.Background(), connect.NewRequest(&v1.ListProjectsRequest{
|
|
UserId: uuid.New().String(),
|
|
}))
|
|
require.NoError(t, err)
|
|
requireEqualProto(t, &v1.ListProjectsResponse{
|
|
Projects: nil,
|
|
TotalResults: 0,
|
|
}, response.Msg)
|
|
})
|
|
|
|
t.Run("retrieves projects for user, when user ID is specified and paginates", func(t *testing.T) {
|
|
serverMock, client := setupProjectsService(t)
|
|
|
|
projects := []*protocol.Project{
|
|
newProject(&protocol.Project{}),
|
|
newProject(&protocol.Project{}),
|
|
newProject(&protocol.Project{}),
|
|
newProject(&protocol.Project{}),
|
|
newProject(&protocol.Project{}),
|
|
}
|
|
|
|
serverMock.EXPECT().GetUserProjects(gomock.Any()).Return(projects, nil).Times(3)
|
|
|
|
firstPage, err := client.ListProjects(context.Background(), connect.NewRequest(&v1.ListProjectsRequest{
|
|
UserId: uuid.New().String(),
|
|
Pagination: &v1.Pagination{
|
|
PageSize: 2,
|
|
},
|
|
}))
|
|
require.NoError(t, err)
|
|
requireEqualProto(t, &v1.ListProjectsResponse{
|
|
Projects: projectsToAPIResponse(projects[0:2]),
|
|
TotalResults: int32(len(projects)),
|
|
}, firstPage.Msg)
|
|
|
|
secondPage, err := client.ListProjects(context.Background(), connect.NewRequest(&v1.ListProjectsRequest{
|
|
UserId: uuid.New().String(),
|
|
Pagination: &v1.Pagination{
|
|
PageSize: 2,
|
|
Page: 2,
|
|
},
|
|
}))
|
|
require.NoError(t, err)
|
|
requireEqualProto(t, &v1.ListProjectsResponse{
|
|
Projects: projectsToAPIResponse(projects[2:4]),
|
|
TotalResults: int32(len(projects)),
|
|
}, secondPage.Msg)
|
|
|
|
thirdPage, err := client.ListProjects(context.Background(), connect.NewRequest(&v1.ListProjectsRequest{
|
|
UserId: uuid.New().String(),
|
|
Pagination: &v1.Pagination{
|
|
PageSize: 2,
|
|
Page: 3,
|
|
},
|
|
}))
|
|
require.NoError(t, err)
|
|
requireEqualProto(t, &v1.ListProjectsResponse{
|
|
Projects: projectsToAPIResponse(projects[4:]),
|
|
TotalResults: int32(len(projects)),
|
|
}, thirdPage.Msg)
|
|
})
|
|
|
|
t.Run("retrieves projects for team, when team ID is specified and paginates", func(t *testing.T) {
|
|
serverMock, client := setupProjectsService(t)
|
|
|
|
teamID := uuid.New().String()
|
|
|
|
projects := []*protocol.Project{
|
|
newProject(&protocol.Project{}),
|
|
newProject(&protocol.Project{}),
|
|
newProject(&protocol.Project{}),
|
|
newProject(&protocol.Project{}),
|
|
newProject(&protocol.Project{}),
|
|
}
|
|
|
|
serverMock.EXPECT().GetTeamProjects(gomock.Any(), teamID).Return(projects, nil).Times(3)
|
|
|
|
firstPage, err := client.ListProjects(context.Background(), connect.NewRequest(&v1.ListProjectsRequest{
|
|
TeamId: teamID,
|
|
Pagination: &v1.Pagination{
|
|
PageSize: 2,
|
|
},
|
|
}))
|
|
require.NoError(t, err)
|
|
requireEqualProto(t, &v1.ListProjectsResponse{
|
|
Projects: projectsToAPIResponse(projects[0:2]),
|
|
TotalResults: int32(len(projects)),
|
|
}, firstPage.Msg)
|
|
|
|
secondPage, err := client.ListProjects(context.Background(), connect.NewRequest(&v1.ListProjectsRequest{
|
|
TeamId: teamID,
|
|
Pagination: &v1.Pagination{
|
|
PageSize: 2,
|
|
Page: 2,
|
|
},
|
|
}))
|
|
require.NoError(t, err)
|
|
requireEqualProto(t, &v1.ListProjectsResponse{
|
|
Projects: projectsToAPIResponse(projects[2:4]),
|
|
TotalResults: int32(len(projects)),
|
|
}, secondPage.Msg)
|
|
|
|
thirdPage, err := client.ListProjects(context.Background(), connect.NewRequest(&v1.ListProjectsRequest{
|
|
TeamId: teamID,
|
|
Pagination: &v1.Pagination{
|
|
PageSize: 2,
|
|
Page: 3,
|
|
},
|
|
}))
|
|
require.NoError(t, err)
|
|
requireEqualProto(t, &v1.ListProjectsResponse{
|
|
Projects: projectsToAPIResponse(projects[4:]),
|
|
TotalResults: int32(len(projects)),
|
|
}, thirdPage.Msg)
|
|
})
|
|
}
|
|
|
|
func TestProjectsService_DeleteProject(t *testing.T) {
|
|
|
|
t.Run("invalid argument when project ID is empty", func(t *testing.T) {
|
|
_, client := setupProjectsService(t)
|
|
|
|
_, err := client.DeleteProject(context.Background(), connect.NewRequest(&v1.DeleteProjectRequest{
|
|
ProjectId: "",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("invalid argument when project ID is not a valid uuid", func(t *testing.T) {
|
|
_, client := setupProjectsService(t)
|
|
|
|
_, err := client.DeleteProject(context.Background(), connect.NewRequest(&v1.DeleteProjectRequest{
|
|
ProjectId: "something",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("proxies to server", func(t *testing.T) {
|
|
serverMock, client := setupProjectsService(t)
|
|
|
|
projectID := uuid.New().String()
|
|
|
|
serverMock.EXPECT().DeleteProject(gomock.Any(), projectID).Return(nil)
|
|
|
|
resp, err := client.DeleteProject(context.Background(), connect.NewRequest(&v1.DeleteProjectRequest{
|
|
ProjectId: projectID,
|
|
}))
|
|
require.NoError(t, err)
|
|
requireEqualProto(t, &v1.DeleteProjectResponse{}, resp.Msg)
|
|
})
|
|
}
|
|
|
|
func setupProjectsService(t *testing.T) (*protocol.MockAPIInterface, v1connect.ProjectsServiceClient) {
|
|
t.Helper()
|
|
|
|
ctrl := gomock.NewController(t)
|
|
t.Cleanup(ctrl.Finish)
|
|
|
|
serverMock := protocol.NewMockAPIInterface(ctrl)
|
|
|
|
svc := NewProjectsService(&FakeServerConnPool{
|
|
api: serverMock,
|
|
})
|
|
|
|
_, handler := v1connect.NewProjectsServiceHandler(svc, connect.WithInterceptors(auth.NewServerInterceptor()))
|
|
|
|
srv := httptest.NewServer(handler)
|
|
t.Cleanup(srv.Close)
|
|
|
|
client := v1connect.NewProjectsServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(
|
|
auth.NewClientInterceptor("auth-token"),
|
|
))
|
|
|
|
return serverMock, client
|
|
}
|
|
|
|
func newProject(p *protocol.Project) *protocol.Project {
|
|
r := rand.Int()
|
|
result := &protocol.Project{
|
|
ID: uuid.New().String(),
|
|
Name: fmt.Sprintf("team-%d", r),
|
|
Slug: fmt.Sprintf("team-%d", r),
|
|
UserID: uuid.New().String(),
|
|
CloneURL: "https://github.com/easyCZ/foobar",
|
|
AppInstallationID: "1337",
|
|
Settings: &protocol.ProjectSettings{
|
|
UseIncrementalPrebuilds: true,
|
|
UsePersistentVolumeClaim: true,
|
|
KeepOutdatedPrebuildsRunning: true,
|
|
AllowUsingPreviousPrebuilds: true,
|
|
PrebuildEveryNthCommit: 5,
|
|
WorkspaceClasses: &protocol.WorkspaceClassesSettings{
|
|
Regular: "default",
|
|
Prebuild: "default",
|
|
},
|
|
},
|
|
CreationTime: "2022-09-09T09:09:09.000Z",
|
|
}
|
|
|
|
if p.ID != "" {
|
|
result.ID = p.ID
|
|
}
|
|
if p.Name != "" {
|
|
result.Name = p.Name
|
|
}
|
|
if p.Slug != "" {
|
|
result.Slug = p.Slug
|
|
}
|
|
if p.UserID != "" {
|
|
result.UserID = p.UserID
|
|
}
|
|
if p.TeamID != "" {
|
|
result.TeamID = p.TeamID
|
|
}
|
|
if p.CloneURL != "" {
|
|
result.CloneURL = p.CloneURL
|
|
}
|
|
if p.AppInstallationID != "" {
|
|
result.AppInstallationID = p.AppInstallationID
|
|
}
|
|
if p.Settings != nil {
|
|
result.Settings = p.Settings
|
|
}
|
|
|
|
return result
|
|
}
|