1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-23226] ToTp updating on Desktop (#15435)

* update `totpInfo$` observable when the cipher changes

* mark cipher as required and remove ignore statements

* adds totp countdown tests
This commit is contained in:
Nick Krantz
2025-07-02 15:26:47 -05:00
committed by GitHub
parent ece5ebe844
commit 24ac3b3a07
2 changed files with 116 additions and 5 deletions

View File

@@ -0,0 +1,95 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { mock } from "jest-mock-extended";
import { of } from "rxjs";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { BitTotpCountdownComponent } from "./totp-countdown.component";
describe("BitTotpCountdownComponent", () => {
let component: BitTotpCountdownComponent;
let fixture: ComponentFixture<BitTotpCountdownComponent>;
let totpService: jest.Mocked<TotpService>;
const mockCipher1 = {
id: "cipher-id",
name: "Test Cipher",
login: { totp: "totp-secret" },
} as CipherView;
const mockCipher2 = {
id: "cipher-id-2",
name: "Test Cipher 2",
login: { totp: "totp-secret-2" },
} as CipherView;
const mockTotpResponse1 = {
code: "123456",
period: 30,
};
const mockTotpResponse2 = {
code: "987654",
period: 10,
};
beforeEach(async () => {
totpService = mock<TotpService>({
getCode$: jest.fn().mockImplementation((totp) => {
if (totp === mockCipher1.login.totp) {
return of(mockTotpResponse1);
}
return of(mockTotpResponse2);
}),
});
await TestBed.configureTestingModule({
providers: [{ provide: TotpService, useValue: totpService }],
}).compileComponents();
fixture = TestBed.createComponent(BitTotpCountdownComponent);
component = fixture.componentInstance;
component.cipher = mockCipher1;
fixture.detectChanges();
});
it("initializes totpInfo$ observable", (done) => {
component.totpInfo$?.subscribe((info) => {
expect(info.totpCode).toBe(mockTotpResponse1.code);
expect(info.totpCodeFormatted).toBe("123 456");
done();
});
});
it("emits sendCopyCode when TOTP code is available", (done) => {
const emitter = jest.spyOn(component.sendCopyCode, "emit");
component.totpInfo$?.subscribe((info) => {
expect(emitter).toHaveBeenCalledWith({
totpCode: info.totpCode,
totpCodeFormatted: info.totpCodeFormatted,
});
done();
});
});
it("updates totpInfo$ when cipher changes", (done) => {
component.cipher = mockCipher2;
component.ngOnChanges({
cipher: {
currentValue: mockCipher2,
previousValue: mockCipher1,
firstChange: false,
isFirstChange: () => false,
},
});
component.totpInfo$?.subscribe((info) => {
expect(info.totpCode).toBe(mockTotpResponse2.code);
expect(info.totpCodeFormatted).toBe("987 654");
done();
});
});
});

View File

@@ -1,7 +1,13 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
OnChanges,
SimpleChanges,
} from "@angular/core";
import { Observable, map, tap } from "rxjs";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
@@ -14,8 +20,8 @@ import { TypographyModule } from "@bitwarden/components";
templateUrl: "totp-countdown.component.html",
imports: [CommonModule, TypographyModule],
})
export class BitTotpCountdownComponent implements OnInit {
@Input() cipher: CipherView;
export class BitTotpCountdownComponent implements OnInit, OnChanges {
@Input({ required: true }) cipher!: CipherView;
@Output() sendCopyCode = new EventEmitter();
/**
@@ -26,6 +32,16 @@ export class BitTotpCountdownComponent implements OnInit {
constructor(protected totpService: TotpService) {}
async ngOnInit() {
this.setTotpInfo();
}
ngOnChanges(changes: SimpleChanges) {
if (changes["cipher"]) {
this.setTotpInfo();
}
}
private setTotpInfo(): void {
this.totpInfo$ = this.cipher?.login?.totp
? this.totpService.getCode$(this.cipher.login.totp).pipe(
map((response) => {