335 lines
13 KiB
TypeScript
335 lines
13 KiB
TypeScript
import { mock } from "jest-mock-extended";
|
|
import { firstValueFrom } from "rxjs";
|
|
|
|
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
|
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
|
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
|
import { Importer, ImportResult, ImportServiceAbstraction } from "@bitwarden/importer/core";
|
|
|
|
import NotificationBackground from "../../autofill/background/notification.background";
|
|
import { createPortSpyMock } from "../../autofill/spec/autofill-mocks";
|
|
import {
|
|
flushPromises,
|
|
sendPortMessage,
|
|
triggerRuntimeOnConnectEvent,
|
|
} from "../../autofill/spec/testing-utils";
|
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
|
import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service";
|
|
import { FilelessImportPort, FilelessImportType } from "../enums/fileless-import.enums";
|
|
|
|
import FilelessImporterBackground from "./fileless-importer.background";
|
|
|
|
jest.mock("rxjs", () => {
|
|
const rxjs = jest.requireActual("rxjs");
|
|
const { firstValueFrom } = rxjs;
|
|
return {
|
|
...rxjs,
|
|
firstValueFrom: jest.fn(firstValueFrom),
|
|
};
|
|
});
|
|
|
|
describe("FilelessImporterBackground ", () => {
|
|
let filelessImporterBackground: FilelessImporterBackground;
|
|
const configService = mock<ConfigService>();
|
|
const authService = mock<AuthService>();
|
|
const policyService = mock<PolicyService>();
|
|
const notificationBackground = mock<NotificationBackground>();
|
|
const importService = mock<ImportServiceAbstraction>();
|
|
const syncService = mock<SyncService>();
|
|
let scriptInjectorService: BrowserScriptInjectorService;
|
|
|
|
beforeEach(() => {
|
|
scriptInjectorService = new BrowserScriptInjectorService();
|
|
filelessImporterBackground = new FilelessImporterBackground(
|
|
configService,
|
|
authService,
|
|
policyService,
|
|
notificationBackground,
|
|
importService,
|
|
syncService,
|
|
scriptInjectorService,
|
|
);
|
|
filelessImporterBackground.init();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe("init", () => {
|
|
it("sets up the port message listeners on initialization of the class", () => {
|
|
expect(chrome.runtime.onConnect.addListener).toHaveBeenCalledWith(expect.any(Function));
|
|
});
|
|
});
|
|
|
|
describe("handle ports onConnect", () => {
|
|
let lpImporterPort: chrome.runtime.Port;
|
|
let manifestVersionSpy: jest.SpyInstance;
|
|
let executeScriptInTabSpy: jest.SpyInstance;
|
|
|
|
beforeEach(() => {
|
|
lpImporterPort = createPortSpyMock(FilelessImportPort.LpImporter);
|
|
manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
|
|
executeScriptInTabSpy = jest.spyOn(BrowserApi, "executeScriptInTab").mockResolvedValue(null);
|
|
jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Unlocked);
|
|
jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(true);
|
|
jest.spyOn(filelessImporterBackground as any, "removeIndividualVault");
|
|
(firstValueFrom as jest.Mock).mockResolvedValue(false);
|
|
});
|
|
|
|
it("ignores the port connection if the port name is not present in the set of filelessImportNames", async () => {
|
|
const port = createPortSpyMock("some-other-port");
|
|
|
|
triggerRuntimeOnConnectEvent(port);
|
|
await flushPromises();
|
|
|
|
expect(port.postMessage).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("posts a message to the port indicating that the fileless import feature is disabled if the user's auth status is not unlocked", async () => {
|
|
jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Locked);
|
|
|
|
triggerRuntimeOnConnectEvent(lpImporterPort);
|
|
await flushPromises();
|
|
|
|
expect(lpImporterPort.postMessage).toHaveBeenCalledWith({
|
|
command: "verifyFeatureFlag",
|
|
filelessImportEnabled: false,
|
|
});
|
|
});
|
|
|
|
it("posts a message to the port indicating that the fileless import feature is disabled if the user's policy removes individual vaults", async () => {
|
|
(firstValueFrom as jest.Mock).mockResolvedValue(true);
|
|
|
|
triggerRuntimeOnConnectEvent(lpImporterPort);
|
|
await flushPromises();
|
|
|
|
expect(lpImporterPort.postMessage).toHaveBeenCalledWith({
|
|
command: "verifyFeatureFlag",
|
|
filelessImportEnabled: false,
|
|
});
|
|
});
|
|
|
|
it("posts a message to the port indicating that the fileless import feature is disabled if the feature flag is turned off", async () => {
|
|
jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(false);
|
|
|
|
triggerRuntimeOnConnectEvent(lpImporterPort);
|
|
await flushPromises();
|
|
|
|
expect(lpImporterPort.postMessage).toHaveBeenCalledWith({
|
|
command: "verifyFeatureFlag",
|
|
filelessImportEnabled: false,
|
|
});
|
|
});
|
|
|
|
it("posts a message to the port indicating that the fileless import feature is enabled", async () => {
|
|
triggerRuntimeOnConnectEvent(lpImporterPort);
|
|
await flushPromises();
|
|
|
|
expect(lpImporterPort.postMessage).toHaveBeenCalledWith({
|
|
command: "verifyFeatureFlag",
|
|
filelessImportEnabled: true,
|
|
});
|
|
});
|
|
|
|
it("triggers an injection of the `lp-suppress-import-download.js` script in manifest v3", async () => {
|
|
manifestVersionSpy.mockReturnValue(3);
|
|
|
|
triggerRuntimeOnConnectEvent(lpImporterPort);
|
|
await flushPromises();
|
|
|
|
expect(executeScriptInTabSpy).toHaveBeenCalledWith(
|
|
lpImporterPort.sender.tab.id,
|
|
{ file: "content/lp-suppress-import-download.js", runAt: "document_start", frameId: 0 },
|
|
{ world: "MAIN" },
|
|
);
|
|
});
|
|
|
|
it("triggers an injection of the `lp-suppress-import-download-script-append-mv2.js` script in manifest v2", async () => {
|
|
manifestVersionSpy.mockReturnValue(2);
|
|
|
|
triggerRuntimeOnConnectEvent(lpImporterPort);
|
|
await flushPromises();
|
|
|
|
expect(executeScriptInTabSpy).toHaveBeenCalledWith(lpImporterPort.sender.tab.id, {
|
|
file: "content/lp-suppress-import-download-script-append-mv2.js",
|
|
runAt: "document_start",
|
|
frameId: 0,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("port messages", () => {
|
|
let notificationPort: chrome.runtime.Port;
|
|
let lpImporterPort: chrome.runtime.Port;
|
|
|
|
beforeEach(async () => {
|
|
jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Unlocked);
|
|
jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(true);
|
|
(firstValueFrom as jest.Mock).mockResolvedValue(false);
|
|
triggerRuntimeOnConnectEvent(createPortSpyMock(FilelessImportPort.NotificationBar));
|
|
triggerRuntimeOnConnectEvent(createPortSpyMock(FilelessImportPort.LpImporter));
|
|
await flushPromises();
|
|
notificationPort = filelessImporterBackground["importNotificationsPort"];
|
|
lpImporterPort = filelessImporterBackground["lpImporterPort"];
|
|
});
|
|
|
|
it("skips handling a message if a message handler is not associated with the port message command", () => {
|
|
sendPortMessage(notificationPort, { command: "commandNotFound" });
|
|
|
|
expect(chrome.tabs.sendMessage).not.toHaveBeenCalled();
|
|
});
|
|
|
|
describe("import notification port messages", () => {
|
|
describe("startFilelessImport", () => {
|
|
it("sends a message to start the LastPass fileless import within the content script", () => {
|
|
sendPortMessage(notificationPort, {
|
|
command: "startFilelessImport",
|
|
importType: FilelessImportType.LP,
|
|
});
|
|
|
|
expect(lpImporterPort.postMessage).toHaveBeenCalledWith({
|
|
command: "startLpFilelessImport",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("cancelFilelessImport", () => {
|
|
it("sends a message to close the notification bar", async () => {
|
|
sendPortMessage(notificationPort, { command: "cancelFilelessImport" });
|
|
|
|
expect(chrome.tabs.sendMessage).toHaveBeenCalledWith(
|
|
notificationPort.sender.tab.id,
|
|
{
|
|
command: "closeNotificationBar",
|
|
},
|
|
null,
|
|
expect.anything(),
|
|
);
|
|
expect(lpImporterPort.postMessage).not.toHaveBeenCalledWith({
|
|
command: "triggerCsvDownload",
|
|
});
|
|
});
|
|
|
|
it("sends a message to trigger a download of the LP importer CSV", () => {
|
|
sendPortMessage(notificationPort, {
|
|
command: "cancelFilelessImport",
|
|
importType: FilelessImportType.LP,
|
|
});
|
|
|
|
expect(lpImporterPort.postMessage).toHaveBeenCalledWith({
|
|
command: "triggerCsvDownload",
|
|
});
|
|
expect(lpImporterPort.disconnect).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("lp importer port messages", () => {
|
|
describe("displayLpImportNotification", () => {
|
|
it("creates a request fileless import notification", async () => {
|
|
jest.spyOn(filelessImporterBackground["notificationBackground"], "requestFilelessImport");
|
|
|
|
sendPortMessage(lpImporterPort, {
|
|
command: "displayLpImportNotification",
|
|
});
|
|
await flushPromises();
|
|
|
|
expect(
|
|
filelessImporterBackground["notificationBackground"].requestFilelessImport,
|
|
).toHaveBeenCalledWith(lpImporterPort.sender.tab, FilelessImportType.LP);
|
|
});
|
|
});
|
|
|
|
describe("startLpImport", () => {
|
|
it("ignores the message if the message does not contain data", () => {
|
|
sendPortMessage(lpImporterPort, {
|
|
command: "startLpImport",
|
|
});
|
|
|
|
expect(filelessImporterBackground["importService"].import).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("triggers the import of the LastPass vault", async () => {
|
|
const data = "url,username,password";
|
|
const importer = mock<Importer>();
|
|
jest
|
|
.spyOn(filelessImporterBackground["importService"], "getImporter")
|
|
.mockReturnValue(importer);
|
|
jest.spyOn(filelessImporterBackground["importService"], "import").mockResolvedValue(
|
|
mock<ImportResult>({
|
|
success: true,
|
|
}),
|
|
);
|
|
jest.spyOn(filelessImporterBackground["syncService"], "fullSync");
|
|
|
|
sendPortMessage(lpImporterPort, {
|
|
command: "startLpImport",
|
|
data,
|
|
});
|
|
await flushPromises();
|
|
|
|
expect(filelessImporterBackground["importService"].import).toHaveBeenCalledWith(
|
|
importer,
|
|
data,
|
|
null,
|
|
null,
|
|
false,
|
|
);
|
|
expect(
|
|
filelessImporterBackground["importNotificationsPort"].postMessage,
|
|
).toHaveBeenCalledWith({ command: "filelessImportCompleted" });
|
|
expect(filelessImporterBackground["syncService"].fullSync).toHaveBeenCalledWith(
|
|
true,
|
|
"import",
|
|
);
|
|
});
|
|
|
|
it("posts a failed message if the import fails", async () => {
|
|
const data = "url,username,password";
|
|
const importer = mock<Importer>();
|
|
jest
|
|
.spyOn(filelessImporterBackground["importService"], "getImporter")
|
|
.mockReturnValue(importer);
|
|
jest
|
|
.spyOn(filelessImporterBackground["importService"], "import")
|
|
.mockImplementation(() => {
|
|
throw new Error("error");
|
|
});
|
|
jest.spyOn(filelessImporterBackground["syncService"], "fullSync");
|
|
|
|
sendPortMessage(lpImporterPort, {
|
|
command: "startLpImport",
|
|
data,
|
|
});
|
|
await flushPromises();
|
|
|
|
expect(
|
|
filelessImporterBackground["importNotificationsPort"].postMessage,
|
|
).toHaveBeenCalledWith({ command: "filelessImportFailed" });
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("handleImporterPortDisconnect", () => {
|
|
it("resets the port properties to null", () => {
|
|
const lpImporterPort = createPortSpyMock(FilelessImportPort.LpImporter);
|
|
const notificationPort = createPortSpyMock(FilelessImportPort.NotificationBar);
|
|
filelessImporterBackground["lpImporterPort"] = lpImporterPort;
|
|
filelessImporterBackground["importNotificationsPort"] = notificationPort;
|
|
|
|
filelessImporterBackground["handleImporterPortDisconnect"](lpImporterPort);
|
|
|
|
expect(filelessImporterBackground["lpImporterPort"]).toBeNull();
|
|
expect(filelessImporterBackground["importNotificationsPort"]).not.toBeNull();
|
|
|
|
filelessImporterBackground["handleImporterPortDisconnect"](notificationPort);
|
|
|
|
expect(filelessImporterBackground["importNotificationsPort"]).toBeNull();
|
|
});
|
|
});
|
|
});
|