From c4258b60e8d71cb726e15503ed70674029b199e9 Mon Sep 17 00:00:00 2001 From: Chhavi-Mandowara Date: Wed, 26 Feb 2025 12:25:45 +0530 Subject: [PATCH 1/5] fix: fix behaviour of redeploy-latest and redeploy-last-upload flag for existing project to not show prompt --- src/adapters/file-upload.ts | 45 +++++++++++++------------ src/adapters/github.ts | 46 +++++++++++++++++--------- src/adapters/pre-check.ts | 10 ------ test/unit/adapters/file-upload.test.ts | 43 ++++++++++++++++++++---- test/unit/adapters/github.test.ts | 45 +++++++++++++++++++++++-- 5 files changed, 132 insertions(+), 57 deletions(-) diff --git a/src/adapters/file-upload.ts b/src/adapters/file-upload.ts index eb3ccba..138d5b9 100755 --- a/src/adapters/file-upload.ts +++ b/src/adapters/file-upload.ts @@ -26,35 +26,36 @@ export default class FileUpload extends BaseClass { */ async run(): Promise { if (this.config.isExistingProject) { - await this.initApolloClient(); - const uploadLastFile = - this.config['redeploy-last-upload'] || - (await cliux.inquire({ - type: 'confirm', - default: false, - name: 'uploadLastFile', - message: 'Redeploy with last file upload?', - })); - if (!uploadLastFile) { - await this.createSignedUploadUrl(); - const { zipName, zipPath } = await this.archive(); - await this.uploadFile(zipName, zipPath); - } - - const { uploadUid } = this.signedUploadUrlData || { - uploadUid: undefined, - }; - await this.createNewDeployment(true, uploadUid); + await this.handleExistingProject(); } else { - await this.prepareForNewProjectCreation(); - await this.createNewProject(); + await this.handleNewProject(); } - + this.prepareLaunchConfig(); await this.showLogs(); this.showDeploymentUrl(); this.showSuggestion(); } + + private async handleExistingProject(): Promise { + await this.initApolloClient(); + + let redeployLatest = this.config['redeploy-latest']; + + if (redeployLatest) { + await this.createSignedUploadUrl(); + const { zipName, zipPath } = await this.archive(); + await this.uploadFile(zipName, zipPath); + } + + const { uploadUid } = this.signedUploadUrlData || { uploadUid: undefined }; + await this.createNewDeployment(true, uploadUid); + } + + private async handleNewProject(): Promise { + await this.prepareForNewProjectCreation(); + await this.createNewProject(); + } /** * @method createNewProject - Create new launch project diff --git a/src/adapters/github.ts b/src/adapters/github.ts index fd44cd6..7d71ede 100755 --- a/src/adapters/github.ts +++ b/src/adapters/github.ts @@ -21,24 +21,10 @@ export default class GitHub extends BaseClass { * @memberof GitHub */ async run(): Promise { - // NOTE New project creation Flow if (this.config.isExistingProject) { - await this.initApolloClient(); - await this.createNewDeployment(); + await this.handleExistingProject(); } else { - // NOTE Existing project flow - // NOTE Step 1: Check is Github connected - if (await this.checkGitHubConnected()) { - // NOTE Step 2: check is the git remote available in the user's repo list - if (await this.checkGitRemoteAvailableAndValid()) { - if (await this.checkUserGitHubAccess()) { - // NOTE Step 3: check is the user has proper git access - await this.prepareForNewProjectCreation(); - } - } - } - - await this.createNewProject(); + await this.handleNewProject(); } this.prepareLaunchConfig(); @@ -47,6 +33,34 @@ export default class GitHub extends BaseClass { this.showSuggestion(); } + private async handleExistingProject(): Promise { + await this.initApolloClient(); + const redeployLastUpload = this.config['redeploy-last-upload']; + + if (redeployLastUpload) { + this.log('redeploy-last-upload flag is not supported for Github Project.', 'error'); + this.exit(1); + return; + } + + await this.initApolloClient(); + await this.createNewDeployment(); + } + + private async handleNewProject(): Promise { + // NOTE Step 1: Check is Github connected + if (await this.checkGitHubConnected()) { + // NOTE Step 2: check is the git remote available in the user's repo list + if (await this.checkGitRemoteAvailableAndValid()) { + if (await this.checkUserGitHubAccess()) { + // NOTE Step 3: check is the user has proper git access + await this.prepareForNewProjectCreation(); + } + } + } + await this.createNewProject(); + } + /** * @method createNewProject - Create new launch project * diff --git a/src/adapters/pre-check.ts b/src/adapters/pre-check.ts index cb08d9a..82e7a1e 100755 --- a/src/adapters/pre-check.ts +++ b/src/adapters/pre-check.ts @@ -47,16 +47,6 @@ export default class PreCheck extends BaseClass { this.log('Existing launch project identified', 'info'); await this.displayPreDeploymentDetails(); - const deployLatestCode = - this.config['redeploy-latest'] || - (await ux.inquire({ - type: 'confirm', - name: 'deployLatestSource', - message: 'Redeploy latest commit/code?', - })); - if (!deployLatestCode) { - this.exit(1); - } } } } diff --git a/test/unit/adapters/file-upload.test.ts b/test/unit/adapters/file-upload.test.ts index 6b1aea8..99b2482 100644 --- a/test/unit/adapters/file-upload.test.ts +++ b/test/unit/adapters/file-upload.test.ts @@ -7,11 +7,12 @@ import { FileUpload, BaseClass } from '../../../src/adapters'; import { BaseCommand } from '../../../src/base-command'; describe('File Upload', () => { - let inquireStub, prepareApiClientsStub, prepareConfigStub, getConfigStub; + let inquireStub, exitStub, prepareApiClientsStub, prepareConfigStub, getConfigStub; let adapterConstructorInputs; beforeEach(() => { inquireStub = stub(cliux, 'inquire'); + exitStub = stub(BaseCommand.prototype, 'exit'); prepareConfigStub = stub(BaseCommand.prototype, 'prepareConfig').resolves(); prepareApiClientsStub = stub(BaseCommand.prototype, 'prepareApiClients').resolves(); getConfigStub = stub(BaseCommand.prototype, 'getConfig').resolves(); @@ -25,6 +26,7 @@ describe('File Upload', () => { afterEach(() => { inquireStub.restore(); + exitStub.restore(); prepareConfigStub.restore(); getConfigStub.restore(); prepareApiClientsStub.restore(); @@ -43,9 +45,6 @@ describe('File Upload', () => { showDeploymentUrlStub, showSuggestionStub; - let adapterConstructorOptions = { - config: { isExistingProject: true, currentConfig: { uid: '123244', organizationUid: 'bltxxxxxxxx' } }, - }; beforeEach(() => { initApolloClientStub = stub(BaseClass.prototype, 'initApolloClient').resolves(); createSignedUploadUrlStub = stub(FileUpload.prototype, 'createSignedUploadUrl').resolves(); @@ -75,8 +74,40 @@ describe('File Upload', () => { showSuggestionStub.restore(); }); - it('should run github flow', async () => { - new FileUpload(adapterConstructorOptions).run(); + describe('Redeploy existing project', () => { + + it('should run file upload flow for existing project where flag passed is redeploy-latest', async () => { + let adapterConstructorOptions = { + config: { + isExistingProject: true, + currentConfig: { uid: '123244', organizationUid: 'bltxxxxxxxx', }, + 'redeploy-latest': true + }, + }; + new FileUpload(adapterConstructorOptions).run(); + }); + + it('should run file upload flow for existing project where flag passed is redeploy-last-upload', async () => { + let adapterConstructorOptions = { + config: { + isExistingProject: true, + currentConfig: { uid: '123244', organizationUid: 'bltxxxxxxxx', }, + 'redeploy-last-upload': true + }, + }; + new FileUpload(adapterConstructorOptions).run(); + }); + }); + + describe('Deploy new project', () => { + let adapterConstructorOptions = { + config: { + isExistingProject: false + }, + }; + it('should run file upload flow for new project', async () => { + new FileUpload(adapterConstructorOptions).run(); + }); }); }); diff --git a/test/unit/adapters/github.test.ts b/test/unit/adapters/github.test.ts index b2fa6f0..4a0832c 100644 --- a/test/unit/adapters/github.test.ts +++ b/test/unit/adapters/github.test.ts @@ -5,6 +5,8 @@ import { cliux } from '@contentstack/cli-utilities'; import { githubAdapterMockData } from '../mock/index'; import { GitHub, BaseClass } from '../../../src/adapters'; import { BaseCommand } from '../../../src/base-command'; +import { exit } from 'process'; +import fs from 'fs'; describe('GitHub', () => { let inquireStub, prepareApiClientsStub, prepareConfigStub, getConfigStub; @@ -41,6 +43,7 @@ describe('GitHub', () => { prepareLaunchConfigStub, showLogsStub, showDeploymentUrlStub, + exitStub, showSuggestionStub; beforeEach(() => { @@ -53,11 +56,11 @@ describe('GitHub', () => { checkUserGitHubAccessStub = stub(GitHub.prototype, 'checkUserGitHubAccess').resolves(true); prepareForNewProjectCreationStub = stub(GitHub.prototype, 'prepareForNewProjectCreation').resolves(); createNewProjectStub = stub(GitHub.prototype, 'createNewProject').resolves(); - prepareLaunchConfigStub = stub(BaseClass.prototype, 'prepareLaunchConfig').resolves(); showLogsStub = stub(BaseClass.prototype, 'showLogs').resolves(); showDeploymentUrlStub = stub(BaseClass.prototype, 'showDeploymentUrl').resolves(); showSuggestionStub = stub(BaseClass.prototype, 'showSuggestion').resolves(); + exitStub = stub(BaseCommand.prototype, 'exit').resolves(); }); afterEach(() => { @@ -72,10 +75,46 @@ describe('GitHub', () => { showLogsStub.restore(); showDeploymentUrlStub.restore(); showSuggestionStub.restore(); + exitStub.restore(); + }); + + describe('Redeploy existing project', () => { + it('should abort github flow for existing project and flag redeploy-last-upload is passed', async () => { + const adapterConstructorOptions = { + config: { + isExistingProject: true, + 'redeploy-last-upload': true + }, + }; + const exitStub = stub(process, 'exit'); + const githubInstance = new GitHub(adapterConstructorOptions); + + await githubInstance.handleExistingProject(); + + expect(exitStub.calledOnceWithExactly(1)).to.be.true; + }); + + it('should run github flow for existing project and flag redeploy-latest is passed ', async () => { + let adapterConstructorOptions = { + config: { + isExistingProject: true, + 'redeploy-latest': true + }, + }; + + new GitHub(adapterConstructorOptions).run() + }); }); - it('should run github flow', async () => { - new GitHub(adapterConstructorInputs).run(); + describe('Deploy new project', () => { + let adapterConstructorOptions = { + config: { + isExistingProject: false + }, + }; + it('should run file upload flow for new project', async () => { + new GitHub(adapterConstructorOptions).run(); + }); }); }); From de16a0fd7145470fc01dcaa08e4a415cf09c937f Mon Sep 17 00:00:00 2001 From: Chhavi-Mandowara Date: Wed, 26 Feb 2025 19:19:57 +0530 Subject: [PATCH 2/5] restructure: restructure fileUpload and Github deployment functionality --- src/adapters/file-upload.ts | 64 +++++++------- src/adapters/github.ts | 1 - src/types/launch.ts | 13 +++ test/unit/adapters/file-upload.test.ts | 117 ++++++++++++++++++------- test/unit/adapters/github.test.ts | 5 +- 5 files changed, 134 insertions(+), 66 deletions(-) diff --git a/src/adapters/file-upload.ts b/src/adapters/file-upload.ts index 138d5b9..7e1b67d 100755 --- a/src/adapters/file-upload.ts +++ b/src/adapters/file-upload.ts @@ -14,10 +14,9 @@ import { print } from '../util'; import BaseClass from './base-class'; import { getFileList } from '../util/fs'; import { createSignedUploadUrlMutation, importProjectMutation } from '../graphql'; +import { SignedUploadUrlData } from '../types/launch'; export default class FileUpload extends BaseClass { - private signedUploadUrlData!: Record; - /** * @method run * @@ -30,31 +29,31 @@ export default class FileUpload extends BaseClass { } else { await this.handleNewProject(); } - + this.prepareLaunchConfig(); await this.showLogs(); this.showDeploymentUrl(); this.showSuggestion(); } - + private async handleExistingProject(): Promise { await this.initApolloClient(); - + let redeployLatest = this.config['redeploy-latest']; if (redeployLatest) { - await this.createSignedUploadUrl(); + const signedUploadUrlData = await this.createSignedUploadUrl(); const { zipName, zipPath } = await this.archive(); - await this.uploadFile(zipName, zipPath); + await this.uploadFile(zipName, zipPath, signedUploadUrlData); } - const { uploadUid } = this.signedUploadUrlData || { uploadUid: undefined }; + const { uploadUid } = { uploadUid: undefined }; await this.createNewDeployment(true, uploadUid); } - + private async handleNewProject(): Promise { - await this.prepareForNewProjectCreation(); - await this.createNewProject(); + const uploadUid = await this.prepareAndUploadNewProjectFile(); + await this.createNewProject(uploadUid); } /** @@ -63,7 +62,7 @@ export default class FileUpload extends BaseClass { * @return {*} {Promise} * @memberof FileUpload */ - async createNewProject(): Promise { + async createNewProject(uploadUid: string): Promise { const { framework, projectName, buildCommand, outputDirectory, environmentName, serverCommand } = this.config; await this.apolloClient .mutate({ @@ -72,7 +71,7 @@ export default class FileUpload extends BaseClass { project: { projectType: 'FILEUPLOAD', name: projectName, - fileUpload: { uploadUid: this.signedUploadUrlData.uploadUid }, + fileUpload: { uploadUid }, environment: { frameworkPreset: framework, outputDirectory: outputDirectory, @@ -96,7 +95,7 @@ export default class FileUpload extends BaseClass { const canRetry = await this.handleNewProjectCreationError(error); if (canRetry) { - return this.createNewProject(); + return this.createNewProject(uploadUid); } }); } @@ -107,7 +106,7 @@ export default class FileUpload extends BaseClass { * @return {*} {Promise} * @memberof FileUpload */ - async prepareForNewProjectCreation(): Promise { + async prepareAndUploadNewProjectFile(): Promise { const { name, framework, @@ -124,9 +123,9 @@ export default class FileUpload extends BaseClass { this.config.deliveryToken = token; // this.fileValidation(); await this.selectOrg(); - await this.createSignedUploadUrl(); + const signedUploadUrlData = await this.createSignedUploadUrl(); const { zipName, zipPath, projectName } = await this.archive(); - await this.uploadFile(zipName, zipPath); + await this.uploadFile(zipName, zipPath, signedUploadUrlData); this.config.projectName = name || (await cliux.inquire({ @@ -187,6 +186,7 @@ export default class FileUpload extends BaseClass { this.config.variableType = variableType as unknown as string; this.config.envVariables = envVariables; await this.handleEnvImportFlow(); + return signedUploadUrlData.uploadUid; } /** @@ -252,19 +252,23 @@ export default class FileUpload extends BaseClass { /** * @method createSignedUploadUrl - create pre signed url for file upload * - * @return {*} {Promise} + * @return {*} {Promise} * @memberof FileUpload */ - async createSignedUploadUrl(): Promise { - this.signedUploadUrlData = await this.apolloClient - .mutate({ mutation: createSignedUploadUrlMutation }) - .then(({ data: { signedUploadUrl } }) => signedUploadUrl) - .catch((error) => { - this.log('Something went wrong. Please try again.', 'warn'); - this.log(error, 'error'); - this.exit(1); - }); - this.config.uploadUid = this.signedUploadUrlData.uploadUid; + async createSignedUploadUrl(): Promise { + try { + const result = await this.apolloClient.mutate({ mutation: createSignedUploadUrlMutation }); + const signedUploadUrlData = result.data.signedUploadUrl; + this.config.uploadUid = signedUploadUrlData.uploadUid; + return signedUploadUrlData; + } catch (error) { + this.log('Something went wrong. Please try again.', 'warn'); + if (error instanceof Error) { + this.log(error.message, 'error'); + } + this.exit(1); + return {} as SignedUploadUrlData; + } } /** @@ -275,8 +279,8 @@ export default class FileUpload extends BaseClass { * @return {*} {Promise} * @memberof FileUpload */ - async uploadFile(fileName: string, filePath: PathLike): Promise { - const { uploadUrl, fields, headers, method } = this.signedUploadUrlData; + async uploadFile(fileName: string, filePath: PathLike, signedUploadUrlData: SignedUploadUrlData): Promise { + const { uploadUrl, fields, headers, method } = signedUploadUrlData; const formData = new FormData(); if (!isEmpty(fields)) { diff --git a/src/adapters/github.ts b/src/adapters/github.ts index 7d71ede..432f7d2 100755 --- a/src/adapters/github.ts +++ b/src/adapters/github.ts @@ -43,7 +43,6 @@ export default class GitHub extends BaseClass { return; } - await this.initApolloClient(); await this.createNewDeployment(); } diff --git a/src/types/launch.ts b/src/types/launch.ts index 3b42cb3..17bfd72 100755 --- a/src/types/launch.ts +++ b/src/types/launch.ts @@ -64,6 +64,18 @@ type GraphqlApiClientInput = { headers?: GraphqlHeaders; }; +type FormField = { + formFieldKey: string; + formFieldValue: string; +}; +type SignedUploadUrlData = { + uploadUrl: string; + fields: FormField[]; + headers: { key: string; value: string }[]; + method: string; + uploadUid: string; +}; + export { LogFn, ExitFn, @@ -73,4 +85,5 @@ export { AdapterConstructorInputs, GraphqlHeaders, GraphqlApiClientInput, + SignedUploadUrlData, }; diff --git a/test/unit/adapters/file-upload.test.ts b/test/unit/adapters/file-upload.test.ts index 99b2482..88890fb 100644 --- a/test/unit/adapters/file-upload.test.ts +++ b/test/unit/adapters/file-upload.test.ts @@ -1,10 +1,13 @@ //@ts-nocheck import { expect } from 'chai'; -import { stub, createSandbox } from 'sinon'; +import { stub, createSandbox , sinon} from 'sinon'; import { cliux } from '@contentstack/cli-utilities'; import fs from 'fs'; import { FileUpload, BaseClass } from '../../../src/adapters'; import { BaseCommand } from '../../../src/base-command'; +import e from 'express'; +import { isNull } from 'util'; +import { log } from 'console'; describe('File Upload', () => { let inquireStub, exitStub, prepareApiClientsStub, prepareConfigStub, getConfigStub; @@ -38,7 +41,7 @@ describe('File Upload', () => { archiveStub, uploadFileStub, createNewDeploymentStub, - prepareForNewProjectCreationStub, + prepareAndUploadNewProjectFile, createNewProjectStub, prepareLaunchConfigStub, showLogsStub, @@ -51,9 +54,8 @@ describe('File Upload', () => { archiveStub = stub(FileUpload.prototype, 'archive').resolves({ zipName: 'test.zip', zipPath: '/path/to/zip' }); uploadFileStub = stub(FileUpload.prototype, 'uploadFile').resolves(); createNewDeploymentStub = stub(FileUpload.prototype, 'createNewDeployment').resolves(); - prepareForNewProjectCreationStub = stub(FileUpload.prototype, 'prepareForNewProjectCreation').resolves(); + prepareAndUploadNewProjectFile = stub(FileUpload.prototype, 'prepareAndUploadNewProjectFile').resolves(); createNewProjectStub = stub(FileUpload.prototype, 'createNewProject').resolves(); - prepareLaunchConfigStub = stub(BaseClass.prototype, 'prepareLaunchConfig').resolves(); showLogsStub = stub(BaseClass.prototype, 'showLogs').resolves(); showDeploymentUrlStub = stub(BaseClass.prototype, 'showDeploymentUrl').resolves(); @@ -66,7 +68,7 @@ describe('File Upload', () => { archiveStub.restore(); uploadFileStub.restore(); createNewDeploymentStub.restore(); - prepareForNewProjectCreationStub.restore(); + prepareAndUploadNewProjectFile.restore(); createNewProjectStub.restore(); prepareLaunchConfigStub.restore(); showLogsStub.restore(); @@ -75,38 +77,64 @@ describe('File Upload', () => { }); describe('Redeploy existing project', () => { - it('should run file upload flow for existing project where flag passed is redeploy-latest', async () => { let adapterConstructorOptions = { - config: { + config: { isExistingProject: true, - currentConfig: { uid: '123244', organizationUid: 'bltxxxxxxxx', }, - 'redeploy-latest': true + currentConfig: { uid: '123244', organizationUid: 'bltxxxxxxxx' }, + 'redeploy-latest': true, }, }; - new FileUpload(adapterConstructorOptions).run(); + await new FileUpload(adapterConstructorOptions).run(); + + expect(initApolloClientStub.calledOnce).to.be.true; + expect(createSignedUploadUrlStub.calledOnce).to.be.true; + expect(archiveStub.calledOnce).to.be.true; + expect(uploadFileStub.calledOnce).to.be.true; + expect(createNewDeploymentStub.calledOnce).to.be.true; + expect(prepareLaunchConfigStub.calledOnce).to.be.true; + expect(showLogsStub.calledOnce).to.be.true; + expect(showDeploymentUrlStub.calledOnce).to.be.true; + expect(showSuggestionStub.calledOnce).to.be.true; }); it('should run file upload flow for existing project where flag passed is redeploy-last-upload', async () => { let adapterConstructorOptions = { - config: { - isExistingProject: true, - currentConfig: { uid: '123244', organizationUid: 'bltxxxxxxxx', }, - 'redeploy-last-upload': true - }, + config: { + isExistingProject: true, + currentConfig: { uid: '123244', organizationUid: 'bltxxxxxxxx' }, + 'redeploy-last-upload': true, + }, }; - new FileUpload(adapterConstructorOptions).run(); + await new FileUpload(adapterConstructorOptions).run(); + + expect(initApolloClientStub.calledOnce).to.be.true; + expect(createSignedUploadUrlStub.calledOnce).to.be.false; + expect(archiveStub.calledOnce).to.be.false; + expect(uploadFileStub.calledOnce).to.be.false; + expect(createNewDeploymentStub.calledOnce).to.be.true; + expect(prepareLaunchConfigStub.calledOnce).to.be.true; + expect(showLogsStub.calledOnce).to.be.true; + expect(showDeploymentUrlStub.calledOnce).to.be.true; + expect(showSuggestionStub.calledOnce).to.be.true; }); }); describe('Deploy new project', () => { let adapterConstructorOptions = { - config: { - isExistingProject: false + config: { + isExistingProject: false, }, }; it('should run file upload flow for new project', async () => { - new FileUpload(adapterConstructorOptions).run(); + await new FileUpload(adapterConstructorOptions).run(); + + expect(prepareAndUploadNewProjectFile.calledOnce).to.be.true; + expect(createNewProjectStub.calledOnce).to.be.true; + expect(prepareLaunchConfigStub.calledOnce).to.be.true; + expect(showLogsStub.calledOnce).to.be.true; + expect(showDeploymentUrlStub.calledOnce).to.be.true; + expect(showSuggestionStub.calledOnce).to.be.true; }); }); }); @@ -162,7 +190,7 @@ describe('File Upload', () => { }); }); - describe('prepareForNewProjectCreation', () => { + describe('prepareAndUploadNewProjectFile', () => { let createSignedUploadUrlStub, archiveStub, uploadFileStub, @@ -183,13 +211,15 @@ describe('File Upload', () => { { name: 'NextJs', value: 'NEXTJS' }, { name: 'Other', value: 'OTHER' }, ], - outputDirectories:"", - supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX'] + outputDirectories: '', + supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX'], }, }; let archiveMockData = { zipName: 'abc.zip', zipPath: 'path/to/zip', projectName: 'test' }; beforeEach(function () { - createSignedUploadUrlStub = stub(FileUpload.prototype, 'createSignedUploadUrl').resolves(); + createSignedUploadUrlStub = stub(FileUpload.prototype, 'createSignedUploadUrl').resolves({ + uploadUid: '123456789', + }); archiveStub = stub(FileUpload.prototype, 'archive').resolves(archiveMockData); uploadFileStub = stub(FileUpload.prototype, 'uploadFile'); uploadFileStub.withArgs(archiveMockData.zipName, archiveMockData.zipPath); @@ -207,7 +237,7 @@ describe('File Upload', () => { }); it('prepare for new project', async function () { - await new FileUpload(adapterConstructorOptions).prepareForNewProjectCreation(); + await new FileUpload(adapterConstructorOptions).prepareAndUploadNewProjectFile(); }); }); @@ -252,36 +282,55 @@ describe('File Upload', () => { }); describe('createSignedUploadUrl', () => { - let sandbox; + let sandbox, logStub, exitStub; beforeEach(() => { sandbox = createSandbox(); + logStub = sandbox.stub(console, 'log'); + exitStub = sandbox.stub(process, 'exit'); }); afterEach(() => { sandbox.restore(); + logStub.restore(); + exitStub.restore(); }); it('should set the signed upload URL and upload UID in the config', async () => { - const signedUploadUrl = 'http://example.com/upload'; + const expectedSignedUploadUrl = { uploadUrl: 'http://example.com/upload', uploadUid: '123456789' }; const apolloClientMock = { - mutate: sandbox.stub().resolves({ data: { signedUploadUrl } }), + mutate: sandbox.stub().resolves({ data: { signedUploadUrl: expectedSignedUploadUrl } }), }; - const logStub = sandbox.stub(console, 'log'); - const exitStub = sandbox.stub(process, 'exit'); const fileUploadInstance = new FileUpload(adapterConstructorInputs); + fileUploadInstance.apolloClient = apolloClientMock; + fileUploadInstance.signedUploadUrlData = expectedSignedUploadUrl.uploadUrl; + fileUploadInstance.config.uploadUid = expectedSignedUploadUrl.uploadUid; + + const signedUploadUrlData = await fileUploadInstance.createSignedUploadUrl(); + + expect(fileUploadInstance.config.uploadUid).to.equal(expectedSignedUploadUrl.uploadUid); + expect(signedUploadUrlData).to.equal(expectedSignedUploadUrl); + }); + + it('should log an error message and exit when the mutation fails', async () => { + const expectedSignedUploadUrl = { uploadUrl: null, uploadUid: null }; + const apolloClientMock = { + mutate: sandbox.stub().rejects(new Error('Mutation failed')), + }; + const fileUploadInstance = new FileUpload(adapterConstructorInputs); + fileUploadInstance.apolloClient = apolloClientMock; fileUploadInstance.log = logStub; fileUploadInstance.exit = exitStub; - fileUploadInstance.signedUploadUrlData = null; - fileUploadInstance.config = { uploadUid: null }; + fileUploadInstance.signedUploadUrlData = expectedSignedUploadUrl.uploadUrl; + fileUploadInstance.config.uploadUid = expectedSignedUploadUrl.uploadUid; await fileUploadInstance.createSignedUploadUrl(); - expect(logStub.called).to.be.false; - expect(exitStub.called).to.be.false; - expect(fileUploadInstance.signedUploadUrlData).to.equal('http://example.com/upload'); + expect(logStub.calledWith('Something went wrong. Please try again.', 'warn')).to.be.true; + expect(logStub.calledWith('Mutation failed', 'error')).to.be.true; + expect(exitStub.calledOnceWithExactly(1)).to.be.true; }); }); }); diff --git a/test/unit/adapters/github.test.ts b/test/unit/adapters/github.test.ts index 4a0832c..1855abb 100644 --- a/test/unit/adapters/github.test.ts +++ b/test/unit/adapters/github.test.ts @@ -102,7 +102,10 @@ describe('GitHub', () => { }, }; - new GitHub(adapterConstructorOptions).run() + await new GitHub(adapterConstructorOptions).run() + + expect(initApolloClientStub.calledOnce).to.be.true; + expect(createNewDeploymentStub.calledOnce).to.be.true; }); }); From d8f117d04bf8527ec954a743abd17471506a6971 Mon Sep 17 00:00:00 2001 From: dhruvparekh12 Date: Thu, 27 Feb 2025 21:22:59 +0530 Subject: [PATCH 3/5] fix: pass uploadUid if available when redeploying file upload projects --- src/adapters/file-upload.ts | 3 ++- test/unit/adapters/file-upload.test.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/adapters/file-upload.ts b/src/adapters/file-upload.ts index 7e1b67d..92d6e31 100755 --- a/src/adapters/file-upload.ts +++ b/src/adapters/file-upload.ts @@ -41,13 +41,14 @@ export default class FileUpload extends BaseClass { let redeployLatest = this.config['redeploy-latest']; + let uploadUid; if (redeployLatest) { const signedUploadUrlData = await this.createSignedUploadUrl(); + uploadUid = signedUploadUrlData.uploadUid; const { zipName, zipPath } = await this.archive(); await this.uploadFile(zipName, zipPath, signedUploadUrlData); } - const { uploadUid } = { uploadUid: undefined }; await this.createNewDeployment(true, uploadUid); } diff --git a/test/unit/adapters/file-upload.test.ts b/test/unit/adapters/file-upload.test.ts index 88890fb..864b6f4 100644 --- a/test/unit/adapters/file-upload.test.ts +++ b/test/unit/adapters/file-upload.test.ts @@ -50,7 +50,7 @@ describe('File Upload', () => { beforeEach(() => { initApolloClientStub = stub(BaseClass.prototype, 'initApolloClient').resolves(); - createSignedUploadUrlStub = stub(FileUpload.prototype, 'createSignedUploadUrl').resolves(); + createSignedUploadUrlStub = stub(FileUpload.prototype, 'createSignedUploadUrl').resolves({ uploadUrl: 'http://example.com/upload', uploadUid: '123456789' }); archiveStub = stub(FileUpload.prototype, 'archive').resolves({ zipName: 'test.zip', zipPath: '/path/to/zip' }); uploadFileStub = stub(FileUpload.prototype, 'uploadFile').resolves(); createNewDeploymentStub = stub(FileUpload.prototype, 'createNewDeployment').resolves(); From 713e2d5fd2f1a84644430c35c74a949675b7b861 Mon Sep 17 00:00:00 2001 From: Chhavi-Mandowara Date: Thu, 27 Feb 2025 22:15:45 +0530 Subject: [PATCH 4/5] test: Add tests for file-upload redeploy without flags scenarios --- src/adapters/file-upload.ts | 48 ++++++++- src/adapters/github.ts | 38 ++++++- src/config/index.ts | 4 +- src/types/launch.ts | 7 ++ test/unit/adapters/file-upload.test.ts | 142 ++++++++++++++++++++++++- test/unit/adapters/github.test.ts | 75 ++++++++++--- 6 files changed, 292 insertions(+), 22 deletions(-) diff --git a/src/adapters/file-upload.ts b/src/adapters/file-upload.ts index 92d6e31..7659c45 100755 --- a/src/adapters/file-upload.ts +++ b/src/adapters/file-upload.ts @@ -14,7 +14,8 @@ import { print } from '../util'; import BaseClass from './base-class'; import { getFileList } from '../util/fs'; import { createSignedUploadUrlMutation, importProjectMutation } from '../graphql'; -import { SignedUploadUrlData } from '../types/launch'; +import { SignedUploadUrlData, FileUploadMethod } from '../types/launch'; +import config from '../config'; export default class FileUpload extends BaseClass { /** @@ -40,6 +41,19 @@ export default class FileUpload extends BaseClass { await this.initApolloClient(); let redeployLatest = this.config['redeploy-latest']; + let redeployLastUpload = this.config['redeploy-last-upload']; + + if (!redeployLatest && !redeployLastUpload) { + await this.confirmRedeployment(); + const latestRedeploymentConfirmed = await this.confirmLatestRedeployment(); + redeployLatest = latestRedeploymentConfirmed; + redeployLastUpload = !latestRedeploymentConfirmed; + } + + if (redeployLastUpload && redeployLatest) { + this.log('redeploy-last-upload and redeploy-latest flags are not supported together.', 'error'); + this.exit(1); + } let uploadUid; if (redeployLatest) { @@ -52,6 +66,38 @@ export default class FileUpload extends BaseClass { await this.createNewDeployment(true, uploadUid); } + private async confirmRedeployment(): Promise { + const redeploy = await cliux.inquire({ + type: 'confirm', + name: 'deployLatestCommit', + message: 'Do you want to redeploy this existing Launch project?', + }); + if (!redeploy) { + this.log('Project redeployment aborted.', 'info'); + this.exit(1); + } + } + + private async confirmLatestRedeployment(): Promise { + const choices = [ + ...map(config.supportedFileUploadMethods, (fileUploadMethod) => ({ + value: fileUploadMethod, + name: `Redeploy with ${fileUploadMethod}`, + })) + ]; + + const selectedFileUploadMethod: FileUploadMethod = await cliux.inquire({ + choices: choices, + type: 'search-list', + name: 'fileUploadMethod', + message: 'Choose a redeploy method to proceed', + }); + if (selectedFileUploadMethod === FileUploadMethod.LastFileUpload) { + return false; + } + return true; + } + private async handleNewProject(): Promise { const uploadUid = await this.prepareAndUploadNewProjectFile(); await this.createNewProject(uploadUid); diff --git a/src/adapters/github.ts b/src/adapters/github.ts index 432f7d2..b86067b 100755 --- a/src/adapters/github.ts +++ b/src/adapters/github.ts @@ -35,7 +35,9 @@ export default class GitHub extends BaseClass { private async handleExistingProject(): Promise { await this.initApolloClient(); + const redeployLastUpload = this.config['redeploy-last-upload']; + const redeployLatest = this.config['redeploy-latest']; if (redeployLastUpload) { this.log('redeploy-last-upload flag is not supported for Github Project.', 'error'); @@ -43,9 +45,25 @@ export default class GitHub extends BaseClass { return; } + if(!redeployLatest && !redeployLastUpload){ + await this.confirmLatestRedeployment(); + } + await this.createNewDeployment(); } + private async confirmLatestRedeployment(): Promise { + const deployLatestCommit = (await ux.inquire({ + type: 'confirm', + name: 'deployLatestCommit', + message: 'Redeploy latest commit?', + })); + if (!deployLatestCommit) { + this.log('Cannot create a new project because its an existing project.', 'info'); + this.exit(1); + } + } + private async handleNewProject(): Promise { // NOTE Step 1: Check is Github connected if (await this.checkGitHubConnected()) { @@ -59,6 +77,22 @@ export default class GitHub extends BaseClass { } await this.createNewProject(); } + // private async handleNewProject(): Promise { + // // NOTE Step 1: Check is Github connected + // // NOTE Step 2: check is the git remote available in the user's repo list + // // NOTE Step 3: check is the user has proper git access + // const isGithubConnected = await this.checkGitHubConnected(); + // console.log('thor1--------------git connected', isGithubConnected); + // const isGitRemoteAvailableAndValid = await this.checkGitRemoteAvailableAndValid(); + // console.log('thor2--------------git remote available', isGitRemoteAvailableAndValid); + // const isUserGitHubAccess = await this.checkUserGitHubAccess(); + // console.log('thor6--------------git user access', isUserGitHubAccess); + + // if(isGithubConnected && isGitRemoteAvailableAndValid && isUserGitHubAccess){ + // await this.prepareForNewProjectCreation(); + // } + // await this.createNewProject(); + // } /** * @method createNewProject - Create new launch project @@ -241,7 +275,7 @@ export default class GitHub extends BaseClass { this.log('GitHub connection not found!', 'warn'); await this.connectToAdapterOnUi(); } - +console.log('thor5--------------git connected'); return this.config.userConnection; } @@ -272,7 +306,7 @@ export default class GitHub extends BaseClass { this.log('Repository not found in the list!', 'error'); this.exit(1); } - +console.log('thor6--------------git remote available'); return true; } diff --git a/src/config/index.ts b/src/config/index.ts index 2bb6438..17617d6 100755 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -40,7 +40,9 @@ const config = { 'Import variables from the local env file', ], variableType: '', - supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX'] + supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX'], + supportedFileUploadMethods: ['last file upload', 'new file'] + }; export default config; diff --git a/src/types/launch.ts b/src/types/launch.ts index 17bfd72..8f1f424 100755 --- a/src/types/launch.ts +++ b/src/types/launch.ts @@ -7,6 +7,11 @@ import { LoggerType } from './utils'; type Providers = 'GitHub' | 'FileUpload'; +enum FileUploadMethod { + LastFileUpload = 'last file upload', + NewFile = 'new file', +} + type LogFn = (message: string | any, logType?: LoggerType | PrintOptions | undefined) => void; type ExitFn = (code?: number | undefined) => void; @@ -38,6 +43,7 @@ type ConfigType = { deployment?: string; environment?: string; provider?: Providers; + fileUploadMethod?: FileUploadMethod; authorization?: string; logsApiBaseUrl: string; projectBasePath: string; @@ -86,4 +92,5 @@ export { GraphqlHeaders, GraphqlApiClientInput, SignedUploadUrlData, + FileUploadMethod, }; diff --git a/test/unit/adapters/file-upload.test.ts b/test/unit/adapters/file-upload.test.ts index 864b6f4..1ff3e18 100644 --- a/test/unit/adapters/file-upload.test.ts +++ b/test/unit/adapters/file-upload.test.ts @@ -5,9 +5,9 @@ import { cliux } from '@contentstack/cli-utilities'; import fs from 'fs'; import { FileUpload, BaseClass } from '../../../src/adapters'; import { BaseCommand } from '../../../src/base-command'; -import e from 'express'; import { isNull } from 'util'; import { log } from 'console'; +import { FileUploadMethod } from '../../../src/types/launch'; describe('File Upload', () => { let inquireStub, exitStub, prepareApiClientsStub, prepareConfigStub, getConfigStub; @@ -77,7 +77,23 @@ describe('File Upload', () => { }); describe('Redeploy existing project', () => { - it('should run file upload flow for existing project where flag passed is redeploy-latest', async () => { + let sandbox; + let processExitStub; + + beforeEach(() => { + sandbox = createSandbox(); + + processExitStub = sandbox.stub(process, 'exit').callsFake((code) => { + throw new Error(code); + }); + + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should run file upload flow successfully for existing project where flag passed is redeploy-latest', async () => { let adapterConstructorOptions = { config: { isExistingProject: true, @@ -98,7 +114,7 @@ describe('File Upload', () => { expect(showSuggestionStub.calledOnce).to.be.true; }); - it('should run file upload flow for existing project where flag passed is redeploy-last-upload', async () => { + it('should run file upload flow successfully for existing project where flag passed is redeploy-last-upload', async () => { let adapterConstructorOptions = { config: { isExistingProject: true, @@ -118,8 +134,126 @@ describe('File Upload', () => { expect(showDeploymentUrlStub.calledOnce).to.be.true; expect(showSuggestionStub.calledOnce).to.be.true; }); - }); + it('should exit with an error message when both --redeploy-last-upload and --redeploy-latest flags are passed', async () => { + let adapterConstructorOptions = { + config: { + isExistingProject: true, + currentConfig: { uid: '123244', organizationUid: 'bltxxxxxxxx' }, + 'redeploy-last-upload': true, + 'redeploy-latest': true, + }, + }; + let exitStatusCode; + + try { + await new FileUpload(adapterConstructorOptions).run(); + } catch (err) { + exitStatusCode = err.message; + } + + expect(processExitStub.calledOnceWithExactly(1)).to.be.true; + expect(exitStatusCode).to.equal('1'); + expect(initApolloClientStub.calledOnce).to.be.true; + expect(createSignedUploadUrlStub.calledOnce).to.be.false; + expect(archiveStub.calledOnce).to.be.false; + expect(uploadFileStub.calledOnce).to.be.false; + expect(createNewDeploymentStub.calledOnce).to.be.false; + expect(prepareLaunchConfigStub.calledOnce).to.be.false; + expect(showLogsStub.calledOnce).to.be.false; + expect(showDeploymentUrlStub.calledOnce).to.be.false; + expect(showSuggestionStub.calledOnce).to.be.false; + }); + + it('should show prompt and successfully redeploy with "new file" if the option to redeploy with new file is selected, when --redeploy-latest and --redeploy-last-upload flags are not passed', async () => { + let adapterConstructorOptions = { + config: { + isExistingProject: true, + currentConfig: { uid: '123244', organizationUid: 'bltxxxxxxxx' }, + }, + }; + inquireStub.withArgs({ + type: 'confirm', + name: 'deployLatestCommit', + message: 'Do you want to redeploy this existing Launch project?', + }).resolves(true); + inquireStub.resolves(FileUploadMethod.NewFile); + + await new FileUpload(adapterConstructorOptions).run(); + + expect(initApolloClientStub.calledOnce).to.be.true; + expect(createSignedUploadUrlStub.calledOnce).to.be.true; + expect(archiveStub.calledOnce).to.be.true; + expect(uploadFileStub.calledOnce).to.be.true; + expect(createNewDeploymentStub.calledOnce).to.be.true; + expect(prepareLaunchConfigStub.calledOnce).to.be.true; + expect(showLogsStub.calledOnce).to.be.true; + expect(showDeploymentUrlStub.calledOnce).to.be.true; + expect(showSuggestionStub.calledOnce).to.be.true; + }); + + it('should show prompt and successfully redeploy with "last file upload" if the option to redeploy with last file upload is selected, when --redeploy-latest and --redeploy-last-upload flags are not passed', async () => { + let adapterConstructorOptions = { + config: { + isExistingProject: true, + currentConfig: { uid: '123244', organizationUid: 'bltxxxxxxxx' }, + }, + }; + inquireStub.withArgs({ + type: 'confirm', + name: 'deployLatestCommit', + message: 'Do you want to redeploy this existing Launch project?', + }).resolves(true); + inquireStub.resolves(FileUploadMethod.LastFileUpload); + + await new FileUpload(adapterConstructorOptions).run(); + + expect(initApolloClientStub.calledOnce).to.be.true; + expect(createSignedUploadUrlStub.calledOnce).to.be.false; + expect(archiveStub.calledOnce).to.be.false; + expect(uploadFileStub.calledOnce).to.be.false; + expect(createNewDeploymentStub.calledOnce).to.be.true; + expect(prepareLaunchConfigStub.calledOnce).to.be.true; + expect(showLogsStub.calledOnce).to.be.true; + expect(showDeploymentUrlStub.calledOnce).to.be.true; + expect(showSuggestionStub.calledOnce).to.be.true; + }); + + it('should exit if "No" is selected for prompt to redeploy, when --redeploy-latest and --redeploy-last-upload flags are not passed', async() => { + let adapterConstructorOptions = { + config: { + isExistingProject: true, + currentConfig: { uid: '123244', organizationUid: 'bltxxxxxxxx' }, + }, + }; + inquireStub.withArgs({ + type: 'confirm', + name: 'deployLatestCommit', + message: 'Do you want to redeploy this existing Launch project?', + }).resolves(false); + let exitStatusCode; + + try { + await new FileUpload(adapterConstructorOptions).run(); + } catch (err) { + exitStatusCode = err.message; + } + + expect(processExitStub.calledOnceWithExactly(1)).to.be.true; + expect(exitStatusCode).to.equal('1'); + expect(initApolloClientStub.calledOnce).to.be.true; + expect(createSignedUploadUrlStub.calledOnce).to.be.false; + expect(archiveStub.calledOnce).to.be.false; + expect(uploadFileStub.calledOnce).to.be.false; + expect(createNewDeploymentStub.calledOnce).to.be.false; + expect(prepareLaunchConfigStub.calledOnce).to.be.false; + expect(showLogsStub.calledOnce).to.be.false; + expect(showDeploymentUrlStub.calledOnce).to.be.false; + expect(showSuggestionStub.calledOnce).to.be.false; + }); + + }); + describe('Deploy new project', () => { let adapterConstructorOptions = { config: { diff --git a/test/unit/adapters/github.test.ts b/test/unit/adapters/github.test.ts index 1855abb..34b71de 100644 --- a/test/unit/adapters/github.test.ts +++ b/test/unit/adapters/github.test.ts @@ -81,28 +81,28 @@ describe('GitHub', () => { describe('Redeploy existing project', () => { it('should abort github flow for existing project and flag redeploy-last-upload is passed', async () => { const adapterConstructorOptions = { - config: { + config: { isExistingProject: true, - 'redeploy-last-upload': true + 'redeploy-last-upload': true, }, }; const exitStub = stub(process, 'exit'); const githubInstance = new GitHub(adapterConstructorOptions); - + await githubInstance.handleExistingProject(); - + expect(exitStub.calledOnceWithExactly(1)).to.be.true; }); - + it('should run github flow for existing project and flag redeploy-latest is passed ', async () => { let adapterConstructorOptions = { - config: { + config: { isExistingProject: true, - 'redeploy-latest': true + 'redeploy-latest': true, }, }; - await new GitHub(adapterConstructorOptions).run() + await new GitHub(adapterConstructorOptions).run(); expect(initApolloClientStub.calledOnce).to.be.true; expect(createNewDeploymentStub.calledOnce).to.be.true; @@ -111,12 +111,59 @@ describe('GitHub', () => { describe('Deploy new project', () => { let adapterConstructorOptions = { - config: { - isExistingProject: false + config: { + isExistingProject: false, }, }; - it('should run file upload flow for new project', async () => { - new GitHub(adapterConstructorOptions).run(); + it('should create new project if GitHub is not connected', async () => { + checkGitHubConnectedStub.resolves(false); + + await new GitHub(adapterConstructorOptions).run(); + + expect(checkGitHubConnectedStub.calledOnce).to.be.true; + expect(checkGitRemoteAvailableAndValidStub.called).to.be.false; + expect(checkUserGitHubAccessStub.called).to.be.false; + expect(prepareForNewProjectCreationStub.called).to.be.false; + expect(createNewProjectStub.calledOnce).to.be.true; + }); + + it('should create new project if git remote is not available', async () => { + checkGitRemoteAvailableAndValidStub.resolves(false); + + await new GitHub(adapterConstructorOptions).run(); + + expect(checkGitHubConnectedStub.calledOnce).to.be.true; + expect(checkGitRemoteAvailableAndValidStub.calledOnce).to.be.true; + expect(checkUserGitHubAccessStub.called).to.be.false; + expect(prepareForNewProjectCreationStub.called).to.be.false; + expect(createNewProjectStub.calledOnce).to.be.true; + }); + it('should not proceed if user does not have GitHub access', async () => { + checkGitHubConnectedStub.resolves(true); + checkGitRemoteAvailableAndValidStub.resolves(true); + checkUserGitHubAccessStub.resolves(false); + + await new GitHub(adapterConstructorOptions).run(); + + expect(checkGitHubConnectedStub.calledOnce).to.be.true; + expect(checkGitRemoteAvailableAndValidStub.calledOnce).to.be.true; + expect(checkUserGitHubAccessStub.calledOnce).to.be.true; + expect(prepareForNewProjectCreationStub.called).to.be.false; + expect(createNewProjectStub.calledOnce).to.be.true; + }); + + it('should proceed to prepare for new project creation if user has GitHub access', async () => { + checkGitHubConnectedStub.resolves(true); + checkGitRemoteAvailableAndValidStub.resolves(true); + checkUserGitHubAccessStub.resolves(true); + + await new GitHub(adapterConstructorOptions).handleNewProject(); + + expect(checkGitHubConnectedStub.calledOnce).to.be.true; + expect(checkGitRemoteAvailableAndValidStub.calledOnce).to.be.true; + expect(checkUserGitHubAccessStub.calledOnce).to.be.true; + expect(prepareForNewProjectCreationStub.calledOnce).to.be.true; + expect(createNewProjectStub.calledOnce).to.be.true; }); }); }); @@ -205,8 +252,8 @@ describe('GitHub', () => { { name: 'Other', value: 'OTHER' }, ], repository: { fullName: 'Gatsby Starter' }, - outputDirectories:"", - supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX'] + outputDirectories: '', + supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX'], }, }; beforeEach(function () { From 06920855df024fd2bca863e87e802d2de600a269 Mon Sep 17 00:00:00 2001 From: dhruvparekh12 Date: Fri, 28 Feb 2025 05:01:38 +0530 Subject: [PATCH 5/5] test: Add tests for github redeploy without flags scenarios --- src/adapters/github.ts | 25 +----- test/unit/adapters/file-upload.test.ts | 15 +++- test/unit/adapters/github.test.ts | 101 ++++++++++++++++++++++--- 3 files changed, 107 insertions(+), 34 deletions(-) diff --git a/src/adapters/github.ts b/src/adapters/github.ts index b86067b..0a93cf8 100755 --- a/src/adapters/github.ts +++ b/src/adapters/github.ts @@ -42,7 +42,6 @@ export default class GitHub extends BaseClass { if (redeployLastUpload) { this.log('redeploy-last-upload flag is not supported for Github Project.', 'error'); this.exit(1); - return; } if(!redeployLatest && !redeployLastUpload){ @@ -53,11 +52,11 @@ export default class GitHub extends BaseClass { } private async confirmLatestRedeployment(): Promise { - const deployLatestCommit = (await ux.inquire({ + const deployLatestCommit = await ux.inquire({ type: 'confirm', name: 'deployLatestCommit', message: 'Redeploy latest commit?', - })); + }); if (!deployLatestCommit) { this.log('Cannot create a new project because its an existing project.', 'info'); this.exit(1); @@ -77,22 +76,6 @@ export default class GitHub extends BaseClass { } await this.createNewProject(); } - // private async handleNewProject(): Promise { - // // NOTE Step 1: Check is Github connected - // // NOTE Step 2: check is the git remote available in the user's repo list - // // NOTE Step 3: check is the user has proper git access - // const isGithubConnected = await this.checkGitHubConnected(); - // console.log('thor1--------------git connected', isGithubConnected); - // const isGitRemoteAvailableAndValid = await this.checkGitRemoteAvailableAndValid(); - // console.log('thor2--------------git remote available', isGitRemoteAvailableAndValid); - // const isUserGitHubAccess = await this.checkUserGitHubAccess(); - // console.log('thor6--------------git user access', isUserGitHubAccess); - - // if(isGithubConnected && isGitRemoteAvailableAndValid && isUserGitHubAccess){ - // await this.prepareForNewProjectCreation(); - // } - // await this.createNewProject(); - // } /** * @method createNewProject - Create new launch project @@ -275,7 +258,7 @@ export default class GitHub extends BaseClass { this.log('GitHub connection not found!', 'warn'); await this.connectToAdapterOnUi(); } -console.log('thor5--------------git connected'); + return this.config.userConnection; } @@ -306,7 +289,7 @@ console.log('thor5--------------git connected'); this.log('Repository not found in the list!', 'error'); this.exit(1); } -console.log('thor6--------------git remote available'); + return true; } diff --git a/test/unit/adapters/file-upload.test.ts b/test/unit/adapters/file-upload.test.ts index 1ff3e18..4358fba 100644 --- a/test/unit/adapters/file-upload.test.ts +++ b/test/unit/adapters/file-upload.test.ts @@ -1,6 +1,6 @@ //@ts-nocheck import { expect } from 'chai'; -import { stub, createSandbox , sinon} from 'sinon'; +import { stub, createSandbox , sinon } from 'sinon'; import { cliux } from '@contentstack/cli-utilities'; import fs from 'fs'; import { FileUpload, BaseClass } from '../../../src/adapters'; @@ -47,11 +47,14 @@ describe('File Upload', () => { showLogsStub, showDeploymentUrlStub, showSuggestionStub; + const signedUploadUrlData = { uploadUrl: 'http://example.com/upload', uploadUid: '123456789' }; + const zipName = 'test.zip'; + const zipPath = '/path/to/zip'; beforeEach(() => { initApolloClientStub = stub(BaseClass.prototype, 'initApolloClient').resolves(); - createSignedUploadUrlStub = stub(FileUpload.prototype, 'createSignedUploadUrl').resolves({ uploadUrl: 'http://example.com/upload', uploadUid: '123456789' }); - archiveStub = stub(FileUpload.prototype, 'archive').resolves({ zipName: 'test.zip', zipPath: '/path/to/zip' }); + createSignedUploadUrlStub = stub(FileUpload.prototype, 'createSignedUploadUrl').resolves(signedUploadUrlData); + archiveStub = stub(FileUpload.prototype, 'archive').resolves({ zipName, zipPath }); uploadFileStub = stub(FileUpload.prototype, 'uploadFile').resolves(); createNewDeploymentStub = stub(FileUpload.prototype, 'createNewDeployment').resolves(); prepareAndUploadNewProjectFile = stub(FileUpload.prototype, 'prepareAndUploadNewProjectFile').resolves(); @@ -107,7 +110,9 @@ describe('File Upload', () => { expect(createSignedUploadUrlStub.calledOnce).to.be.true; expect(archiveStub.calledOnce).to.be.true; expect(uploadFileStub.calledOnce).to.be.true; + expect(uploadFileStub.args[0]).to.deep.equal([zipName, zipPath, signedUploadUrlData]); expect(createNewDeploymentStub.calledOnce).to.be.true; + expect(createNewDeploymentStub.args[0]).to.deep.equal([true, signedUploadUrlData.uploadUid]); expect(prepareLaunchConfigStub.calledOnce).to.be.true; expect(showLogsStub.calledOnce).to.be.true; expect(showDeploymentUrlStub.calledOnce).to.be.true; @@ -129,6 +134,7 @@ describe('File Upload', () => { expect(archiveStub.calledOnce).to.be.false; expect(uploadFileStub.calledOnce).to.be.false; expect(createNewDeploymentStub.calledOnce).to.be.true; + expect(createNewDeploymentStub.args[0]).to.deep.equal([true, undefined]); expect(prepareLaunchConfigStub.calledOnce).to.be.true; expect(showLogsStub.calledOnce).to.be.true; expect(showDeploymentUrlStub.calledOnce).to.be.true; @@ -185,7 +191,9 @@ describe('File Upload', () => { expect(createSignedUploadUrlStub.calledOnce).to.be.true; expect(archiveStub.calledOnce).to.be.true; expect(uploadFileStub.calledOnce).to.be.true; + expect(uploadFileStub.args[0]).to.deep.equal([zipName, zipPath, signedUploadUrlData]); expect(createNewDeploymentStub.calledOnce).to.be.true; + expect(createNewDeploymentStub.args[0]).to.deep.equal([true, signedUploadUrlData.uploadUid]); expect(prepareLaunchConfigStub.calledOnce).to.be.true; expect(showLogsStub.calledOnce).to.be.true; expect(showDeploymentUrlStub.calledOnce).to.be.true; @@ -213,6 +221,7 @@ describe('File Upload', () => { expect(archiveStub.calledOnce).to.be.false; expect(uploadFileStub.calledOnce).to.be.false; expect(createNewDeploymentStub.calledOnce).to.be.true; + expect(createNewDeploymentStub.args[0]).to.deep.equal([true, undefined]); expect(prepareLaunchConfigStub.calledOnce).to.be.true; expect(showLogsStub.calledOnce).to.be.true; expect(showDeploymentUrlStub.calledOnce).to.be.true; diff --git a/test/unit/adapters/github.test.ts b/test/unit/adapters/github.test.ts index 34b71de..dd53098 100644 --- a/test/unit/adapters/github.test.ts +++ b/test/unit/adapters/github.test.ts @@ -5,7 +5,6 @@ import { cliux } from '@contentstack/cli-utilities'; import { githubAdapterMockData } from '../mock/index'; import { GitHub, BaseClass } from '../../../src/adapters'; import { BaseCommand } from '../../../src/base-command'; -import { exit } from 'process'; import fs from 'fs'; describe('GitHub', () => { @@ -79,34 +78,116 @@ describe('GitHub', () => { }); describe('Redeploy existing project', () => { - it('should abort github flow for existing project and flag redeploy-last-upload is passed', async () => { + let sandbox; + let processExitStub; + + beforeEach(() => { + sandbox = createSandbox(); + + processExitStub = sandbox.stub(process, 'exit').callsFake((code) => { + throw new Error(code); + }); + + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should successfully run github flow for existing project when flag redeploy-latest is passed ', async () => { + let adapterConstructorOptions = { + config: { + isExistingProject: true, + 'redeploy-latest': true, + }, + }; + + await new GitHub(adapterConstructorOptions).run(); + + expect(initApolloClientStub.calledOnce).to.be.true; + expect(createNewDeploymentStub.calledOnce).to.be.true; + expect(prepareLaunchConfigStub.calledOnce).to.be.true; + expect(showLogsStub.calledOnce).to.be.true; + expect(showDeploymentUrlStub.calledOnce).to.be.true; + expect(showSuggestionStub.calledOnce).to.be.true; + }); + + it('should abort github flow for existing project when flag redeploy-last-upload is passed', async () => { const adapterConstructorOptions = { config: { isExistingProject: true, 'redeploy-last-upload': true, }, }; - const exitStub = stub(process, 'exit'); - const githubInstance = new GitHub(adapterConstructorOptions); + let exitStatusCode; - await githubInstance.handleExistingProject(); + try { + await new GitHub(adapterConstructorOptions).run(); + } catch (err) { + exitStatusCode = err.message; + } - expect(exitStub.calledOnceWithExactly(1)).to.be.true; + expect(processExitStub.calledOnceWithExactly(1)).to.be.true; + expect(exitStatusCode).to.equal('1'); + expect(initApolloClientStub.calledOnce).to.be.true; + expect(createNewDeploymentStub.calledOnce).to.be.false; + expect(prepareLaunchConfigStub.calledOnce).to.be.false; + expect(showLogsStub.calledOnce).to.be.false; + expect(showDeploymentUrlStub.calledOnce).to.be.false; + expect(showSuggestionStub.calledOnce).to.be.false; }); - it('should run github flow for existing project and flag redeploy-latest is passed ', async () => { - let adapterConstructorOptions = { + it('should show prompt and successfully redeploy with "latest commit" if the option to redeploy is selected, when --redeploy-latest flag is not passed', async() => { + const adapterConstructorOptions = { config: { - isExistingProject: true, - 'redeploy-latest': true, + isExistingProject: true }, }; + inquireStub.withArgs({ + type: 'confirm', + name: 'deployLatestCommit', + message: 'Redeploy latest commit?', + }).resolves(true); await new GitHub(adapterConstructorOptions).run(); expect(initApolloClientStub.calledOnce).to.be.true; expect(createNewDeploymentStub.calledOnce).to.be.true; + expect(prepareLaunchConfigStub.calledOnce).to.be.true; + expect(showLogsStub.calledOnce).to.be.true; + expect(showDeploymentUrlStub.calledOnce).to.be.true; + expect(showSuggestionStub.calledOnce).to.be.true; }); + + it('should exit if "No" is selected for prompt to redeploy, when --redeploy-latest flag is not passed', async() => { + const adapterConstructorOptions = { + config: { + isExistingProject: true + }, + }; + inquireStub.withArgs({ + type: 'confirm', + name: 'deployLatestCommit', + message: 'Redeploy latest commit?', + }).resolves(false); + let exitStatusCode; + + try { + await new GitHub(adapterConstructorOptions).run(); + } catch (err) { + exitStatusCode = err.message; + } + + expect(processExitStub.calledOnceWithExactly(1)).to.be.true; + expect(exitStatusCode).to.equal('1'); + expect(initApolloClientStub.calledOnce).to.be.true; + expect(createNewDeploymentStub.calledOnce).to.be.false; + expect(prepareLaunchConfigStub.calledOnce).to.be.false; + expect(showLogsStub.calledOnce).to.be.false; + expect(showDeploymentUrlStub.calledOnce).to.be.false; + expect(showSuggestionStub.calledOnce).to.be.false; + }); + }); describe('Deploy new project', () => {