Skip to content

Added tests for restore database webview controller#21566

Open
laurenastrid1 wants to merge 3 commits intomainfrom
laurenastrid1/restoreDatabaseTets
Open

Added tests for restore database webview controller#21566
laurenastrid1 wants to merge 3 commits intomainfrom
laurenastrid1/restoreDatabaseTets

Conversation

@laurenastrid1
Copy link
Contributor

Description

Added tests for restoreDatabaseWebviewController

Code Changes Checklist

  • New or updated unit tests added
  • All existing tests pass (npm run test)
  • Code follows contributing guidelines
  • Telemetry/logging updated if relevant
  • No regressions or UX breakage

Reviewers: Please read our reviewer guidelines

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds unit test coverage around RestoreDatabaseWebviewController behavior and reducer handlers, along with small initialization flow changes in the existing restore controller (and an additional restore controller implementation introduced in the backup controller module).

Changes:

  • Added a comprehensive unit test suite for RestoreDatabaseWebviewController covering initialization paths, reducers, and helper methods.
  • Adjusted RestoreDatabaseWebviewController.initializeDialog default initialization ordering and view-model update calls.
  • Introduced a second RestoreDatabaseWebviewController implementation inside backupDatabaseWebviewController.ts (in addition to the existing dedicated restore controller file).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
extensions/mssql/test/unit/restoreDatabaseWebviewController.test.ts New unit tests for restore controller initialization, reducers, and helpers.
extensions/mssql/src/controllers/restoreDatabaseWebviewController.ts Tweaks to restore controller initialization defaults and update calls.
extensions/mssql/src/controllers/backupDatabaseWebviewController.ts Adds a full restore controller implementation (duplicate location) and supporting imports.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +966 to +969
export class RestoreDatabaseWebviewController extends ObjectManagementWebviewController<
RestoreDatabaseFormState,
RestoreDatabaseReducers<RestoreDatabaseFormState>
> {
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

PR description/title indicate only adding tests, but this change adds a large new production controller (RestoreDatabaseWebviewController) into backupDatabaseWebviewController.ts and also modifies the existing restore controller. Please confirm whether these production code changes are intentional and update the PR description accordingly (or revert the unrelated additions).

Copilot uses AI. Check for mistakes.
Comment on lines +102 to +112
controller = new RestoreDatabaseWebviewController(
mockContext,
vscodeWrapper,
mockObjectManagementService,
mockConnectionManager,
mockFileBrowserService,
mockAzureBlobService,
mockProfile,
"ownerUri",
mockProfile.database,
);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

mockFileBrowserService is declared but never assigned before being passed into the RestoreDatabaseWebviewController constructor. This will trigger TS2454 (used before assignment) and can also cause runtime failures when file browser reducers are registered. Initialize it in setup (e.g., create a stub instance) before constructing the controller.

Copilot uses AI. Check for mistakes.

getRestoreConfigInfoStub =
mockObjectManagementService.getRestoreConfigInfo as sinon.SinonStub;
mockObjectManagementService.getBackupConfigInfo as sinon.SinonStub;
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This line is a no-op expression (mockObjectManagementService.getBackupConfigInfo as sinon.SinonStub;) and looks like leftover scaffolding. It can be flagged by linting/no-unused-expressions rules; remove it or assign it to a variable if you intended to configure the stub.

Suggested change
mockObjectManagementService.getBackupConfigInfo as sinon.SinonStub;

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

You could probably remove this since it's not being assigned to anything.

Comment on lines +178 to +180
mockInitialState.formComponents = controller[
"setFormComponents"
] as typeof mockInitialState.formComponents;
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

mockInitialState.formComponents is being assigned to the setFormComponents function reference instead of the function result. This makes formComponents incorrect for later tests that spread/use it. Call controller["setFormComponents"]() (or reuse controller.state.formComponents) to capture the actual components object.

Suggested change
mockInitialState.formComponents = controller[
"setFormComponents"
] as typeof mockInitialState.formComponents;
mockInitialState.formComponents = controller["state"]
.formComponents as typeof mockInitialState.formComponents;

Copilot uses AI. Check for mistakes.
Comment on lines +102 to +177
controller = new RestoreDatabaseWebviewController(
mockContext,
vscodeWrapper,
mockObjectManagementService,
mockConnectionManager,
mockFileBrowserService,
mockAzureBlobService,
mockProfile,
"ownerUri",
mockProfile.database,
);

mockInitialState = {
viewModel: {
dialogType: ObjectManagementDialogType.RestoreDatabase,
model: {
loadState: ApiStatus.Loaded,
azureComponentStatuses: {
accountId: ApiStatus.NotStarted,
tenantId: ApiStatus.NotStarted,
subscriptionId: ApiStatus.NotStarted,
storageAccountId: ApiStatus.NotStarted,
blobContainerId: ApiStatus.NotStarted,
},
type: DisasterRecoveryType.BackupFile,
backupFiles: [
{
filePath: `${mockConfigInfo.configInfo.defaultBackupFolder}/${defaultBackupName}`,
isExisting: false,
},
],
tenants: [],
subscriptions: [],
storageAccounts: [],
blobContainers: [],
url: "",
serverName: "serverName",
restorePlan: undefined,
restorePlanStatus: ApiStatus.NotStarted,
blobs: [],
cachedRestorePlanParams: undefined,
selectedBackupSets: [],
} as RestoreDatabaseViewModel,
},
ownerUri: "ownerUri",
databaseName: "testDatabase",
defaultFileBrowserExpandPath: mockConfigInfo.configInfo.defaultBackupFolder,
formState: {
sourceDatabaseName: "",
targetDatabaseName: "",
accountId: "",
tenantId: "",
subscriptionId: "",
storageAccountId: "",
blobContainerId: "",
dataFileFolder: mockConfigInfo.configInfo.dataFileFolder,
logFileFolder: mockConfigInfo.configInfo.logFileFolder,
} as RestoreDatabaseFormState,
formComponents: {},
fileBrowserState: undefined,
dialog: undefined,
formErrors: [],
fileFilterOptions: [
{
displayName: LocConstants.BackupDatabase.backupFileTypes,
value: defaultBackupFileTypes,
},
{
displayName: LocConstants.BackupDatabase.allFiles,
value: allFileTypes,
},
],
} as ObjectManagementWebviewState<RestoreDatabaseFormState>;

await controller["initializeDialog"]();

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The controller constructor calls start(), which kicks off an async initializeDialog() automatically. The test then calls initializeDialog() again, causing multiple overlapping initializations and potential race/flakiness. Consider stubbing RestoreDatabaseWebviewController.prototype.start to a no-op (or using a test subclass) so the test controls when initialization runs.

Copilot uses AI. Check for mistakes.
expect(result.options["backupFilePaths"]).to.equal("");
expect(result.options["readHeaderFromMedia"]).to.be.false;
expect(result.readHeaderFromMedia).to.be.false;
// AssertionError: expected '' to equal 'testDatabase'
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The inline // AssertionError: expected '' to equal 'testDatabase' looks like leftover debugging output. Please remove it (or replace with a brief comment describing the intended behavior) to keep the test readable.

Suggested change
// AssertionError: expected '' to equal 'testDatabase'
// Expect the source database name to be preserved from the state

Copilot uses AI. Check for mistakes.

void this.getRestorePlan(false)
.then((state) => {
restoreViewModel = this.setDefaultFormValuesFromPlan(state);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

setDefaultFormValuesFromPlan(state) mutates state.formState / view model defaults, but the result is no longer propagated back to the webview (the previous updateViewModel(..., state) call was removed). This makes the default values computed from the restore plan effectively invisible to the UI. Consider calling updateState(state) or updating the view model/state after applying defaults so the webview receives the updated values.

Suggested change
restoreViewModel = this.setDefaultFormValuesFromPlan(state);
restoreViewModel = this.setDefaultFormValuesFromPlan(state);
this.updateViewModel(restoreViewModel);

Copilot uses AI. Check for mistakes.
Comment on lines +966 to +969
export class RestoreDatabaseWebviewController extends ObjectManagementWebviewController<
RestoreDatabaseFormState,
RestoreDatabaseReducers<RestoreDatabaseFormState>
> {
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This file now contains a full RestoreDatabaseWebviewController implementation. There is already an exported RestoreDatabaseWebviewController in src/controllers/restoreDatabaseWebviewController.ts; duplicating the controller here will diverge over time and makes it ambiguous which module should be imported. Please remove the restore controller from backupDatabaseWebviewController.ts (and keep it in its dedicated file), or refactor to a shared helper if code reuse is the goal.

Copilot uses AI. Check for mistakes.
//#endregion
}

export class RestoreDatabaseWebviewController extends ObjectManagementWebviewController<
Copy link
Contributor

@lewis-sanchez lewis-sanchez Mar 12, 2026

Choose a reason for hiding this comment

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

What's the difference between the RestoreDatabaseWebViewController class here and the one defined in restoreDatabaseWebviewController.ts? The two classes look identical. You might want to remove this one.

azureProfile.database,
);

let getRestorePlanStub = sandbox.stub(azureController as any, "getRestorePlan");
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you able to clean up some of the any usages in this file? Public methods/services could use sandbox.createStubInstance<T>().

If your using any types to mock private methods, then sinon won't be able to help there, but you could try exercising private methods indirectly through public reducers and mocking any injected services with createStubInstance.

let mockObjectManagementService: ObjectManagementService;
let mockProfile: ConnectionProfile;
let mockConnectionManager: sinon.SinonStubbedInstance<ConnectionManager>;
let mockFileBrowserService: FileBrowserService;
Copy link
Contributor

Choose a reason for hiding this comment

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

You can probably remove this. It's only being passed into the controller constructor and since it's not setup or interacted with as a mock this is just being set to undefined

let mockInitialState: ObjectManagementWebviewState<RestoreDatabaseFormState>;
let vscodeWrapper: sinon.SinonStubbedInstance<VscodeWrapper>;
let getRestoreConfigInfoStub: sinon.SinonStub;
// let getRestorePlanStub: sinon.SinonStub;
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove this commented out code.

.stub(happyController as any, "setDefaultFormValuesFromPlan")
.returns(mockInitialState.viewModel.model);
await happyController["initializeDialog"]();
await new Promise((resolve) => setTimeout(resolve, 0));
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace this line with await Promise.resolve();

sandbox.restore();
});

test("should initialize with correct state", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This test has 5 paths. See if you can break each path into it's own unit test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants