From b0494dba522f68973e4bc8e4d52a7b9ede64437b Mon Sep 17 00:00:00 2001 From: Edaena Salinas Date: Mon, 23 Mar 2020 20:32:25 -0700 Subject: [PATCH 1/4] Check that repository exits for installing pipeline --- src/commands/project/pipeline.test.ts | 9 ++---- src/commands/project/pipeline.ts | 11 +++++-- src/lib/azdoClient.test.ts | 44 +++++++++++++++++++++++++++ src/lib/azdoClient.ts | 21 +++++++++++++ 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/src/commands/project/pipeline.test.ts b/src/commands/project/pipeline.test.ts index 596c93ffc..7d332ad44 100644 --- a/src/commands/project/pipeline.test.ts +++ b/src/commands/project/pipeline.test.ts @@ -36,11 +36,12 @@ const mockValues: CommandOptions = { personalAccessToken: "PAT", pipelineName: "pipelineName", repoName: "repoName", - repoUrl: "repoUrl", + repoUrl: "https://dev.azure.com/myOrg/myProject/_git/myRepo", yamlFileBranch: "master", }; jest.spyOn(azdo, "repositoryHasFile").mockReturnValue(Promise.resolve()); +jest.spyOn(azdo, "repositoryExists").mockReturnValue(Promise.resolve()); const mockMissingValues: CommandOptions = { buildScriptUrl: undefined, @@ -123,12 +124,6 @@ describe("installLifecyclePipeline and execute tests", () => { expect(exitFn).toBeCalledTimes(1); expect(exitFn.mock.calls).toEqual([[0]]); }); - it("test execute function: missing repo url and pipeline name", async () => { - const exitFn = jest.fn(); - await execute(mockMissingValues, "", exitFn); - expect(exitFn).toBeCalledTimes(1); - expect(exitFn.mock.calls).toEqual([[1]]); - }); it("test execute function: github repos not supported", async () => { const exitFn = jest.fn(); await execute(nullValues, "", exitFn); diff --git a/src/commands/project/pipeline.ts b/src/commands/project/pipeline.ts index 22763f34d..95fc12ada 100644 --- a/src/commands/project/pipeline.ts +++ b/src/commands/project/pipeline.ts @@ -6,7 +6,7 @@ import { } from "azure-devops-node-api/interfaces/BuildInterfaces"; import commander from "commander"; import { Config } from "../../config"; -import { repositoryHasFile } from "../../lib/azdoClient"; +import { repositoryExists, repositoryHasFile } from "../../lib/azdoClient"; import { fileInfo as bedrockFileInfo } from "../../lib/bedrockYaml"; import { build as buildCmd, @@ -77,6 +77,7 @@ export const fetchValidateValues = ( if (!opts.repoUrl) { throw Error(`Repo url not defined`); } + const values: CommandOptions = { buildScriptUrl: opts.buildScriptUrl || BUILD_SCRIPT_URL, devopsProject: opts.devopsProject || azureDevops?.project, @@ -84,7 +85,8 @@ export const fetchValidateValues = ( personalAccessToken: opts.personalAccessToken || azureDevops?.access_token, pipelineName: opts.pipelineName || getRepositoryName(gitOriginUrl) + "-lifecycle", - repoName: getRepositoryName(gitOriginUrl), + repoName: + getRepositoryName(opts.repoUrl) || getRepositoryName(gitOriginUrl), repoUrl: opts.repoUrl || getRepositoryUrl(gitOriginUrl), yamlFileBranch: opts.yamlFileBranch, }; @@ -233,6 +235,11 @@ export const execute = async ( personalAccessToken: values.personalAccessToken, project: values.devopsProject, }; + await repositoryExists( + values.devopsProject!, + values.repoName, + accessOpts + ); await repositoryHasFile( PROJECT_PIPELINE_FILENAME, values.yamlFileBranch ? opts.yamlFileBranch : "master", diff --git a/src/lib/azdoClient.test.ts b/src/lib/azdoClient.test.ts index d00f6545b..ef2108250 100644 --- a/src/lib/azdoClient.test.ts +++ b/src/lib/azdoClient.test.ts @@ -11,6 +11,7 @@ import { getTaskAgentApi, getWebApi, invalidateWebApi, + repositoryExists, repositoryHasFile, } from "./azdoClient"; import * as azdoClient from "./azdoClient"; @@ -144,6 +145,49 @@ describe("test getBuildApi function", () => { }); }); +describe("repositoryExists", () => { + test("repository exists", async () => { + const createPullRequestFunc = jest.spyOn(azure, "GitAPI"); + createPullRequestFunc.mockReturnValueOnce( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Promise.resolve({ getRepository: () => ({ id: "3839fjfkj" }) } as any) + ); + const accessOpts: AzureDevOpsOpts = { + orgName: "testOrg", + personalAccessToken: "mytoken", + project: "testProject", + }; + let hasError = false; + + try { + await repositoryExists("my-project", "my-repo", accessOpts); + } catch (err) { + hasError = true; + } + expect(hasError).toBe(false); + }); + test("repository does not exist", async () => { + const createPullRequestFunc = jest.spyOn(azure, "GitAPI"); + createPullRequestFunc.mockReturnValueOnce( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Promise.resolve({ getRepository: () => null } as any) + ); + const accessOpts: AzureDevOpsOpts = { + orgName: "testOrg", + personalAccessToken: "mytoken", + project: "testProject", + }; + let hasError = false; + + try { + await repositoryExists("my-project", "my-repo", accessOpts); + } catch (err) { + hasError = true; + } + expect(hasError).toBe(true); + }); +}); + describe("repositoryHasFile", () => { test("repository contains the given file", async () => { const createPullRequestFunc = jest.spyOn(azure, "GitAPI"); diff --git a/src/lib/azdoClient.ts b/src/lib/azdoClient.ts index 8f05de28b..9fa9fb554 100644 --- a/src/lib/azdoClient.ts +++ b/src/lib/azdoClient.ts @@ -157,3 +157,24 @@ export const repositoryHasFile = async ( ); } }; + +/** + * Checks if a repository exists in the given project + * @param project The Azure DevOps project name + * @param repoName The name of the repository + * @param accessOpts The Azure DevOps access options to the repository + */ +export const repositoryExists = async ( + project: string, + repoName: string, + accessOpts: AzureDevOpsOpts +): Promise => { + const gitApi = await GitAPI(accessOpts); + const repo = await gitApi.getRepository(repoName, project); + + if (!repo) { + throw Error( + `Project '${project}' does not contain repository '${repoName}'.` + ); + } +}; From d56a90a2e71511d4532b728c0e207deb02900d7c Mon Sep 17 00:00:00 2001 From: Edaena Salinas Date: Mon, 23 Mar 2020 23:15:44 -0700 Subject: [PATCH 2/4] Add repository validation to other commands --- src/commands/hld/pipeline.test.ts | 2 +- src/commands/hld/pipeline.ts | 5 +++-- src/commands/project/pipeline.test.ts | 3 +-- src/commands/project/pipeline.ts | 10 +++------- src/commands/service/pipeline.test.ts | 2 +- src/commands/service/pipeline.ts | 5 +++-- src/lib/azdoClient.test.ts | 20 ++++++++++++++++---- src/lib/azdoClient.ts | 12 +++++++++--- 8 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/commands/hld/pipeline.test.ts b/src/commands/hld/pipeline.test.ts index 7255ce74b..228c7e19b 100644 --- a/src/commands/hld/pipeline.test.ts +++ b/src/commands/hld/pipeline.test.ts @@ -59,7 +59,7 @@ afterAll(() => { disableVerboseLogging(); }); -jest.spyOn(azdo, "repositoryHasFile").mockReturnValue(Promise.resolve()); +jest.spyOn(azdo, "validateRepository").mockReturnValue(Promise.resolve()); describe("test emptyStringIfUndefined function", () => { it("pass in undefined", () => { diff --git a/src/commands/hld/pipeline.ts b/src/commands/hld/pipeline.ts index 40d5683ad..b7e122a06 100644 --- a/src/commands/hld/pipeline.ts +++ b/src/commands/hld/pipeline.ts @@ -6,7 +6,7 @@ import { } from "azure-devops-node-api/interfaces/BuildInterfaces"; import commander from "commander"; import { Config } from "../../config"; -import { repositoryHasFile } from "../../lib/azdoClient"; +import { validateRepository } from "../../lib/azdoClient"; import { build as buildCmd, exit as exitCmd } from "../../lib/commandBuilder"; import { BUILD_SCRIPT_URL, @@ -200,7 +200,8 @@ export const execute = async ( }; // By default the version descriptor is for the master branch - await repositoryHasFile( + await validateRepository( + opts.devopsProject, RENDER_HLD_PIPELINE_FILENAME, opts.yamlFileBranch ? opts.yamlFileBranch : "master", opts.hldName, diff --git a/src/commands/project/pipeline.test.ts b/src/commands/project/pipeline.test.ts index 7d332ad44..e0bf10bca 100644 --- a/src/commands/project/pipeline.test.ts +++ b/src/commands/project/pipeline.test.ts @@ -40,8 +40,7 @@ const mockValues: CommandOptions = { yamlFileBranch: "master", }; -jest.spyOn(azdo, "repositoryHasFile").mockReturnValue(Promise.resolve()); -jest.spyOn(azdo, "repositoryExists").mockReturnValue(Promise.resolve()); +jest.spyOn(azdo, "validateRepository").mockReturnValue(Promise.resolve()); const mockMissingValues: CommandOptions = { buildScriptUrl: undefined, diff --git a/src/commands/project/pipeline.ts b/src/commands/project/pipeline.ts index 95fc12ada..5137242fa 100644 --- a/src/commands/project/pipeline.ts +++ b/src/commands/project/pipeline.ts @@ -6,7 +6,7 @@ import { } from "azure-devops-node-api/interfaces/BuildInterfaces"; import commander from "commander"; import { Config } from "../../config"; -import { repositoryExists, repositoryHasFile } from "../../lib/azdoClient"; +import { validateRepository } from "../../lib/azdoClient"; import { fileInfo as bedrockFileInfo } from "../../lib/bedrockYaml"; import { build as buildCmd, @@ -235,15 +235,11 @@ export const execute = async ( personalAccessToken: values.personalAccessToken, project: values.devopsProject, }; - await repositoryExists( + await validateRepository( values.devopsProject!, - values.repoName, - accessOpts - ); - await repositoryHasFile( PROJECT_PIPELINE_FILENAME, values.yamlFileBranch ? opts.yamlFileBranch : "master", - values.repoName!, + values.repoName, accessOpts ); await installLifecyclePipeline(values); diff --git a/src/commands/service/pipeline.test.ts b/src/commands/service/pipeline.test.ts index f881e4c66..4cdf48108 100644 --- a/src/commands/service/pipeline.test.ts +++ b/src/commands/service/pipeline.test.ts @@ -46,7 +46,7 @@ const getMockedValues = (): CommandOptions => { return deepClone(MOCKED_VALUES); }; -jest.spyOn(azdo, "repositoryHasFile").mockReturnValue(Promise.resolve()); +jest.spyOn(azdo, "validateRepository").mockReturnValue(Promise.resolve()); describe("test fetchValues function", () => { it("with all values set", async () => { diff --git a/src/commands/service/pipeline.ts b/src/commands/service/pipeline.ts index a17ad10c4..939693370 100644 --- a/src/commands/service/pipeline.ts +++ b/src/commands/service/pipeline.ts @@ -8,7 +8,7 @@ import { import commander from "commander"; import path from "path"; import { Config } from "../../config"; -import { repositoryHasFile } from "../../lib/azdoClient"; +import { validateRepository } from "../../lib/azdoClient"; import { build as buildCmd, exit as exitCmd } from "../../lib/commandBuilder"; import { BUILD_SCRIPT_URL, @@ -90,7 +90,8 @@ export const execute = async ( path.join(serviceName, SERVICE_PIPELINE_FILENAME); // By default the version descriptor is for the master branch - await repositoryHasFile( + await validateRepository( + opts.devopsProject, pipelinesYamlPath, opts.yamlFileBranch ? opts.yamlFileBranch : "master", opts.repoName, diff --git a/src/lib/azdoClient.test.ts b/src/lib/azdoClient.test.ts index ef2108250..d86ee2c52 100644 --- a/src/lib/azdoClient.test.ts +++ b/src/lib/azdoClient.test.ts @@ -13,6 +13,7 @@ import { invalidateWebApi, repositoryExists, repositoryHasFile, + validateRepository, } from "./azdoClient"; import * as azdoClient from "./azdoClient"; import { AzureDevOpsOpts } from "./git"; @@ -145,13 +146,18 @@ describe("test getBuildApi function", () => { }); }); -describe("repositoryExists", () => { +describe("validateRepository", () => { test("repository exists", async () => { - const createPullRequestFunc = jest.spyOn(azure, "GitAPI"); - createPullRequestFunc.mockReturnValueOnce( + const getRepositoryFunc = jest.spyOn(azure, "GitAPI"); + getRepositoryFunc.mockReturnValueOnce( // eslint-disable-next-line @typescript-eslint/no-explicit-any Promise.resolve({ getRepository: () => ({ id: "3839fjfkj" }) } as any) ); + const getItemFunc = jest.spyOn(azure, "GitAPI"); + getItemFunc.mockReturnValueOnce( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Promise.resolve({ getItem: () => ({ commitId: "3839fjfkj" }) } as any) + ); const accessOpts: AzureDevOpsOpts = { orgName: "testOrg", personalAccessToken: "mytoken", @@ -160,7 +166,13 @@ describe("repositoryExists", () => { let hasError = false; try { - await repositoryExists("my-project", "my-repo", accessOpts); + await validateRepository( + "my-project", + "myFile", + "master", + "my-repo", + accessOpts + ); } catch (err) { hasError = true; } diff --git a/src/lib/azdoClient.ts b/src/lib/azdoClient.ts index 9fa9fb554..bab4fcd94 100644 --- a/src/lib/azdoClient.ts +++ b/src/lib/azdoClient.ts @@ -159,13 +159,17 @@ export const repositoryHasFile = async ( }; /** - * Checks if a repository exists in the given project - * @param project The Azure DevOps project name + * Validates if a repository exists and if it contains the given file + * @param project The Azure DevOps project name + * @param fileName The name of the file + * @param branch The branch name * @param repoName The name of the repository * @param accessOpts The Azure DevOps access options to the repository */ -export const repositoryExists = async ( +export const validateRepository = async ( project: string, + fileName: string, + branch: string, repoName: string, accessOpts: AzureDevOpsOpts ): Promise => { @@ -177,4 +181,6 @@ export const repositoryExists = async ( `Project '${project}' does not contain repository '${repoName}'.` ); } + + await repositoryHasFile(fileName, branch, repoName, accessOpts); }; From fa0148e7cbb37e96c9dbc18e5f6be02dc72ada37 Mon Sep 17 00:00:00 2001 From: Edaena Salinas Date: Mon, 23 Mar 2020 23:24:42 -0700 Subject: [PATCH 3/4] Remove repositoryExists function --- src/lib/azdoClient.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/azdoClient.test.ts b/src/lib/azdoClient.test.ts index d86ee2c52..311b19256 100644 --- a/src/lib/azdoClient.test.ts +++ b/src/lib/azdoClient.test.ts @@ -11,7 +11,6 @@ import { getTaskAgentApi, getWebApi, invalidateWebApi, - repositoryExists, repositoryHasFile, validateRepository, } from "./azdoClient"; @@ -192,7 +191,13 @@ describe("validateRepository", () => { let hasError = false; try { - await repositoryExists("my-project", "my-repo", accessOpts); + await validateRepository( + "my-project", + "myFile", + "master", + "my-repo", + accessOpts + ); } catch (err) { hasError = true; } From d5b95d5c4a72e7fc06bb96ad18edebeeff3bf6b4 Mon Sep 17 00:00:00 2001 From: Edaena Salinas Date: Wed, 25 Mar 2020 11:23:21 -0700 Subject: [PATCH 4/4] Update unit tests --- src/lib/azdoClient.test.ts | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/lib/azdoClient.test.ts b/src/lib/azdoClient.test.ts index 311b19256..ab3592790 100644 --- a/src/lib/azdoClient.test.ts +++ b/src/lib/azdoClient.test.ts @@ -162,20 +162,16 @@ describe("validateRepository", () => { personalAccessToken: "mytoken", project: "testProject", }; - let hasError = false; - try { - await validateRepository( + await expect( + validateRepository( "my-project", "myFile", "master", "my-repo", accessOpts - ); - } catch (err) { - hasError = true; - } - expect(hasError).toBe(false); + ) + ).resolves.not.toThrow(); }); test("repository does not exist", async () => { const createPullRequestFunc = jest.spyOn(azure, "GitAPI"); @@ -188,20 +184,15 @@ describe("validateRepository", () => { personalAccessToken: "mytoken", project: "testProject", }; - let hasError = false; - - try { - await validateRepository( + await expect( + validateRepository( "my-project", "myFile", "master", "my-repo", accessOpts - ); - } catch (err) { - hasError = true; - } - expect(hasError).toBe(true); + ) + ).rejects.toThrow(); }); });