Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add convert aad manifest to new schema in fx-core #12928

Merged
merged 3 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/fx-core/resource/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,9 @@
"core.confirmManifestQuestion.placeholder": "Confirm you've selected the correct manifest file",
"core.aadAppQuestion.label": "Microsoft Entra app",
"core.aadAppQuestion.description": "Your Microsoft Entra app for Single Sign On",
"core.convertAadToNewSchema.continue": "Continue",
"core.convertAadToNewSchema.warning": "Convert Microsoft Entra app manifest to new schema will overwrite the original file. Are you sure to continue?",
Copy link
Contributor

@supkasar supkasar Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposed: Converting Microsoft Entra app manifest file to new schema will replace the original file. Do you still want to continue?

"core.convertAadToNewSchema.success": "Microsoft Entra app manifest converted to new schema successfully.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposed: Microsoft Entra app manifest file successfully converted to new schema.

"core.teamsAppQuestion.label": "Teams app",
"core.teamsAppQuestion.description": "Your Teams app",
"core.M365SsoLaunchPageOptionItem.label": "React with Fluent UI",
Expand Down
38 changes: 38 additions & 0 deletions packages/fx-core/src/core/FxCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
import { generateDriverContext } from "../common/utils";
import { addExistingPlugin } from "../component/generator/copilotExtension/helper";
import { featureFlagManager, FeatureFlags } from "../common/featureFlags";
import { AadManifestHelper } from "../component/driver/aad/utility/aadManifestHelper";

export class FxCore {
constructor(tools: Tools) {
Expand Down Expand Up @@ -835,6 +836,43 @@
await buildAadManifest(Context, manifestTemplatePath, manifestOutputPath);
return ok(undefined);
}

@hooks([
ErrorContextMW({ component: "FxCore", stage: "convertAadToNewSchema", reset: true }),
ErrorHandlerMW,
QuestionMW("convertAadToNewSchema"),
])
async convertAadToNewSchema(inputs: Inputs): Promise<Result<undefined, FxError>> {
const manifestTemplatePath: string = inputs[QuestionNames.AadAppManifestFilePath];
if (!(await fs.pathExists(manifestTemplatePath))) {
return err(new FileNotFoundError("convertAadToNewSchema", manifestTemplatePath));
}

const manifest = await fs.readJson(manifestTemplatePath);
const context = createContext();
const confirmRes = await context.userInteraction.showMessage(
"warn",
getLocalizedString("core.convertAadToNewSchema.warning"),
true,
getLocalizedString("core.convertAadToNewSchema.continue")
);

if (confirmRes.isErr()) {
return err(confirmRes.error);

Check warning on line 861 in packages/fx-core/src/core/FxCore.ts

View check run for this annotation

Codecov / codecov/patch

packages/fx-core/src/core/FxCore.ts#L861

Added line #L861 was not covered by tests
} else if (confirmRes.value !== getLocalizedString("core.convertAadToNewSchema.continue")) {
return err(new UserCancelError());
}

const result = AadManifestHelper.manifestToApplication(manifest);
await fs.writeJson(manifestTemplatePath, result, { spaces: 2 });
void (await context.userInteraction.showMessage(
"info",
getLocalizedString("core.convertAadToNewSchema.success"),
false
));
return ok(undefined);
}

/**
* v3 only none lifecycle command
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/fx-core/src/question/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
validateTeamsAppQuestionNode,
syncManifestQuestionNode,
kiotaRegenerateQuestion,
convertAadToNewSchemaQuestionNode,
} from "./other";
export * from "./constants";
export * from "./create";
Expand Down Expand Up @@ -60,6 +61,9 @@ export class QuestionNodes {
deployAadManifest(): IQTreeNode {
return deployAadManifestQuestionNode();
}
convertAadToNewSchema(): IQTreeNode {
return convertAadToNewSchemaQuestionNode();
}
createNewEnv(): IQTreeNode {
return createNewEnvQuestionNode();
}
Expand Down
26 changes: 26 additions & 0 deletions packages/fx-core/src/question/other.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,32 @@ export function grantPermissionQuestionNode(): IQTreeNode {
};
}

export function convertAadToNewSchemaQuestionNode(): IQTreeNode {
return {
data: { type: "group" },
children: [
{
condition: (inputs: Inputs) =>
DynamicPlatforms.includes(inputs.platform) &&
!inputs[QuestionNames.AadAppManifestFilePath],
data: selectAadManifestQuestion(),
children: [
{
condition: (inputs: Inputs) =>
inputs.platform === Platform.VSCode && // confirm question only works for VSC
inputs.projectPath !== undefined &&
path.resolve(inputs[QuestionNames.AadAppManifestFilePath]) !==
path.join(inputs.projectPath, "aad.manifest.json"),
data: confirmManifestQuestion(false, false),
cliOptionDisabled: "self",
inputsDisabled: "self",
},
],
},
],
};
}

export function deployAadManifestQuestionNode(): IQTreeNode {
return {
data: { type: "group" },
Expand Down
77 changes: 77 additions & 0 deletions packages/fx-core/tests/core/FxCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ import { ConstantString } from "../../src/common/constants";
import { SyncManifestArgs } from "../../src/component/driver/teamsApp/interfaces/SyncManifest";
import { WrapDriverContext } from "../../src/component/driver/util/wrapUtil";
import * as copilotExtensionHelper from "../../src/component/generator/copilotExtension//helper";
import { AadManifestHelper } from "../../src/component/driver/aad/utility/aadManifestHelper";

const tools = new MockTools();

Expand Down Expand Up @@ -604,6 +605,82 @@ describe("Core basic APIs", () => {
}
});

it("convertAadToNewSchema method should work fine", async () => {
const restore = mockedEnv({
TEAMSFX_DEBUG_TEMPLATE: "true", // workaround test failure that when local template not released to GitHub
NODE_ENV: "development", // workaround test failure that when local template not released to GitHub
});

try {
const core = new FxCore(tools);
const appName = await mockV3Project();
const projectPath = path.join(os.tmpdir(), appName);
const inputs: Inputs = {
platform: Platform.VSCode,
projectPath: projectPath,
[QuestionNames.AadAppManifestFilePath]: `${projectPath}/aad.manifest.json`,
};

sandbox.stub(tools.ui, "showMessage").resolves(ok("Continue"));
const result = await core.convertAadToNewSchema(inputs);
assert.isTrue(result.isOk());
} finally {
restore();
}
});

it("convertAadToNewSchema should throw file not exist error if aad.manifest.json does not exist", async () => {
const restore = mockedEnv({
TEAMSFX_DEBUG_TEMPLATE: "true", // workaround test failure that when local template not released to GitHub
NODE_ENV: "development", // workaround test failure that when local template not released to GitHub
});

try {
const core = new FxCore(tools);
const appName = await mockV3Project();
const projectPath = path.join(os.tmpdir(), appName);
const inputs: Inputs = {
platform: Platform.VSCode,
projectPath: projectPath,
[QuestionNames.AadAppManifestFilePath]: `/not-exist-path/aad.manifest.json`,
};

const result = await core.convertAadToNewSchema(inputs);
assert.isTrue(result.isErr());
if (result.isErr()) {
assert.isTrue(result.error instanceof FileNotFoundError);
}
} finally {
restore();
}
});

it("convertAadToNewSchema throw user cancel error if not confirmed", async () => {
const restore = mockedEnv({
TEAMSFX_DEBUG_TEMPLATE: "true", // workaround test failure that when local template not released to GitHub
NODE_ENV: "development", // workaround test failure that when local template not released to GitHub
});

try {
const core = new FxCore(tools);
const appName = await mockV3Project();
const projectPath = path.join(os.tmpdir(), appName);
const inputs: Inputs = {
platform: Platform.VSCode,
projectPath: projectPath,
[QuestionNames.AadAppManifestFilePath]: `${projectPath}/aad.manifest.json`,
};

const result = await core.convertAadToNewSchema(inputs);
assert.isTrue(result.isErr());
if (result.isErr()) {
assert.isTrue(result.error instanceof UserCancelError);
}
} finally {
restore();
}
});

it("addSso method should exist", async () => {
const restore = mockedEnv({
TEAMSFX_DEBUG_TEMPLATE: "true", // workaround test failures when template changed but not release to GitHub alpha template
Expand Down
42 changes: 42 additions & 0 deletions packages/fx-core/tests/question/question.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,48 @@ describe("grantPermission", async () => {
]);
});
});

describe("convertAadToNewSchemaQuestionNode", async () => {
const sandbox = sinon.createSandbox();

afterEach(async () => {
sandbox.restore();
});

it("happy path", async () => {
const inputs: Inputs = {
platform: Platform.VSCode,
projectPath: ".",
};
sandbox.stub(fs, "pathExistsSync").returns(true);
sandbox.stub(fs, "pathExists").resolves(true);
sandbox.stub(fs, "readFile").resolves(Buffer.from("${{fake_placeHolder}}"));
const questions: string[] = [];
const visitor: QuestionTreeVisitor = async (
question: Question,
ui: UserInteraction,
inputs: Inputs,
step?: number,
totalSteps?: number
) => {
questions.push(question.name);
await callFuncs(question, inputs);
if (question.name === QuestionNames.AadAppManifestFilePath) {
return ok({ type: "success", result: "aadAppManifest" });
} else if (question.name === QuestionNames.ConfirmManifest) {
return ok({ type: "success", result: "manifest" });
}
return ok({ type: "success", result: undefined });
};
const res = questionNodes.convertAadToNewSchema();
await traverse(res, inputs, ui, undefined, visitor);
assert.deepEqual(questions, [
QuestionNames.AadAppManifestFilePath,
QuestionNames.ConfirmAadManifest,
]);
});
});

describe("deployAadManifest", async () => {
const sandbox = sinon.createSandbox();

Expand Down
Loading