From 9e84b3d4dbf1b6fae870e31b103a518a27c6fdf2 Mon Sep 17 00:00:00 2001 From: Huiwen Date: Wed, 22 Nov 2023 08:33:06 +0000 Subject: [PATCH] Add unit tests --- .../public-api/gitpod/v1/configuration.proto | 1 - .../public-api/gitpod/v1/workspace.proto | 15 +- .../test/service-testing-container-module.ts | 6 + .../src/workspace/context-service.spec.db.ts | 325 ++++++++++++++++++ 4 files changed, 339 insertions(+), 8 deletions(-) create mode 100644 components/server/src/workspace/context-service.spec.db.ts diff --git a/components/public-api/gitpod/v1/configuration.proto b/components/public-api/gitpod/v1/configuration.proto index cfca9460a15619..989ed728a30b7f 100644 --- a/components/public-api/gitpod/v1/configuration.proto +++ b/components/public-api/gitpod/v1/configuration.proto @@ -50,7 +50,6 @@ service ConfigurationService { // Updates a configuration. rpc UpdateConfiguration(UpdateConfigurationRequest) returns (UpdateConfigurationResponse) {} - // Deletes a configuration. rpc DeleteConfiguration(DeleteConfigurationRequest) returns (DeleteConfigurationResponse) {} } diff --git a/components/public-api/gitpod/v1/workspace.proto b/components/public-api/gitpod/v1/workspace.proto index e11bd0d7d292c9..f17cd6e08cb0b1 100644 --- a/components/public-api/gitpod/v1/workspace.proto +++ b/components/public-api/gitpod/v1/workspace.proto @@ -23,8 +23,7 @@ service WorkspaceService { rpc ListWorkspaces(ListWorkspacesRequest) returns (ListWorkspacesResponse) {} // CreateAndStartWorkspace creates a new workspace and starts it. - rpc CreateAndStartWorkspace(CreateAndStartWorkspaceRequest) - returns (CreateAndStartWorkspaceResponse) {} + rpc CreateAndStartWorkspace(CreateAndStartWorkspaceRequest) returns (CreateAndStartWorkspaceResponse) {} // StartWorkspace starts an existing workspace. // If the specified workspace is not in stopped phase, this will return the workspace as is. @@ -82,7 +81,6 @@ message ListWorkspacesResponse { } message CreateAndStartWorkspaceRequest { - message Git { // clone_url is the URL of the repository to clone string clone_url = 1; @@ -108,7 +106,6 @@ message CreateAndStartWorkspaceRequest { // // +required oneof source { - // git describes the source refer of workspace // Obtain available git using the ContextService.ParseContext operation if // not sure about it. @@ -116,7 +113,7 @@ message CreateAndStartWorkspaceRequest { // context_url is for backward compatiblity with the current dashboard, use // ContextService.ParseContext get get a Git source instead - string context_url = 4 [ deprecated = true ]; + string context_url = 4 [deprecated = true]; } // additional_env_variables provide additional environment variables to the @@ -143,7 +140,9 @@ message CreateAndStartWorkspaceRequest { bool force_default_config = 11; } -message CreateAndStartWorkspaceResponse { Workspace workspace = 1; } +message CreateAndStartWorkspaceResponse { + Workspace workspace = 1; +} message StartWorkspaceRequest { // workspace_id specifies the workspace that is going to start @@ -154,7 +153,9 @@ message StartWorkspaceRequest { bool force_default_config = 2; } -message StartWorkspaceResponse { Workspace workspace = 1; } +message StartWorkspaceResponse { + Workspace workspace = 1; +} // +resource get workspace message Workspace { diff --git a/components/server/src/test/service-testing-container-module.ts b/components/server/src/test/service-testing-container-module.ts index f337316ec6e541..104eede97e118a 100644 --- a/components/server/src/test/service-testing-container-module.ts +++ b/components/server/src/test/service-testing-container-module.ts @@ -293,6 +293,12 @@ const mockApplyingContainerModule = new ContainerModule((bind, unbound, isbound, authProviderConfigs: [], installationShortname: "gitpod", auth: mockAuthConfig, + prebuildLimiter: { + "*": { + limit: 50, + period: 50, + }, + }, }); rebind(IAnalyticsWriter).toConstantValue(NullAnalyticsWriter); rebind(HostContextProviderFactory) diff --git a/components/server/src/workspace/context-service.spec.db.ts b/components/server/src/workspace/context-service.spec.db.ts new file mode 100644 index 00000000000000..97da2d968dea1e --- /dev/null +++ b/components/server/src/workspace/context-service.spec.db.ts @@ -0,0 +1,325 @@ +/** + * Copyright (c) 2023 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. + */ + +import { TypeORM } from "@gitpod/gitpod-db/lib"; +import { resetDB } from "@gitpod/gitpod-db/lib/test/reset-db"; +import { + CommitContext, + Organization, + Project, + User, + Workspace as ProtocolWorkspace, + Snapshot, + WorkspaceContext, + StartPrebuildResult, + SnapshotContext, + PrebuiltWorkspaceContext, +} from "@gitpod/gitpod-protocol"; +import * as chai from "chai"; +import { Container } from "inversify"; +import "mocha"; +import { OrganizationService } from "../orgs/organization-service"; +import { createTestContainer } from "../test/service-testing-container-module"; +import { WorkspaceService } from "./workspace-service"; +import { ProjectsService } from "../projects/projects-service"; +import { UserService } from "../user/user-service"; +import { SnapshotService } from "./snapshot-service"; +import { ContextService } from "./context-service"; +import { ContextParser } from "./context-parser-service"; +import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing"; +import { ConfigProvider } from "./config-provider"; +import { PrebuildManager } from "../prebuilds/prebuild-manager"; +import { HostContextProvider } from "../auth/host-context-provider"; +import { AuthProvider } from "../auth/auth-provider"; +import { Experiments } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server"; + +const expect = chai.expect; + +const gitpodEmptyContext = { + ref: "main", + refType: "branch", + path: "", + isFile: false, + repo: "", + repository: { + host: "github.com", + owner: "gitpod-io", + name: "empty", + cloneUrl: "https://github.com/gitpod-io/empty.git", + defaultBranch: "main", + private: false, + }, + normalizedContextURL: "https://github.com/gitpod-io/empty", + revision: "asdf", + title: "gitpod-io/empty - main", +}; + +const SNAPSHOT_BUCKET = "https://gitpod.io/none-bucket"; + +describe("ContextService", async () => { + let container: Container; + let owner: User; + let member: User; + let stranger: User; + let org: Organization; + let org2: Organization; + let project: Project; + let workspace: ProtocolWorkspace; + let snapshot: Snapshot; + let snapshot_stranger: Snapshot; + let prebuild: StartPrebuildResult; + + beforeEach(async () => { + container = createTestContainer(); + Experiments.configureTestingClient({ + centralizedPermissions: true, + }); + container.rebind(ConfigProvider).toConstantValue({ + fetchConfig: () => { + return { + config: { + image: "gitpod/workspace-base", + }, + }; + }, + } as any as ConfigProvider); + + const bindContextParser = () => { + container.rebind(ContextParser).toConstantValue({ + normalizeContextURL: function (contextURL: string): string { + return contextURL + "normalizeContextURL"; + }, + handle: function (ctx: TraceContext, user: User, contextURL: string): Promise { + const url = contextURL.replace("normalizeContextURL", ""); + switch (url) { + case "https://github.com/gitpod-io/empty": + return gitpodEmptyContext as any; + case `open-prebuild/${prebuild.prebuildId}/https://github.com/gitpod-io/empty/tree/main`: + return { + ...gitpodEmptyContext, + openPrebuildID: prebuild.prebuildId, + } as any; + case `snapshot/${snapshot.id}`: { + return { + ...gitpodEmptyContext, + snapshotId: snapshot.id, + snapshotBucketId: SNAPSHOT_BUCKET, + } as any; + } + case `snapshot/${snapshot_stranger.id}`: { + return { + ...gitpodEmptyContext, + snapshotId: snapshot_stranger.id, + snapshotBucketId: SNAPSHOT_BUCKET, + } as any; + } + default: + return { + ref: "master", + } as any; + } + }, + } as any as ContextParser); + }; + + bindContextParser(); + + container.rebind(HostContextProvider).toConstantValue({ + get: () => { + const authProviderId = "Public-GitHub"; + return { + authProvider: { + authProviderId, + info: { + authProviderId, + authProviderType: "GitHub", + }, + }, + services: { + repositoryService: { + installAutomatedPrebuilds: () => {}, + canInstallAutomatedPrebuilds: async () => {}, + }, + repositoryProvider: { + hasReadAccess: async (user: any, owner: string, repo: string) => { + return true; + }, + getBranch: () => { + return { + url: "https://github.com/gitpod-io/empty.git", + name: "main", + htmlUrl: "https://github.com/gitpod-io/empty", + commit: {}, + }; + }, + getRepo: () => { + return { + defaultBranch: "main", + }; + }, + getCommitHistory: () => { + return []; + }, + getCommitInfo: () => { + return undefined; + }, + }, + }, + }; + }, + }); + + const dataInit = async () => { + const userService = container.get(UserService); + // create the owner + owner = await userService.createUser({ + identity: { + authId: "33891423", + authName: "owner", + authProviderId: "Public-GitHub", + }, + }); + + // create the org + const orgService = container.get(OrganizationService); + org = await orgService.createOrganization(owner.id, "my-org"); + + // create and add a member + member = await userService.createUser({ + identity: { + authId: "33891424", + authName: "member", + authProviderId: "Public-GitHub", + }, + }); + const invite = await orgService.getOrCreateInvite(owner.id, org.id); + await orgService.joinOrganization(member.id, invite.id); + + // create a project + const projectService = container.get(ProjectsService); + project = await projectService.createProject( + { + name: "my-project", + slug: "my-project", + teamId: org.id, + cloneUrl: "https://github.com/gitpod-io/empty", + appInstallationId: "noid", + }, + owner, + { + prebuilds: { + enable: true, + branchMatchingPattern: "**", + prebuildInterval: 20, + branchStrategy: "all-branches", + }, + }, + ); + + // create a stranger + stranger = await userService.createUser({ + identity: { + authId: "33891425", + authName: "stranger", + authProviderId: "Public-GitHub", + }, + }); + org2 = await orgService.createOrganization(stranger.id, "stranger-org"); + + // create a workspace + const workspaceService = container.get(WorkspaceService); + workspace = await createTestWorkspace(workspaceService, org, owner, project); + + // take a snapshot + const snapshotService = container.get(SnapshotService); + snapshot = await snapshotService.createSnapshot({ workspaceId: workspace.id }, SNAPSHOT_BUCKET); + + // trigger prebuild + const prebuildManager = container.get(PrebuildManager); + prebuild = await prebuildManager.triggerPrebuild({}, owner, project.id, "main"); + + // create a workspace and snapshot for another user + const anotherWorkspace = await createTestWorkspace(workspaceService, org2, stranger, project); + snapshot_stranger = await snapshotService.createSnapshot( + { workspaceId: anotherWorkspace.id }, + SNAPSHOT_BUCKET, + ); + }; + + await dataInit(); + + bindContextParser(); + }); + + afterEach(async () => { + await resetDB(container.get(TypeORM)); + container.unbindAll(); + }); + + it("should parse normal context", async () => { + const svc = container.get(ContextService); + + const ctx = await svc.parseContext(owner, "https://github.com/gitpod-io/empty", { + projectId: project.id, + organizationId: org.id, + forceDefaultConfig: false, + }); + expect(ctx.project?.id).to.equal(project.id); + expect(CommitContext.is(ctx.context)).to.equal(true); + + expect(ctx.context.ref).to.equal(gitpodEmptyContext.ref); + expect((ctx.context as CommitContext).revision).to.equal(gitpodEmptyContext.revision); + }); + + it("should parser prebuild context", async () => { + const svc = container.get(ContextService); + const ctx = await svc.parseContext( + owner, + `open-prebuild/${prebuild.prebuildId}/https://github.com/gitpod-io/empty/tree/main`, + { + projectId: project.id, + organizationId: org.id, + forceDefaultConfig: false, + }, + ); + expect(ctx.project?.id).to.equal(project.id); + expect(PrebuiltWorkspaceContext.is(ctx.context)).to.equal(true); + }); + + it("should parser snapshot context", async () => { + const svc = container.get(ContextService); + const ctx = await svc.parseContext(owner, `snapshot/${snapshot.id}`, { + projectId: project.id, + organizationId: org.id, + forceDefaultConfig: false, + }); + expect(ctx.project?.id).to.equal(project.id); + expect(SnapshotContext.is(ctx.context)).to.equal(true); + }); + + it("it can start workspace base on stranger's snapshot", async () => { + const svc = container.get(ContextService); + const ctx = await svc.parseContext(owner, `snapshot/${snapshot_stranger.id}`, { + projectId: project.id, + organizationId: org.id, + forceDefaultConfig: false, + }); + expect(ctx.project?.id).to.equal(project.id); + expect(SnapshotContext.is(ctx.context)).to.equal(true); + }); +}); + +async function createTestWorkspace(svc: WorkspaceService, org: Organization, owner: User, project: Project) { + const ws = await svc.createWorkspace( + {}, + owner, + org.id, + project, + gitpodEmptyContext as any as CommitContext, + "github.com/gitpod-io/empty", + ); + return ws; +}