1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-22 11:13:46 +00:00
Files
browser/apps/desktop/src/autofill/modal/credentials/fido2-create.component.spec.ts
Todd Martin 27d82aaf28 feat(accounts): Add creationDate of account to AccountInfo
* Add creationDate of account to AccountInfo

* Added initialization of creationDate.

* Removed extra changes.

* Fixed tests to initialize creation date

* Added helper method to abstract account initialization in tests.

* More test updates.

* Linting

* Additional test fixes.

* Fixed spec reference

* Fixed imports

* Linting.

* Fixed browser test.

* Modified tsconfig to reference spec file.

* Fixed import.

* Removed dependency on os.  This is necessary so that the @bitwarden/common/spec lib package can be referenced in tests without node.

* Revert "Removed dependency on os.  This is necessary so that the @bitwarden/common/spec lib package can be referenced in tests without node."

This reverts commit 669f6557b6.

* Updated stories to hard-code new field.

* Removed changes to tsconfig

* Revert "Removed changes to tsconfig"

This reverts commit b7d916e8dc.
2025-12-12 10:03:31 -05:00

241 lines
9.2 KiB
TypeScript

import { TestBed } from "@angular/core/testing";
import { Router } from "@angular/router";
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, of } from "rxjs";
import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { mockAccountInfoWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { DialogService } from "@bitwarden/components";
import { PasswordRepromptService } from "@bitwarden/vault";
import { DesktopAutofillService } from "../../../autofill/services/desktop-autofill.service";
import { DesktopSettingsService } from "../../../platform/services/desktop-settings.service";
import {
DesktopFido2UserInterfaceService,
DesktopFido2UserInterfaceSession,
} from "../../services/desktop-fido2-user-interface.service";
import { Fido2CreateComponent } from "./fido2-create.component";
describe("Fido2CreateComponent", () => {
let component: Fido2CreateComponent;
let mockDesktopSettingsService: MockProxy<DesktopSettingsService>;
let mockFido2UserInterfaceService: MockProxy<DesktopFido2UserInterfaceService>;
let mockAccountService: MockProxy<AccountService>;
let mockCipherService: MockProxy<CipherService>;
let mockDesktopAutofillService: MockProxy<DesktopAutofillService>;
let mockDialogService: MockProxy<DialogService>;
let mockDomainSettingsService: MockProxy<DomainSettingsService>;
let mockLogService: MockProxy<LogService>;
let mockPasswordRepromptService: MockProxy<PasswordRepromptService>;
let mockRouter: MockProxy<Router>;
let mockSession: MockProxy<DesktopFido2UserInterfaceSession>;
let mockI18nService: MockProxy<I18nService>;
const activeAccountSubject = new BehaviorSubject<Account | null>({
id: "test-user-id" as UserId,
...mockAccountInfoWith({
email: "test@example.com",
name: "Test User",
}),
});
beforeEach(async () => {
mockDesktopSettingsService = mock<DesktopSettingsService>();
mockFido2UserInterfaceService = mock<DesktopFido2UserInterfaceService>();
mockAccountService = mock<AccountService>();
mockCipherService = mock<CipherService>();
mockDesktopAutofillService = mock<DesktopAutofillService>();
mockDialogService = mock<DialogService>();
mockDomainSettingsService = mock<DomainSettingsService>();
mockLogService = mock<LogService>();
mockPasswordRepromptService = mock<PasswordRepromptService>();
mockRouter = mock<Router>();
mockSession = mock<DesktopFido2UserInterfaceSession>();
mockI18nService = mock<I18nService>();
mockFido2UserInterfaceService.getCurrentSession.mockReturnValue(mockSession);
mockAccountService.activeAccount$ = activeAccountSubject;
await TestBed.configureTestingModule({
providers: [
Fido2CreateComponent,
{ provide: DesktopSettingsService, useValue: mockDesktopSettingsService },
{ provide: DesktopFido2UserInterfaceService, useValue: mockFido2UserInterfaceService },
{ provide: AccountService, useValue: mockAccountService },
{ provide: CipherService, useValue: mockCipherService },
{ provide: DesktopAutofillService, useValue: mockDesktopAutofillService },
{ provide: DialogService, useValue: mockDialogService },
{ provide: DomainSettingsService, useValue: mockDomainSettingsService },
{ provide: LogService, useValue: mockLogService },
{ provide: PasswordRepromptService, useValue: mockPasswordRepromptService },
{ provide: Router, useValue: mockRouter },
{ provide: I18nService, useValue: mockI18nService },
],
}).compileComponents();
component = TestBed.inject(Fido2CreateComponent);
});
afterEach(() => {
jest.restoreAllMocks();
});
function createMockCiphers(): CipherView[] {
const cipher1 = new CipherView();
cipher1.id = "cipher-1";
cipher1.name = "Test Cipher 1";
cipher1.type = CipherType.Login;
cipher1.login = {
username: "test1@example.com",
uris: [{ uri: "https://example.com", match: null }],
matchesUri: jest.fn().mockReturnValue(true),
get hasFido2Credentials() {
return false;
},
} as any;
cipher1.reprompt = CipherRepromptType.None;
cipher1.deletedDate = null;
return [cipher1];
}
describe("ngOnInit", () => {
beforeEach(() => {
mockSession.getRpId.mockResolvedValue("example.com");
Object.defineProperty(mockDesktopAutofillService, "lastRegistrationRequest", {
get: jest.fn().mockReturnValue({
userHandle: new Uint8Array([1, 2, 3]),
}),
configurable: true,
});
mockDomainSettingsService.getUrlEquivalentDomains.mockReturnValue(of(new Set<string>()));
});
it("should initialize session and set show header to false", async () => {
const mockCiphers = createMockCiphers();
mockCipherService.getAllDecrypted.mockResolvedValue(mockCiphers);
await component.ngOnInit();
expect(mockFido2UserInterfaceService.getCurrentSession).toHaveBeenCalled();
expect(component.session).toBe(mockSession);
});
it("should show error dialog when no active session found", async () => {
mockFido2UserInterfaceService.getCurrentSession.mockReturnValue(null);
mockDialogService.openSimpleDialog.mockResolvedValue(false);
await component.ngOnInit();
expect(mockDialogService.openSimpleDialog).toHaveBeenCalledWith({
title: { key: "unableToSavePasskey" },
content: { key: "closeThisBitwardenWindow" },
type: "danger",
acceptButtonText: { key: "closeThisWindow" },
acceptAction: expect.any(Function),
cancelButtonText: null,
});
});
});
describe("addCredentialToCipher", () => {
beforeEach(() => {
component.session = mockSession;
});
it("should add passkey to cipher", async () => {
const cipher = createMockCiphers()[0];
await component.addCredentialToCipher(cipher);
expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(true, cipher);
});
it("should not add passkey when password reprompt is cancelled", async () => {
const cipher = createMockCiphers()[0];
cipher.reprompt = CipherRepromptType.Password;
mockPasswordRepromptService.showPasswordPrompt.mockResolvedValue(false);
await component.addCredentialToCipher(cipher);
expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(false, cipher);
});
it("should call openSimpleDialog when cipher already has a fido2 credential", async () => {
const cipher = createMockCiphers()[0];
Object.defineProperty(cipher.login, "hasFido2Credentials", {
get: jest.fn().mockReturnValue(true),
});
mockDialogService.openSimpleDialog.mockResolvedValue(true);
await component.addCredentialToCipher(cipher);
expect(mockDialogService.openSimpleDialog).toHaveBeenCalledWith({
title: { key: "overwritePasskey" },
content: { key: "alreadyContainsPasskey" },
type: "warning",
});
expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(true, cipher);
});
it("should not add passkey when user cancels overwrite dialog", async () => {
const cipher = createMockCiphers()[0];
Object.defineProperty(cipher.login, "hasFido2Credentials", {
get: jest.fn().mockReturnValue(true),
});
mockDialogService.openSimpleDialog.mockResolvedValue(false);
await component.addCredentialToCipher(cipher);
expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(false, cipher);
});
});
describe("confirmPasskey", () => {
beforeEach(() => {
component.session = mockSession;
});
it("should confirm passkey creation successfully", async () => {
await component.confirmPasskey();
expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(true);
});
it("should call openSimpleDialog when session is null", async () => {
component.session = null;
mockDialogService.openSimpleDialog.mockResolvedValue(false);
await component.confirmPasskey();
expect(mockDialogService.openSimpleDialog).toHaveBeenCalledWith({
title: { key: "unableToSavePasskey" },
content: { key: "closeThisBitwardenWindow" },
type: "danger",
acceptButtonText: { key: "closeThisWindow" },
acceptAction: expect.any(Function),
cancelButtonText: null,
});
});
});
describe("closeModal", () => {
it("should close modal and notify session", async () => {
component.session = mockSession;
await component.closeModal();
expect(mockSession.notifyConfirmCreateCredential).toHaveBeenCalledWith(false);
expect(mockSession.confirmChosenCipher).toHaveBeenCalledWith(null);
});
});
});