1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 13:40:06 +00:00

Add tests for autofill modal work

This commit is contained in:
Jeffrey Holland
2025-02-25 15:21:43 +01:00
parent 78758b1c29
commit 04b01f88cb
3 changed files with 319 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
import { Router } from "@angular/router";
import { mock } from "jest-mock-extended";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { DesktopSettingsService } from "src/platform/services/desktop-settings.service";
import {
DesktopFido2UserInterfaceService,
DesktopFido2UserInterfaceSession,
} from "./desktop-fido2-user-interface.service";
import type { NativeWindowObject } from "./desktop-fido2-user-interface.service";
describe("Desktop Fido2 User Interface Service", () => {
const accountService = mock<AccountService>();
const authService = mock<AuthService>();
const cipherService = mock<CipherService>();
const desktopSettingsService = mock<DesktopSettingsService>();
const logService = mock<LogService>();
const messagingService = mock<MessagingService>();
const router = mock<Router>();
let desktopFido2UserInterfaceService: DesktopFido2UserInterfaceService;
beforeEach(() => {
desktopFido2UserInterfaceService = new DesktopFido2UserInterfaceService(
authService,
cipherService,
accountService,
logService,
messagingService,
router,
desktopSettingsService,
);
});
afterEach(() => {
jest.resetAllMocks();
});
describe("newSession", () => {
it("logs a warning", async () => {
const fallbackSupported = false;
const nativeWindowObject = {};
await desktopFido2UserInterfaceService.newSession(fallbackSupported, nativeWindowObject);
expect(logService.warning).toHaveBeenCalled();
});
});
});
describe("Desktop Fido2 User Interface Session", () => {
const accountService = mock<AccountService>();
const authService = mock<AuthService>();
const cipherService = mock<CipherService>();
const desktopSettingsService = mock<DesktopSettingsService>();
const logService = mock<LogService>();
const router = mock<Router>();
const windowObject = mock<NativeWindowObject>();
let desktopFido2UserInterfaceSession: DesktopFido2UserInterfaceSession;
beforeEach(() => {
desktopFido2UserInterfaceSession = new DesktopFido2UserInterfaceSession(
authService,
cipherService,
accountService,
logService,
router,
desktopSettingsService,
windowObject,
);
});
afterEach(() => {
jest.resetAllMocks();
});
describe("availableCipherId$", () => {
it("returns available cipher ids", () => {
desktopFido2UserInterfaceSession.availableCipherIds$.subscribe({
next: (ids) => {
expect(ids).toEqual(["id1", "id2"]);
},
});
desktopFido2UserInterfaceSession["availableCipherIdsSubject"].next(["id1", "id2"]);
});
});
describe("pickCredential", () => {
it("returns a cipherId when one exists", async () => {
const result = await desktopFido2UserInterfaceSession.pickCredential({
cipherIds: ["id"],
userVerification: false,
assumeUserPresence: false,
masterPasswordRepromptRequired: false,
});
expect(result).toStrictEqual({ cipherId: "id", userVerified: false });
});
it("returns a user chosen cipherId", async () => {
jest
.spyOn(desktopFido2UserInterfaceSession as any, "waitForUiChosenCipher")
.mockResolvedValue("id2");
const result = await desktopFido2UserInterfaceSession.pickCredential({
cipherIds: ["id", "id2"],
userVerification: false,
assumeUserPresence: false,
masterPasswordRepromptRequired: false,
});
expect(result).toStrictEqual({ cipherId: "id2", userVerified: true });
});
});
});

View File

@@ -0,0 +1,161 @@
import { CommonModule } from "@angular/common";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { RouterModule, Router } from "@angular/router";
import { MockProxy, mock } from "jest-mock-extended";
import { of } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
BadgeModule,
ButtonModule,
DialogModule,
IconModule,
ItemModule,
SectionComponent,
TableModule,
} from "@bitwarden/components";
import { BitIconButtonComponent } from "../../../../../../libs/components/src/icon-button/icon-button.component";
import { SectionHeaderComponent } from "../../../../../../libs/components/src/section/section-header.component";
import {
DesktopFido2UserInterfaceService,
DesktopFido2UserInterfaceSession,
} from "../../../autofill/services/desktop-fido2-user-interface.service";
import { DesktopSettingsService } from "../../../platform/services/desktop-settings.service";
import { Fido2CreateComponent } from "./fido2-create.component";
describe("Fido2CreateComponent", () => {
let rpid: string;
let component: Fido2CreateComponent;
let fixture: ComponentFixture<Fido2CreateComponent>;
let cipherService: MockProxy<CipherService>;
let desktopSettingsService: MockProxy<DesktopSettingsService>;
let domainSettingService: MockProxy<DomainSettingsService>;
let interfaceService: MockProxy<DesktopFido2UserInterfaceService>;
let session: MockProxy<DesktopFido2UserInterfaceSession>;
let router: MockProxy<Router>;
beforeEach(async () => {
rpid = "example.com";
router = mock<Router>();
session = mock<DesktopFido2UserInterfaceSession>({
getRpId: jest.fn().mockResolvedValue(rpid),
});
cipherService = mock<CipherService>({
getAllDecrypted: jest.fn().mockResolvedValue([
{
login: {
hasUris: true,
fido2Credentials: [],
matchesUri: jest.fn().mockReturnValue(true),
},
},
{
login: {
hasUris: false,
fido2Credentials: [],
matchesUri: jest.fn().mockReturnValue(false),
},
},
]),
});
desktopSettingsService = mock<DesktopSettingsService>();
domainSettingService = mock<DomainSettingsService>({
getUrlEquivalentDomains: jest
.fn()
.mockReturnValue(of(new Set(["example.com", "example.org"]))),
equivalentDomains$: of([
["example.com", "example.org"],
["example.net", "example.info"],
]),
});
interfaceService = mock<DesktopFido2UserInterfaceService>({
getCurrentSession: jest.fn().mockReturnValue(session),
});
await TestBed.configureTestingModule({
imports: [
CommonModule,
RouterModule,
SectionHeaderComponent,
BitIconButtonComponent,
TableModule,
JslibModule,
IconModule,
ButtonModule,
DialogModule,
SectionComponent,
ItemModule,
BadgeModule,
],
providers: [
{ provide: DesktopSettingsService, useValue: desktopSettingsService },
{ provide: DesktopFido2UserInterfaceService, useValue: interfaceService },
{ provide: DesktopFido2UserInterfaceSession, useValue: session },
{ provide: CipherService, useValue: cipherService },
{ provide: DomainSettingsService, useValue: domainSettingService },
{ provide: I18nService, useValue: mock<I18nService>() },
{ provide: Router, useValue: router },
],
}).compileComponents();
fixture = TestBed.createComponent(Fido2CreateComponent);
component = fixture.componentInstance;
});
it("creates the component", () => {
expect(component).toBeTruthy();
});
it("ngOnInit", async () => {
component.session = session;
await component.ngOnInit();
expect(session.getRpId).toHaveBeenCalledTimes(1);
expect(domainSettingService.getUrlEquivalentDomains).toHaveBeenCalledWith(rpid);
expect(cipherService.getAllDecrypted).toHaveBeenCalledTimes(1);
});
it("adds pass key to cipher", async () => {
component.session = session;
const cipher = new CipherView();
await component.addPasskeyToCipher(cipher);
expect(session.notifyConfirmCredential).toHaveBeenCalledWith(true, cipher);
});
describe("confirming the pass key", () => {
// it("throws an error when no session exists", async () => {
// component.session = undefined;
// await expect(component.confirmPasskey()).rejects.toThrow("No session found");
// });
it("confirms the pass key", async () => {
component.session = session;
await component.confirmPasskey();
expect(router.navigate).toHaveBeenCalledWith(["/"]);
expect(session.notifyConfirmCredential).toHaveBeenCalledWith(true);
expect(desktopSettingsService.setModalMode).toHaveBeenCalledWith(false);
});
});
it("closes the modal", async () => {
component.session = session;
await component.closeModal();
expect(router.navigate).toHaveBeenCalledWith(["/"]);
expect(desktopSettingsService.setModalMode).toHaveBeenCalledWith(false);
expect(session.notifyConfirmCredential).toHaveBeenCalledWith(false);
expect(session.confirmChosenCipher).toHaveBeenCalledWith(null);
});
});

View File

@@ -36,6 +36,7 @@ import { CipherCreateRequest } from "../models/request/cipher-create.request";
import { CipherPartialRequest } from "../models/request/cipher-partial.request";
import { CipherRequest } from "../models/request/cipher.request";
import { CipherView } from "../models/view/cipher.view";
import { Fido2CredentialView } from "../models/view/fido2-credential.view";
import { LoginUriView } from "../models/view/login-uri.view";
import { CipherService } from "./cipher.service";
@@ -414,4 +415,41 @@ describe("Cipher Service", () => {
);
});
});
describe("getAllDecryptedForIds", () => {
it("returns ciphers for the given ids", async () => {
const startingCiphers = [{ ...cipherObj }, { ...cipherObj, id: "id2" }];
const expectedObj = [cipherObj];
jest.spyOn(cipherService as any, "getAllDecrypted").mockResolvedValue(startingCiphers);
const result = await cipherService.getAllDecryptedForIds(["id"]);
expect(result).toEqual(expectedObj);
});
});
describe("getPasskeyCiphers", () => {
it("returns the fido2 ciphers when they are available", async () => {
const passkeyDate = new Date();
const cipherWithFido2 = [
{
...cipherObj,
login: { fido2Credentials: [{ creationDate: passkeyDate } as Fido2CredentialView] },
},
];
jest.spyOn(cipherService as any, "getAllDecrypted").mockResolvedValue(cipherWithFido2);
const result = await cipherService.getAllDecrypted();
expect(result).toBe(cipherWithFido2);
});
it("returns null when there are no ciphers", async () => {
jest.spyOn(cipherService as any, "getAllDecrypted").mockResolvedValue(null);
const result = await cipherService.getPasskeyCiphers();
expect(result).toBe(null);
});
});
});