diff --git a/apps/web/src/app/tools/access-intelligence/all-applications.component.html b/apps/web/src/app/tools/access-intelligence/all-applications.component.html index 48eb7e2bac6..fceba583cec 100644 --- a/apps/web/src/app/tools/access-intelligence/all-applications.component.html +++ b/apps/web/src/app/tools/access-intelligence/all-applications.component.html @@ -10,7 +10,7 @@

- {{ "noAppsInOrgTitle" | i18n: organization.name }} + {{ "noAppsInOrgTitle" | i18n: organization?.name }}

@@ -83,9 +83,11 @@ + {{ r.name }} diff --git a/apps/web/src/app/tools/access-intelligence/all-applications.component.spec.ts b/apps/web/src/app/tools/access-intelligence/all-applications.component.spec.ts new file mode 100644 index 00000000000..fcacb064153 --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/all-applications.component.spec.ts @@ -0,0 +1,99 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ActivatedRoute, convertToParamMap } from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; + +// eslint-disable-next-line no-restricted-imports +import { + CriticalAppsApiService, + PasswordHealthReportApplicationsResponse, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { TableModule, ToastService } from "@bitwarden/components"; +import { TableBodyDirective } from "@bitwarden/components/src/table/table.component"; + +import { LooseComponentsModule } from "../../shared"; +import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; + +import { AllApplicationsComponent } from "./all-applications.component"; + +describe("AllApplicationsComponent", () => { + const organizationServiceMock: OrganizationService = mock(); + const configServiceMock: ConfigService = mock(); + const criticalAppsApiServiceMock: CriticalAppsApiService = mock(); + const i18nService: MockProxy = mock(); + let fixture: ComponentFixture; + let component: AllApplicationsComponent; + const activeRouteParams = convertToParamMap({ organizationId: "orgId" }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AllApplicationsComponent, PipesModule, TableModule, LooseComponentsModule], + declarations: [TableBodyDirective], + providers: [ + { provide: CipherService, useValue: mock() }, + { + provide: PasswordStrengthServiceAbstraction, + useValue: mock(), + }, + { provide: AuditService, useValue: mock() }, + { provide: I18nService, useValue: i18nService }, + { provide: ActivatedRoute, useValue: { paramMap: of(activeRouteParams), url: of([]) } }, + { provide: ToastService, useValue: mock() }, + { provide: OrganizationService, useValue: organizationServiceMock }, + { provide: ConfigService, useValue: configServiceMock }, + { provide: CriticalAppsApiService, useValue: criticalAppsApiServiceMock }, + ], + }).compileComponents(); + }); + + beforeEach(async () => { + const data = new OrganizationData({} as any, {} as any); + const organization = new Organization(data); + organization.name = "orgName"; + jest.spyOn(organizationServiceMock, "get$").mockReturnValue(of(organization)); + + const pwdHealthReportAppsResponse = [ + { id: "1", organizationId: "app1", uri: "" }, + ] as PasswordHealthReportApplicationsResponse[]; + jest + .spyOn(criticalAppsApiServiceMock, "getCriticalApps") + .mockResolvedValue(Promise.resolve(pwdHealthReportAppsResponse)); + + jest.spyOn(configServiceMock, "getFeatureFlag").mockResolvedValue(true); + + fixture = TestBed.createComponent(AllApplicationsComponent); + component = fixture.componentInstance; + + await component.ngOnInit(); + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should initialize component", () => { + expect(component).toBeTruthy(); + }); + + it("should add to selectedUrls on checkbox change", async () => { + const event = { target: { checked: true } as HTMLInputElement } as unknown as Event; + component.onCheckboxChange("app1", event); + expect(component.getSelectedUrls().length).toBe(1); + }); + + it("should invoke CriticalAppsApiService", async () => { + jest.spyOn(criticalAppsApiServiceMock, "setCriticalApps").mockResolvedValue(Promise.resolve()); + const event = { target: { checked: true } as HTMLInputElement } as unknown as Event; + component.onCheckboxChange("app1", event); + + await component.markAppsAsCritical(); + expect(criticalAppsApiServiceMock.setCriticalApps).toHaveBeenCalled(); + expect(component.getSelectedUrls().length).toBe(0); + }); +}); diff --git a/apps/web/src/app/tools/access-intelligence/all-applications.component.ts b/apps/web/src/app/tools/access-intelligence/all-applications.component.ts index 02b63edb57c..0415ad8ac4e 100644 --- a/apps/web/src/app/tools/access-intelligence/all-applications.component.ts +++ b/apps/web/src/app/tools/access-intelligence/all-applications.component.ts @@ -58,6 +58,7 @@ export class AllApplicationsComponent implements OnInit { this.activatedRoute.paramMap .pipe( takeUntilDestroyed(this.destroyRef), + map(async (params) => { const organizationId = params.get("organizationId"); this.organization = await firstValueFrom(this.organizationService.get$(organizationId)); @@ -66,10 +67,15 @@ export class AllApplicationsComponent implements OnInit { }), switchMap(async (params) => { const organizationId = (await params).get("organizationId"); - await this.criticalAppsService.getCriticalApps(organizationId); + return await this.criticalAppsService.getCriticalApps(organizationId); }), ) - .subscribe(); + .subscribe((dbCriticalAppRecords) => { + applicationTableMockData.forEach((data) => { + data.isMarkedAsCritical = dbCriticalAppRecords.some((app) => app.uri === data.name); + }); + this.dataSource.data = applicationTableMockData; + }); this.isCritialAppsFeatureEnabled = await this.configService.getFeatureFlag( FeatureFlag.CriticalApps, @@ -87,7 +93,7 @@ export class AllApplicationsComponent implements OnInit { protected configService: ConfigService, protected criticalAppsService: CriticalAppsApiService, ) { - this.dataSource.data = applicationTableMockData; + // this.dataSource.data = applicationTableMockData; this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) .subscribe((v) => (this.dataSource.filter = v)); @@ -132,4 +138,6 @@ export class AllApplicationsComponent implements OnInit { this.selectedUrls.delete(urlName); } } + + getSelectedUrls = () => Array.from(this.selectedUrls); }