diff --git a/apps/web/src/app/auth/emergency-access/models/emergency-access.ts b/apps/web/src/app/auth/emergency-access/models/emergency-access.ts index b8ae5907bb9..51edca6e671 100644 --- a/apps/web/src/app/auth/emergency-access/models/emergency-access.ts +++ b/apps/web/src/app/auth/emergency-access/models/emergency-access.ts @@ -5,6 +5,10 @@ import { KdfType } from "@bitwarden/key-management"; import { EmergencyAccessStatusType } from "../enums/emergency-access-status-type"; import { EmergencyAccessType } from "../enums/emergency-access-type"; +import { + EmergencyAccessGranteeDetailsResponse, + EmergencyAccessGrantorDetailsResponse, +} from "../response/emergency-access.response"; export class GranteeEmergencyAccess { id: string; @@ -16,6 +20,24 @@ export class GranteeEmergencyAccess { waitTimeDays: number; creationDate: string; avatarColor: string; + + constructor(partial: Partial = {}) { + Object.assign(this, partial); + } + + static fromResponse(response: EmergencyAccessGranteeDetailsResponse) { + return new GranteeEmergencyAccess({ + id: response.id, + granteeId: response.granteeId, + name: response.name, + email: response.email, + type: response.type, + status: response.status, + waitTimeDays: response.waitTimeDays, + creationDate: response.creationDate, + avatarColor: response.avatarColor, + }); + } } export class GrantorEmergencyAccess { @@ -28,6 +50,24 @@ export class GrantorEmergencyAccess { waitTimeDays: number; creationDate: string; avatarColor: string; + + constructor(partial: Partial = {}) { + Object.assign(this, partial); + } + + static fromResponse(response: EmergencyAccessGrantorDetailsResponse) { + return new GrantorEmergencyAccess({ + id: response.id, + grantorId: response.grantorId, + name: response.name, + email: response.email, + type: response.type, + status: response.status, + waitTimeDays: response.waitTimeDays, + creationDate: response.creationDate, + avatarColor: response.avatarColor, + }); + } } export class TakeoverTypeEmergencyAccess { diff --git a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts index 752e9dc1ce0..05373534ce7 100644 --- a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts +++ b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts @@ -22,9 +22,11 @@ import { KdfType, KeyService } from "@bitwarden/key-management"; import { EmergencyAccessStatusType } from "../enums/emergency-access-status-type"; import { EmergencyAccessType } from "../enums/emergency-access-type"; +import { GranteeEmergencyAccess, GrantorEmergencyAccess } from "../models/emergency-access"; import { EmergencyAccessPasswordRequest } from "../request/emergency-access-password.request"; import { EmergencyAccessGranteeDetailsResponse, + EmergencyAccessGrantorDetailsResponse, EmergencyAccessTakeoverResponse, } from "../response/emergency-access.response"; @@ -242,11 +244,19 @@ describe("EmergencyAccessService", () => { const mockEmergencyAccess = { data: [ - createMockEmergencyAccess("0", "EA 0", EmergencyAccessStatusType.Invited), - createMockEmergencyAccess("1", "EA 1", EmergencyAccessStatusType.Accepted), - createMockEmergencyAccess("2", "EA 2", EmergencyAccessStatusType.Confirmed), - createMockEmergencyAccess("3", "EA 3", EmergencyAccessStatusType.RecoveryInitiated), - createMockEmergencyAccess("4", "EA 4", EmergencyAccessStatusType.RecoveryApproved), + createMockEmergencyAccessGranteeDetails("0", "EA 0", EmergencyAccessStatusType.Invited), + createMockEmergencyAccessGranteeDetails("1", "EA 1", EmergencyAccessStatusType.Accepted), + createMockEmergencyAccessGranteeDetails("2", "EA 2", EmergencyAccessStatusType.Confirmed), + createMockEmergencyAccessGranteeDetails( + "3", + "EA 3", + EmergencyAccessStatusType.RecoveryInitiated, + ), + createMockEmergencyAccessGranteeDetails( + "4", + "EA 4", + EmergencyAccessStatusType.RecoveryApproved, + ), ], } as ListResponse; @@ -295,9 +305,113 @@ describe("EmergencyAccessService", () => { ).rejects.toThrow("New user key is required for rotation."); }); }); + + describe("getEmergencyAccessTrusted", () => { + it("should return an empty array if no emergency access is granted", async () => { + emergencyAccessApiService.getEmergencyAccessTrusted.mockResolvedValue({ + data: [], + } as ListResponse); + + const result = await emergencyAccessService.getEmergencyAccessTrusted(); + + expect(result).toEqual([]); + }); + + it("should return an empty array if the API returns an empty response", async () => { + emergencyAccessApiService.getEmergencyAccessTrusted.mockResolvedValue( + null as unknown as ListResponse, + ); + + const result = await emergencyAccessService.getEmergencyAccessTrusted(); + + expect(result).toEqual([]); + }); + + it("should return a list of trusted emergency access contacts", async () => { + const mockEmergencyAccess = [ + createMockEmergencyAccessGranteeDetails("1", "EA 1", EmergencyAccessStatusType.Invited), + createMockEmergencyAccessGranteeDetails("2", "EA 2", EmergencyAccessStatusType.Invited), + createMockEmergencyAccessGranteeDetails("3", "EA 3", EmergencyAccessStatusType.Accepted), + createMockEmergencyAccessGranteeDetails("4", "EA 4", EmergencyAccessStatusType.Confirmed), + createMockEmergencyAccessGranteeDetails( + "5", + "EA 5", + EmergencyAccessStatusType.RecoveryInitiated, + ), + ]; + emergencyAccessApiService.getEmergencyAccessTrusted.mockResolvedValue({ + data: mockEmergencyAccess, + } as ListResponse); + + const result = await emergencyAccessService.getEmergencyAccessTrusted(); + + expect(result).toHaveLength(mockEmergencyAccess.length); + + result.forEach((access, index) => { + expect(access).toBeInstanceOf(GranteeEmergencyAccess); + + expect(access.id).toBe(mockEmergencyAccess[index].id); + expect(access.name).toBe(mockEmergencyAccess[index].name); + expect(access.status).toBe(mockEmergencyAccess[index].status); + expect(access.type).toBe(mockEmergencyAccess[index].type); + }); + }); + }); + + describe("getEmergencyAccessGranted", () => { + it("should return an empty array if no emergency access is granted", async () => { + emergencyAccessApiService.getEmergencyAccessGranted.mockResolvedValue({ + data: [], + } as ListResponse); + + const result = await emergencyAccessService.getEmergencyAccessGranted(); + + expect(result).toEqual([]); + }); + + it("should return an empty array if the API returns an empty response", async () => { + emergencyAccessApiService.getEmergencyAccessGranted.mockResolvedValue( + null as unknown as ListResponse, + ); + + const result = await emergencyAccessService.getEmergencyAccessGranted(); + + expect(result).toEqual([]); + }); + + it("should return a list of granted emergency access contacts", async () => { + const mockEmergencyAccess = [ + createMockEmergencyAccessGrantorDetails("1", "EA 1", EmergencyAccessStatusType.Invited), + createMockEmergencyAccessGrantorDetails("2", "EA 2", EmergencyAccessStatusType.Invited), + createMockEmergencyAccessGrantorDetails("3", "EA 3", EmergencyAccessStatusType.Accepted), + createMockEmergencyAccessGrantorDetails("4", "EA 4", EmergencyAccessStatusType.Confirmed), + createMockEmergencyAccessGrantorDetails( + "5", + "EA 5", + EmergencyAccessStatusType.RecoveryInitiated, + ), + ]; + emergencyAccessApiService.getEmergencyAccessGranted.mockResolvedValue({ + data: mockEmergencyAccess, + } as ListResponse); + + const result = await emergencyAccessService.getEmergencyAccessGranted(); + + expect(result).toHaveLength(mockEmergencyAccess.length); + + result.forEach((access, index) => { + expect(access).toBeInstanceOf(GrantorEmergencyAccess); + + expect(access.id).toBe(mockEmergencyAccess[index].id); + expect(access.name).toBe(mockEmergencyAccess[index].name); + expect(access.status).toBe(mockEmergencyAccess[index].status); + expect(access.type).toBe(mockEmergencyAccess[index].type); + }); + }); + }); }); -function createMockEmergencyAccess( +function createMockEmergencyAccessGranteeDetails( id: string, name: string, status: EmergencyAccessStatusType, @@ -309,3 +423,16 @@ function createMockEmergencyAccess( emergencyAccess.status = status; return emergencyAccess; } + +function createMockEmergencyAccessGrantorDetails( + id: string, + name: string, + status: EmergencyAccessStatusType, +): EmergencyAccessGrantorDetailsResponse { + const emergencyAccess = new EmergencyAccessGrantorDetailsResponse({}); + emergencyAccess.id = id; + emergencyAccess.name = name; + emergencyAccess.type = 0; + emergencyAccess.status = status; + return emergencyAccess; +} diff --git a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts index 673ab7443f9..a814af32505 100644 --- a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts +++ b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts @@ -77,14 +77,22 @@ export class EmergencyAccessService * Gets all emergency access that the user has been granted. */ async getEmergencyAccessTrusted(): Promise { - return (await this.emergencyAccessApiService.getEmergencyAccessTrusted()).data; + const listResponse = await this.emergencyAccessApiService.getEmergencyAccessTrusted(); + if (!listResponse || listResponse.data.length === 0) { + return []; + } + return listResponse.data.map((response) => GranteeEmergencyAccess.fromResponse(response)); } /** * Gets all emergency access that the user has granted. */ async getEmergencyAccessGranted(): Promise { - return (await this.emergencyAccessApiService.getEmergencyAccessGranted()).data; + const listResponse = await this.emergencyAccessApiService.getEmergencyAccessGranted(); + if (!listResponse || listResponse.data.length === 0) { + return []; + } + return listResponse.data.map((response) => GrantorEmergencyAccess.fromResponse(response)); } /**