mirror of
https://github.com/bitwarden/clients.git
synced 2026-01-31 14:23:38 +08:00
PM-20112 fixing type errors
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<app-header>
|
||||
@if (!(isLoading$ | async)) {
|
||||
@if (!isLoading()) {
|
||||
<bit-search
|
||||
[formControl]="searchControl"
|
||||
[placeholder]="'searchMembers' | i18n"
|
||||
@@ -22,7 +22,7 @@
|
||||
@if (currentProgressStep(); as progressState) {
|
||||
<!-- Show progress loading component during data generation -->
|
||||
<app-member-access-loading [progressState]="progressState"></app-member-access-loading>
|
||||
} @else if (isLoading$ | async) {
|
||||
} @else if (isLoading()) {
|
||||
<!-- Fallback loading state (before progress tracking starts) -->
|
||||
<div class="tw-flex-col tw-flex tw-justify-center tw-items-center tw-gap-5 tw-mt-4">
|
||||
<i
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, DestroyRef, inject, OnInit, signal } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormControl } from "@angular/forms";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { BehaviorSubject, debounceTime, firstValueFrom, lastValueFrom, skip } from "rxjs";
|
||||
import { debounceTime, firstValueFrom, lastValueFrom, skip } from "rxjs";
|
||||
|
||||
import {
|
||||
CollectionAdminService,
|
||||
@@ -78,9 +76,9 @@ type ProgressStep = MemberAccessProgressState | null;
|
||||
export class MemberAccessReportComponent implements OnInit {
|
||||
protected dataSource = new TableDataSource<MemberAccessReportView>();
|
||||
protected searchControl = new FormControl("", { nonNullable: true });
|
||||
protected organizationId: OrganizationId;
|
||||
protected orgIsOnSecretsManagerStandalone: boolean;
|
||||
protected isLoading$ = new BehaviorSubject(true);
|
||||
protected organizationId!: OrganizationId;
|
||||
protected orgIsOnSecretsManagerStandalone!: boolean;
|
||||
protected readonly isLoading = signal(true);
|
||||
|
||||
/** Current progress state for the loading component */
|
||||
protected readonly currentProgressStep = signal<ProgressStep>(null);
|
||||
@@ -120,7 +118,7 @@ export class MemberAccessReportComponent implements OnInit {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.isLoading$.next(true);
|
||||
this.isLoading.set(true);
|
||||
|
||||
const params = await firstValueFrom(this.route.params);
|
||||
this.organizationId = params.organizationId;
|
||||
@@ -133,7 +131,7 @@ export class MemberAccessReportComponent implements OnInit {
|
||||
|
||||
await this.load();
|
||||
|
||||
this.isLoading$.next(false);
|
||||
this.isLoading.set(false);
|
||||
}
|
||||
|
||||
async load() {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
@@ -10,11 +8,11 @@ import { MemberAccessReportView } from "../view/member-access-report.view";
|
||||
|
||||
export abstract class MemberAccessReportServiceAbstraction {
|
||||
/** Observable for progress state updates during report generation */
|
||||
progress$: Observable<MemberAccessProgressState | null>;
|
||||
generateMemberAccessReportView: (
|
||||
abstract readonly progress$: Observable<MemberAccessProgressState | null>;
|
||||
abstract generateMemberAccessReportView(
|
||||
organizationId: OrganizationId,
|
||||
) => Promise<MemberAccessReportView[]>;
|
||||
generateUserReportExportItems: (
|
||||
): Promise<MemberAccessReportView[]>;
|
||||
abstract generateUserReportExportItems(
|
||||
organizationId: OrganizationId,
|
||||
) => Promise<MemberAccessExportItem[]>;
|
||||
): Promise<MemberAccessExportItem[]>;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { of } from "rxjs";
|
||||
import {
|
||||
CollectionAdminService,
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserUserDetailsResponse,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CollectionAdminView } from "@bitwarden/common/admin-console/models/collections";
|
||||
@@ -66,43 +67,52 @@ describe("MemberAccessReportService", () => {
|
||||
describe("(No Name) fallback", () => {
|
||||
it("should use '(No Name)' when user has empty name string", async () => {
|
||||
// Setup mock data with empty name
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue({
|
||||
data: [
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue(
|
||||
new ListResponse(
|
||||
{
|
||||
id: "user1",
|
||||
name: "", // Empty name
|
||||
email: "user@example.com",
|
||||
twoFactorEnabled: false,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1"],
|
||||
avatarColor: null,
|
||||
} as any,
|
||||
],
|
||||
} as ListResponse<any>);
|
||||
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(
|
||||
of([
|
||||
{
|
||||
id: "col1",
|
||||
name: "Test Collection",
|
||||
groups: [{ id: "group1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
users: [],
|
||||
} as CollectionAdminView,
|
||||
]),
|
||||
Data: [
|
||||
{
|
||||
id: "user1",
|
||||
name: "", // Empty name
|
||||
email: "user@example.com",
|
||||
twoFactorEnabled: false,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1"],
|
||||
avatarColor: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
OrganizationUserUserDetailsResponse,
|
||||
),
|
||||
);
|
||||
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([
|
||||
{
|
||||
id: "group1",
|
||||
name: "Test Group",
|
||||
collections: [{ id: "col1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
} as GroupDetailsView,
|
||||
]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue({
|
||||
data: [] as CipherResponse[],
|
||||
const collection = new CollectionAdminView({
|
||||
id: "col1" as any,
|
||||
name: "Test Collection",
|
||||
organizationId: mockOrganizationId,
|
||||
});
|
||||
Object.assign(collection, {
|
||||
groups: [{ id: "group1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
users: [],
|
||||
});
|
||||
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(of([collection]));
|
||||
|
||||
const group = new GroupDetailsView();
|
||||
Object.assign(group, {
|
||||
id: "group1",
|
||||
organizationId: mockOrganizationId as any,
|
||||
name: "Test Group",
|
||||
externalId: null,
|
||||
collections: [{ id: "col1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
});
|
||||
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([group]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue(
|
||||
new ListResponse({ Data: [], ContinuationToken: null }, CipherResponse),
|
||||
);
|
||||
|
||||
// Generate report view first (required before export)
|
||||
await service.generateMemberAccessReportView(mockOrganizationId);
|
||||
@@ -119,43 +129,52 @@ describe("MemberAccessReportService", () => {
|
||||
|
||||
it("should use actual name when user has non-empty name", async () => {
|
||||
// Setup mock data with actual name
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue({
|
||||
data: [
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue(
|
||||
new ListResponse(
|
||||
{
|
||||
id: "user1",
|
||||
name: "John Doe",
|
||||
email: "john@example.com",
|
||||
twoFactorEnabled: false,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1"],
|
||||
avatarColor: null,
|
||||
} as any,
|
||||
],
|
||||
} as ListResponse<any>);
|
||||
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(
|
||||
of([
|
||||
{
|
||||
id: "col1",
|
||||
name: "Test Collection",
|
||||
groups: [{ id: "group1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
users: [],
|
||||
} as CollectionAdminView,
|
||||
]),
|
||||
Data: [
|
||||
{
|
||||
id: "user1",
|
||||
name: "John Doe",
|
||||
email: "john@example.com",
|
||||
twoFactorEnabled: false,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1"],
|
||||
avatarColor: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
OrganizationUserUserDetailsResponse,
|
||||
),
|
||||
);
|
||||
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([
|
||||
{
|
||||
id: "group1",
|
||||
name: "Test Group",
|
||||
collections: [{ id: "col1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
} as GroupDetailsView,
|
||||
]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue({
|
||||
data: [] as CipherResponse[],
|
||||
const collection = new CollectionAdminView({
|
||||
id: "col1" as any,
|
||||
name: "Test Collection",
|
||||
organizationId: mockOrganizationId,
|
||||
});
|
||||
Object.assign(collection, {
|
||||
groups: [{ id: "group1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
users: [],
|
||||
});
|
||||
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(of([collection]));
|
||||
|
||||
const group = new GroupDetailsView();
|
||||
Object.assign(group, {
|
||||
id: "group1",
|
||||
organizationId: mockOrganizationId as any,
|
||||
name: "Test Group",
|
||||
externalId: null,
|
||||
collections: [{ id: "col1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
});
|
||||
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([group]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue(
|
||||
new ListResponse({ Data: [], ContinuationToken: null }, CipherResponse),
|
||||
);
|
||||
|
||||
// Generate report view first
|
||||
await service.generateMemberAccessReportView(mockOrganizationId);
|
||||
@@ -172,26 +191,31 @@ describe("MemberAccessReportService", () => {
|
||||
|
||||
it("should use '(No Name)' for users with no collection or group access", async () => {
|
||||
// Setup mock data with empty name and no access
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue({
|
||||
data: [
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue(
|
||||
new ListResponse(
|
||||
{
|
||||
id: "user1",
|
||||
name: "", // Empty name
|
||||
email: "user@example.com",
|
||||
twoFactorEnabled: false,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: [], // No groups
|
||||
avatarColor: null,
|
||||
} as any,
|
||||
],
|
||||
} as ListResponse<any>);
|
||||
Data: [
|
||||
{
|
||||
id: "user1",
|
||||
name: "", // Empty name
|
||||
email: "user@example.com",
|
||||
twoFactorEnabled: false,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: [], // No groups
|
||||
avatarColor: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
OrganizationUserUserDetailsResponse,
|
||||
),
|
||||
);
|
||||
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(of([]));
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([]);
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue({
|
||||
data: [] as CipherResponse[],
|
||||
});
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue(
|
||||
new ListResponse({ Data: [], ContinuationToken: null }, CipherResponse),
|
||||
);
|
||||
|
||||
// Generate report view first
|
||||
await service.generateMemberAccessReportView(mockOrganizationId);
|
||||
@@ -211,37 +235,45 @@ describe("MemberAccessReportService", () => {
|
||||
describe("Groups with no collections", () => {
|
||||
it("should include group membership even when group has no collections", async () => {
|
||||
// Setup: user in a group, but group has no collections
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue({
|
||||
data: [
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue(
|
||||
new ListResponse(
|
||||
{
|
||||
id: "user1",
|
||||
name: "Jane Doe",
|
||||
email: "jane@example.com",
|
||||
twoFactorEnabled: true,
|
||||
resetPasswordEnrolled: true,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1"], // User is in group1
|
||||
avatarColor: null,
|
||||
} as any,
|
||||
],
|
||||
} as ListResponse<any>);
|
||||
Data: [
|
||||
{
|
||||
id: "user1",
|
||||
name: "Jane Doe",
|
||||
email: "jane@example.com",
|
||||
twoFactorEnabled: true,
|
||||
resetPasswordEnrolled: true,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1"], // User is in group1
|
||||
avatarColor: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
OrganizationUserUserDetailsResponse,
|
||||
),
|
||||
);
|
||||
|
||||
// No collections at all
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(of([]));
|
||||
|
||||
// Group exists but has no collections
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([
|
||||
{
|
||||
id: "group1",
|
||||
name: "Empty Group",
|
||||
collections: [], // No collections
|
||||
} as GroupDetailsView,
|
||||
]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue({
|
||||
data: [] as CipherResponse[],
|
||||
const group = new GroupDetailsView();
|
||||
Object.assign(group, {
|
||||
id: "group1",
|
||||
organizationId: mockOrganizationId as any,
|
||||
name: "Empty Group",
|
||||
externalId: null,
|
||||
collections: [], // No collections
|
||||
});
|
||||
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([group]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue(
|
||||
new ListResponse({ Data: [], ContinuationToken: null }, CipherResponse),
|
||||
);
|
||||
|
||||
// Generate report view first
|
||||
await service.generateMemberAccessReportView(mockOrganizationId);
|
||||
|
||||
@@ -260,50 +292,62 @@ describe("MemberAccessReportService", () => {
|
||||
|
||||
it("should create separate rows for groups with collections and groups without", async () => {
|
||||
// Setup: user in two groups, one with collections and one without
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue({
|
||||
data: [
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue(
|
||||
new ListResponse(
|
||||
{
|
||||
id: "user1",
|
||||
name: "Multi Group User",
|
||||
email: "multi@example.com",
|
||||
twoFactorEnabled: false,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1", "group2"], // In both groups
|
||||
avatarColor: null,
|
||||
} as any,
|
||||
],
|
||||
} as ListResponse<any>);
|
||||
|
||||
// One collection assigned to group1
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(
|
||||
of([
|
||||
{
|
||||
id: "col1",
|
||||
name: "Collection 1",
|
||||
groups: [{ id: "group1", readOnly: false, hidePasswords: false, manage: true }],
|
||||
users: [],
|
||||
} as CollectionAdminView,
|
||||
]),
|
||||
Data: [
|
||||
{
|
||||
id: "user1",
|
||||
name: "Multi Group User",
|
||||
email: "multi@example.com",
|
||||
twoFactorEnabled: false,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1", "group2"], // In both groups
|
||||
avatarColor: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
OrganizationUserUserDetailsResponse,
|
||||
),
|
||||
);
|
||||
|
||||
// group1 has collection, group2 does not
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([
|
||||
{
|
||||
id: "group1",
|
||||
name: "Group With Collection",
|
||||
collections: [{ id: "col1", readOnly: false, hidePasswords: false, manage: true }],
|
||||
} as GroupDetailsView,
|
||||
{
|
||||
id: "group2",
|
||||
name: "Group Without Collection",
|
||||
collections: [],
|
||||
} as GroupDetailsView,
|
||||
]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue({
|
||||
data: [] as CipherResponse[],
|
||||
// One collection assigned to group1
|
||||
const collection = new CollectionAdminView({
|
||||
id: "col1" as any,
|
||||
name: "Collection 1",
|
||||
organizationId: mockOrganizationId,
|
||||
});
|
||||
Object.assign(collection, {
|
||||
groups: [{ id: "group1", readOnly: false, hidePasswords: false, manage: true }],
|
||||
users: [],
|
||||
});
|
||||
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(of([collection]));
|
||||
|
||||
// group1 has collection, group2 does not
|
||||
const group1 = new GroupDetailsView();
|
||||
Object.assign(group1, {
|
||||
id: "group1",
|
||||
organizationId: mockOrganizationId as any,
|
||||
name: "Group With Collection",
|
||||
externalId: null,
|
||||
collections: [{ id: "col1", readOnly: false, hidePasswords: false, manage: true }],
|
||||
});
|
||||
const group2 = new GroupDetailsView();
|
||||
Object.assign(group2, {
|
||||
id: "group2",
|
||||
organizationId: mockOrganizationId as any,
|
||||
name: "Group Without Collection",
|
||||
externalId: null,
|
||||
collections: [],
|
||||
});
|
||||
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([group1, group2]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue(
|
||||
new ListResponse({ Data: [], ContinuationToken: null }, CipherResponse),
|
||||
);
|
||||
|
||||
// Generate report view first
|
||||
await service.generateMemberAccessReportView(mockOrganizationId);
|
||||
@@ -332,53 +376,68 @@ describe("MemberAccessReportService", () => {
|
||||
|
||||
it("should show multiple collections for group with collections", async () => {
|
||||
// Setup: user in group with multiple collections
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue({
|
||||
data: [
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue(
|
||||
new ListResponse(
|
||||
{
|
||||
id: "user1",
|
||||
name: "User Name",
|
||||
email: "user@example.com",
|
||||
twoFactorEnabled: false,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1"],
|
||||
avatarColor: null,
|
||||
} as any,
|
||||
],
|
||||
} as ListResponse<any>);
|
||||
|
||||
// Two collections both assigned to group1
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(
|
||||
of([
|
||||
{
|
||||
id: "col1",
|
||||
name: "Collection 1",
|
||||
groups: [{ id: "group1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
users: [],
|
||||
} as CollectionAdminView,
|
||||
{
|
||||
id: "col2",
|
||||
name: "Collection 2",
|
||||
groups: [{ id: "group1", readOnly: true, hidePasswords: false, manage: false }],
|
||||
users: [],
|
||||
} as CollectionAdminView,
|
||||
]),
|
||||
Data: [
|
||||
{
|
||||
id: "user1",
|
||||
name: "User Name",
|
||||
email: "user@example.com",
|
||||
twoFactorEnabled: false,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1"],
|
||||
avatarColor: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
OrganizationUserUserDetailsResponse,
|
||||
),
|
||||
);
|
||||
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([
|
||||
{
|
||||
id: "group1",
|
||||
name: "Multi Collection Group",
|
||||
collections: [
|
||||
{ id: "col1", readOnly: false, hidePasswords: false, manage: false },
|
||||
{ id: "col2", readOnly: true, hidePasswords: false, manage: false },
|
||||
],
|
||||
} as GroupDetailsView,
|
||||
]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue({
|
||||
data: [] as CipherResponse[],
|
||||
// Two collections both assigned to group1
|
||||
const collection1 = new CollectionAdminView({
|
||||
id: "col1" as any,
|
||||
name: "Collection 1",
|
||||
organizationId: mockOrganizationId,
|
||||
});
|
||||
Object.assign(collection1, {
|
||||
groups: [{ id: "group1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
users: [],
|
||||
});
|
||||
|
||||
const collection2 = new CollectionAdminView({
|
||||
id: "col2" as any,
|
||||
name: "Collection 2",
|
||||
organizationId: mockOrganizationId,
|
||||
});
|
||||
Object.assign(collection2, {
|
||||
groups: [{ id: "group1", readOnly: true, hidePasswords: false, manage: false }],
|
||||
users: [],
|
||||
});
|
||||
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(
|
||||
of([collection1, collection2]),
|
||||
);
|
||||
|
||||
const group = new GroupDetailsView();
|
||||
Object.assign(group, {
|
||||
id: "group1",
|
||||
organizationId: mockOrganizationId as any,
|
||||
name: "Multi Collection Group",
|
||||
externalId: null,
|
||||
collections: [
|
||||
{ id: "col1", readOnly: false, hidePasswords: false, manage: false },
|
||||
{ id: "col2", readOnly: true, hidePasswords: false, manage: false },
|
||||
],
|
||||
});
|
||||
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([group]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue(
|
||||
new ListResponse({ Data: [], ContinuationToken: null }, CipherResponse),
|
||||
);
|
||||
|
||||
// Generate report view first
|
||||
await service.generateMemberAccessReportView(mockOrganizationId);
|
||||
@@ -399,35 +458,43 @@ describe("MemberAccessReportService", () => {
|
||||
describe("Combined scenarios", () => {
|
||||
it("should handle user with empty name in group with no collections", async () => {
|
||||
// Combines both fixes: empty name + group without collections
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue({
|
||||
data: [
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue(
|
||||
new ListResponse(
|
||||
{
|
||||
id: "user1",
|
||||
name: "",
|
||||
email: "noname@example.com",
|
||||
twoFactorEnabled: true,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1"],
|
||||
avatarColor: null,
|
||||
} as any,
|
||||
],
|
||||
} as ListResponse<any>);
|
||||
Data: [
|
||||
{
|
||||
id: "user1",
|
||||
name: "",
|
||||
email: "noname@example.com",
|
||||
twoFactorEnabled: true,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1"],
|
||||
avatarColor: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
OrganizationUserUserDetailsResponse,
|
||||
),
|
||||
);
|
||||
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(of([]));
|
||||
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([
|
||||
{
|
||||
id: "group1",
|
||||
name: "No Collection Group",
|
||||
collections: [],
|
||||
} as GroupDetailsView,
|
||||
]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue({
|
||||
data: [] as CipherResponse[],
|
||||
const group = new GroupDetailsView();
|
||||
Object.assign(group, {
|
||||
id: "group1",
|
||||
organizationId: mockOrganizationId as any,
|
||||
name: "No Collection Group",
|
||||
externalId: null,
|
||||
collections: [],
|
||||
});
|
||||
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([group]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue(
|
||||
new ListResponse({ Data: [], ContinuationToken: null }, CipherResponse),
|
||||
);
|
||||
|
||||
// Generate report view first
|
||||
await service.generateMemberAccessReportView(mockOrganizationId);
|
||||
|
||||
@@ -442,58 +509,70 @@ describe("MemberAccessReportService", () => {
|
||||
|
||||
it("should handle multiple users with mixed name and group scenarios", async () => {
|
||||
// Complex scenario with multiple users
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue({
|
||||
data: [
|
||||
mockOrganizationUserApiService.getAllUsers.mockResolvedValue(
|
||||
new ListResponse(
|
||||
{
|
||||
id: "user1",
|
||||
name: "Alice",
|
||||
email: "alice@example.com",
|
||||
twoFactorEnabled: true,
|
||||
resetPasswordEnrolled: true,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1"],
|
||||
avatarColor: null,
|
||||
} as any,
|
||||
{
|
||||
id: "user2",
|
||||
name: "",
|
||||
email: "bob@example.com",
|
||||
twoFactorEnabled: false,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group2"],
|
||||
avatarColor: null,
|
||||
} as any,
|
||||
],
|
||||
} as ListResponse<any>);
|
||||
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(
|
||||
of([
|
||||
{
|
||||
id: "col1",
|
||||
name: "Collection A",
|
||||
groups: [{ id: "group1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
users: [],
|
||||
} as CollectionAdminView,
|
||||
]),
|
||||
Data: [
|
||||
{
|
||||
id: "user1",
|
||||
name: "Alice",
|
||||
email: "alice@example.com",
|
||||
twoFactorEnabled: true,
|
||||
resetPasswordEnrolled: true,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group1"],
|
||||
avatarColor: null,
|
||||
},
|
||||
{
|
||||
id: "user2",
|
||||
name: "",
|
||||
email: "bob@example.com",
|
||||
twoFactorEnabled: false,
|
||||
resetPasswordEnrolled: false,
|
||||
usesKeyConnector: false,
|
||||
groups: ["group2"],
|
||||
avatarColor: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
OrganizationUserUserDetailsResponse,
|
||||
),
|
||||
);
|
||||
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([
|
||||
{
|
||||
id: "group1",
|
||||
name: "Group A",
|
||||
collections: [{ id: "col1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
} as GroupDetailsView,
|
||||
{
|
||||
id: "group2",
|
||||
name: "Group B",
|
||||
collections: [], // No collections
|
||||
} as GroupDetailsView,
|
||||
]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue({
|
||||
data: [] as CipherResponse[],
|
||||
const collection = new CollectionAdminView({
|
||||
id: "col1" as any,
|
||||
name: "Collection A",
|
||||
organizationId: mockOrganizationId,
|
||||
});
|
||||
Object.assign(collection, {
|
||||
groups: [{ id: "group1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
users: [],
|
||||
});
|
||||
|
||||
mockCollectionAdminService.collectionAdminViews$.mockReturnValue(of([collection]));
|
||||
|
||||
const group1 = new GroupDetailsView();
|
||||
Object.assign(group1, {
|
||||
id: "group1",
|
||||
organizationId: mockOrganizationId as any,
|
||||
name: "Group A",
|
||||
externalId: null,
|
||||
collections: [{ id: "col1", readOnly: false, hidePasswords: false, manage: false }],
|
||||
});
|
||||
const group2 = new GroupDetailsView();
|
||||
Object.assign(group2, {
|
||||
id: "group2",
|
||||
organizationId: mockOrganizationId as any,
|
||||
name: "Group B",
|
||||
externalId: null,
|
||||
collections: [], // No collections
|
||||
});
|
||||
|
||||
mockGroupApiService.getAllDetails.mockResolvedValue([group1, group2]);
|
||||
|
||||
mockApiService.getCiphersOrganization.mockResolvedValue(
|
||||
new ListResponse({ Data: [], ContinuationToken: null }, CipherResponse),
|
||||
);
|
||||
|
||||
// Generate report view first
|
||||
await service.generateMemberAccessReportView(mockOrganizationId);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Injectable } from "@angular/core";
|
||||
import { BehaviorSubject, firstValueFrom, Observable } from "rxjs";
|
||||
|
||||
@@ -83,7 +81,8 @@ export class MemberAccessReportService {
|
||||
private progressSubject = new BehaviorSubject<MemberAccessProgressState | null>(null);
|
||||
|
||||
/** Observable for progress state updates */
|
||||
progress$: Observable<MemberAccessProgressState | null> = this.progressSubject.asObservable();
|
||||
readonly progress$: Observable<MemberAccessProgressState | null> =
|
||||
this.progressSubject.asObservable();
|
||||
|
||||
/** Cached lookup maps for export generation */
|
||||
private cachedLookupMaps: LookupMaps | null = null;
|
||||
@@ -492,8 +491,9 @@ export class MemberAccessReportService {
|
||||
hidePasswords: access.hidePasswords,
|
||||
manage: access.manage,
|
||||
});
|
||||
return this.i18nService.t(
|
||||
permissionList.find((p) => p.perm === convertToPermission(collectionSelectionView))?.labelId,
|
||||
);
|
||||
const labelId =
|
||||
permissionList.find((p) => p.perm === convertToPermission(collectionSelectionView))
|
||||
?.labelId ?? "canView";
|
||||
return this.i18nService.t(labelId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user