Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
108 changes: 108 additions & 0 deletions src/client/test/integration/authEnvironment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { expect } from "chai";
import * as sinon from "sinon";
import * as vscode from "vscode";
import { AuthEnvironmentService } from "../../uriHandler/utils/authEnvironment";
import { UriParameters } from "../../uriHandler/utils/uriHandlerUtils";
import { PacWrapper } from "../../pac/PacWrapper";

type ProgressReporter = vscode.Progress<{ message?: string; increment?: number }>;
type ProgressTask = (progress: ProgressReporter, token: vscode.CancellationToken) => Thenable<void>;

// Minimal stubbed surface of PacWrapper that the service depends on.
interface PacWrapperStub {
activeOrg: sinon.SinonStub;
orgSelect: sinon.SinonStub;
authCreateNewAuthProfileForOrg: sinon.SinonStub;
resetPacProcess: sinon.SinonStub;
}

describe("AuthEnvironmentService", () => {
let sandbox: sinon.SinonSandbox;
let pacWrapperStub: PacWrapperStub;
let service: AuthEnvironmentService;
let warningStub: sinon.SinonStub;

const uriParams = {
environmentId: "env-1",
orgUrl: "https://org.crm.dynamics.com/"
} as unknown as UriParameters;

beforeEach(() => {
sandbox = sinon.createSandbox();

pacWrapperStub = {
activeOrg: sandbox.stub(),
orgSelect: sandbox.stub().resolves(),
authCreateNewAuthProfileForOrg: sandbox.stub().resolves(),
resetPacProcess: sandbox.stub().resolves()
};

// Run the progress task immediately with a no-op reporter.
sandbox.stub(vscode.window, "withProgress").callsFake(
((_options: vscode.ProgressOptions, task: ProgressTask): Thenable<void> =>
task({ report: () => undefined }, new vscode.CancellationTokenSource().token)
) as unknown as typeof vscode.window.withProgress
);

warningStub = sandbox.stub(vscode.window, "showWarningMessage");
sandbox.stub(vscode.window, "showInformationMessage");

service = new AuthEnvironmentService(pacWrapperStub as unknown as PacWrapper);
});

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

it("does not prompt when already authenticated to the requested environment", async () => {
pacWrapperStub.activeOrg.resolves({
Status: "Success",
Results: { EnvironmentId: "env-1" }
});

await service.prepareAuthenticationAndEnvironment(uriParams, {});

expect(warningStub.called).to.be.false;
expect(pacWrapperStub.orgSelect.called).to.be.false;
expect(pacWrapperStub.authCreateNewAuthProfileForOrg.called).to.be.false;
});

it("switches environment when the active org points at a different environment", async () => {
pacWrapperStub.activeOrg
.onFirstCall().resolves({ Status: "Success", Results: { EnvironmentId: "env-1" } })
.onSecondCall().resolves({ Status: "Success", Results: { EnvironmentId: "other-env" } })
.onThirdCall().resolves({ Status: "Success", Results: { EnvironmentId: "env-1" } });
warningStub.resolves("Yes");

await service.prepareAuthenticationAndEnvironment(uriParams, {});

expect(pacWrapperStub.orgSelect.calledOnceWith("https://org.crm.dynamics.com/")).to.be.true;
});

it("resetPacProcessSafely swallows reset errors", async () => {
pacWrapperStub.resetPacProcess.rejects(new Error("reset boom"));

await service.resetPacProcessSafely({});

expect(pacWrapperStub.resetPacProcess.calledOnce).to.be.true;
});

it("resetPacProcessAndThrow resets and rethrows the original error", async () => {
const original = new Error("boom");
let thrown: unknown;

try {
await service.resetPacProcessAndThrow(original, {}, "message", "error_type");
} catch (error) {
thrown = error;
}

expect(thrown).to.equal(original);
expect(pacWrapperStub.resetPacProcess.calledOnce).to.be.true;
});
});
68 changes: 68 additions & 0 deletions src/client/test/integration/uriHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { expect } from "chai";
import * as sinon from "sinon";
import * as vscode from "vscode";
import { UriHandler } from "../../uriHandler/uriHandler";
import { URI_CONSTANTS } from "../../uriHandler/constants/uriConstants";
import { PacWrapper } from "../../pac/PacWrapper";

// Shape used to stub the private route targets on the prototype without resorting to `any`.
type UriHandlerRoutes = {
pcfInit: () => Promise<void>;
handleOpenPowerPages: (uri: vscode.Uri) => Promise<void>;
};

describe("UriHandler routing", () => {
let sandbox: sinon.SinonSandbox;
let pcfInitStub: sinon.SinonStub;
let openStub: sinon.SinonStub;
let handler: UriHandler;

const makeUri = (path: string): vscode.Uri =>
vscode.Uri.parse(`vscode://${URI_CONSTANTS.EXTENSION_ID}${path}`);

beforeEach(() => {
sandbox = sinon.createSandbox();
const prototype = UriHandler.prototype as unknown as UriHandlerRoutes;
pcfInitStub = sandbox.stub(prototype, "pcfInit").resolves();
openStub = sandbox.stub(prototype, "handleOpenPowerPages").resolves();
handler = new UriHandler({} as PacWrapper);
});

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

it("dispatches /pcfInit to the PCF init handler", async () => {
await handler.handleUri(makeUri(URI_CONSTANTS.PATHS.PCF_INIT));

expect(pcfInitStub.calledOnce).to.be.true;
expect(openStub.called).to.be.false;
});

it("dispatches /open to the open Power Pages handler", async () => {
await handler.handleUri(makeUri(URI_CONSTANTS.PATHS.OPEN));

expect(openStub.calledOnce).to.be.true;
expect(pcfInitStub.called).to.be.false;
});

it("ignores reserved deep-link paths that are not yet wired up", async () => {
await handler.handleUri(makeUri(URI_CONSTANTS.PATHS.AGENTIC_CREATE));
await handler.handleUri(makeUri(URI_CONSTANTS.PATHS.PAC_CREATE));

expect(pcfInitStub.called).to.be.false;
expect(openStub.called).to.be.false;
});

it("ignores unknown paths without throwing", async () => {
await handler.handleUri(makeUri("/someUnknownPath"));

expect(pcfInitStub.called).to.be.false;
expect(openStub.called).to.be.false;
});
});
40 changes: 40 additions & 0 deletions src/client/test/unit/uriConstants.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { expect } from "chai";
import { URI_CONSTANTS } from "../../uriHandler/constants/uriConstants";

// These constants form the versioned deep-link contract between the Power Pages
// home page and the VS Code extension. They are asserted here so that any accidental
// rename or removal is caught before it can break an already-shipped deep link.
describe("URI_CONSTANTS deep-link contract", () => {
it("keeps the existing open and pcfInit paths unchanged", () => {
expect(URI_CONSTANTS.PATHS.PCF_INIT).to.equal("/pcfInit");
expect(URI_CONSTANTS.PATHS.OPEN).to.equal("/open");
});

it("registers the agentic and PAC create paths", () => {
expect(URI_CONSTANTS.PATHS.AGENTIC_CREATE).to.equal("/agenticCreate");
expect(URI_CONSTANTS.PATHS.PAC_CREATE).to.equal("/pacCreate");
});

it("exposes the shared context parameter names", () => {
expect(URI_CONSTANTS.PARAMETERS.ENV_ID).to.equal("envid");
expect(URI_CONSTANTS.PARAMETERS.ORG_URL).to.equal("orgurl");
expect(URI_CONSTANTS.PARAMETERS.REGION).to.equal("region");
expect(URI_CONSTANTS.PARAMETERS.TENANT_ID).to.equal("tenantid");
expect(URI_CONSTANTS.PARAMETERS.SOURCE).to.equal("source");
expect(URI_CONSTANTS.PARAMETERS.AGENT_HOST).to.equal("agenthost");
expect(URI_CONSTANTS.PARAMETERS.VERSION).to.equal("v");
});

it("defines the versioned contract and known enumerated values", () => {
expect(URI_CONSTANTS.CONTRACT_VERSION.CURRENT).to.equal("1");
expect(URI_CONSTANTS.SOURCE_VALUES.POWER_PAGES_HOME).to.equal("powerPagesHome");
expect(URI_CONSTANTS.AGENT_HOST_VALUES.COPILOT).to.equal("copilot");
expect(URI_CONSTANTS.AGENT_HOST_VALUES.CLAUDE).to.equal("claude");
expect(URI_CONSTANTS.AGENT_HOST_VALUES.AUTO).to.equal("auto");
});
});
24 changes: 22 additions & 2 deletions src/client/uriHandler/constants/uriConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export const URI_CONSTANTS = {
EXTENSION_ID: 'microsoft-IsvExpTools.powerplatform-vscode',
PATHS: {
PCF_INIT: '/pcfInit',
OPEN: '/open'
OPEN: '/open',
AGENTIC_CREATE: '/agenticCreate',
PAC_CREATE: '/pacCreate'
},
PARAMETERS: {
WEBSITE_ID: 'websiteid',
Expand All @@ -20,11 +22,27 @@ export const URI_CONSTANTS = {
SITE_NAME: 'sitename',
WEBSITE_NAME: 'websitename',
SITE_URL: 'siteurl',
WEBSITE_PREVIEW_URL: 'websitepreviewurl'
WEBSITE_PREVIEW_URL: 'websitepreviewurl',
REGION: 'region',
TENANT_ID: 'tenantid',
SOURCE: 'source',
AGENT_HOST: 'agenthost',
VERSION: 'v'
},
SCHEMA_VALUES: {
PORTAL_SCHEMA_V2: 'portalschemav2'
},
SOURCE_VALUES: {
POWER_PAGES_HOME: 'powerPagesHome'
},
AGENT_HOST_VALUES: {
COPILOT: 'copilot',
CLAUDE: 'claude',
AUTO: 'auto'
},
CONTRACT_VERSION: {
CURRENT: '1'
},
MODEL_VERSIONS: {
VERSION_1: 1,
VERSION_2: 2
Expand All @@ -40,4 +58,6 @@ export const URI_CONSTANTS = {
export const enum UriPath {
PcfInit = '/pcfInit',
Open = '/open',
AgenticCreate = '/agenticCreate',
PacCreate = '/pacCreate',
}
Loading
Loading