mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
865 lines
26 KiB
Go
865 lines
26 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"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gitpod-io/gitpod/common-go/namegen"
|
|
|
|
fuzz "github.com/AdaLogics/go-fuzz-headers"
|
|
connect "github.com/bufbuild/connect-go"
|
|
"github.com/gitpod-io/gitpod/components/public-api/go/config"
|
|
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/gitpod-io/gitpod/public-api-server/pkg/jws"
|
|
"github.com/gitpod-io/gitpod/public-api-server/pkg/jws/jwstest"
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/sourcegraph/jsonrpc2"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/testing/protocmp"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
func TestWorkspaceService_GetWorkspace(t *testing.T) {
|
|
|
|
workspaceID := workspaceTestData[0].Protocol.Workspace.ID
|
|
|
|
t.Run("invalid argument when workspace ID is missing", func(t *testing.T) {
|
|
_, client := setupWorkspacesService(t)
|
|
|
|
_, err := client.GetWorkspace(context.Background(), connect.NewRequest(&v1.GetWorkspaceRequest{
|
|
WorkspaceId: "",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("invalid argument when workspace ID does not validate", func(t *testing.T) {
|
|
_, client := setupWorkspacesService(t)
|
|
|
|
_, err := client.GetWorkspace(context.Background(), connect.NewRequest(&v1.GetWorkspaceRequest{
|
|
WorkspaceId: "some-random-not-valid-workspace-id",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("not found when workspace does not exist", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
serverMock.EXPECT().GetWorkspace(gomock.Any(), workspaceID).Return(nil, &jsonrpc2.Error{
|
|
Code: 404,
|
|
Message: "not found",
|
|
})
|
|
|
|
_, err := client.GetWorkspace(context.Background(), connect.NewRequest(&v1.GetWorkspaceRequest{
|
|
WorkspaceId: workspaceID,
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeNotFound, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("returns a workspace when it exists", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
serverMock.EXPECT().GetWorkspace(gomock.Any(), workspaceID).Return(&workspaceTestData[0].Protocol, nil)
|
|
|
|
resp, err := client.GetWorkspace(context.Background(), connect.NewRequest(&v1.GetWorkspaceRequest{
|
|
WorkspaceId: workspaceID,
|
|
}))
|
|
require.NoError(t, err)
|
|
|
|
requireEqualProto(t, workspaceTestData[0].API, resp.Msg.GetResult())
|
|
})
|
|
|
|
t.Run("returns a proper RecentFolders with when config.WorkspaceLocation exists", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
wsInfo := workspaceTestData[0].Protocol
|
|
wsInfo.Workspace = nil
|
|
wsWorkspace := *workspaceTestData[0].Protocol.Workspace
|
|
wsWorkspace.Config = &protocol.WorkspaceConfig{
|
|
WorkspaceLocation: "gitpod/gitpod-ws.code-workspace",
|
|
}
|
|
wsInfo.Workspace = &wsWorkspace
|
|
|
|
serverMock.EXPECT().GetWorkspace(gomock.Any(), workspaceID).Return(&wsInfo, nil)
|
|
|
|
resp, err := client.GetWorkspace(context.Background(), connect.NewRequest(&v1.GetWorkspaceRequest{
|
|
WorkspaceId: workspaceID,
|
|
}))
|
|
require.NoError(t, err)
|
|
|
|
expectedWs := *workspaceTestData[0].API
|
|
expectedWs.Status = nil
|
|
expectedWsStatus := *workspaceTestData[0].API.Status
|
|
expectedWsStatus.Instance = nil
|
|
expectedInstance := *workspaceTestData[0].API.Status.Instance
|
|
expectedInstance.Status = nil
|
|
expectedInstanceStatus := *workspaceTestData[0].API.Status.Instance.Status
|
|
expectedInstanceStatus.RecentFolders = []string{"/workspace/gitpod/gitpod-ws.code-workspace"}
|
|
expectedInstance.Status = &expectedInstanceStatus
|
|
expectedWsStatus.Instance = &expectedInstance
|
|
expectedWs.Status = &expectedWsStatus
|
|
|
|
requireEqualProto(t, expectedWs, resp.Msg.GetResult())
|
|
})
|
|
|
|
t.Run("returns a proper RecentFolders with when config.CheckoutLocation exists", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
wsInfo := workspaceTestData[0].Protocol
|
|
wsInfo.Workspace = nil
|
|
wsWorkspace := *workspaceTestData[0].Protocol.Workspace
|
|
wsWorkspace.Config = &protocol.WorkspaceConfig{
|
|
CheckoutLocation: "foo",
|
|
}
|
|
wsInfo.Workspace = &wsWorkspace
|
|
|
|
serverMock.EXPECT().GetWorkspace(gomock.Any(), workspaceID).Return(&wsInfo, nil)
|
|
|
|
resp, err := client.GetWorkspace(context.Background(), connect.NewRequest(&v1.GetWorkspaceRequest{
|
|
WorkspaceId: workspaceID,
|
|
}))
|
|
require.NoError(t, err)
|
|
|
|
expectedWs := *workspaceTestData[0].API
|
|
expectedWs.Status = nil
|
|
expectedWsStatus := *workspaceTestData[0].API.Status
|
|
expectedWsStatus.Instance = nil
|
|
expectedInstance := *workspaceTestData[0].API.Status.Instance
|
|
expectedInstance.Status = nil
|
|
expectedInstanceStatus := *workspaceTestData[0].API.Status.Instance.Status
|
|
expectedInstanceStatus.RecentFolders = []string{"/workspace/foo"}
|
|
expectedInstance.Status = &expectedInstanceStatus
|
|
expectedWsStatus.Instance = &expectedInstance
|
|
expectedWs.Status = &expectedWsStatus
|
|
|
|
requireEqualProto(t, expectedWs, resp.Msg.GetResult())
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceService_StartWorkspace(t *testing.T) {
|
|
|
|
workspaceID := workspaceTestData[0].Protocol.Workspace.ID
|
|
|
|
t.Run("invalid argument when workspace ID is missing", func(t *testing.T) {
|
|
_, client := setupWorkspacesService(t)
|
|
|
|
_, err := client.StartWorkspace(context.Background(), connect.NewRequest(&v1.StartWorkspaceRequest{
|
|
WorkspaceId: "",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("invalid argument when workspace ID does not validate", func(t *testing.T) {
|
|
_, client := setupWorkspacesService(t)
|
|
|
|
_, err := client.StartWorkspace(context.Background(), connect.NewRequest(&v1.StartWorkspaceRequest{
|
|
WorkspaceId: "some-random-not-valid-workspace-id",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("not found when workspace does not exist", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
serverMock.EXPECT().StartWorkspace(gomock.Any(), workspaceID, &protocol.StartWorkspaceOptions{}).Return(nil, &jsonrpc2.Error{
|
|
Code: 404,
|
|
Message: "not found",
|
|
})
|
|
|
|
_, err := client.StartWorkspace(context.Background(), connect.NewRequest(&v1.StartWorkspaceRequest{
|
|
WorkspaceId: workspaceID,
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeNotFound, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("delegates to server", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
serverMock.EXPECT().StartWorkspace(gomock.Any(), workspaceID, &protocol.StartWorkspaceOptions{}).Return(&protocol.StartWorkspaceResult{
|
|
InstanceID: workspaceTestData[0].Protocol.LatestInstance.ID,
|
|
WorkspaceURL: workspaceTestData[0].Protocol.LatestInstance.IdeURL,
|
|
}, nil)
|
|
serverMock.EXPECT().GetWorkspace(gomock.Any(), workspaceID).Return(&workspaceTestData[0].Protocol, nil)
|
|
|
|
resp, err := client.StartWorkspace(context.Background(), connect.NewRequest(&v1.StartWorkspaceRequest{
|
|
WorkspaceId: workspaceID,
|
|
}))
|
|
require.NoError(t, err)
|
|
|
|
requireEqualProto(t, workspaceTestData[0].API, resp.Msg.GetResult())
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceService_StopWorkspace(t *testing.T) {
|
|
|
|
workspaceID := workspaceTestData[0].Protocol.Workspace.ID
|
|
|
|
t.Run("invalid argument when workspace ID is missing", func(t *testing.T) {
|
|
_, client := setupWorkspacesService(t)
|
|
|
|
_, err := client.StopWorkspace(context.Background(), connect.NewRequest(&v1.StopWorkspaceRequest{
|
|
WorkspaceId: "",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("invalid argument when workspace ID does not validate", func(t *testing.T) {
|
|
_, client := setupWorkspacesService(t)
|
|
|
|
_, err := client.StopWorkspace(context.Background(), connect.NewRequest(&v1.StopWorkspaceRequest{
|
|
WorkspaceId: "some-random-not-valid-workspace-id",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("not found when workspace does not exist", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
serverMock.EXPECT().StopWorkspace(gomock.Any(), workspaceID).Return(&jsonrpc2.Error{
|
|
Code: 404,
|
|
Message: "not found",
|
|
})
|
|
|
|
_, err := client.StopWorkspace(context.Background(), connect.NewRequest(&v1.StopWorkspaceRequest{
|
|
WorkspaceId: workspaceID,
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeNotFound, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("delegates to server", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
serverMock.EXPECT().StopWorkspace(gomock.Any(), workspaceID).Return(nil)
|
|
serverMock.EXPECT().GetWorkspace(gomock.Any(), workspaceID).Return(&workspaceTestData[0].Protocol, nil)
|
|
|
|
resp, err := client.StopWorkspace(context.Background(), connect.NewRequest(&v1.StopWorkspaceRequest{
|
|
WorkspaceId: workspaceID,
|
|
}))
|
|
require.NoError(t, err)
|
|
|
|
requireEqualProto(t, workspaceTestData[0].API, resp.Msg.GetResult())
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceService_DeleteWorkspace(t *testing.T) {
|
|
|
|
workspaceID := workspaceTestData[0].Protocol.Workspace.ID
|
|
|
|
t.Run("invalid argument when workspace ID is missing", func(t *testing.T) {
|
|
_, client := setupWorkspacesService(t)
|
|
|
|
_, err := client.DeleteWorkspace(context.Background(), connect.NewRequest(&v1.DeleteWorkspaceRequest{
|
|
WorkspaceId: "",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("invalid argument when workspace ID does not validate", func(t *testing.T) {
|
|
_, client := setupWorkspacesService(t)
|
|
|
|
_, err := client.DeleteWorkspace(context.Background(), connect.NewRequest(&v1.DeleteWorkspaceRequest{
|
|
WorkspaceId: "some-random-not-valid-workspace-id",
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("not found when workspace does not exist", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
serverMock.EXPECT().DeleteWorkspace(gomock.Any(), workspaceID).Return(&jsonrpc2.Error{
|
|
Code: 404,
|
|
Message: "not found",
|
|
})
|
|
|
|
_, err := client.DeleteWorkspace(context.Background(), connect.NewRequest(&v1.DeleteWorkspaceRequest{
|
|
WorkspaceId: workspaceID,
|
|
}))
|
|
require.Error(t, err)
|
|
require.Equal(t, connect.CodeNotFound, connect.CodeOf(err))
|
|
})
|
|
|
|
t.Run("delegates to server", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
serverMock.EXPECT().DeleteWorkspace(gomock.Any(), workspaceID).Return(nil)
|
|
|
|
resp, err := client.DeleteWorkspace(context.Background(), connect.NewRequest(&v1.DeleteWorkspaceRequest{
|
|
WorkspaceId: workspaceID,
|
|
}))
|
|
require.NoError(t, err)
|
|
|
|
requireEqualProto(t, &v1.DeleteWorkspaceResponse{}, resp.Msg)
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceService_GetOwnerToken(t *testing.T) {
|
|
const (
|
|
foundWorkspaceID = "easycz-seer-xl8o1zacpyw"
|
|
ownerToken = "some-owner-token"
|
|
)
|
|
|
|
type Expectation struct {
|
|
Code connect.Code
|
|
Response *v1.GetOwnerTokenResponse
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
WorkspaceID string
|
|
Tokens map[string]string
|
|
Expect Expectation
|
|
}{
|
|
{
|
|
name: "returns an owner token when workspace is found by ID",
|
|
WorkspaceID: foundWorkspaceID,
|
|
Tokens: map[string]string{foundWorkspaceID: ownerToken},
|
|
Expect: Expectation{
|
|
Response: &v1.GetOwnerTokenResponse{
|
|
Token: ownerToken,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "not found when workspace is not found by ID",
|
|
WorkspaceID: mustGenerateWorkspaceID(t),
|
|
Expect: Expectation{
|
|
Code: connect.CodeNotFound,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
serverMock.EXPECT().GetOwnerToken(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, workspaceID string) (res string, err error) {
|
|
w, ok := test.Tokens[workspaceID]
|
|
if !ok {
|
|
return "", &jsonrpc2.Error{
|
|
Code: 404,
|
|
Message: "not found",
|
|
}
|
|
}
|
|
return w, nil
|
|
})
|
|
|
|
resp, err := client.GetOwnerToken(context.Background(), connect.NewRequest(&v1.GetOwnerTokenRequest{
|
|
WorkspaceId: test.WorkspaceID,
|
|
}))
|
|
requireErrorCode(t, test.Expect.Code, err)
|
|
if test.Expect.Response != nil {
|
|
requireEqualProto(t, test.Expect.Response, resp.Msg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkspaceService_ListWorkspaces(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
type Expectation struct {
|
|
Code connect.Code
|
|
Response *v1.ListWorkspacesResponse
|
|
}
|
|
|
|
tests := []struct {
|
|
Name string
|
|
Workspaces []*protocol.WorkspaceInfo
|
|
PageSize int32
|
|
Setup func(t *testing.T, srv *protocol.MockAPIInterface)
|
|
Expectation Expectation
|
|
}{
|
|
{
|
|
Name: "empty list",
|
|
Workspaces: []*protocol.WorkspaceInfo{},
|
|
Expectation: Expectation{
|
|
Response: &v1.ListWorkspacesResponse{},
|
|
},
|
|
},
|
|
{
|
|
Name: "valid workspaces",
|
|
Workspaces: []*protocol.WorkspaceInfo{
|
|
&workspaceTestData[0].Protocol,
|
|
},
|
|
Expectation: Expectation{
|
|
Response: &v1.ListWorkspacesResponse{
|
|
Result: []*v1.Workspace{
|
|
workspaceTestData[0].API,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "invalid workspaces",
|
|
Workspaces: func() []*protocol.WorkspaceInfo {
|
|
ws := workspaceTestData[0].Protocol
|
|
wsi := *workspaceTestData[0].Protocol.LatestInstance
|
|
wsi.CreationTime = "invalid date"
|
|
ws.LatestInstance = &wsi
|
|
return []*protocol.WorkspaceInfo{&ws}
|
|
}(),
|
|
Expectation: Expectation{
|
|
Code: connect.CodeFailedPrecondition,
|
|
},
|
|
},
|
|
{
|
|
Name: "valid page size",
|
|
Setup: func(t *testing.T, srv *protocol.MockAPIInterface) {
|
|
srv.EXPECT().GetWorkspaces(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, options *protocol.GetWorkspacesOptions) (res []*protocol.WorkspaceInfo, err error) {
|
|
// Note: using to gomock argument matcher causes the test to block indefinitely instead of failing.
|
|
if int(options.Limit) != 42 {
|
|
t.Errorf("public-api passed from limit: %f instead of 42", options.Limit)
|
|
}
|
|
return nil, nil
|
|
})
|
|
},
|
|
PageSize: 42,
|
|
Expectation: Expectation{
|
|
Response: &v1.ListWorkspacesResponse{},
|
|
},
|
|
},
|
|
{
|
|
Name: "excessive page size",
|
|
PageSize: 1000,
|
|
Expectation: Expectation{
|
|
Code: connect.CodeInvalidArgument,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
var pagination *v1.Pagination
|
|
if test.PageSize != 0 {
|
|
pagination = &v1.Pagination{PageSize: test.PageSize}
|
|
}
|
|
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
if test.Workspaces != nil {
|
|
serverMock.EXPECT().GetWorkspaces(gomock.Any(), gomock.Any()).Return(test.Workspaces, nil)
|
|
} else if test.Setup != nil {
|
|
test.Setup(t, serverMock)
|
|
}
|
|
|
|
resp, err := client.ListWorkspaces(ctx, connect.NewRequest(&v1.ListWorkspacesRequest{
|
|
Pagination: pagination,
|
|
}))
|
|
requireErrorCode(t, test.Expectation.Code, err)
|
|
|
|
if test.Expectation.Response != nil {
|
|
if diff := cmp.Diff(test.Expectation.Response, resp.Msg, protocmp.Transform()); diff != "" {
|
|
t.Errorf("unexpected difference:\n%v", diff)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkspaceService_StreamWorkspaceStatus(t *testing.T) {
|
|
const (
|
|
workspaceID = "easycz-seer-xl8o1zacpyw"
|
|
instanceID = "f2effcfd-3ddb-4187-b584-256e88a42442"
|
|
ownerToken = "some-owner-token"
|
|
)
|
|
|
|
t.Run("not found when workspace does not exist", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
serverMock.EXPECT().GetWorkspace(gomock.Any(), workspaceID).Return(nil, &jsonrpc2.Error{
|
|
Code: 404,
|
|
Message: "not found",
|
|
})
|
|
|
|
resp, _ := client.StreamWorkspaceStatus(context.Background(), connect.NewRequest(&v1.StreamWorkspaceStatusRequest{
|
|
WorkspaceId: workspaceID,
|
|
}))
|
|
|
|
resp.Receive()
|
|
|
|
require.Error(t, resp.Err())
|
|
require.Equal(t, connect.CodeNotFound, connect.CodeOf(resp.Err()))
|
|
})
|
|
|
|
t.Run("returns a workspace status", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
serverMock.EXPECT().GetWorkspace(gomock.Any(), workspaceID).Return(&workspaceTestData[0].Protocol, nil)
|
|
serverMock.EXPECT().WorkspaceUpdates(gomock.Any(), workspaceID).DoAndReturn(func(ctx context.Context, workspaceID string) (<-chan *protocol.WorkspaceInstance, error) {
|
|
ch := make(chan *protocol.WorkspaceInstance)
|
|
go func() {
|
|
ch <- workspaceTestData[0].Protocol.LatestInstance
|
|
}()
|
|
go func() {
|
|
<-ctx.Done()
|
|
close(ch)
|
|
}()
|
|
return ch, nil
|
|
})
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
resp, err := client.StreamWorkspaceStatus(ctx, connect.NewRequest(&v1.StreamWorkspaceStatusRequest{
|
|
WorkspaceId: workspaceID,
|
|
}))
|
|
|
|
require.NoError(t, err)
|
|
|
|
resp.Receive()
|
|
cancel()
|
|
|
|
requireEqualProto(t, workspaceTestData[0].API.Status, resp.Msg().Result)
|
|
})
|
|
}
|
|
|
|
func TestClientServerStreamInterceptor(t *testing.T) {
|
|
testInterceptor := &TestInterceptor{
|
|
expectedToken: "auth-token",
|
|
t: t,
|
|
}
|
|
|
|
ctrl := gomock.NewController(t)
|
|
t.Cleanup(ctrl.Finish)
|
|
|
|
serverMock := protocol.NewMockAPIInterface(ctrl)
|
|
|
|
svc := NewWorkspaceService(&FakeServerConnPool{
|
|
api: serverMock,
|
|
}, nil)
|
|
|
|
keyset := jwstest.GenerateKeySet(t)
|
|
rsa256, err := jws.NewRSA256(keyset)
|
|
require.NoError(t, err)
|
|
|
|
_, handler := v1connect.NewWorkspacesServiceHandler(svc, connect.WithInterceptors(auth.NewServerInterceptor(config.SessionConfig{
|
|
Issuer: "unitetest.com",
|
|
Cookie: config.CookieConfig{
|
|
Name: "cookie_jwt",
|
|
},
|
|
}, rsa256), testInterceptor))
|
|
|
|
srv := httptest.NewServer(handler)
|
|
t.Cleanup(srv.Close)
|
|
|
|
client := v1connect.NewWorkspacesServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(
|
|
auth.NewClientInterceptor("auth-token"),
|
|
testInterceptor,
|
|
))
|
|
|
|
resp, _ := client.StreamWorkspaceStatus(context.Background(), connect.NewRequest(&v1.StreamWorkspaceStatusRequest{
|
|
WorkspaceId: "",
|
|
}))
|
|
|
|
resp.Close()
|
|
}
|
|
|
|
func TestWorkspaceService_ListWorkspaceClasses(t *testing.T) {
|
|
|
|
t.Run("proxies request to server", func(t *testing.T) {
|
|
serverMock, client := setupWorkspacesService(t)
|
|
|
|
serverMock.EXPECT().GetSupportedWorkspaceClasses(gomock.Any()).Return([]*protocol.SupportedWorkspaceClass{
|
|
{
|
|
ID: "smol",
|
|
DisplayName: "Tiny",
|
|
Description: "The littlest there is",
|
|
IsDefault: true,
|
|
},
|
|
{
|
|
ID: "big",
|
|
DisplayName: "Huge",
|
|
Description: "The biggest there is",
|
|
IsDefault: false,
|
|
}}, nil)
|
|
|
|
retrieved, err := client.ListWorkspaceClasses(context.Background(), connect.NewRequest(&v1.ListWorkspaceClassesRequest{}))
|
|
require.NoError(t, err)
|
|
requireEqualProto(t, &v1.ListWorkspaceClassesResponse{
|
|
Result: []*v1.WorkspaceClass{
|
|
{
|
|
Id: "smol",
|
|
DisplayName: "Tiny",
|
|
Description: "The littlest there is",
|
|
IsDefault: true,
|
|
},
|
|
{
|
|
Id: "big",
|
|
DisplayName: "Huge",
|
|
Description: "The biggest there is",
|
|
IsDefault: false,
|
|
},
|
|
},
|
|
}, retrieved.Msg)
|
|
})
|
|
}
|
|
|
|
type TestInterceptor struct {
|
|
expectedToken string
|
|
t *testing.T
|
|
}
|
|
|
|
func (ti *TestInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc {
|
|
return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
|
|
return next(ctx, req)
|
|
}
|
|
}
|
|
|
|
func (ti *TestInterceptor) WrapStreamingClient(next connect.StreamingClientFunc) connect.StreamingClientFunc {
|
|
return func(ctx context.Context, spec connect.Spec) connect.StreamingClientConn {
|
|
token, _ := auth.TokenFromContext(ctx)
|
|
require.Equal(ti.t, ti.expectedToken, token.Value)
|
|
return next(ctx, spec)
|
|
}
|
|
}
|
|
|
|
func (ti *TestInterceptor) WrapStreamingHandler(next connect.StreamingHandlerFunc) connect.StreamingHandlerFunc {
|
|
return func(ctx context.Context, conn connect.StreamingHandlerConn) error {
|
|
token, _ := auth.TokenFromContext(ctx)
|
|
require.Equal(ti.t, ti.expectedToken, token.Value)
|
|
return next(ctx, conn)
|
|
}
|
|
}
|
|
|
|
type workspaceTestDataEntry struct {
|
|
Name string
|
|
Protocol protocol.WorkspaceInfo
|
|
API *v1.Workspace
|
|
}
|
|
|
|
var workspaceTestData = []workspaceTestDataEntry{
|
|
{
|
|
Name: "comprehensive",
|
|
Protocol: protocol.WorkspaceInfo{
|
|
Workspace: &protocol.Workspace{
|
|
BaseImageNameResolved: "foo:bar",
|
|
ID: "gitpodio-gitpod-isq6xj458lj",
|
|
OwnerID: "fake-owner-id",
|
|
ContextURL: "open-prebuild/126ac54a-5922-4a45-9a18-670b057bf540/https://github.com/gitpod-io/gitpod/pull/18291",
|
|
Context: &protocol.WorkspaceContext{
|
|
NormalizedContextURL: "https://github.com/gitpod-io/gitpod/pull/18291",
|
|
Title: "tes ttitle",
|
|
Repository: &protocol.Repository{
|
|
Host: "github.com",
|
|
Name: "gitpod",
|
|
},
|
|
},
|
|
Description: "test description",
|
|
},
|
|
LatestInstance: &protocol.WorkspaceInstance{
|
|
ID: "f2effcfd-3ddb-4187-b584-256e88a42442",
|
|
IdeURL: "https://gitpodio-gitpod-isq6xj458lj.ws-eu53.protocol.io/",
|
|
CreationTime: "2022-07-12T10:04:49+0000",
|
|
WorkspaceID: "gitpodio-gitpod-isq6xj458lj",
|
|
Status: &protocol.WorkspaceInstanceStatus{
|
|
Conditions: &protocol.WorkspaceInstanceConditions{
|
|
Failed: "nope",
|
|
FirstUserActivity: "2022-07-12T10:04:49+0000",
|
|
Timeout: "nada",
|
|
},
|
|
Message: "has no message",
|
|
Phase: "running",
|
|
Version: 42,
|
|
ExposedPorts: []*protocol.WorkspaceInstancePort{
|
|
{
|
|
Port: 9000,
|
|
URL: "https://9000-gitpodio-gitpod-isq6xj458lj.ws-eu53.protocol.io",
|
|
Visibility: protocol.PortVisibilityPublic,
|
|
Protocol: protocol.PortProtocolHTTP,
|
|
},
|
|
{
|
|
Port: 9001,
|
|
URL: "https://9001-gitpodio-gitpod-isq6xj458lj.ws-eu53.protocol.io",
|
|
Visibility: protocol.PortVisibilityPrivate,
|
|
Protocol: protocol.PortProtocolHTTPS,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
API: &v1.Workspace{
|
|
WorkspaceId: "gitpodio-gitpod-isq6xj458lj",
|
|
OwnerId: "fake-owner-id",
|
|
Context: &v1.WorkspaceContext{
|
|
ContextUrl: "open-prebuild/126ac54a-5922-4a45-9a18-670b057bf540/https://github.com/gitpod-io/gitpod/pull/18291",
|
|
Details: &v1.WorkspaceContext_Git_{
|
|
Git: &v1.WorkspaceContext_Git{
|
|
NormalizedContextUrl: "https://github.com/gitpod-io/gitpod/pull/18291",
|
|
Repository: &v1.WorkspaceContext_Repository{
|
|
Name: "gitpod",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Description: "test description",
|
|
Status: &v1.WorkspaceStatus{
|
|
Instance: &v1.WorkspaceInstance{
|
|
InstanceId: "f2effcfd-3ddb-4187-b584-256e88a42442",
|
|
WorkspaceId: "gitpodio-gitpod-isq6xj458lj",
|
|
CreatedAt: timestamppb.New(must(time.Parse(time.RFC3339, "2022-07-12T10:04:49Z"))),
|
|
Status: &v1.WorkspaceInstanceStatus{
|
|
StatusVersion: 42,
|
|
Phase: v1.WorkspaceInstanceStatus_PHASE_RUNNING,
|
|
Conditions: &v1.WorkspaceInstanceStatus_Conditions{
|
|
Failed: "nope",
|
|
Timeout: "nada",
|
|
FirstUserActivity: timestamppb.New(must(time.Parse(time.RFC3339, "2022-07-12T10:04:49Z"))),
|
|
},
|
|
Message: "has no message",
|
|
Url: "https://gitpodio-gitpod-isq6xj458lj.ws-eu53.protocol.io/",
|
|
Admission: v1.AdmissionLevel_ADMISSION_LEVEL_OWNER_ONLY,
|
|
Ports: []*v1.Port{
|
|
{
|
|
Port: 9000,
|
|
Policy: v1.PortPolicy_PORT_POLICY_PUBLIC,
|
|
Url: "https://9000-gitpodio-gitpod-isq6xj458lj.ws-eu53.protocol.io",
|
|
Protocol: v1.PortProtocol_PORT_PROTOCOL_HTTP,
|
|
},
|
|
{
|
|
Port: 9001,
|
|
Policy: v1.PortPolicy_PORT_POLICY_PRIVATE,
|
|
Url: "https://9001-gitpodio-gitpod-isq6xj458lj.ws-eu53.protocol.io",
|
|
Protocol: v1.PortProtocol_PORT_PROTOCOL_HTTPS,
|
|
},
|
|
},
|
|
RecentFolders: []string{"/workspace/gitpod"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestConvertWorkspaceInfo(t *testing.T) {
|
|
type Expectation struct {
|
|
Result *v1.Workspace
|
|
Error string
|
|
}
|
|
tests := []struct {
|
|
Name string
|
|
Input protocol.WorkspaceInfo
|
|
Expectation Expectation
|
|
}{
|
|
{
|
|
Name: "happy path",
|
|
Input: workspaceTestData[0].Protocol,
|
|
Expectation: Expectation{Result: workspaceTestData[0].API},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
var (
|
|
act Expectation
|
|
err error
|
|
)
|
|
act.Result, err = convertWorkspaceInfo(&test.Input)
|
|
if err != nil {
|
|
act.Error = err.Error()
|
|
}
|
|
|
|
if diff := cmp.Diff(test.Expectation, act, protocmp.Transform()); diff != "" {
|
|
t.Errorf("unexpected convertWorkspaceInfo (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func FuzzConvertWorkspaceInfo(f *testing.F) {
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
|
var nfo protocol.WorkspaceInfo
|
|
err := fuzz.NewConsumer(data).GenerateStruct(&nfo)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// we really just care for panics
|
|
_, _ = convertWorkspaceInfo(&nfo)
|
|
})
|
|
}
|
|
|
|
func must[T any](t T, err error) T {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return t
|
|
}
|
|
|
|
func setupWorkspacesService(t *testing.T) (*protocol.MockAPIInterface, v1connect.WorkspacesServiceClient) {
|
|
t.Helper()
|
|
|
|
ctrl := gomock.NewController(t)
|
|
t.Cleanup(ctrl.Finish)
|
|
|
|
serverMock := protocol.NewMockAPIInterface(ctrl)
|
|
|
|
svc := NewWorkspaceService(&FakeServerConnPool{
|
|
api: serverMock,
|
|
}, nil)
|
|
|
|
keyset := jwstest.GenerateKeySet(t)
|
|
rsa256, err := jws.NewRSA256(keyset)
|
|
require.NoError(t, err)
|
|
|
|
_, handler := v1connect.NewWorkspacesServiceHandler(svc, connect.WithInterceptors(auth.NewServerInterceptor(config.SessionConfig{
|
|
Issuer: "unitetest.com",
|
|
Cookie: config.CookieConfig{
|
|
Name: "cookie_jwt",
|
|
},
|
|
}, rsa256)))
|
|
|
|
srv := httptest.NewServer(handler)
|
|
t.Cleanup(srv.Close)
|
|
|
|
client := v1connect.NewWorkspacesServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(
|
|
auth.NewClientInterceptor("auth-token"),
|
|
))
|
|
|
|
return serverMock, client
|
|
}
|
|
|
|
type FakeServerConnPool struct {
|
|
api protocol.APIInterface
|
|
}
|
|
|
|
func (f *FakeServerConnPool) Get(ctx context.Context, token auth.Token) (protocol.APIInterface, error) {
|
|
return f.api, nil
|
|
}
|
|
|
|
func requireErrorCode(t *testing.T, expected connect.Code, err error) {
|
|
t.Helper()
|
|
if expected == 0 && err == nil {
|
|
return
|
|
}
|
|
|
|
actual := connect.CodeOf(err)
|
|
require.Equal(t, expected, actual, "expected code %s, but got %s from error %v", expected.String(), actual.String(), err)
|
|
}
|
|
|
|
func mustGenerateWorkspaceID(t *testing.T) string {
|
|
t.Helper()
|
|
|
|
wsid, err := namegen.GenerateWorkspaceID()
|
|
require.NoError(t, err)
|
|
|
|
return wsid
|
|
}
|