diff --git a/.github/ISSUE_TEMPLATE/test-failed-issue.md b/.github/ISSUE_TEMPLATE/test-failed-issue.md new file mode 100644 index 00000000..3ec6b3c3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/test-failed-issue.md @@ -0,0 +1,16 @@ +--- +name: Build Failed Issue +about: Something went wrong with CI build +title: Build Failed Issue [{{payload.sender.login}}] +labels: Build Failed +assignees: bishal-pdMSFT, vineetmimrot, kanika1894 + +--- + +Event triggered by: {{payload.sender.login}} +Workflow: {{ workflow }} +Branch: {{ref}} + +Something went wrong with GitHub checks for {{ workflow }}. For more info visit- + +https://github.com/microsoft/vscode-deploy-azure/actions?query=workflow%3A%22Extension%20Test%20analysis%22%20is%3Afailure \ No newline at end of file diff --git a/.github/workflows/extension-tests.yml b/.github/workflows/extension-tests.yml new file mode 100644 index 00000000..1d9eefa9 --- /dev/null +++ b/.github/workflows/extension-tests.yml @@ -0,0 +1,44 @@ +name: "Extension Test analysis" + +on: [push] + +jobs: + build: + + strategy: + fail-fast: false + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v2-beta + with: + node-version: '12' + + - name: npm install + run: npm install + + - name: Build extension + run: npm run compile + + - name: Run tests + uses: GabrielBB/xvfb-action@v1.2 + with: + run: npm test + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + Azure_UserName: ${{ secrets.Azure_UserName }} + Azure_PAT: ${{ secrets.Azure_PAT }} + + - name: Create an issue + uses: JasonEtco/create-an-issue@v2.4.0 + if: ${{ failure() && github.ref == 'refs/heads/master' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + filename: .github/ISSUE_TEMPLATE/test-failed-issue.md \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 1dadb93d..497405a3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -27,13 +27,19 @@ "request": "launch", "runtimeExecutable": "${execPath}", "args": [ - "${workspaceRoot}/src/test/workspace/", + // Add repository path on which you want to test + "", "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test" + // Change this to whatever test you want to debug + "--extensionTestsPath=${workspaceFolder}/out/configure/test/E2ETests/Static_Win_WebApp_Suite" ], "outFiles": [ - "${workspaceFolder}/out/test/**/*.js" - ] + "${workspaceFolder}/out/configure/test/**/*.js" + ], + "env": { + // Uncomment below line to show telemetry on Debug console + "DEBUGTELEMETRY": "v", + } }, { "name": "Extension Tests From Server", @@ -60,4 +66,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/copyStaticFiles.js b/copyStaticFiles.js index 029f7ab4..20352ef5 100644 --- a/copyStaticFiles.js +++ b/copyStaticFiles.js @@ -32,3 +32,6 @@ var mkdir = function (options, target) { mkdir("-p", path.join(__dirname, 'out/configure/templates')); cp("-Rf", path.join(__dirname, 'src/configure/templates/*'), path.join(__dirname, 'out/configure/templates')); //# sourceMappingURL=copyStaticFiles.js.map + +mkdir("-p", path.join(__dirname, 'out/configure/test/E2ETests/testFixtures')); +cp("-Rf", path.join(__dirname, 'src/configure/test/E2ETests/testFixtures/*'), path.join(__dirname, 'out/configure/test/E2ETests/testFixtures')); diff --git a/package-lock.json b/package-lock.json index e042721c..76380b34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,51 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@sinonjs/commons": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/formatio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", + "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^5.0.2" + } + }, + "@sinonjs/samsam": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.1.0.tgz", + "integrity": "sha512-42nyaQOVunX5Pm6GRJobmzbS7iLI+fhERITnETXzzwDZh+TtDr/Au3yAvXVjFmZ4wEUaE4Y3NFZfKv0bV0cbtg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", @@ -72,6 +117,21 @@ "integrity": "sha512-sWj7AMiG0fYmta6ug1ublLjtj/tqn+CnCZeo7yswR1ykxel0FOWFGdWviTcGSNAMmtLbycDqbg6w98VPFKJmbw==", "dev": true }, + "@types/sinon": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.5.tgz", + "integrity": "sha512-4CnkGdM/5/FXDGqL32JQ1ttVrGvhOoesLLF7VnTh4KdjK5N5VQOtxaylFqqTjnHx55MnD9O02Nbk5c1ELC8wlQ==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz", + "integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==", + "dev": true + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -734,7 +794,7 @@ "dependencies": { "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -1167,6 +1227,15 @@ "type-detect": "^4.0.5" } }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "requires": { + "check-error": "^1.0.2" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1372,7 +1441,7 @@ }, "commander": { "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, @@ -3166,6 +3235,12 @@ "verror": "1.10.0" } }, + "just-extend": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", + "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", + "dev": true + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -3285,6 +3360,12 @@ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -3548,7 +3629,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -3941,6 +4022,19 @@ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, + "nise": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", + "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, "nock": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.2.tgz", @@ -4375,6 +4469,23 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", @@ -4571,9 +4682,9 @@ "dev": true }, "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, "randomatic": { @@ -5251,6 +5362,44 @@ } } }, + "sinon": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.3.tgz", + "integrity": "sha512-IKo9MIM111+smz9JGwLmw5U1075n1YXeAq8YeSFlndCLhAL5KGn6bLgu7b/4AYHTV/LcEMcRm2wU2YiL55/6Pg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.2", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/formatio": "^5.0.1", + "@sinonjs/samsam": "^5.1.0", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", diff --git a/package.json b/package.json index 34cba677..37d30c37 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,6 @@ "compile": "tsc -p ./ && node copyStaticFiles.js", "watch": "node copyStaticFiles.js && tsc -watch -p ./", "postinstall": "node ./node_modules/vscode/bin/install", - "pretest": "npm run compile", "test": "node ./out/configure/test/runTest.js" }, "devDependencies": { @@ -123,13 +122,16 @@ "@types/mustache": "0.8.32", "@types/node": "7.0.43", "@types/q": "1.5.0", + "@types/sinon": "^9.0.5", "@types/underscore": "1.8.9", "ajv": "^6.9.1", "assert": "1.4.1", "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", "glob": "^7.1.6", "mocha": "^7.1.2", "nock": "^13.0.2", + "sinon": "^9.0.3", "ts-node": "7.0.1", "tslint": "5.8.0", "tslint-microsoft-contrib": "^6.2.0", @@ -163,4 +165,4 @@ "extensionDependencies": [ "ms-vscode.azure-account" ] -} +} \ No newline at end of file diff --git a/src/configure/clients/portalExtensionRepositoryAnalysisClient.ts b/src/configure/clients/portalExtensionRepositoryAnalysisClient.ts index ba30c3dc..37d4a35c 100644 --- a/src/configure/clients/portalExtensionRepositoryAnalysisClient.ts +++ b/src/configure/clients/portalExtensionRepositoryAnalysisClient.ts @@ -7,7 +7,7 @@ export class PortalExtensionRepositoryAnalysisClient implements IRepositoryAnaly private restClient: RestClient; private url: string; constructor(url: string, credentials: ServiceClientCredentials) { - this.restClient = new RestClient(credentials); + this.restClient = new RestClient(credentials, { noRetryPolicy: true }); this.url = url; } diff --git a/src/configure/configure.ts b/src/configure/configure.ts index e350a4a3..290cb654 100644 --- a/src/configure/configure.ts +++ b/src/configure/configure.ts @@ -1,5 +1,6 @@ import { GenericResource } from 'azure-arm-resource/lib/resource/models'; import { ApplicationSettings, RepositoryAnalysis } from 'azureintegration-repoanalysis-client-internal'; +import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; import { AzureTreeItem, UserCancelledError } from 'vscode-azureextensionui'; @@ -29,10 +30,7 @@ import { TracePoints } from './resources/tracePoints'; import { InputControlProvider } from './templateInputHelper/InputControlProvider'; import { Utilities, WhiteListedError } from './utilities/utilities'; -const uuid = require('uuid/v4'); - const Layer: string = 'configure'; -export let UniqueResourceNameSuffix: string = uuid().substr(0, 5); export async function configurePipeline(node: AzureTreeItem) { await telemetryHelper.executeFunctionWithTimeTelemetry(async () => { @@ -94,7 +92,6 @@ class Orchestrator { public constructor() { this.inputs = new WizardInputs(); this.controlProvider = new ControlProvider(); - UniqueResourceNameSuffix = uuid().substr(0, 5); } public async configure(node: any): Promise { @@ -291,7 +288,7 @@ class Orchestrator { if (workspaceFolders.length === 1) { telemetryHelper.setTelemetry(TelemetryKeys.MultipleWorkspaceFolders, 'false'); - this.workspacePath = workspaceFolders[0].uri.fsPath; + this.workspacePath = fs.realpathSync(workspaceFolders[0].uri.fsPath); } else { telemetryHelper.setTelemetry(TelemetryKeys.MultipleWorkspaceFolders, 'true'); diff --git a/src/configure/configurers/azurePipelineConfigurer.ts b/src/configure/configurers/azurePipelineConfigurer.ts index d1a232d1..735eaed3 100644 --- a/src/configure/configurers/azurePipelineConfigurer.ts +++ b/src/configure/configurers/azurePipelineConfigurer.ts @@ -6,7 +6,6 @@ import { UserCancelledError } from 'vscode-azureextensionui'; import { AppServiceClient, VSTSDeploymentMessage } from '../clients/azure/appServiceClient'; import { AzureResourceClient } from '../clients/azure/azureResourceClient'; import { AzureDevOpsClient } from "../clients/devOps/azureDevOpsClient"; -import { UniqueResourceNameSuffix } from '../configure'; import { generateDevOpsOrganizationName, generateDevOpsProjectName } from '../helper/commonHelper'; import { ControlProvider } from '../helper/controlProvider'; import { AzureDevOpsHelper } from "../helper/devOps/azureDevOpsHelper"; @@ -22,7 +21,7 @@ import * as constants from '../resources/constants'; import { Messages } from '../resources/messages'; import { TelemetryKeys } from '../resources/telemetryKeys'; import { TracePoints } from '../resources/tracePoints'; -import { WhiteListedError } from '../utilities/utilities'; +import { Utilities, WhiteListedError } from '../utilities/utilities'; import { Configurer } from "./configurerBase"; import Q = require('q'); @@ -150,7 +149,7 @@ export class AzurePipelineConfigurer implements Configurer { }, async () => { try { - let serviceConnectionName = `${inputs.sourceRepository.repositoryName}-${UniqueResourceNameSuffix}`; + let serviceConnectionName = `${inputs.sourceRepository.repositoryName}-${Utilities.shortGuid()}`; inputs.sourceRepository.serviceConnectionId = await serviceConnectionHelper.createGitHubServiceConnection(serviceConnectionName, inputs.githubPATToken); } catch (error) { @@ -170,7 +169,7 @@ export class AzurePipelineConfigurer implements Configurer { }, async () => { try { - let serviceConnectionName = `${inputs.targetResource.resource.name}-${UniqueResourceNameSuffix}`; + let serviceConnectionName = `${inputs.targetResource.resource.name}-${Utilities.shortGuid()}`; switch ((inputs.pipelineConfiguration.template as LocalPipelineTemplate).azureConnectionType) { case AzureConnectionType.None: return ''; @@ -240,7 +239,8 @@ export class AzurePipelineConfigurer implements Configurer { } while (!inputs.sourceRepository.commitId) { - let commitOrDiscard = await vscode.window.showInformationMessage( + let commitOrDiscard = await this.controlProvider.showInformationBox( + constants.CheckInPipelineFilesToRepository, commitMessage, Messages.commitAndPush, Messages.discardPipeline); @@ -250,7 +250,7 @@ export class AzurePipelineConfigurer implements Configurer { try { if (!inputs.sourceRepository.remoteUrl) { let repositoryName = path.basename(inputs.sourceRepository.localPath).trim().replace(/[^a-zA-Z0-9-]/g, ''); - repositoryName = !!repositoryName ? (repositoryName + UniqueResourceNameSuffix) : "codetoazure"; + repositoryName = !!repositoryName ? (repositoryName + Utilities.shortGuid()) : "codetoazure"; let repository = await this.azureDevOpsClient.createRepository(inputs.organizationName, inputs.project.id, repositoryName); inputs.sourceRepository.repositoryName = repository.name; inputs.sourceRepository.repositoryId = repository.id; @@ -289,7 +289,7 @@ export class AzurePipelineConfigurer implements Configurer { try { let targetResource = AzurePipelineConfigurer.getTargetResource(inputs); - let pipelineName = `${(targetResource ? targetResource.name : inputs.pipelineConfiguration.template.label)}-${UniqueResourceNameSuffix}`; + let pipelineName = `${(targetResource ? targetResource.name : inputs.pipelineConfiguration.template.label)}-${Utilities.shortGuid()}`; return await this.azureDevOpsHelper.createAndRunPipeline(pipelineName, inputs); } catch (error) { diff --git a/src/configure/configurers/localGithubWorkflowConfigurer.ts b/src/configure/configurers/localGithubWorkflowConfigurer.ts index c9dc2b8f..b17e4b2e 100644 --- a/src/configure/configurers/localGithubWorkflowConfigurer.ts +++ b/src/configure/configurers/localGithubWorkflowConfigurer.ts @@ -22,10 +22,9 @@ import * as constants from '../resources/constants'; import { Messages } from '../resources/messages'; import { TelemetryKeys } from '../resources/telemetryKeys'; import { TracePoints } from '../resources/tracePoints'; +import { Utilities } from '../utilities/utilities'; import { Configurer } from "./configurerBase"; - -const uuid = require('uuid/v4'); const Layer = 'LocalGitHubWorkflowConfigurer'; export class LocalGitHubWorkflowConfigurer implements Configurer { @@ -109,7 +108,7 @@ export class LocalGitHubWorkflowConfigurer implements Configurer { }); if (!!azureConnectionSecret) { - inputs.targetResource.serviceConnectionId = 'AZURE_CREDENTIALS_' + uuid().substr(0, 8); + inputs.targetResource.serviceConnectionId = constants.githubSecretNamePrefix + Utilities.shortGuid(8); try { await vscode.window.withProgress( { @@ -202,7 +201,8 @@ export class LocalGitHubWorkflowConfigurer implements Configurer { displayMessage = Messages.modifyAndCommitMultipleFiles; } - let commitOrDiscard = await vscode.window.showInformationMessage( + const commitOrDiscard = await this.controlProvider.showInformationBox( + constants.CheckInPipelineFilesToRepository, utils.format(displayMessage, Messages.commitAndPush, inputs.sourceRepository.branch, inputs.sourceRepository.remoteName), Messages.commitAndPush, Messages.discardPipeline); diff --git a/src/configure/helper/AssetHandler.ts b/src/configure/helper/AssetHandler.ts index ede21d64..85a18891 100644 --- a/src/configure/helper/AssetHandler.ts +++ b/src/configure/helper/AssetHandler.ts @@ -2,11 +2,11 @@ import * as utils from 'util'; import * as vscode from 'vscode'; import { AppServiceClient } from '../clients/azure/appServiceClient'; import { ArmRestClient } from '../clients/azure/armRestClient'; -import { UniqueResourceNameSuffix } from '../configure'; import { TargetResourceType, WizardInputs } from "../model/models"; import { LocalPipelineTemplate, TemplateAsset, TemplateAssetType, TemplateParameterType } from '../model/templateModels'; import { Messages } from '../resources/messages'; import { TracePoints } from '../resources/tracePoints'; +import { Utilities } from '../utilities/utilities'; import { GraphHelper } from './graphHelper'; import { SodiumLibHelper } from './sodium/SodiumLibHelper'; import { telemetryHelper } from './telemetryHelper'; @@ -43,7 +43,7 @@ export class AssetHandler { let aadAppName = GraphHelper.generateAadApplicationName(inputs.organizationName, inputs.project.name); let aadApp = await GraphHelper.createSpnAndAssignRole(inputs.azureSession, aadAppName, scope); // Use param name for first azure resource param - let serviceConnectionName = `${inputs.pipelineConfiguration.params[(inputs.pipelineConfiguration.template as LocalPipelineTemplate).parameters.find((parameter) => parameter.type === TemplateParameterType.GenericAzureResource).name]}-${UniqueResourceNameSuffix}`; + let serviceConnectionName = `${inputs.pipelineConfiguration.params[(inputs.pipelineConfiguration.template as LocalPipelineTemplate).parameters.find((parameter) => parameter.type === TemplateParameterType.GenericAzureResource).name]}-${Utilities.shortGuid()}`; return await createAsset(serviceConnectionName, asset.type, { "aadApp": aadApp, "scope": scope }, inputs); } catch (error) { @@ -64,7 +64,7 @@ export class AssetHandler { // find LCS of all azure resource params let appServiceClient = new AppServiceClient(inputs.azureSession.credentials, inputs.azureSession.environment, inputs.azureSession.tenantId, inputs.subscriptionId); let publishProfile = await appServiceClient.getWebAppPublishProfileXml(inputs.targetResource.resource.id); - let serviceConnectionName = `${targetWebAppResource.name}-${UniqueResourceNameSuffix}`; + let serviceConnectionName = `${targetWebAppResource.name}-${Utilities.shortGuid()}`; return await createAsset(serviceConnectionName, asset.type, publishProfile, inputs); } catch (error) { @@ -162,7 +162,7 @@ export class AssetHandler { * @returns sanitized asset name and makes it unique by appending 5 digit random alpha numeric string to asset name. */ public static getSanitizedUniqueAssetName(assetName: string): string { - assetName = assetName + "_" + UniqueResourceNameSuffix; + assetName = assetName + "_" + Utilities.shortGuid(); assetName = assetName.replace(/\W/g, ''); return assetName; } diff --git a/src/configure/helper/LocalGitRepoHelper.ts b/src/configure/helper/LocalGitRepoHelper.ts index 7d949642..0fa04ced 100644 --- a/src/configure/helper/LocalGitRepoHelper.ts +++ b/src/configure/helper/LocalGitRepoHelper.ts @@ -146,7 +146,7 @@ export class LocalGitRepoHelper { public async getGitRootDirectory(): Promise { let gitRootDir = await this.gitReference.revparse(["--show-toplevel"]); - return path.normalize(gitRootDir.trim()); + return fs.realpathSync(path.normalize(gitRootDir.trim())); } public async initializeGitRepository(remoteName: string, remoteUrl: string, filesToExcludeRegex?: string): Promise { diff --git a/src/configure/resources/constants.ts b/src/configure/resources/constants.ts index 532812bb..1943e654 100644 --- a/src/configure/resources/constants.ts +++ b/src/configure/resources/constants.ts @@ -167,6 +167,7 @@ export const TargetResource = 'targetResource'; export const ResourceDynamicValidationFailure = 'ResourceDynamicValidationFailure'; export const EnterGithubRepositoryName = 'EnterGithubRepositoryName'; export const SelectGitHubOrganization = 'selectGitHubOrganization'; +export const CheckInPipelineFilesToRepository = "checkInPipelineFilesToRepository"; //RepoAnalysis constants expected in response of Repository Analysis Service export const RepoAnalysisConstants = { @@ -197,6 +198,7 @@ export const azurePipeline: string = "Azure-pipeline"; export const githubWorkflow: string = "Github-workflow"; export const clientPropertyKey: string = "ms.client.vscode"; export const inputModeProperty: string = "inputMode"; +export const githubSecretNamePrefix: string = "AZURE_CREDENTIALS_"; export const ExceptionType = { UnauthorizedRequestException: 'UNAUTHORIZEDREQUESTEXCEPTION' diff --git a/src/configure/test/E2ETests/Static_Win_WebApp_Suite/Static_Windows_WebApp.test.ts b/src/configure/test/E2ETests/Static_Win_WebApp_Suite/Static_Windows_WebApp.test.ts new file mode 100644 index 00000000..730819cc --- /dev/null +++ b/src/configure/test/E2ETests/Static_Win_WebApp_Suite/Static_Windows_WebApp.test.ts @@ -0,0 +1,63 @@ +import * as vscode from 'vscode'; +import { TestConstants } from "../testConstants"; +import { TestMocks } from '../testMocks'; + +var path = require('path'); +var fs = require('fs'); +let sinon = require('sinon'); +let chai = require("chai"); +let chaiAsPromised = require("chai-as-promised"); + +chai.use(chaiAsPromised); +let expect = chai.expect; + +describe('# Configure Pipeline for Static Website on Windows AppSvc @vscode-deploy-azure ', function () { + context('Should start extension @vscode-deploy-azure', function () { + it('should be able to activate the extension', async function (done) { + this.timeout(2000); + const extension = vscode.extensions.getExtension(TestConstants.extensionId); + if (!extension.isActive) { + expect(extension.activate()).should.be.fulfilled; + } + done(); + }); + }); + + context('Should configure pipeline for Static Website on Windows AppSvc', function () { + let workflowFilePath: string; + before(function () { + this.timeout(5000); + workflowFilePath = path.join(vscode.workspace.workspaceFolders[0].uri.path, ".github/workflows/workflow.yml"); + if (fs.existsSync(workflowFilePath)) { + fs.unlinkSync(workflowFilePath); + } + }); + + it('configure pipeline @vscode-deploy-azure', function () { + this.timeout(0); + var mocks = new TestMocks(); + return vscode.extensions.getExtension(TestConstants.extensionId).activate() + .then(() => { + + mocks.mockCommonMethodsOrProperties(); + mocks.mockGetAppServices_Windows(); + mocks.mockIsScmTypeSet(); + mocks.mockPublishProfile(); + mocks.mockSettingSecret(); + mocks.mockShowInputBox(); + mocks.mockShowQuickPick(); + mocks.mockShowInformationMessage(); + return vscode.commands.executeCommand("configure-cicd-pipeline"); + }).then(() => { + mocks.assertMocks(); + const buf1 = fs.readFileSync(path.join(__dirname, "../testFixtures/workflows", "Static_Windows_WebApp_Workflow.yml")); + const buf2 = fs.readFileSync(workflowFilePath); + expect(Buffer.compare(buf1, buf2)).to.equal(0); + }); + }); + + after(function () { + sinon.restore(); + }); + }); +}); \ No newline at end of file diff --git a/src/configure/test/E2ETests/Static_Win_WebApp_Suite/index.ts b/src/configure/test/E2ETests/Static_Win_WebApp_Suite/index.ts new file mode 100644 index 00000000..3be6988d --- /dev/null +++ b/src/configure/test/E2ETests/Static_Win_WebApp_Suite/index.ts @@ -0,0 +1,6 @@ +import * as path from 'path'; +import { setupTest } from "../../commonIndexUtilities"; + +export function run(): Promise { + return setupTest(path.basename(__dirname), __dirname); +} \ No newline at end of file diff --git a/src/configure/test/E2ETests/testConstants.ts b/src/configure/test/E2ETests/testConstants.ts new file mode 100644 index 00000000..399a2db9 --- /dev/null +++ b/src/configure/test/E2ETests/testConstants.ts @@ -0,0 +1,58 @@ +import { GenericResource } from "azure-arm-resource/lib/resource/models"; +import { BasicAuthenticationCredentials } from "ms-rest"; +import { AzureEnvironment } from 'ms-rest-azure'; + +export class TestConstants { + public static extensionId = "ms-vscode-deploy-azure.azure-deploy"; + + public static userName = process.env.Azure_UserName; + public static AzurePAT = process.env.Azure_PAT; + public static GithubPAT = process.env.GITHUB_TOKEN; + public static PublishProfile = process.env.PublishProfile || "DummyPublishProfile"; + + public static credentials = new BasicAuthenticationCredentials(TestConstants.userName, TestConstants.AzurePAT); + + public static shortGuid = "test"; + + public static session = { + userId: TestConstants.userName, + tenantId: "f7841ef5-a478-4164-8a1e-e8e95a786cd7", + credentials: TestConstants.credentials, + environment: AzureEnvironment.Azure + } + + public static subscription = { + id: "/subscriptions/8f67c458-5874-41fe-8cf9-f3b0429be6ff", + subscriptionId: "8f67c458-5874-41fe-8cf9-f3b0429be6ff", + displayName: "RMDev" + } + + public static subscriptionData = { + label: TestConstants.subscription.displayName, + data: { + session: TestConstants.session, + subscription: TestConstants.subscription + }, + description: TestConstants.subscription.subscriptionId + }; + + public static windowsWebAppResource = { + id: "/subscriptions/8f67c458-5874-41fe-8cf9-f3b0429be6ff/resourceGroups/vnmtestlinux93c8/providers/Microsoft.Web/sites/test-app", + name: "test-app", + type: "Microsoft.Web/sites", + kind: "app" + }; + + public static windowsWebAppResourceData = { + label: TestConstants.windowsWebAppResource.name, + data: TestConstants.windowsWebAppResource + }; + + public static azureAccountExtensionAPI = { + sessions: [TestConstants.session], + subscriptions: [{ session: TestConstants.session, subscription: TestConstants.subscription }], + filters: [{ session: TestConstants.session, subscription: TestConstants.subscription }], + waitForLogin: () => Promise.resolve(true), + waitForSubscriptions: () => Promise.resolve(true) + } +} \ No newline at end of file diff --git a/src/configure/test/E2ETests/testFixtures/workflows/Static_Windows_WebApp_Workflow.yml b/src/configure/test/E2ETests/testFixtures/workflows/Static_Windows_WebApp_Workflow.yml new file mode 100644 index 00000000..45f4c072 --- /dev/null +++ b/src/configure/test/E2ETests/testFixtures/workflows/Static_Windows_WebApp_Workflow.yml @@ -0,0 +1,20 @@ +on: + push: + branches: + - master + +name: Package and deploy simple web app + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + # checkout the repo + - uses: actions/checkout@master + + # deploy web app using publish profile credentials + - uses: azure/webapps-deploy@v1 + with: + app-name: test-app + package: . + publish-profile: ${{ secrets.AZURE_CREDENTIALS_test }} \ No newline at end of file diff --git a/src/configure/test/E2ETests/testMocks.ts b/src/configure/test/E2ETests/testMocks.ts new file mode 100644 index 00000000..e6686a3f --- /dev/null +++ b/src/configure/test/E2ETests/testMocks.ts @@ -0,0 +1,88 @@ +import { ResourceListResult } from "azure-arm-resource/lib/resource/models"; +import { AppServiceClient } from "../../clients/azure/appServiceClient"; +import { GithubClient } from "../../clients/github/githubClient"; +import { ControlProvider } from "../../helper/controlProvider"; +import { extensionVariables } from "../../model/models"; +import * as constants from '../../resources/constants'; +import { Messages } from "../../resources/messages"; +import { Utilities } from "../../utilities/utilities"; +import { TestConstants } from "./testConstants"; +import sinon = require("sinon"); + +export class TestMocks { + + constructor() { } + private mockGetInputObj; + private mockShowQuickPickObj; + private mockShowInformationMessageObj; + private mockGetAppServiceObj; + private mockIsScmTypeSetObj; + private mockPublishProfileObj; + private mockSettingSecretObj; + + private mockedObjectArray: any[] = []; + + public mockCommonMethodsOrProperties() { + sinon.stub(extensionVariables, 'azureAccountExtensionApi').value(TestConstants.azureAccountExtensionAPI); + sinon.stub(Utilities, 'shortGuid').returns(TestConstants.shortGuid); + } + + public mockGetAppServices_Windows(args: any = ['app,linux', 'app']) { + this.mockGetAppServiceObj = sinon.stub(AppServiceClient.prototype, 'GetAppServices') + .withArgs(args) + .resolves([TestConstants.windowsWebAppResource]); + + this.mockedObjectArray.push({ mockObject: this.mockGetAppServiceObj, count: 1 }); + } + + public mockIsScmTypeSet() { + this.mockIsScmTypeSetObj = sinon.stub(AppServiceClient.prototype, 'isScmTypeSet') + .withArgs(TestConstants.windowsWebAppResource.id) + .resolves(false); + this.mockedObjectArray.push({ mockObject: this.mockIsScmTypeSetObj, count: 1 }); + } + + public mockPublishProfile() { + this.mockPublishProfileObj = sinon.stub(AppServiceClient.prototype, 'getWebAppPublishProfileXml') + .withArgs(TestConstants.windowsWebAppResource.id) + .resolves(TestConstants.PublishProfile); + this.mockedObjectArray.push({ mockObject: this.mockPublishProfileObj, count: 1 }); + } + + public mockSettingSecret() { + this.mockSettingSecretObj = sinon.stub(GithubClient.prototype, 'createOrUpdateGithubSecret') + .withArgs(constants.githubSecretNamePrefix + TestConstants.shortGuid, sinon.match.any) + .resolves(); + this.mockedObjectArray.push({ mockObject: this.mockSettingSecretObj, count: 1 }); + } + + public mockShowInputBox(resolve: string[] = [TestConstants.GithubPAT]) { + this.mockGetInputObj = sinon.stub(ControlProvider.prototype, 'showInputBox'); + for (var i = 0; i < resolve.length; i++) { + this.mockGetInputObj.onCall(i).resolves(resolve[i]); + } + this.mockedObjectArray.push({ mockObject: this.mockGetInputObj, count: resolve.length }); + } + + public mockShowQuickPick(resolve: any[] = [TestConstants.subscriptionData, TestConstants.windowsWebAppResourceData]) { + this.mockShowQuickPickObj = sinon.stub(ControlProvider.prototype, 'showQuickPick'); + for (var i = 0; i < resolve.length; i++) { + this.mockShowQuickPickObj.onCall(i).resolves(resolve[i]); + } + this.mockedObjectArray.push({ mockObject: this.mockShowQuickPickObj, count: resolve.length }); + } + + public mockShowInformationMessage(resolve: any[] = [Messages.discardPipeline]) { + this.mockShowInformationMessageObj = sinon.stub(ControlProvider.prototype, 'showInformationBox'); + for (var i = 0; i < resolve.length; i++) { + this.mockShowInformationMessageObj.onCall(i).resolves(resolve[i]); + } + this.mockedObjectArray.push({ mockObject: this.mockShowInformationMessageObj, count: resolve.length }); + } + + public assertMocks() { + this.mockedObjectArray.forEach(({ mockObject: object, count: cnt }) => { + sinon.assert.callCount(object, cnt); + }); + } +} \ No newline at end of file diff --git a/src/configure/test/suite/GitHubClient.test.ts b/src/configure/test/UnitTestsSuite/GitHubClient.test.ts similarity index 89% rename from src/configure/test/suite/GitHubClient.test.ts rename to src/configure/test/UnitTestsSuite/GitHubClient.test.ts index b0fcd2d0..4fce11e3 100644 --- a/src/configure/test/suite/GitHubClient.test.ts +++ b/src/configure/test/UnitTestsSuite/GitHubClient.test.ts @@ -28,6 +28,13 @@ var orgList = [ "repos_url": "https://api.github.com/orgs/SampleOrganizationC/repos" }]; +var userInfo = { + "login": "username", + "id": 66721313, + "url": "https://api.github.com/users/username", + "repos_url": "https://api.github.com/users/username/repos" +} + var repoCreationResponseOnSuccess = { "id": 278008782, "name": repoName, @@ -51,10 +58,12 @@ describe('# Testing listOrganizations() ', function () { it('should return a list of organizations', function (done) { nock('https://api.github.com') .get('/user/orgs') - .reply(200, orgList); + .reply(200, orgList) + .get('/user') + .reply(200, userInfo); githubClient.listOrganizations(true).then((orgs) => { expect(Array.isArray(orgs)).to.equal(true); - expect(orgs.length).to.equal(3); + expect(orgs.length).to.equal(4); orgs.forEach((org) => { expect(org).to.have.property('login'); expect(org).to.have.property('id'); @@ -69,10 +78,12 @@ describe('# Testing listOrganizations() ', function () { it('Should return an empty list of organization', function (done) { nock('https://api.github.com') .get('/user/orgs') - .reply(200, []); + .reply(200, []) + .get('/user') + .reply(200, userInfo); githubClient.listOrganizations(true).then((orgs) => { expect(Array.isArray(orgs)).to.equal(true); - expect(orgs.length).to.equal(0); + expect(orgs.length).to.equal(1); done(); }); }); diff --git a/src/configure/test/suite/commonHelper.test.ts b/src/configure/test/UnitTestsSuite/commonHelper.test.ts similarity index 100% rename from src/configure/test/suite/commonHelper.test.ts rename to src/configure/test/UnitTestsSuite/commonHelper.test.ts diff --git a/src/configure/test/UnitTestsSuite/index.ts b/src/configure/test/UnitTestsSuite/index.ts new file mode 100644 index 00000000..7e8c2744 --- /dev/null +++ b/src/configure/test/UnitTestsSuite/index.ts @@ -0,0 +1,6 @@ +import * as path from 'path'; +import { setupTest } from "../commonIndexUtilities"; + +export function run(): Promise { + return setupTest(path.basename(__dirname), __dirname); +} \ No newline at end of file diff --git a/src/configure/test/commonIndexUtilities.ts b/src/configure/test/commonIndexUtilities.ts new file mode 100644 index 00000000..a271cc8a --- /dev/null +++ b/src/configure/test/commonIndexUtilities.ts @@ -0,0 +1,46 @@ +import * as fs from 'fs'; +import * as glob from 'glob'; +import * as Mocha from 'mocha'; +import * as path from 'path'; + +export function setupTest(suiteName: string, suitePath: string): Promise { + const testsRoot = path.resolve(__dirname); + const testResultDir = path.join(testsRoot, 'deploy-azure-extension-testResult'); + if (!fs.existsSync(testResultDir)) { + fs.mkdirSync(testResultDir); + } + const testResultFileName = path.normalize(path.join(testResultDir, suiteName + "-" + ((new Date()).toJSON().slice(0, 19).replace('T', '_').replace(/:/g, '')) + ".xml")); + const mocha = new Mocha({ + ui: 'bdd', + color: true, + reporter: "xunit", + reporterOptions: { + output: testResultFileName, + } + }); + + return new Promise((c, e) => { + glob(suitePath + '/**.test.js', { cwd: testsRoot }, (err, files) => { + if (err) { + return e(err); + } + + // Add files to the test suite + files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); + + try { + // Run the mocha test + mocha.run(failures => { + if (failures > 0) { + e(new Error(`${failures} tests failed.`)); + } else { + c(); + } + }); + } catch (err) { + console.error(err); + e(err); + } + }); + }); +} \ No newline at end of file diff --git a/src/configure/test/index.ts b/src/configure/test/index.ts deleted file mode 100644 index cc6ffb47..00000000 --- a/src/configure/test/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import * as glob from 'glob'; -import * as Mocha from 'mocha'; -import * as path from 'path'; - -export function run(): Promise { - // Create the mocha test - const mocha = new Mocha({ - ui: 'bdd', - color: true - }); - - const testsRoot = path.resolve(__dirname, '..'); - - return new Promise((c, e) => { - glob('**/suite/**.test.js', { cwd: testsRoot }, (err, files) => { - if (err) { - return e(err); - } - - // Add files to the test suite - files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); - - try { - // Run the mocha test - mocha.run(failures => { - if (failures > 0) { - e(new Error(`${failures} tests failed.`)); - } else { - c(); - } - }); - } catch (err) { - console.error(err); - e(err); - } - }); - }); -} diff --git a/src/configure/test/runTest.ts b/src/configure/test/runTest.ts index 0bdd6b8a..33aa59a3 100644 --- a/src/configure/test/runTest.ts +++ b/src/configure/test/runTest.ts @@ -1,23 +1,93 @@ +import * as cp from 'child_process'; +import * as fs from 'fs'; +import * as os from 'os'; import * as path from 'path'; -import { runTests } from 'vscode-test'; - +import * as git from 'simple-git/promise'; +import { downloadAndUnzipVSCode, resolveCliPathFromVSCodeExecutablePath, runTests } from 'vscode-test'; async function testHost() { try { // The folder containing the Extension Manifest package.json // Passed to `--extensionDevelopmentPath` - const extensionDevelopmentPath = path.resolve(__dirname, '../../'); + validateEnvironmentVariables(); + const extensionDevelopmentPath = path.resolve(__dirname, '../../../'); + + const extensionTestsEnv = { + "DEBUGTELEMETRY": "v" + } + + let sampleRepoFolder: string; // The path to test runner // Passed to --extensionTestsPath - const extensionTestsPath = path.resolve(__dirname, './index'); + let extensionTestsPath: string; + + const vscodeExecutablePath = await downloadAndUnzipVSCode(); + const cliPath = resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath); + cp.spawnSync(cliPath, ['--install-extension', 'ms-vscode.azure-account'], { + encoding: 'utf-8', + stdio: 'inherit' + }); + + + console.log("### Running Unit Tests ###") + extensionTestsPath = path.resolve(__dirname, './UnitTestsSuite'); + await runTests({ launchArgs: [], extensionDevelopmentPath, extensionTestsPath, extensionTestsEnv }); - // Download VS Code, unzip it and run the integration test - await runTests({ extensionDevelopmentPath, extensionTestsPath }); + console.log("### Running Static Website on Windows WebApp Test ####") + extensionTestsPath = path.resolve(__dirname, './E2ETests/Static_Win_WebApp_Suite'); + sampleRepoFolder = await setupGitHubRepoFolderForStaticWebApp(); + await runTests({ launchArgs: [sampleRepoFolder], extensionDevelopmentPath, extensionTestsPath, extensionTestsEnv }); + printTestResultFiles(); } catch (err) { console.error('Failed to run tests. Error : ' + err); + printTestResultFiles(); process.exit(1); } } -testHost(); +async function setupGitHubRepoFolderForStaticWebApp(): Promise { + const projectPath = fs.mkdtempSync(path.join(os.tmpdir(), 'staticwebapp-')); + console.log("## Static WebApp ProjectPath: " + projectPath); + const gitUrl = "https://github.com/vineetmimrot/StaticWebapp.git"; + await git(projectPath).clone(gitUrl, projectPath); + return projectPath; +} + +function validateEnvironmentVariables() { + let unsetVariables: string = ""; + if (!process.env.Azure_UserName) { + unsetVariables += "Azure_UserName, " + } + + if (!process.env.Azure_PAT) { + unsetVariables += "Azure_PAT, " + } + + if (!process.env.GITHUB_TOKEN) { + unsetVariables += "GITHUB_TOKEN" + } + if (unsetVariables != "") { + throw new Error(`Env variable ${unsetVariables} are not set`); + } +} + +function printTestResultFiles() { + const testsRoot = path.resolve(__dirname); + const testResultDir = path.join(testsRoot, 'deploy-azure-extension-testResult'); + if (!fs.existsSync(testResultDir)) { + return; + } + + let filenames = fs.readdirSync(testResultDir); + + console.log("\n### Test Report ###"); + let count = 1; + filenames.forEach((file) => { + console.log("### " + count++ + ". Test Suite: " + path.parse(file).name + " ###"); + console.log("\n" + fs.readFileSync(path.resolve(testResultDir, file), 'utf-8')); + console.log("\n"); + }); +} + +testHost(); \ No newline at end of file diff --git a/src/configure/utilities/utilities.ts b/src/configure/utilities/utilities.ts index 686c32f7..bf2b6b19 100644 --- a/src/configure/utilities/utilities.ts +++ b/src/configure/utilities/utilities.ts @@ -1,9 +1,14 @@ import * as crypto from 'crypto'; +import uuid = require('uuid/v4'); export class Utilities { public static createSha256Hash(input: string): string { return crypto.createHash('sha256').update(input).digest('hex'); } + + public static shortGuid(len: number = 5): string { + return uuid().substr(0, len) + } } export class WhiteListedError extends Error {