From cfd12f812a59b3d22cc01b6274981df4bc57eaf3 Mon Sep 17 00:00:00 2001 From: Shannon Lewis Date: Mon, 18 Jul 2022 14:53:16 +1000 Subject: [PATCH 01/11] fix: Update the client to assume it could be given an Id or a Name for the space to connect to --- src/client.ts | 20 ++++++++++++++++---- src/repository.ts | 4 ++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/client.ts b/src/client.ts index 0cb0a78..e767c96 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,4 +1,5 @@ -import type { GlobalRootLinks, OctopusError, RootResource, SpaceRootLinks, SpaceRootResource } from "@octopusdeploy/message-contracts"; +import type { GlobalRootLinks, OctopusError, RootResource, SpaceRootLinks, SpaceResource, SpaceRootResource } from "@octopusdeploy/message-contracts"; +import { PagingCollection } from "@octopusdeploy/message-contracts"; import ApiClient from "./apiClient"; import { ClientConfiguration, processConfiguration } from "./clientConfiguration"; import type { ClientErrorResponseDetails } from "./clientErrorResponseDetails"; @@ -170,15 +171,26 @@ export class Client { return new Client(this.session, this.resolver, this.rootDocument, null, null, this.configuration); } - async switchToSpace(spaceId: string): Promise { + async switchToSpace(spaceIdOrName: string): Promise { if (this.rootDocument === null) { throw new Error( "Root document is null; this document is required for the API client. Please ensure that the API endpoint is accessible along with its root document." ); } - this.spaceId = spaceId; - this.spaceRootDocument = await this.get(this.rootDocument.Links["SpaceHome"], { spaceId: this.spaceId }); + if (spaceIdOrName.startsWith("Spaces-")) { + this.spaceId = spaceIdOrName; + this.spaceRootDocument = await this.get(this.rootDocument.Links["SpaceHome"], { spaceId: this.spaceId }); + } + + const spaceList = await this.get>(this.rootDocument.Links["Spaces"], { partialName: spaceIdOrName }); + if (spaceList.Items.length != 1) { + throw new Error(`Unable to uniquely identify a space using the partial name of '${spaceIdOrName}'.`); + } + + const spaceResource = spaceList.Items[0]; + this.spaceId = spaceResource.Id; + this.spaceRootDocument = await this.get(spaceResource.Links["SpaceHome"]); } switchToSystem(): void { diff --git a/src/repository.ts b/src/repository.ts index 1a337e0..3c5d004 100644 --- a/src/repository.ts +++ b/src/repository.ts @@ -339,8 +339,8 @@ export class Repository implements OctopusSpaceRepository, OctopusSystemReposito return new Repository(this.client.forSystem()); } - switchToSpace(spaceId: string): Promise { - return this.client.switchToSpace(spaceId); + switchToSpace(spaceIdOrName: string): Promise { + return this.client.switchToSpace(spaceIdOrName); } switchToSystem(): void { From 4da5cb5c107031207dad57cdaffd8f96753f02c8 Mon Sep 17 00:00:00 2001 From: Shannon Lewis Date: Mon, 18 Jul 2022 14:54:12 +1000 Subject: [PATCH 02/11] chore: corrected imports --- src/client.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index e767c96..f4b12a1 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,5 +1,12 @@ -import type { GlobalRootLinks, OctopusError, RootResource, SpaceRootLinks, SpaceResource, SpaceRootResource } from "@octopusdeploy/message-contracts"; -import { PagingCollection } from "@octopusdeploy/message-contracts"; +import type { + GlobalRootLinks, + PagingCollection, + OctopusError, + RootResource, + SpaceRootLinks, + SpaceResource, + SpaceRootResource, +} from "@octopusdeploy/message-contracts"; import ApiClient from "./apiClient"; import { ClientConfiguration, processConfiguration } from "./clientConfiguration"; import type { ClientErrorResponseDetails } from "./clientErrorResponseDetails"; From d45f9087722e90b46640cfaab452c2d337aa9700 Mon Sep 17 00:00:00 2001 From: Shannon Lewis Date: Mon, 18 Jul 2022 15:08:00 +1000 Subject: [PATCH 03/11] fix: checking of space from configuration wasn't handling blank string correctly --- src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index f4b12a1..e0a8f0c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -47,7 +47,7 @@ export class Client { if (error instanceof Error) client.error("Could not connect", error); throw error; } - if (configuration.space !== null && configuration.space !== undefined) { + if (configuration.space !== undefined && configuration.space !== "") { try { await client.switchToSpace(configuration.space); } catch (error: unknown) { From c6e62d1da9756a358fa08c6f6321e58a64163ada Mon Sep 17 00:00:00 2001 From: Shannon Lewis Date: Wed, 20 Jul 2022 10:24:05 +1000 Subject: [PATCH 04/11] chore: change the logic processing and do filtering on the client side --- src/client.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/client.ts b/src/client.ts index e0a8f0c..d0cf5a2 100644 --- a/src/client.ts +++ b/src/client.ts @@ -185,19 +185,24 @@ export class Client { ); } - if (spaceIdOrName.startsWith("Spaces-")) { - this.spaceId = spaceIdOrName; - this.spaceRootDocument = await this.get(this.rootDocument.Links["SpaceHome"], { spaceId: this.spaceId }); + const spaceList = await this.get>(this.rootDocument.Links["Spaces"]); + var spaceResources = spaceList.Items.filter((s: SpaceResource) => s.Name == spaceIdOrName); + + if (spaceResources.Items.length > 1) { + throw new Error(`Multiple spaces matched '${spaceIdOrName}', the provided name must match uniquely.`); } - const spaceList = await this.get>(this.rootDocument.Links["Spaces"], { partialName: spaceIdOrName }); - if (spaceList.Items.length != 1) { - throw new Error(`Unable to uniquely identify a space using the partial name of '${spaceIdOrName}'.`); + if (spaceResources.Items.length == 0) { + spaceResources = spaceList.Items.filter((s: SpaceResource) => s.Id == spaceIdOrName); + } + + if (spaceResources.Items.length == 1) { + const spaceResource = spaceResources.Items[0]; + this.spaceId = spaceIdOrName; + this.spaceRootDocument = await this.get(spaceResource.Links["SpaceHome"]); } - const spaceResource = spaceList.Items[0]; - this.spaceId = spaceResource.Id; - this.spaceRootDocument = await this.get(spaceResource.Links["SpaceHome"]); + throw new Error(`Unable to uniquely identify a space using '${spaceIdOrName}'.`); } switchToSystem(): void { From 53cd31ab96aae423c2e3240ed2849833f11a28e0 Mon Sep 17 00:00:00 2001 From: Shannon Lewis Date: Wed, 20 Jul 2022 10:26:14 +1000 Subject: [PATCH 05/11] chore: case insensitive comparisons --- src/client.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index d0cf5a2..868972b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -7,6 +7,8 @@ import type { SpaceResource, SpaceRootResource, } from "@octopusdeploy/message-contracts"; +import { toUpper } from "lodash"; +import { stringify } from "querystring"; import ApiClient from "./apiClient"; import { ClientConfiguration, processConfiguration } from "./clientConfiguration"; import type { ClientErrorResponseDetails } from "./clientErrorResponseDetails"; @@ -186,14 +188,15 @@ export class Client { } const spaceList = await this.get>(this.rootDocument.Links["Spaces"]); - var spaceResources = spaceList.Items.filter((s: SpaceResource) => s.Name == spaceIdOrName); + const uppercaseSpaceIdOrName = toUpper(spaceIdOrName); + var spaceResources = spaceList.Items.filter((s: SpaceResource) => toUpper(s.Name) == uppercaseSpaceIdOrName); if (spaceResources.Items.length > 1) { throw new Error(`Multiple spaces matched '${spaceIdOrName}', the provided name must match uniquely.`); } if (spaceResources.Items.length == 0) { - spaceResources = spaceList.Items.filter((s: SpaceResource) => s.Id == spaceIdOrName); + spaceResources = spaceList.Items.filter((s: SpaceResource) => toUpper(s.Id) == uppercaseSpaceIdOrName); } if (spaceResources.Items.length == 1) { From 65e77c765121820815d855311e8f0c575d649fdd Mon Sep 17 00:00:00 2001 From: hnrkndrssn Date: Wed, 20 Jul 2022 11:33:24 +1000 Subject: [PATCH 06/11] chore: we're working with an array now so Items property doesn't exist --- src/client.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client.ts b/src/client.ts index 868972b..83a11cf 100644 --- a/src/client.ts +++ b/src/client.ts @@ -191,16 +191,16 @@ export class Client { const uppercaseSpaceIdOrName = toUpper(spaceIdOrName); var spaceResources = spaceList.Items.filter((s: SpaceResource) => toUpper(s.Name) == uppercaseSpaceIdOrName); - if (spaceResources.Items.length > 1) { + if (spaceResources.length > 1) { throw new Error(`Multiple spaces matched '${spaceIdOrName}', the provided name must match uniquely.`); } - if (spaceResources.Items.length == 0) { + if (spaceResources.length == 0) { spaceResources = spaceList.Items.filter((s: SpaceResource) => toUpper(s.Id) == uppercaseSpaceIdOrName); } - if (spaceResources.Items.length == 1) { - const spaceResource = spaceResources.Items[0]; + if (spaceResources.length == 1) { + const spaceResource = spaceResources[0]; this.spaceId = spaceIdOrName; this.spaceRootDocument = await this.get(spaceResource.Links["SpaceHome"]); } From 52dc4e2b6f794189ff46e04f4cedabc1b9181afc Mon Sep 17 00:00:00 2001 From: hnrkndrssn Date: Wed, 20 Jul 2022 11:33:41 +1000 Subject: [PATCH 07/11] chore: add missing return statement --- src/client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client.ts b/src/client.ts index 83a11cf..ddc854d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -203,6 +203,7 @@ export class Client { const spaceResource = spaceResources[0]; this.spaceId = spaceIdOrName; this.spaceRootDocument = await this.get(spaceResource.Links["SpaceHome"]); + return; } throw new Error(`Unable to uniquely identify a space using '${spaceIdOrName}'.`); From d5f8a9d99b95e1d0b6be618d78dd88f415cdd074 Mon Sep 17 00:00:00 2001 From: hnrkndrssn Date: Wed, 20 Jul 2022 12:10:07 +1000 Subject: [PATCH 08/11] chore(tests): add tests for space id/name logic --- src/client.test.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/client.test.ts b/src/client.test.ts index e8295f2..4f80e1d 100644 --- a/src/client.test.ts +++ b/src/client.test.ts @@ -10,4 +10,39 @@ describe("client", () => { }; await expect(Client.create(clientConfiguration)).rejects.toThrow(); }); + + test("connects using space id", async () => { + const clientConfiguration: ClientConfiguration = { + apiKey: process.env["OCTOPUS_API_KEY"] || "", + apiUri: process.env["OCTOPUS_HOST"] || "", + space: "Spaces-1", + autoConnect: true + }; + + const client = await Client.create(clientConfiguration); + expect(client.isConnected()).toBe(true); + }); + + test("connects using space name", async () => { + const clientConfiguration: ClientConfiguration = { + apiKey: process.env["OCTOPUS_API_KEY"] || "", + apiUri: process.env["OCTOPUS_HOST"] || "", + space: "Default", + autoConnect: true + }; + + const client = await Client.create(clientConfiguration); + expect(client.isConnected()).toBe(true); + }); + + test("throws with invalid space", async () => { + const clientConfiguration: ClientConfiguration = { + apiKey: process.env["OCTOPUS_API_KEY"] || "", + apiUri: process.env["OCTOPUS_HOST"] || "", + space: "NonExistent", + autoConnect: true + }; + + await expect(Client.create(clientConfiguration)).rejects.toThrow(); + }) }); From 8cead1133b1db38a09f13f4b03cf04f8bae0dde2 Mon Sep 17 00:00:00 2001 From: Shannon Lewis Date: Thu, 21 Jul 2022 10:01:13 +1000 Subject: [PATCH 09/11] Update src/client.ts Co-authored-by: Henrik Andersson --- src/client.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index ddc854d..9f830fe 100644 --- a/src/client.ts +++ b/src/client.ts @@ -7,8 +7,6 @@ import type { SpaceResource, SpaceRootResource, } from "@octopusdeploy/message-contracts"; -import { toUpper } from "lodash"; -import { stringify } from "querystring"; import ApiClient from "./apiClient"; import { ClientConfiguration, processConfiguration } from "./clientConfiguration"; import type { ClientErrorResponseDetails } from "./clientErrorResponseDetails"; From 15c1731ba9a10a0e06362829ee1103d7b0342c00 Mon Sep 17 00:00:00 2001 From: Shannon Lewis Date: Thu, 21 Jul 2022 10:01:37 +1000 Subject: [PATCH 10/11] Apply suggestions from code review Co-authored-by: Henrik Andersson --- src/client.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.ts b/src/client.ts index 9f830fe..8a06ffc 100644 --- a/src/client.ts +++ b/src/client.ts @@ -186,15 +186,15 @@ export class Client { } const spaceList = await this.get>(this.rootDocument.Links["Spaces"]); - const uppercaseSpaceIdOrName = toUpper(spaceIdOrName); - var spaceResources = spaceList.Items.filter((s: SpaceResource) => toUpper(s.Name) == uppercaseSpaceIdOrName); + const uppercaseSpaceIdOrName = spaceIdOrName.toUpperCase(); + var spaceResources = spaceList.Items.filter((s: SpaceResource) => s.Name.toUpperCase() === uppercaseSpaceIdOrName); if (spaceResources.length > 1) { throw new Error(`Multiple spaces matched '${spaceIdOrName}', the provided name must match uniquely.`); } if (spaceResources.length == 0) { - spaceResources = spaceList.Items.filter((s: SpaceResource) => toUpper(s.Id) == uppercaseSpaceIdOrName); + spaceResources = spaceList.Items.filter((s: SpaceResource) => s.Id.toUpperCase() === uppercaseSpaceIdOrName); } if (spaceResources.length == 1) { From 274c9bb5a43804e44418e8511b2c6b84edc16730 Mon Sep 17 00:00:00 2001 From: Shannon Lewis Date: Thu, 21 Jul 2022 10:03:43 +1000 Subject: [PATCH 11/11] chore: simplified loops to a single pass --- src/client.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/client.ts b/src/client.ts index 8a06ffc..3a4d9c0 100644 --- a/src/client.ts +++ b/src/client.ts @@ -187,19 +187,13 @@ export class Client { const spaceList = await this.get>(this.rootDocument.Links["Spaces"]); const uppercaseSpaceIdOrName = spaceIdOrName.toUpperCase(); - var spaceResources = spaceList.Items.filter((s: SpaceResource) => s.Name.toUpperCase() === uppercaseSpaceIdOrName); - - if (spaceResources.length > 1) { - throw new Error(`Multiple spaces matched '${spaceIdOrName}', the provided name must match uniquely.`); - } - - if (spaceResources.length == 0) { - spaceResources = spaceList.Items.filter((s: SpaceResource) => s.Id.toUpperCase() === uppercaseSpaceIdOrName); - } + var spaceResources = spaceList.Items.filter( + (s: SpaceResource) => s.Name.toUpperCase() === uppercaseSpaceIdOrName || s.Id.toUpperCase() === uppercaseSpaceIdOrName + ); if (spaceResources.length == 1) { const spaceResource = spaceResources[0]; - this.spaceId = spaceIdOrName; + this.spaceId = spaceResource.Id; this.spaceRootDocument = await this.get(spaceResource.Links["SpaceHome"]); return; }