diff --git a/src/actions/index.ts b/src/actions/index.ts index 66a5cd6..657bc55 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -35,3 +35,4 @@ export * from './submitCatalog'; export * from "./pipelineDeploy"; export * from "./updateOrgSettings"; export * from "./setGovernanceConfig"; +export * from "./syncSolution"; diff --git a/src/actions/syncSolution.ts b/src/actions/syncSolution.ts new file mode 100644 index 0000000..bf85cb9 --- /dev/null +++ b/src/actions/syncSolution.ts @@ -0,0 +1,52 @@ +import { RunnerParameters } from "../Parameters"; +import { CommonActionParameters, HostParameterEntry, IHostAbstractions } from "../host/IHostAbstractions"; +import { authenticateEnvironment, clearAuthentication } from "../pac/auth/authenticate"; +import { AuthCredentials } from "../pac/auth/authParameters"; +import { SolutionPackUnpackParameters } from "./actionParameters"; +import { InputValidator } from "../host/InputValidator"; +import createPacRunner from "../pac/createPacRunner"; + +export type SyncSolutionParameters = Pick & CommonActionParameters & { + credentials: AuthCredentials; + environmentUrl: string; + include?: HostParameterEntry; + solutionType?: HostParameterEntry; + solutionFolder: HostParameterEntry; + async?: HostParameterEntry; + maxAsyncWaitTimeInMin?: HostParameterEntry; +}; + +export async function syncSolution(parameters: SyncSolutionParameters, runnerParameters: RunnerParameters, host: IHostAbstractions): Promise { + + const logger = runnerParameters.logger; + const pac = createPacRunner(runnerParameters); + + try { + const authenticateResult = await authenticateEnvironment(pac, parameters.credentials, parameters.environmentUrl, logger); + logger.log("Authentication Result: " + authenticateResult); + + const pacArgs = ["solution", "sync"]; + const validator = new InputValidator(host); + + validator.pushInput(pacArgs, "--solution-folder", parameters.solutionFolder); + validator.pushInput(pacArgs, "--include", parameters.include); + validator.pushInput(pacArgs, "--packagetype", parameters.solutionType); + validator.pushInput(pacArgs, "--map", parameters.mapFile); + validator.pushInput(pacArgs, "--localize", parameters.localize); + validator.pushInput(pacArgs, "--async", parameters.async); + validator.pushInput(pacArgs, "--max-async-wait-time", parameters.maxAsyncWaitTimeInMin); + validator.pushCommon(pacArgs, parameters); + + logger.log("Calling pac cli inputs: " + pacArgs.join(" ")); + const pacResult = await pac(...pacArgs); + logger.log("SyncSolution Action Result: " + pacResult); + + } catch (error) { + // some sort of error + logger.error(`syncSolution failed: ${error instanceof Error ? error.message : error}`); + throw error; + } finally { + const clearAuthResult = await clearAuthentication(pac); + logger.log(`Cleared authentication: ${clearAuthResult}`); + } +} \ No newline at end of file diff --git a/test/actions/mock/mockHost.ts b/test/actions/mock/mockHost.ts index b56a598..434f06e 100644 --- a/test/actions/mock/mockHost.ts +++ b/test/actions/mock/mockHost.ts @@ -15,6 +15,7 @@ export class mockHost implements IHostAbstractions { solutionName = 'Mock-Solution'; relativeSolutionPath = './ContosoSolution.zip'; absoluteSolutionPath = (os.platform() === "win32") ? 'D:\\Test\\working\\ContosoSolution.zip' : '/Test/working/ContosoSolution.zip'; + solutionFolder = (os.platform() === "win32") ? 'D:\\Test\\working\\folder' : '/Test/working/folder'; deploymentSettingsFile = '/Test/deploymentSettings.txt'; logDataFile = 'c:\\samplelogdata' maxAsyncWaitTime = '120'; @@ -71,6 +72,7 @@ export class mockHost implements IHostAbstractions { switch (entry.name) { case 'SolutionInputFile': case 'SolutionOutputFile': return this.relativeSolutionPath; + case 'SolutionFolder': return this.solutionFolder; case 'SolutionName': return this.solutionName; case 'MaxAsyncWaitTime': return this.maxAsyncWaitTime; case 'DeploymentSettingsFile': return this.deploymentSettingsFile; @@ -146,5 +148,5 @@ export class MockArtifactStore implements IArtifactStore { public upload(artifactName: string, files: string[]): Promise { console.log(`MockArtifactStore: name = ${artifactName}, files = ${files.join(', ')}`); return Promise.resolve(); - } + } } diff --git a/test/actions/syncSolution.test.ts b/test/actions/syncSolution.test.ts new file mode 100644 index 0000000..9aff0a8 --- /dev/null +++ b/test/actions/syncSolution.test.ts @@ -0,0 +1,65 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import rewiremock from "../rewiremock"; +import * as sinonChai from "sinon-chai"; +import * as chaiAsPromised from "chai-as-promised"; +import { should, use } from "chai"; +import { ClientCredentials, RunnerParameters } from "../../src"; +import { createDefaultMockRunnerParameters, createMockClientCredentials, mockEnvironmentUrl } from "./mock/mockData"; +import { SyncSolutionParameters } from "src/actions"; +import { mockHost } from "./mock/mockHost"; +import Sinon = require("sinon"); +should(); +use(sinonChai); +use(chaiAsPromised); + +describe("action: sync solution", () => { + let pacStub: Sinon.SinonStub; + let authenticateEnvironmentStub: Sinon.SinonStub; + let clearAuthenticationStub: Sinon.SinonStub; + const host = new mockHost(); + const mockClientCredentials: ClientCredentials = createMockClientCredentials(); + const environmentUrl: string = mockEnvironmentUrl; + + beforeEach(() => { + pacStub = Sinon.stub(); + authenticateEnvironmentStub = Sinon.stub(); + clearAuthenticationStub = Sinon.stub(); + }) + afterEach(() => Sinon.restore()) + + async function runActionWithMocks(syncSolutionParameters: SyncSolutionParameters) { + const runnerParameters: RunnerParameters = createDefaultMockRunnerParameters(); + + const mockedActionModule = await rewiremock.around(() => import("../../src/actions/syncSolution"), + (mock) => { + mock(() => import("../../src/pac/createPacRunner")).withDefault(() => pacStub); + mock(() => import("../../src/pac/auth/authenticate")).with( + { + authenticateEnvironment: authenticateEnvironmentStub, + clearAuthentication: clearAuthenticationStub + }); + }); + + authenticateEnvironmentStub.returns("Authentication successfully created."); + clearAuthenticationStub.returns("Authentication profiles and token cache removed"); + pacStub.returns(""); + await mockedActionModule.syncSolution(syncSolutionParameters, runnerParameters, host); + } + + it("with required params, calls pac runner with correct args", async () => { + const syncSolutionParameters: SyncSolutionParameters = { + credentials: mockClientCredentials, + environmentUrl: environmentUrl, + solutionFolder: { name: "SolutionFolder", required: true }, + logToConsole: false + } + + + await runActionWithMocks(syncSolutionParameters); + + authenticateEnvironmentStub.should.have.been.calledOnceWith(pacStub, mockClientCredentials, environmentUrl); + pacStub.should.have.been.calledOnceWith("solution", "sync", + "--solution-folder", host.solutionFolder); + clearAuthenticationStub.should.have.been.calledOnceWith(pacStub); + }); +}); \ No newline at end of file