From 7125d0d6e1369633012fea480bea9e7beae6e54d Mon Sep 17 00:00:00 2001 From: Amit Joshi Date: Tue, 21 Jul 2026 16:27:44 +0530 Subject: [PATCH 1/2] Extract shared PAC auth/environment helpers into AuthEnvironmentService Move the PAC CLI authentication, environment-selection, and process-reset logic out of the `UriHandler` class into a new `utils/authEnvironment.ts` `AuthEnvironmentService` that takes a `PacWrapper`. `UriHandler` now composes the service and delegates: - `handleOpenPowerPages` (`/open`) calls `authEnvironmentService.prepareAuthenticationAndEnvironment(...)` - `executeDownload` calls `authEnvironmentService.resetPacProcessSafely(...)` This is a behavior-preserving refactor: the moved methods (`prepareAuthenticationAndEnvironment`, `ensureAuthentication`, `ensureCorrectEnvironment`, `resetPacProcessAndThrow`, `resetPacProcessSafely`) keep identical logic, telemetry, prompts, and error handling. Extracting them lets the upcoming `/pacCreate` and `/agenticCreate` handlers reuse the exact same auth/environment flow instead of duplicating it. Adds an integration test (`test/integration/authEnvironment.test.ts`) covering the no-prompt happy path, the environment-switch path, and the reset-safely / reset-and-throw error behaviors with a stubbed PacWrapper. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../test/integration/authEnvironment.test.ts | 108 +++++++++ src/client/uriHandler/uriHandler.ts | 206 +--------------- .../uriHandler/utils/authEnvironment.ts | 224 ++++++++++++++++++ 3 files changed, 337 insertions(+), 201 deletions(-) create mode 100644 src/client/test/integration/authEnvironment.test.ts create mode 100644 src/client/uriHandler/utils/authEnvironment.ts diff --git a/src/client/test/integration/authEnvironment.test.ts b/src/client/test/integration/authEnvironment.test.ts new file mode 100644 index 000000000..78f327d36 --- /dev/null +++ b/src/client/test/integration/authEnvironment.test.ts @@ -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; + +// 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 => + 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; + }); +}); diff --git a/src/client/uriHandler/uriHandler.ts b/src/client/uriHandler/uriHandler.ts index 2de3285ff..9f7975528 100644 --- a/src/client/uriHandler/uriHandler.ts +++ b/src/client/uriHandler/uriHandler.ts @@ -10,6 +10,7 @@ import { UriPath } from "./constants/uriConstants"; import { URI_HANDLER_STRINGS } from "./constants/uriStrings"; import { uriHandlerTelemetryEventNames } from "./telemetry/uriHandlerTelemetryEvents"; import { UriHandlerUtils, UriParameters } from "./utils/uriHandlerUtils"; +import { AuthEnvironmentService } from "./utils/authEnvironment"; /** * Signature for a deep-link route handler. Each registered URI path maps to one handler. @@ -24,9 +25,11 @@ export function RegisterUriHandler(pacWrapper: PacWrapper): vscode.Disposable { export class UriHandler implements vscode.UriHandler { private readonly pacWrapper: PacWrapper; private readonly routes: ReadonlyMap; + private readonly authEnvironmentService: AuthEnvironmentService; constructor(pacWrapper: PacWrapper) { this.pacWrapper = pacWrapper; + this.authEnvironmentService = new AuthEnvironmentService(pacWrapper); this.routes = this.buildRoutes(); } @@ -124,7 +127,7 @@ export class UriHandler implements vscode.UriHandler { this.validateRequiredParameters(uriParams, telemetryData); // Prepare authentication and environment - await this.prepareAuthenticationAndEnvironment(uriParams, telemetryData); + await this.authEnvironmentService.prepareAuthenticationAndEnvironment(uriParams, telemetryData); // Handle the download process await this.handleSiteDownload(uriParams, telemetryData, startTime); @@ -230,170 +233,6 @@ export class UriHandler implements vscode.UriHandler { } } - /** - * Handle authentication and environment setup - */ - private async prepareAuthenticationAndEnvironment(uriParams: UriParameters, telemetryData: Record): Promise { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: URI_HANDLER_STRINGS.TITLES.POWER_PAGES, - cancellable: false - }, - async (progress) => { - progress.report({ - message: URI_HANDLER_STRINGS.PROGRESS.PREPARING, - increment: 10 - }); - - progress.report({ - message: URI_HANDLER_STRINGS.PROGRESS.VALIDATING_AUTH, - increment: 20 - }); - - // Check and handle authentication - await this.ensureAuthentication(uriParams, telemetryData, progress); - - progress.report({ - message: URI_HANDLER_STRINGS.PROGRESS.CHECKING_ENV, - increment: 20 - }); - - // Check and handle environment switching - await this.ensureCorrectEnvironment(uriParams, telemetryData, progress); - - progress.report({ - message: URI_HANDLER_STRINGS.PROGRESS.READY_TO_SELECT, - increment: 30 - }); - - // Brief delay to let user see the final progress message - await new Promise(resolve => setTimeout(resolve, 500)); - } - ); - } - - /** - * Ensure user is authenticated with PAC CLI - */ - private async ensureAuthentication(uriParams: UriParameters, telemetryData: Record, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise { - let authInfo; - try { - authInfo = await this.pacWrapper.activeOrg(); - } catch (error) { - await this.resetPacProcessAndThrow(error, telemetryData, 'Failed to check authentication status', 'auth_check_failed'); - } - - if (!authInfo || authInfo.Status !== "Success") { - oneDSLoggerWrapper.getLogger().traceInfo( - uriHandlerTelemetryEventNames.URI_HANDLER_AUTH_REQUIRED, - { ...telemetryData, authStatus: authInfo?.Status || 'none' } - ); - - progress.report({ - message: URI_HANDLER_STRINGS.PROGRESS.AUTH_REQUIRED, - increment: 10 - }); - - const authRequired = await vscode.window.showWarningMessage( - URI_HANDLER_STRINGS.PROMPTS.AUTH_REQUIRED, - { modal: true }, - URI_HANDLER_STRINGS.BUTTONS.YES, - URI_HANDLER_STRINGS.BUTTONS.NO - ); - - if (authRequired === URI_HANDLER_STRINGS.BUTTONS.YES) { - try { - progress.report({ - message: URI_HANDLER_STRINGS.PROGRESS.AUTHENTICATING, - increment: 10 - }); - - await this.pacWrapper.authCreateNewAuthProfileForOrg(uriParams.orgUrl!); - - const newAuthInfo = await this.pacWrapper.activeOrg(); - if (!newAuthInfo || newAuthInfo.Status !== "Success") { - throw new Error(URI_HANDLER_STRINGS.ERRORS.AUTH_FAILED); - } - - oneDSLoggerWrapper.getLogger().traceInfo( - uriHandlerTelemetryEventNames.URI_HANDLER_AUTH_COMPLETED, - { ...telemetryData, newAuthStatus: newAuthInfo.Status } - ); - } catch (authError) { - await this.resetPacProcessAndThrow(authError, telemetryData, 'Authentication operation failed', 'auth_operation_failed'); - } - } else { - vscode.window.showInformationMessage(URI_HANDLER_STRINGS.INFO.DOWNLOAD_CANCELLED_AUTH); - oneDSLoggerWrapper.getLogger().traceInfo( - uriHandlerTelemetryEventNames.URI_HANDLER_OPEN_POWER_PAGES_FAILED, - { ...telemetryData, reason: 'user_cancelled_auth' } - ); - throw new Error(URI_HANDLER_STRINGS.ERRORS.USER_CANCELLED_AUTH); - } - } - } - - /** - * Ensure we're connected to the correct environment - */ - private async ensureCorrectEnvironment(uriParams: UriParameters, telemetryData: Record, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise { - let currentAuthInfo; - try { - currentAuthInfo = await this.pacWrapper.activeOrg(); - } catch (error) { - await this.resetPacProcessAndThrow(error, telemetryData, 'Failed to check current environment', 'env_check_failed'); - } - - if (currentAuthInfo?.Status === "Success" && currentAuthInfo.Results?.EnvironmentId !== uriParams.environmentId) { - oneDSLoggerWrapper.getLogger().traceInfo( - uriHandlerTelemetryEventNames.URI_HANDLER_ENV_SWITCH_REQUIRED, - { - ...telemetryData, - currentEnvId: currentAuthInfo.Results?.EnvironmentId || 'unknown', - requestedEnvId: uriParams.environmentId - } - ); - - const switchEnv = await vscode.window.showWarningMessage( - URI_HANDLER_STRINGS.PROMPTS.ENV_SWITCH_REQUIRED, - { modal: true }, - URI_HANDLER_STRINGS.BUTTONS.YES, - URI_HANDLER_STRINGS.BUTTONS.NO - ); - - if (switchEnv === URI_HANDLER_STRINGS.BUTTONS.YES) { - try { - progress.report({ - message: URI_HANDLER_STRINGS.PROGRESS.SWITCHING_ENV, - increment: 10 - }); - - await this.pacWrapper.orgSelect(uriParams.orgUrl!); - - const verifyAuthInfo = await this.pacWrapper.activeOrg(); - if (verifyAuthInfo?.Status !== "Success" || verifyAuthInfo.Results?.EnvironmentId !== uriParams.environmentId) { - throw new Error(URI_HANDLER_STRINGS.ERRORS.ENV_SWITCH_FAILED); - } - - oneDSLoggerWrapper.getLogger().traceInfo( - uriHandlerTelemetryEventNames.URI_HANDLER_ENV_SWITCH_COMPLETED, - { ...telemetryData, switchedToEnvId: verifyAuthInfo.Results?.EnvironmentId } - ); - } catch (error) { - await this.resetPacProcessAndThrow(error, telemetryData, 'Error switching environment', 'env_switch_error'); - } - } else { - vscode.window.showInformationMessage(URI_HANDLER_STRINGS.INFO.DOWNLOAD_CANCELLED_ENV); - oneDSLoggerWrapper.getLogger().traceInfo( - uriHandlerTelemetryEventNames.URI_HANDLER_OPEN_POWER_PAGES_FAILED, - { ...telemetryData, reason: 'user_cancelled_env_switch' } - ); - throw new Error(URI_HANDLER_STRINGS.ERRORS.USER_CANCELLED_ENV_SWITCH); - } - } - } - /** * Handle the site download process */ @@ -478,44 +317,9 @@ export class UriHandler implements vscode.UriHandler { { ...telemetryData, error: 'download_failed' } ); - await this.resetPacProcessSafely(telemetryData); + await this.authEnvironmentService.resetPacProcessSafely(telemetryData); throw error; } } - /** - * Reset PAC process and throw error - */ - private async resetPacProcessAndThrow(error: unknown, telemetryData: Record, message: string, errorType: string): Promise { - oneDSLoggerWrapper.getLogger().traceError( - uriHandlerTelemetryEventNames.URI_HANDLER_OPEN_POWER_PAGES_FAILED, - message, - error instanceof Error ? error : new Error(String(error)), - { ...telemetryData, error: errorType } - ); - - await this.resetPacProcessSafely(telemetryData); - throw error; - } - - /** - * Safely reset PAC process without throwing - */ - private async resetPacProcessSafely(telemetryData: Record): Promise { - try { - await this.pacWrapper.resetPacProcess(); - oneDSLoggerWrapper.getLogger().traceInfo( - uriHandlerTelemetryEventNames.URI_HANDLER_OPEN_POWER_PAGES_FAILED, - { ...telemetryData, message: 'PAC process reset after failure' } - ); - } catch (resetError) { - oneDSLoggerWrapper.getLogger().traceError( - uriHandlerTelemetryEventNames.URI_HANDLER_OPEN_POWER_PAGES_FAILED, - 'Failed to reset PAC process after failure', - resetError instanceof Error ? resetError : new Error(String(resetError)), - { ...telemetryData, error: 'pac_reset_failed' } - ); - } - } - } diff --git a/src/client/uriHandler/utils/authEnvironment.ts b/src/client/uriHandler/utils/authEnvironment.ts new file mode 100644 index 000000000..d0cc94b18 --- /dev/null +++ b/src/client/uriHandler/utils/authEnvironment.ts @@ -0,0 +1,224 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import * as vscode from "vscode"; +import { PacWrapper } from "../../pac/PacWrapper"; +import { oneDSLoggerWrapper } from "../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; +import { uriHandlerTelemetryEventNames } from "../telemetry/uriHandlerTelemetryEvents"; +import { URI_HANDLER_STRINGS } from "../constants/uriStrings"; +import { UriParameters } from "./uriHandlerUtils"; + +/** + * Encapsulates the PAC CLI authentication and environment-selection steps shared by the + * Power Pages deep-link flows. Extracted from `UriHandler` so that each deep-link handler + * (`/open` today, `/pacCreate` and `/agenticCreate` in follow-ups) can reuse the exact same + * auth/environment/reset behavior without duplicating it. + */ +export class AuthEnvironmentService { + private readonly pacWrapper: PacWrapper; + + constructor(pacWrapper: PacWrapper) { + this.pacWrapper = pacWrapper; + } + + /** + * Handle authentication and environment setup, reporting progress to the user. + */ + public async prepareAuthenticationAndEnvironment(uriParams: UriParameters, telemetryData: Record): Promise { + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: URI_HANDLER_STRINGS.TITLES.POWER_PAGES, + cancellable: false + }, + async (progress) => { + progress.report({ + message: URI_HANDLER_STRINGS.PROGRESS.PREPARING, + increment: 10 + }); + + progress.report({ + message: URI_HANDLER_STRINGS.PROGRESS.VALIDATING_AUTH, + increment: 20 + }); + + // Check and handle authentication + await this.ensureAuthentication(uriParams, telemetryData, progress); + + progress.report({ + message: URI_HANDLER_STRINGS.PROGRESS.CHECKING_ENV, + increment: 20 + }); + + // Check and handle environment switching + await this.ensureCorrectEnvironment(uriParams, telemetryData, progress); + + progress.report({ + message: URI_HANDLER_STRINGS.PROGRESS.READY_TO_SELECT, + increment: 30 + }); + + // Brief delay to let user see the final progress message + await new Promise(resolve => setTimeout(resolve, 500)); + } + ); + } + + /** + * Ensure user is authenticated with PAC CLI + */ + private async ensureAuthentication(uriParams: UriParameters, telemetryData: Record, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise { + let authInfo; + try { + authInfo = await this.pacWrapper.activeOrg(); + } catch (error) { + await this.resetPacProcessAndThrow(error, telemetryData, 'Failed to check authentication status', 'auth_check_failed'); + } + + if (!authInfo || authInfo.Status !== "Success") { + oneDSLoggerWrapper.getLogger().traceInfo( + uriHandlerTelemetryEventNames.URI_HANDLER_AUTH_REQUIRED, + { ...telemetryData, authStatus: authInfo?.Status || 'none' } + ); + + progress.report({ + message: URI_HANDLER_STRINGS.PROGRESS.AUTH_REQUIRED, + increment: 10 + }); + + const authRequired = await vscode.window.showWarningMessage( + URI_HANDLER_STRINGS.PROMPTS.AUTH_REQUIRED, + { modal: true }, + URI_HANDLER_STRINGS.BUTTONS.YES, + URI_HANDLER_STRINGS.BUTTONS.NO + ); + + if (authRequired === URI_HANDLER_STRINGS.BUTTONS.YES) { + try { + progress.report({ + message: URI_HANDLER_STRINGS.PROGRESS.AUTHENTICATING, + increment: 10 + }); + + await this.pacWrapper.authCreateNewAuthProfileForOrg(uriParams.orgUrl!); + + const newAuthInfo = await this.pacWrapper.activeOrg(); + if (!newAuthInfo || newAuthInfo.Status !== "Success") { + throw new Error(URI_HANDLER_STRINGS.ERRORS.AUTH_FAILED); + } + + oneDSLoggerWrapper.getLogger().traceInfo( + uriHandlerTelemetryEventNames.URI_HANDLER_AUTH_COMPLETED, + { ...telemetryData, newAuthStatus: newAuthInfo.Status } + ); + } catch (authError) { + await this.resetPacProcessAndThrow(authError, telemetryData, 'Authentication operation failed', 'auth_operation_failed'); + } + } else { + vscode.window.showInformationMessage(URI_HANDLER_STRINGS.INFO.DOWNLOAD_CANCELLED_AUTH); + oneDSLoggerWrapper.getLogger().traceInfo( + uriHandlerTelemetryEventNames.URI_HANDLER_OPEN_POWER_PAGES_FAILED, + { ...telemetryData, reason: 'user_cancelled_auth' } + ); + throw new Error(URI_HANDLER_STRINGS.ERRORS.USER_CANCELLED_AUTH); + } + } + } + + /** + * Ensure we're connected to the correct environment + */ + private async ensureCorrectEnvironment(uriParams: UriParameters, telemetryData: Record, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise { + let currentAuthInfo; + try { + currentAuthInfo = await this.pacWrapper.activeOrg(); + } catch (error) { + await this.resetPacProcessAndThrow(error, telemetryData, 'Failed to check current environment', 'env_check_failed'); + } + + if (currentAuthInfo?.Status === "Success" && currentAuthInfo.Results?.EnvironmentId !== uriParams.environmentId) { + oneDSLoggerWrapper.getLogger().traceInfo( + uriHandlerTelemetryEventNames.URI_HANDLER_ENV_SWITCH_REQUIRED, + { + ...telemetryData, + currentEnvId: currentAuthInfo.Results?.EnvironmentId || 'unknown', + requestedEnvId: uriParams.environmentId + } + ); + + const switchEnv = await vscode.window.showWarningMessage( + URI_HANDLER_STRINGS.PROMPTS.ENV_SWITCH_REQUIRED, + { modal: true }, + URI_HANDLER_STRINGS.BUTTONS.YES, + URI_HANDLER_STRINGS.BUTTONS.NO + ); + + if (switchEnv === URI_HANDLER_STRINGS.BUTTONS.YES) { + try { + progress.report({ + message: URI_HANDLER_STRINGS.PROGRESS.SWITCHING_ENV, + increment: 10 + }); + + await this.pacWrapper.orgSelect(uriParams.orgUrl!); + + const verifyAuthInfo = await this.pacWrapper.activeOrg(); + if (verifyAuthInfo?.Status !== "Success" || verifyAuthInfo.Results?.EnvironmentId !== uriParams.environmentId) { + throw new Error(URI_HANDLER_STRINGS.ERRORS.ENV_SWITCH_FAILED); + } + + oneDSLoggerWrapper.getLogger().traceInfo( + uriHandlerTelemetryEventNames.URI_HANDLER_ENV_SWITCH_COMPLETED, + { ...telemetryData, switchedToEnvId: verifyAuthInfo.Results?.EnvironmentId } + ); + } catch (error) { + await this.resetPacProcessAndThrow(error, telemetryData, 'Error switching environment', 'env_switch_error'); + } + } else { + vscode.window.showInformationMessage(URI_HANDLER_STRINGS.INFO.DOWNLOAD_CANCELLED_ENV); + oneDSLoggerWrapper.getLogger().traceInfo( + uriHandlerTelemetryEventNames.URI_HANDLER_OPEN_POWER_PAGES_FAILED, + { ...telemetryData, reason: 'user_cancelled_env_switch' } + ); + throw new Error(URI_HANDLER_STRINGS.ERRORS.USER_CANCELLED_ENV_SWITCH); + } + } + } + + /** + * Reset PAC process and throw error + */ + public async resetPacProcessAndThrow(error: unknown, telemetryData: Record, message: string, errorType: string): Promise { + oneDSLoggerWrapper.getLogger().traceError( + uriHandlerTelemetryEventNames.URI_HANDLER_OPEN_POWER_PAGES_FAILED, + message, + error instanceof Error ? error : new Error(String(error)), + { ...telemetryData, error: errorType } + ); + + await this.resetPacProcessSafely(telemetryData); + throw error; + } + + /** + * Safely reset PAC process without throwing + */ + public async resetPacProcessSafely(telemetryData: Record): Promise { + try { + await this.pacWrapper.resetPacProcess(); + oneDSLoggerWrapper.getLogger().traceInfo( + uriHandlerTelemetryEventNames.URI_HANDLER_OPEN_POWER_PAGES_FAILED, + { ...telemetryData, message: 'PAC process reset after failure' } + ); + } catch (resetError) { + oneDSLoggerWrapper.getLogger().traceError( + uriHandlerTelemetryEventNames.URI_HANDLER_OPEN_POWER_PAGES_FAILED, + 'Failed to reset PAC process after failure', + resetError instanceof Error ? resetError : new Error(String(resetError)), + { ...telemetryData, error: 'pac_reset_failed' } + ); + } + } +} From 68739b955a81ec2f83c98f647bbc3e1810ecf551 Mon Sep 17 00:00:00 2001 From: Amit Joshi Date: Fri, 31 Jul 2026 14:10:49 +0530 Subject: [PATCH 2/2] Fix failing AuthEnvironmentService tests by stubbing OneDS logger The three failing integration tests (switches environment..., resetPacProcessSafely swallows reset errors, resetPacProcessAndThrow resets and rethrows the original error) all exercise code paths that emit telemetry via oneDSLoggerWrapper.getLogger(). In the integration test host the OneDS logger is never instantiated, so getLogger() returns undefined and the telemetry calls threw "Cannot read properties of undefined (reading 'traceInfo'/'traceError')". The one test that passed never touched telemetry. Stub oneDSLoggerWrapper.getLogger() in beforeEach to return a no-op logger (traceInfo/traceWarning/traceError/featureUsage), matching the existing pattern in WebsiteUtil.test.ts. All four AuthEnvironmentService tests now pass; full desktop integration suite: 895 passing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 21dcede3-a8f3-4593-9ee6-367b083c2993 --- src/client/test/integration/authEnvironment.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/client/test/integration/authEnvironment.test.ts b/src/client/test/integration/authEnvironment.test.ts index 78f327d36..b0a6b45b5 100644 --- a/src/client/test/integration/authEnvironment.test.ts +++ b/src/client/test/integration/authEnvironment.test.ts @@ -9,6 +9,7 @@ import * as vscode from "vscode"; import { AuthEnvironmentService } from "../../uriHandler/utils/authEnvironment"; import { UriParameters } from "../../uriHandler/utils/uriHandlerUtils"; import { PacWrapper } from "../../pac/PacWrapper"; +import { oneDSLoggerWrapper } from "../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; type ProgressReporter = vscode.Progress<{ message?: string; increment?: number }>; type ProgressTask = (progress: ProgressReporter, token: vscode.CancellationToken) => Thenable; @@ -35,6 +36,16 @@ describe("AuthEnvironmentService", () => { beforeEach(() => { sandbox = sinon.createSandbox(); + // The OneDS logger is never instantiated in the integration test host, so + // `oneDSLoggerWrapper.getLogger()` would return undefined and any telemetry + // call inside the service would throw. Stub it with a no-op logger. + sandbox.stub(oneDSLoggerWrapper, "getLogger").returns({ + traceInfo: sandbox.stub(), + traceWarning: sandbox.stub(), + traceError: sandbox.stub(), + featureUsage: sandbox.stub() + } as unknown as ReturnType); + pacWrapperStub = { activeOrg: sandbox.stub(), orgSelect: sandbox.stub().resolves(),