Path: blob/main/components/gitpod-db/src/workspace-db.spec.db.ts
2497 views
/**1* Copyright (c) 2020 Gitpod GmbH. All rights reserved.2* Licensed under the GNU Affero General Public License (AGPL).3* See License.AGPL.txt in the project root for license information.4*/56import * as chai from "chai";7const expect = chai.expect;8import { suite, test, timeout } from "@testdeck/mocha";9import { fail } from "assert";1011import { WorkspaceInstance, Workspace, PrebuiltWorkspace, CommitContext } from "@gitpod/gitpod-protocol";12import { testContainer } from "./test-container";13import { TypeORMWorkspaceDBImpl } from "./typeorm/workspace-db-impl";14import { TypeORM } from "./typeorm/typeorm";15import { DBPrebuiltWorkspace } from "./typeorm/entity/db-prebuilt-workspace";16import { secondsBefore } from "@gitpod/gitpod-protocol/lib/util/timeutil";17import { resetDB } from "./test/reset-db";18import { v4 } from "uuid";1920@suite21class WorkspaceDBSpec {22db = testContainer.get<TypeORMWorkspaceDBImpl>(TypeORMWorkspaceDBImpl);23typeorm = testContainer.get<TypeORM>(TypeORM);2425readonly timeWs = new Date(2018, 2, 16, 10, 0, 0).toISOString();26readonly timeBefore = new Date(2018, 2, 16, 11, 5, 10).toISOString();27readonly timeAfter = new Date(2019, 2, 16, 11, 5, 10).toISOString();28readonly timeAfter2 = new Date(2019, 2, 17, 4, 20, 10).toISOString();29readonly userId = "12345";30readonly projectAID = "projectA";31readonly projectBID = "projectB";32readonly orgidA = "orgA";33readonly orgidB = "orgB";34readonly ws: Workspace = {35id: "1",36type: "regular",37creationTime: this.timeWs,38config: {39ports: [],40image: "",41tasks: [],42},43projectId: this.projectAID,44context: <CommitContext>{45title: "example",46repository: {47cloneUrl: "https://github.com/gitpod-io/gitpod",48},49revision: "abc",50},51contextURL: "example.org",52description: "blabla",53ownerId: this.userId,54organizationId: this.orgidA,55};56readonly wsi1: WorkspaceInstance = {57workspaceId: this.ws.id,58id: "123",59ideUrl: "example.org",60region: "unknown",61workspaceClass: undefined,62workspaceImage: "abc.io/test/image:123",63creationTime: this.timeBefore,64startedTime: undefined,65deployedTime: undefined,66stoppingTime: undefined,67stoppedTime: undefined,68status: {69version: 1,70phase: "preparing",71conditions: {},72},73configuration: {74theiaVersion: "unknown",75ideImage: "unknown",76},77deleted: false,78usageAttributionId: undefined,79};80readonly wsi2: WorkspaceInstance = {81workspaceId: this.ws.id,82id: "1234",83ideUrl: "example.org",84region: "unknown",85workspaceClass: undefined,86workspaceImage: "abc.io/test/image:123",87creationTime: this.timeAfter,88startedTime: undefined,89deployedTime: undefined,90stoppingTime: undefined,91stoppedTime: undefined,92status: {93version: 1,94phase: "running",95conditions: {},96},97configuration: {98theiaVersion: "unknown",99ideImage: "unknown",100},101deleted: false,102usageAttributionId: undefined,103};104readonly wsi3: WorkspaceInstance = {105workspaceId: this.ws.id,106id: "12345",107ideUrl: "example.org",108region: "unknown",109workspaceClass: undefined,110workspaceImage: "abc.io/test/image:123",111creationTime: this.timeAfter2,112startedTime: undefined,113deployedTime: undefined,114stoppingTime: undefined,115stoppedTime: undefined,116status: {117version: 1,118phase: "stopped",119conditions: {},120},121configuration: {122theiaVersion: "unknown",123ideImage: "unknown",124},125deleted: false,126usageAttributionId: undefined,127};128readonly ws2: Workspace = {129id: "2",130type: "regular",131creationTime: this.timeWs,132config: {133ports: [],134image: "",135tasks: [],136},137projectId: this.projectBID,138context: <CommitContext>{139title: "example",140repository: {141cloneUrl: "https://github.com/gitpod-io/gitpod",142},143revision: "abc",144},145contextURL: "https://github.com/gitpod-io/gitpod",146description: "Gitpod",147ownerId: this.userId,148organizationId: this.orgidA,149};150readonly ws2i1: WorkspaceInstance = {151workspaceId: this.ws2.id,152id: "4",153ideUrl: "example.org",154region: "unknown",155workspaceClass: undefined,156workspaceImage: "abc.io/test/image:123",157creationTime: this.timeBefore,158startedTime: undefined,159deployedTime: undefined,160stoppingTime: undefined,161stoppedTime: undefined,162status: {163version: 1,164phase: "preparing",165conditions: {},166},167configuration: {168theiaVersion: "unknown",169ideImage: "unknown",170},171deleted: false,172usageAttributionId: undefined,173};174175readonly ws3: Workspace = {176id: "3",177type: "regular",178creationTime: this.timeWs,179config: {180ports: [],181image: "",182tasks: [],183},184context: <CommitContext>{185title: "example",186repository: {187cloneUrl: "https://github.com/gitpod-io/gitpod",188},189revision: "abc",190},191contextURL: "example.org",192description: "blabla",193ownerId: this.userId,194organizationId: this.orgidB,195};196readonly ws3i1: WorkspaceInstance = {197workspaceId: this.ws3.id,198id: "3_1",199ideUrl: "example.org",200region: "unknown",201workspaceClass: undefined,202workspaceImage: "abc.io/test/image:123",203creationTime: this.timeBefore,204startedTime: undefined,205deployedTime: undefined,206stoppingTime: undefined,207stoppedTime: undefined,208status: {209version: 1,210phase: "preparing",211conditions: {},212},213configuration: {214theiaVersion: "unknown",215ideImage: "unknown",216},217deleted: false,218usageAttributionId: undefined,219};220221async before() {222await this.wipeRepo();223}224225async after() {226await this.wipeRepo();227}228229async wipeRepo() {230await resetDB(this.typeorm);231}232233@test(timeout(10000))234public async testFindInstancesLast() {235try {236await this.db.transaction(async (db) => {237await Promise.all([db.store(this.ws), db.storeInstance(this.wsi1), db.storeInstance(this.wsi2)]);238const dbResult = await db.findInstances(this.ws.id);239expect(dbResult).to.have.deep.members([this.wsi1, this.wsi2]);240throw "rollback";241});242} catch (e) {243if (e !== "rollback") throw e;244const dbResult = await this.db.findInstances(this.ws.id);245expect(dbResult).to.not.have.deep.members([this.wsi1, this.wsi2]);246return;247}248fail("Rollback failed");249}250251@test(timeout(10000))252public async testFindByInstanceId() {253await this.db.transaction(async (db) => {254await Promise.all([db.store(this.ws), db.storeInstance(this.wsi1)]);255const dbResult = await db.findByInstanceId(this.wsi1.id);256const expected = await db.findById(this.wsi1.workspaceId);257expect(dbResult).to.deep.eq(expected);258});259}260261@test(timeout(10000))262public async testFindEligibleWorkspacesForSoftDeletion_markedEligible_Prebuild() {263const { ws } = await this.createPrebuild(20, 15);264const dbResult = await this.db.findEligibleWorkspacesForSoftDeletion(new Date(), 10, "prebuild");265expect(dbResult.length).to.equal(1);266expect(dbResult[0].id).to.eq(ws.id);267expect(dbResult[0].ownerId).to.eq(ws.ownerId);268}269270@test(timeout(10000))271public async testFindEligibleWorkspacesForSoftDeletion_notMarkedEligible_Prebuild() {272await this.createPrebuild(20, -7);273const dbResult = await this.db.findEligibleWorkspacesForSoftDeletion(new Date(), 10, "prebuild");274expect(dbResult.length).to.eq(0);275}276277@test(timeout(10000))278public async testPrebuildGarbageCollection() {279const { pbws } = await this.createPrebuild(20, 15);280281// mimic the behavior of the Garbage Collector282const gcWorkspaces = await this.db.findEligibleWorkspacesForSoftDeletion(new Date(), 10, "prebuild");283expect(gcWorkspaces.length).to.equal(1);284285const now = new Date().toISOString();286await this.db.updatePartial(gcWorkspaces[0].id, {287contentDeletedTime: now,288softDeletedTime: now,289softDeleted: "gc",290});291292// next cycle is empty293const nextGcCycle = await this.db.findEligibleWorkspacesForSoftDeletion(new Date(), 10, "prebuild");294expect(nextGcCycle.length).to.equal(0);295296// prebuild can't be discovered anymore because it's workspace has been GC'ed297const prebuild = await this.db.findPrebuildByID(pbws.id);298expect(prebuild).to.be.undefined;299}300301protected async createPrebuild(createdDaysAgo: number, deletionEligibilityTimeDaysAgo?: number) {302const now = new Date();303now.setDate(now.getDate() - createdDaysAgo);304const creationTime = now.toISOString();305const ws = await this.db.store({306id: "12345",307creationTime,308description: "something",309contextURL: "https://github.com/foo/bar",310ownerId: "1221423",311organizationId: "org123",312context: {313title: "my title",314},315config: {},316type: "prebuild",317});318const pbws = await this.db.storePrebuiltWorkspace({319id: "prebuild123",320buildWorkspaceId: "12345",321creationTime,322cloneURL: "https://github.com/foo/bar",323commit: "",324state: "available",325statusVersion: 0,326});327328if (deletionEligibilityTimeDaysAgo !== undefined) {329const deletionEligibilityTime = new Date();330deletionEligibilityTime.setDate(deletionEligibilityTime.getDate() - deletionEligibilityTimeDaysAgo);331await this.db.updatePartial(ws.id, { deletionEligibilityTime: deletionEligibilityTime.toISOString() });332}333334return { ws, pbws };335}336337@test(timeout(10000))338public async testFindEligibleWorkspacesForSoftDeletion_markedEligible() {339this.ws.deletionEligibilityTime = this.timeWs;340await Promise.all([341this.db.store(this.ws),342this.db.storeInstance(this.wsi1),343this.db.storeInstance(this.wsi2),344this.db.storeInstance(this.wsi3),345]);346const dbResult = await this.db.findEligibleWorkspacesForSoftDeletion(new Date(this.timeAfter), 10);347expect(dbResult[0].id).to.eq(this.ws.id);348expect(dbResult[0].ownerId).to.eq(this.ws.ownerId);349}350351@test(timeout(10000))352public async testFindEligibleWorkspacesForSoftDeletion_notMarkedEligible() {353await Promise.all([354this.db.store(this.ws),355this.db.storeInstance(this.wsi1),356this.db.storeInstance(this.wsi2),357this.db.storeInstance(this.wsi3),358]);359const dbResult = await this.db.findEligibleWorkspacesForSoftDeletion(new Date(this.timeAfter), 10);360expect(dbResult.length).to.eq(0);361}362363@test(timeout(10000))364public async testFindAllWorkspaceAndInstances_workspaceId() {365await Promise.all([366this.db.store(this.ws),367this.db.storeInstance(this.wsi1),368this.db.store(this.ws2),369this.db.storeInstance(this.ws2i1),370]);371const dbResult = await this.db.findAllWorkspaceAndInstances(0, 10, "workspaceId", "DESC", {372workspaceId: this.ws2.id,373});374// It should only find one workspace instance375expect(dbResult.total).to.eq(1);376377// It should find the workspace with the queried id378const workspaceAndInstance = dbResult.rows[0];379expect(workspaceAndInstance.workspaceId).to.eq(this.ws2.id);380}381382@test(timeout(10000))383public async testFindAllWorkspaceAndInstances_workspaceIdOrInstanceId() {384await Promise.all([385this.db.store(this.ws),386this.db.storeInstance(this.wsi1),387this.db.store(this.ws2),388this.db.storeInstance(this.ws2i1),389]);390const dbResult = await this.db.findAllWorkspaceAndInstances(0, 10, "workspaceId", "DESC", {391instanceIdOrWorkspaceId: this.ws2.id,392});393// It should only find one workspace instance394expect(dbResult.total).to.eq(1);395396// It should find the workspace with the queried id397const workspaceAndInstance = dbResult.rows[0];398expect(workspaceAndInstance.workspaceId).to.eq(this.ws2.id);399}400401@test(timeout(10000))402public async testFindAllWorkspaceAndInstances_instanceId() {403await Promise.all([404this.db.store(this.ws),405this.db.storeInstance(this.wsi1),406this.db.storeInstance(this.wsi2),407this.db.store(this.ws2),408this.db.storeInstance(this.ws2i1),409]);410const dbResult = await this.db.findAllWorkspaceAndInstances(0, 10, "instanceId", "DESC", {411instanceId: this.wsi1.id,412});413414// It should only find one workspace instance415expect(dbResult.total).to.eq(1);416417// It should find the workspace with the queried id418const workspaceAndInstance = dbResult.rows[0];419expect(workspaceAndInstance.workspaceId).to.eq(this.ws.id);420421// It should select the workspace instance that was queried, not the most recent one422expect(workspaceAndInstance.instanceId).to.eq(this.wsi1.id);423}424425@test(timeout(10000))426public async testFind_ByProjectIds() {427await Promise.all([428this.db.store(this.ws),429this.db.storeInstance(this.wsi1),430this.db.storeInstance(this.wsi2),431this.db.store(this.ws2),432this.db.storeInstance(this.ws2i1),433]);434const dbResult = await this.db.find({435userId: this.userId,436includeHeadless: false,437projectId: [this.projectAID],438includeWithoutProject: false,439});440441// It should only find one workspace instance442expect(dbResult.length).to.eq(1);443444expect(dbResult[0].workspace.id).to.eq(this.ws.id);445}446447@test(timeout(10000))448public async testFind_ByProjectIds_01() {449await Promise.all([450this.db.store(this.ws),451this.db.storeInstance(this.wsi1),452this.db.storeInstance(this.wsi2),453this.db.store(this.ws2),454this.db.storeInstance(this.ws2i1),455]);456const dbResult = await this.db.find({457userId: this.userId,458includeHeadless: false,459projectId: [this.projectBID],460includeWithoutProject: false,461});462463// It should only find one workspace instance464expect(dbResult.length).to.eq(1);465466expect(dbResult[0].workspace.id).to.eq(this.ws2.id);467}468469@test(timeout(10000))470public async testFind_ByProjectIds_02() {471await Promise.all([472this.db.store(this.ws),473this.db.storeInstance(this.wsi1),474this.db.storeInstance(this.wsi2),475this.db.store(this.ws2),476this.db.storeInstance(this.ws2i1),477]);478const dbResult = await this.db.find({479userId: this.userId,480includeHeadless: false,481projectId: [this.projectAID, this.projectBID],482includeWithoutProject: false,483});484485expect(dbResult.length).to.eq(2);486487expect(dbResult[0].workspace.id).to.eq(this.ws.id);488expect(dbResult[1].workspace.id).to.eq(this.ws2.id);489}490491@test(timeout(10000))492public async testFind_ByProjectIds_03() {493await Promise.all([494this.db.store(this.ws),495this.db.storeInstance(this.wsi1),496this.db.storeInstance(this.wsi2),497this.db.store(this.ws2),498this.db.storeInstance(this.ws2i1),499]);500const dbResult = await this.db.find({501userId: this.userId,502includeHeadless: false,503projectId: [],504includeWithoutProject: false,505});506507expect(dbResult.length).to.eq(0);508509// expect(dbResult[0].workspace.id).to.eq(this.ws.id);510// expect(dbResult[1].workspace.id).to.eq(this.ws2.id);511}512513@test(timeout(10000))514public async testFind_ByProjectIds_04() {515await Promise.all([516this.db.store(this.ws),517this.db.storeInstance(this.wsi1),518this.db.storeInstance(this.wsi2),519this.db.store(this.ws2),520this.db.storeInstance(this.ws2i1),521this.db.store(this.ws3),522this.db.storeInstance(this.ws3i1),523]);524const dbResult = await this.db.find({525userId: this.userId,526includeHeadless: false,527projectId: [],528includeWithoutProject: true,529});530531expect(dbResult.length).to.eq(1);532533expect(dbResult[0].workspace.id).to.eq(this.ws3.id);534}535536@test(timeout(10000))537public async testFind_ByProjectIds_05() {538await Promise.all([539this.db.store(this.ws),540this.db.storeInstance(this.wsi1),541this.db.storeInstance(this.wsi2),542this.db.store(this.ws2),543this.db.storeInstance(this.ws2i1),544this.db.store(this.ws3),545this.db.storeInstance(this.ws3i1),546]);547const dbResult = await this.db.find({548userId: this.userId,549includeHeadless: false,550projectId: [this.projectBID],551includeWithoutProject: true,552});553554expect(dbResult.length).to.eq(2);555556expect(dbResult[0].workspace.id).to.eq(this.ws2.id);557expect(dbResult[1].workspace.id).to.eq(this.ws3.id);558}559560@test(timeout(10000))561public async testCountUnabortedPrebuildsSince() {562const now = new Date();563const cloneURL = "https://github.com/gitpod-io/gitpod";564const projectId = v4();565566await Promise.all([567// Created now, and queued568this.storePrebuiltWorkspace({569id: "prebuild123",570buildWorkspaceId: "apples",571creationTime: now.toISOString(),572cloneURL: cloneURL,573projectId,574commit: "",575state: "queued",576statusVersion: 0,577}),578// now and aborted579this.storePrebuiltWorkspace({580id: "prebuild456",581buildWorkspaceId: "bananas",582creationTime: now.toISOString(),583cloneURL: cloneURL,584projectId,585commit: "",586state: "aborted",587statusVersion: 0,588}),589// completed over a minute ago590this.storePrebuiltWorkspace({591id: "prebuild789",592buildWorkspaceId: "oranges",593creationTime: secondsBefore(now.toISOString(), 62),594cloneURL: cloneURL,595projectId,596commit: "",597state: "available",598statusVersion: 0,599}),600// different project now and queued601this.storePrebuiltWorkspace({602id: "prebuild123-other",603buildWorkspaceId: "apples",604creationTime: now.toISOString(),605cloneURL: cloneURL,606projectId: "other-projectId",607commit: "",608state: "queued",609statusVersion: 0,610}),611]);612613const minuteAgo = secondsBefore(now.toISOString(), 60);614const unabortedCount = await this.db.countUnabortedPrebuildsSince(projectId, new Date(minuteAgo));615expect(unabortedCount).to.eq(1);616}617618@test(timeout(10000))619public async testGetWorkspaceCountForCloneURL() {620const now = new Date();621const eightDaysAgo = new Date();622eightDaysAgo.setDate(eightDaysAgo.getDate() - 8);623const activeRepo = "http://github.com/myorg/active.git";624const inactiveRepo = "http://github.com/myorg/inactive.git";625await Promise.all([626this.db.store({627id: "12345",628creationTime: eightDaysAgo.toISOString(),629description: "something",630contextURL: "http://github.com/myorg/inactive",631ownerId: "1221423",632organizationId: "org123",633context: <CommitContext>{634title: "my title",635repository: {636cloneUrl: inactiveRepo,637},638revision: "abc",639},640config: {},641type: "regular",642}),643this.db.store({644id: "12346",645creationTime: now.toISOString(),646description: "something",647contextURL: "http://github.com/myorg/active",648ownerId: "1221423",649organizationId: "org123",650context: <CommitContext>{651title: "my title",652repository: {653cloneUrl: activeRepo,654},655revision: "abc",656},657config: {},658type: "regular",659}),660]);661662const inactiveCount = await this.db.getWorkspaceCountByCloneURL(inactiveRepo, 7, "regular");663expect(inactiveCount).to.eq(0, "there should be no regular workspaces in the past 7 days");664const activeCount = await this.db.getWorkspaceCountByCloneURL(activeRepo, 7, "regular");665expect(activeCount).to.eq(1, "there should be exactly one regular workspace");666}667668@test(timeout(10000))669public async testGetUnresolvedUpdatables() {670{671// setup ws, wsi, pws, and updatables672const timeWithOffset = (offsetInHours: number) => {673const date = new Date();674date.setHours(date.getHours() - offsetInHours);675return date;676};677678for (let i = 1; i <= 10; i++) {679const ws = await this.db.store({680...this.ws,681id: `ws-${i}`,682creationTime: timeWithOffset(i).toISOString(),683});684const pws = await this.db.storePrebuiltWorkspace({685buildWorkspaceId: ws.id,686cloneURL: ws.cloneUrl!,687commit: "abc",688creationTime: ws.creationTime,689id: ws.id + "-pws",690state: "queued",691statusVersion: 123,692});693await this.db.storeInstance({694...this.wsi1,695workspaceId: ws.id,696id: ws.id + "-wsi",697});698await this.db.attachUpdatableToPrebuild("pwsid-which-is-ignored-anyways", {699id: `pwu-${i}`,700installationId: "foobar",701isResolved: false,702owner: "owner",703repo: "repo",704prebuiltWorkspaceId: pws.id,705});706}707}708709expect((await this.db.getUnresolvedUpdatables()).length).to.eq(10, "there should be 10 updatables in total");710expect((await this.db.getUnresolvedUpdatables(5)).length).to.eq(5, "there should be 5 updatables");711}712713private async storePrebuiltWorkspace(pws: PrebuiltWorkspace) {714// store the creationTime directly, before it is modified by the store function in the ORM layer715const creationTime = pws.creationTime;716await this.db.storePrebuiltWorkspace(pws);717718const conn = await this.typeorm.getConnection();719const repo = conn.getRepository(DBPrebuiltWorkspace);720721if (!!creationTime) {722// MySQL requires the time format to be 2022-03-07 15:44:01.746141723// Looks almost like an ISO time string, hack it a bit.724const mysqlTimeFormat = creationTime.replace("T", " ").replace("Z", "");725await repo.query("UPDATE d_b_prebuilt_workspace SET creationTime = ? WHERE id = ?", [726mysqlTimeFormat,727pws.id,728]);729}730}731732@test(timeout(10000))733public async findWorkspacesForPurging() {734const creationTime = "2018-01-01T00:00:00.000Z";735const ownerId = "1221423";736const organizationId = "org123";737const purgeDate = new Date("2019-02-01T00:00:00.000Z");738const d20180202 = "2018-02-02T00:00:00.000Z";739const d20180201 = "2018-02-01T00:00:00.000Z";740const d20180131 = "2018-01-31T00:00:00.000Z";741await Promise.all([742this.db.store({743id: "1",744creationTime,745description: "something",746contextURL: "http://github.com/myorg/inactive",747ownerId,748organizationId,749context: {750title: "my title",751},752config: {},753type: "regular",754contentDeletedTime: d20180131,755}),756this.db.store({757id: "2",758creationTime,759description: "something",760contextURL: "http://github.com/myorg/active",761ownerId,762organizationId,763context: {764title: "my title",765},766config: {},767type: "regular",768contentDeletedTime: d20180201,769}),770this.db.store({771id: "3",772creationTime,773description: "something",774contextURL: "http://github.com/myorg/active",775ownerId,776organizationId,777context: {778title: "my title",779},780config: {},781type: "regular",782contentDeletedTime: d20180202,783}),784this.db.store({785id: "4",786creationTime,787description: "something",788contextURL: "http://github.com/myorg/active",789ownerId,790organizationId,791context: {792title: "my title",793},794config: {},795type: "regular",796contentDeletedTime: undefined,797}),798]);799800const wsIds = await this.db.findWorkspacesForPurging(365, 1000, purgeDate);801expect(wsIds).to.deep.equal([802{803id: "1",804ownerId,805contentDeletedTime: d20180131,806},807]);808}809810@test(timeout(10000))811public async findWorkspacesByOrganizationId() {812await this.db.store(this.ws);813await this.db.store(this.ws2);814await this.db.store(this.ws3);815let result = await this.db.find({816userId: this.userId,817organizationId: this.orgidA,818});819820expect(result.length).to.eq(2);821for (const ws of result) {822expect(ws.workspace.organizationId).to.equal(this.orgidA);823}824825result = await this.db.find({826userId: this.userId,827organizationId: this.orgidB,828});829830expect(result.length).to.eq(1);831for (const ws of result) {832expect(ws.workspace.organizationId).to.equal(this.orgidB);833}834835result = await this.db.find({836userId: this.userId,837organizationId: "no-org",838});839840expect(result.length).to.eq(0);841}842843@test(timeout(10000))844public async hardDeleteWorkspace() {845await this.db.store(this.ws);846await this.db.storeInstance(this.wsi1);847await this.db.storeInstance(this.wsi2);848let result = await this.db.findInstances(this.ws.id);849expect(result.length).to.eq(2);850await this.db.hardDeleteWorkspace(this.ws.id);851result = await this.db.findInstances(this.ws.id);852expect(result.length).to.eq(0);853}854855@test()856public async storeAndUpdateGitStatus() {857const inst = {858...this.wsi1,859gitstatus: undefined,860};861862await this.db.storeInstance(inst);863let result = await this.db.findInstances(inst.workspaceId);864expect(!result[0].gitStatus).to.be.true;865866inst.gitStatus = {867branch: "my/branch",868};869await this.db.storeInstance(inst);870871result = await this.db.findInstances(inst.workspaceId);872expect(result[0].gitStatus?.branch).to.eq("my/branch");873}874}875module.exports = new WorkspaceDBSpec();876877878