Skip to content

Commit

Permalink
wire up vNextOrganizationService in web client
Browse files Browse the repository at this point in the history
  • Loading branch information
BTreston committed Jan 10, 2025
1 parent 6e6cf38 commit 1ba6d90
Show file tree
Hide file tree
Showing 85 changed files with 1,195 additions and 433 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ import { TestBed } from "@angular/core/testing";
import { provideRouter } from "@angular/router";
import { RouterTestingHarness } from "@angular/router/testing";
import { MockProxy, any, mock } from "jest-mock-extended";
import { of } from "rxjs";

import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { DialogService } from "@bitwarden/components";

import { isEnterpriseOrgGuard } from "./is-enterprise-org.guard";
Expand Down Expand Up @@ -41,18 +46,22 @@ const orgFactory = (props: Partial<Organization> = {}) =>
);

describe("Is Enterprise Org Guard", () => {
let organizationService: MockProxy<OrganizationService>;
let organizationService: MockProxy<vNextOrganizationService>;
let dialogService: MockProxy<DialogService>;
let routerHarness: RouterTestingHarness;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;

beforeEach(async () => {
organizationService = mock<OrganizationService>();
organizationService = mock<vNextOrganizationService>();
dialogService = mock<DialogService>();
accountService = mockAccountServiceWith(userId);

TestBed.configureTestingModule({
providers: [
{ provide: OrganizationService, useValue: organizationService },
{ provide: vNextOrganizationService, useValue: organizationService },
{ provide: DialogService, useValue: dialogService },
{ provide: AccountService, useValue: accountService },
provideRouter([
{
path: "",
Expand Down Expand Up @@ -82,7 +91,7 @@ describe("Is Enterprise Org Guard", () => {

it("redirects to `/` if the organization id provided is not found", async () => {
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(null);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([]));
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
"This is the home screen!",
Expand All @@ -101,7 +110,7 @@ describe("Is Enterprise Org Guard", () => {
type: OrganizationUserType.User,
productTierType: productTierType,
});
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`);
expect(dialogService.openSimpleDialog).toHaveBeenCalled();
expect(
Expand All @@ -115,7 +124,7 @@ describe("Is Enterprise Org Guard", () => {
type: OrganizationUserType.Owner,
productTierType: ProductTierType.Teams,
});
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
dialogService.openSimpleDialog.calledWith(any()).mockResolvedValue(true);
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
Expand All @@ -133,7 +142,7 @@ describe("Is Enterprise Org Guard", () => {
type: OrganizationUserType.User,
productTierType: productTierType,
});
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnlyNoError`);
expect(dialogService.openSimpleDialog).not.toHaveBeenCalled();
expect(
Expand All @@ -143,7 +152,7 @@ describe("Is Enterprise Org Guard", () => {

it("proceeds with navigation if the organization in question is a enterprise organization", async () => {
const org = orgFactory({ productTierType: ProductTierType.Enterprise });
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
"This component can only be accessed by a enterprise organization!",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import {
Router,
RouterStateSnapshot,
} from "@angular/router";
import { firstValueFrom, map } from "rxjs";

import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
vNextOrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { DialogService } from "@bitwarden/components";

Expand All @@ -22,10 +27,16 @@ import { DialogService } from "@bitwarden/components";
export function isEnterpriseOrgGuard(showError: boolean = true): CanActivateFn {
return async (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
const router = inject(Router);
const organizationService = inject(OrganizationService);
const organizationService = inject(vNextOrganizationService);
const accountService = inject(AccountService);
const dialogService = inject(DialogService);

const org = await organizationService.get(route.params.organizationId);
const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id)));
const org = await firstValueFrom(
organizationService
.organizations$(userId)
.pipe(getOrganizationById(route.params.organizationId)),
);

if (org == null) {
return router.createUrlTree(["/"]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import { TestBed } from "@angular/core/testing";
import { provideRouter } from "@angular/router";
import { RouterTestingHarness } from "@angular/router/testing";
import { MockProxy, any, mock } from "jest-mock-extended";
import { of } from "rxjs";

import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { DialogService } from "@bitwarden/components";

import { isPaidOrgGuard } from "./is-paid-org.guard";
Expand Down Expand Up @@ -40,18 +45,22 @@ const orgFactory = (props: Partial<Organization> = {}) =>
);

describe("Is Paid Org Guard", () => {
let organizationService: MockProxy<OrganizationService>;
let organizationService: MockProxy<vNextOrganizationService>;
let dialogService: MockProxy<DialogService>;
let routerHarness: RouterTestingHarness;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;

beforeEach(async () => {
organizationService = mock<OrganizationService>();
organizationService = mock<vNextOrganizationService>();
dialogService = mock<DialogService>();
accountService = mockAccountServiceWith(userId);

TestBed.configureTestingModule({
providers: [
{ provide: OrganizationService, useValue: organizationService },
{ provide: vNextOrganizationService, useValue: organizationService },
{ provide: DialogService, useValue: dialogService },
{ provide: AccountService, useValue: accountService },
provideRouter([
{
path: "",
Expand All @@ -75,7 +84,7 @@ describe("Is Paid Org Guard", () => {

it("redirects to `/` if the organization id provided is not found", async () => {
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(null);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([]));
await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
"This is the home screen!",
Expand All @@ -86,7 +95,7 @@ describe("Is Paid Org Guard", () => {
// `useTotp` is the current indicator of a free org, it is the baseline
// feature offered above the free organization level.
const org = orgFactory({ type: OrganizationUserType.User, useTotp: false });
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`);
expect(dialogService.openSimpleDialog).toHaveBeenCalled();
expect(
Expand All @@ -98,7 +107,7 @@ describe("Is Paid Org Guard", () => {
// `useTotp` is the current indicator of a free org, it is the baseline
// feature offered above the free organization level.
const org = orgFactory({ type: OrganizationUserType.Owner, useTotp: false });
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
dialogService.openSimpleDialog.calledWith(any()).mockResolvedValue(true);
await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
Expand All @@ -108,7 +117,7 @@ describe("Is Paid Org Guard", () => {

it("proceeds with navigation if the organization in question is a paid organization", async () => {
const org = orgFactory({ useTotp: true });
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));
await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`);
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
"This component can only be accessed by a paid organization!",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import {
Router,
RouterStateSnapshot,
} from "@angular/router";
import { firstValueFrom, map } from "rxjs";

import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
getOrganizationById,
vNextOrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DialogService } from "@bitwarden/components";

/**
Expand All @@ -21,10 +26,16 @@ import { DialogService } from "@bitwarden/components";
export function isPaidOrgGuard(): CanActivateFn {
return async (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
const router = inject(Router);
const organizationService = inject(OrganizationService);
const organizationService = inject(vNextOrganizationService);
const accountService = inject(AccountService);
const dialogService = inject(DialogService);

const org = await organizationService.get(route.params.organizationId);
const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id)));
const org = await firstValueFrom(
organizationService
.organizations$(userId)
.pipe(getOrganizationById(route.params.organizationId)),
);

if (org == null) {
return router.createUrlTree(["/"]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ import {
RouterStateSnapshot,
} from "@angular/router";
import { mock, MockProxy } from "jest-mock-extended";
import { of } from "rxjs";

import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { ToastService } from "@bitwarden/components";

Expand All @@ -31,13 +36,16 @@ const orgFactory = (props: Partial<Organization> = {}) =>

describe("Organization Permissions Guard", () => {
let router: MockProxy<Router>;
let organizationService: MockProxy<OrganizationService>;
let organizationService: MockProxy<vNextOrganizationService>;
let state: MockProxy<RouterStateSnapshot>;
let route: MockProxy<ActivatedRouteSnapshot>;
let accountService: FakeAccountService;
const userId = Utils.newGuid() as UserId;

beforeEach(() => {
router = mock<Router>();
organizationService = mock<OrganizationService>();
organizationService = mock<vNextOrganizationService>();
accountService = mockAccountServiceWith(userId);
state = mock<RouterStateSnapshot>();
route = mock<ActivatedRouteSnapshot>({
params: {
Expand All @@ -48,7 +56,8 @@ describe("Organization Permissions Guard", () => {
TestBed.configureTestingModule({
providers: [
{ provide: Router, useValue: router },
{ provide: OrganizationService, useValue: organizationService },
{ provide: AccountService, useValue: accountService },
{ provide: vNextOrganizationService, useValue: organizationService },
{ provide: ToastService, useValue: mock<ToastService>() },
{ provide: I18nService, useValue: mock<I18nService>() },
{ provide: SyncService, useValue: mock<SyncService>() },
Expand All @@ -57,7 +66,7 @@ describe("Organization Permissions Guard", () => {
});

it("blocks navigation if organization does not exist", async () => {
organizationService.get.mockReturnValue(null);
organizationService.organizations$.mockReturnValue(of([]));

const actual = await TestBed.runInInjectionContext(
async () => await organizationPermissionsGuard()(route, state),
Expand All @@ -68,7 +77,7 @@ describe("Organization Permissions Guard", () => {

it("permits navigation if no permissions are specified", async () => {
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));

const actual = await TestBed.runInInjectionContext(async () =>
organizationPermissionsGuard()(route, state),
Expand All @@ -81,7 +90,7 @@ describe("Organization Permissions Guard", () => {
const permissionsCallback = jest.fn();
permissionsCallback.mockImplementation((_org) => true);
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));

const actual = await TestBed.runInInjectionContext(
async () => await organizationPermissionsGuard(permissionsCallback)(route, state),
Expand All @@ -103,7 +112,7 @@ describe("Organization Permissions Guard", () => {
});

const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));

const actual = await TestBed.runInInjectionContext(
async () => await organizationPermissionsGuard(permissionsCallback)(route, state),
Expand All @@ -122,7 +131,7 @@ describe("Organization Permissions Guard", () => {
}),
});
const org = orgFactory();
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));

const actual = await TestBed.runInInjectionContext(
async () => await organizationPermissionsGuard((_org: Organization) => false)(route, state),
Expand All @@ -141,7 +150,7 @@ describe("Organization Permissions Guard", () => {
type: OrganizationUserType.Admin,
enabled: false,
});
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));

const actual = await TestBed.runInInjectionContext(
async () => await organizationPermissionsGuard()(route, state),
Expand All @@ -155,7 +164,7 @@ describe("Organization Permissions Guard", () => {
type: OrganizationUserType.Owner,
enabled: false,
});
organizationService.get.calledWith(org.id).mockResolvedValue(org);
organizationService.organizations$.calledWith(userId).mockReturnValue(of([org]));

const actual = await TestBed.runInInjectionContext(
async () => await organizationPermissionsGuard()(route, state),
Expand Down
Loading

0 comments on commit 1ba6d90

Please sign in to comment.