Skip to content

Commit

Permalink
Fixes 'pa app export' without packageDisplayName. Closes #6215
Browse files Browse the repository at this point in the history
  • Loading branch information
milanholemans committed Aug 6, 2024
1 parent bf59841 commit cc8ce11
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 23 deletions.
67 changes: 48 additions & 19 deletions src/m365/pa/commands/app/app-export.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ describe(commands.APP_EXPORT, () => {
let log: string[];
let logger: Logger;
let commandInfo: CommandInfo;
let loggerLogToStderrSpy: sinon.SinonSpy;

const actualFilename = 'Power App.zip';
const packageDisplayName = 'Power App';
const packageDescription = 'Power App Description';
const packageCreatedBy = 'John Doe';
Expand Down Expand Up @@ -142,7 +140,7 @@ describe(commands.APP_EXPORT, () => {
}
};

const fileBlobResponse = {
const fileBlobResponse: any = {
type: 'Buffer',
data: [80, 75, 3, 4, 20, 0, 0, 0, 8, 0, 237, 115, 99, 86, 250, 76, 155, 216, 248, 3, 0, 0, 7, 8, 0, 0, 71, 0, 0, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 46, 80, 111, 119, 101, 114, 65, 112, 112, 115, 47, 97, 112, 112, 115, 47, 49, 56, 48, 50, 54, 54, 51, 51, 48]
};
Expand Down Expand Up @@ -171,7 +169,6 @@ describe(commands.APP_EXPORT, () => {
log.push(msg);
}
};
loggerLogToStderrSpy = sinon.spy(logger, 'logToStderr');
});

afterEach(() => {
Expand All @@ -195,12 +192,10 @@ describe(commands.APP_EXPORT, () => {
assert.notStrictEqual(command.description, null);
});

it('exports the specified App', async () => {
let index = 0;
sinon.stub(request, 'get').callsFake(async (opts) => {
it('exports the specified app correctly', async () => {
const getStub = sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === exportPackageResponse.headers.location) {
if (index === 0) {
index = 1;
if (getStub.calledOnce) {
return locationRunningResponse;
}
else {
Expand All @@ -226,17 +221,16 @@ describe(commands.APP_EXPORT, () => {

throw 'invalid request';
});
sinon.stub(fs, 'writeFileSync').returns();
const writeFileStub = sinon.stub(fs, 'writeFileSync').returns();

await assert.doesNotReject(command.action(logger, { options: { name: appId, environmentName: environmentName, packageDisplayName: packageDisplayName } }));
await command.action(logger, { options: { name: appId, environmentName: environmentName } });
assert(writeFileStub.calledOnceWithExactly(`./${appId}.zip`, fileBlobResponse, 'binary'));
});

it('exports the specified App (debug)', async () => {
let index = 0;
sinon.stub(request, 'get').callsFake(async (opts) => {
it('exports the specified app correctly with packageDisplayName', async () => {
const getStub = sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === exportPackageResponse.headers.location) {
if (index === 0) {
index = 1;
if (getStub.calledOnce) {
return locationRunningResponse;
}
else {
Expand All @@ -262,10 +256,45 @@ describe(commands.APP_EXPORT, () => {

throw 'invalid request';
});
sinon.stub(fs, 'writeFileSync').returns();
const writeFileStub = sinon.stub(fs, 'writeFileSync').returns();

await command.action(logger, { options: { name: appId, environmentName: environmentName, packageDisplayName: packageDisplayName } });
assert(writeFileStub.calledOnceWithExactly(`./${packageDisplayName}.zip`, fileBlobResponse, 'binary'));
});

it('exports the specified App correctly with all options', async () => {
const getStub = sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === exportPackageResponse.headers.location) {
if (getStub.calledOnce) {
return locationRunningResponse;
}
else {
return locationSuccessResponse;
}
}

if (opts.url === locationSuccessResponse.properties.packageLink.value) {
return fileBlobResponse;
}

throw 'invalid request';
});

sinon.stub(request, 'post').callsFake(async (opts) => {
if (opts.url === `https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/environments/${environmentName}/listPackageResources?api-version=2016-11-01`) {
return listPackageResourcesResponse;
}

if (opts.url === `https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/environments/${environmentName}/exportPackage?api-version=2016-11-01`) {
return exportPackageResponse;
}

throw 'invalid request';
});
const writeFileStub = sinon.stub(fs, 'writeFileSync').returns();

await command.action(logger, { options: { verbose: true, name: appId, environmentName: environmentName, packageDisplayName: packageDisplayName, packageDescription: packageDescription, packageCreatedBy: packageCreatedBy, packageSourceEnvironment: packageSourceEnvironment, path: path } });
assert(loggerLogToStderrSpy.calledWith(`File saved to path '${path}/${actualFilename}'`));
assert(writeFileStub.calledOnceWithExactly(`${path}/${packageDisplayName}.zip`, fileBlobResponse, 'binary'));
});

it('fails validation if the name is not a GUID', async () => {
Expand Down Expand Up @@ -294,7 +323,7 @@ describe(commands.APP_EXPORT, () => {

sinon.stub(request, 'post').rejects(error);

await assert.rejects(command.action(logger, { options: { name: appId, environmentName: environmentName, packageDisplayName: packageDisplayName } } as any),
await assert.rejects(command.action(logger, { options: { name: appId, environmentName: environmentName, packageDisplayName: packageDisplayName } }),
new CommandError(error.error.message));
});
});
14 changes: 10 additions & 4 deletions src/m365/pa/commands/app/app-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class PaAppExportCommand extends PowerPlatformCommand {
this.#initTelemetry();
this.#initOptions();
this.#initValidators();
this.#initTypes();
}

#initTelemetry(): void {
Expand Down Expand Up @@ -95,13 +96,18 @@ class PaAppExportCommand extends PowerPlatformCommand {
);
}

#initTypes(): void {
this.types.string.push('name', 'environmentName', 'packageDisplayName', 'packageDescription', 'packageCreatedBy', 'packageSourceEnvironment', 'path');
}

public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
try {
const location = await this.exportPackage(args, logger);
const packageLink = await this.getPackageLink(args, logger, location);
const packageLink = await this.getPackageLink(logger, location);
//Replace all illegal characters from the file name
const illegalCharsRegEx = /[\\\/:*?"<>|]/g;
const filename = args.options.packageDisplayName.replace(illegalCharsRegEx, '_');
const packageName = args.options.packageDisplayName || args.options.name;
const filename = packageName.replace(illegalCharsRegEx, '_');

const requestOptions: CliRequestOptions = {
url: packageLink,
Expand All @@ -113,7 +119,7 @@ class PaAppExportCommand extends PowerPlatformCommand {
}
};

const file = await request.get<string>(requestOptions);
const file = await request.get<any>(requestOptions);

let path = args.options.path || './';

Expand Down Expand Up @@ -192,7 +198,7 @@ class PaAppExportCommand extends PowerPlatformCommand {
return response.headers.location;
}

private async getPackageLink(args: CommandArgs, logger: Logger, location: string): Promise<string> {
private async getPackageLink(logger: Logger, location: string): Promise<string> {
if (this.verbose) {
await logger.logToStderr('Retrieving the package link and waiting on the exported package.');
}
Expand Down

0 comments on commit cc8ce11

Please sign in to comment.