mirror of
https://github.com/bitwarden/browser
synced 2026-02-13 06:54:07 +00:00
refactor(billing): Return organization ID from PremiumOrgUpgradeService
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
Component,
|
||||
computed,
|
||||
DestroyRef,
|
||||
inject,
|
||||
input,
|
||||
OnInit,
|
||||
output,
|
||||
@@ -179,14 +180,12 @@ export class PremiumOrgUpgradePaymentComponent implements OnInit, AfterViewInit
|
||||
};
|
||||
});
|
||||
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private subscriptionPricingService: SubscriptionPricingServiceAbstraction,
|
||||
private toastService: ToastService,
|
||||
private logService: LogService,
|
||||
private destroyRef: DestroyRef,
|
||||
private premiumOrgUpgradeService: PremiumOrgUpgradeService,
|
||||
) {}
|
||||
private readonly i18nService = inject(I18nService);
|
||||
private readonly subscriptionPricingService = inject(SubscriptionPricingServiceAbstraction);
|
||||
private readonly toastService = inject(ToastService);
|
||||
private readonly logService = inject(LogService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly premiumOrgUpgradeService = inject(PremiumOrgUpgradeService);
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
// If the selected plan is Personal Premium, no upgrade is needed
|
||||
@@ -288,7 +287,7 @@ export class PremiumOrgUpgradePaymentComponent implements OnInit, AfterViewInit
|
||||
throw new Error("Payment method is required");
|
||||
}
|
||||
|
||||
await this.premiumOrgUpgradeService.upgradeToOrganization(
|
||||
const organizationId = await this.premiumOrgUpgradeService.upgradeToOrganization(
|
||||
this.account(),
|
||||
organizationName,
|
||||
this.selectedPlan()!,
|
||||
@@ -297,7 +296,7 @@ export class PremiumOrgUpgradePaymentComponent implements OnInit, AfterViewInit
|
||||
|
||||
return {
|
||||
status: this.getUpgradeStatus(this.selectedPlanId()),
|
||||
organizationId: null,
|
||||
organizationId,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BusinessSubscriptionPricingTierIds } from "@bitwarden/common/billing/types/subscription-pricing-tier";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
@@ -21,6 +23,7 @@ describe("PremiumOrgUpgradeService", () => {
|
||||
let previewInvoiceClient: jest.Mocked<PreviewInvoiceClient>;
|
||||
let syncService: jest.Mocked<SyncService>;
|
||||
let keyService: jest.Mocked<KeyService>;
|
||||
let organizationService: jest.Mocked<OrganizationService>;
|
||||
|
||||
const mockAccount = { id: "user-id", email: "test@bitwarden.com" } as Account;
|
||||
const mockPlanDetails: PremiumOrgUpgradePlanDetails = {
|
||||
@@ -61,6 +64,17 @@ describe("PremiumOrgUpgradeService", () => {
|
||||
.fn()
|
||||
.mockResolvedValue([{ encryptedString: "encrypted-string" }, "decrypted-key"]),
|
||||
} as any;
|
||||
organizationService = {
|
||||
organizations$: jest.fn().mockReturnValue(
|
||||
of([
|
||||
{
|
||||
id: "new-org-id",
|
||||
name: "Test Organization",
|
||||
isOwner: true,
|
||||
} as Organization,
|
||||
]),
|
||||
),
|
||||
} as any;
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
@@ -70,6 +84,7 @@ describe("PremiumOrgUpgradeService", () => {
|
||||
{ provide: SyncService, useValue: syncService },
|
||||
{ provide: AccountService, useValue: { activeAccount$: of(mockAccount) } },
|
||||
{ provide: KeyService, useValue: keyService },
|
||||
{ provide: OrganizationService, useValue: organizationService },
|
||||
],
|
||||
});
|
||||
|
||||
@@ -77,8 +92,8 @@ describe("PremiumOrgUpgradeService", () => {
|
||||
});
|
||||
|
||||
describe("upgradeToOrganization", () => {
|
||||
it("should successfully upgrade premium account to organization", async () => {
|
||||
await service.upgradeToOrganization(
|
||||
it("should successfully upgrade premium account to organization and return organization ID", async () => {
|
||||
const result = await service.upgradeToOrganization(
|
||||
mockAccount,
|
||||
"Test Organization",
|
||||
mockPlanDetails,
|
||||
@@ -94,6 +109,8 @@ describe("PremiumOrgUpgradeService", () => {
|
||||
);
|
||||
expect(keyService.makeOrgKey).toHaveBeenCalledWith("user-id");
|
||||
expect(syncService.fullSync).toHaveBeenCalledWith(true);
|
||||
expect(organizationService.organizations$).toHaveBeenCalledWith("user-id");
|
||||
expect(result).toBe("new-org-id");
|
||||
});
|
||||
|
||||
it("should throw an error if organization name is missing", async () => {
|
||||
@@ -151,7 +168,10 @@ describe("PremiumOrgUpgradeService", () => {
|
||||
});
|
||||
|
||||
it("should throw an error if encrypted string is undefined", async () => {
|
||||
keyService.makeOrgKey.mockResolvedValue([{ encryptedString: undefined }, "decrypted-key"]);
|
||||
keyService.makeOrgKey.mockResolvedValue([
|
||||
{ encryptedString: null } as any,
|
||||
"decrypted-key" as any,
|
||||
]);
|
||||
await expect(
|
||||
service.upgradeToOrganization(
|
||||
mockAccount,
|
||||
@@ -187,6 +207,40 @@ describe("PremiumOrgUpgradeService", () => {
|
||||
),
|
||||
).rejects.toThrow("Sync failed");
|
||||
});
|
||||
|
||||
it("should throw an error if organization is not found after sync", async () => {
|
||||
organizationService.organizations$.mockReturnValue(
|
||||
of([
|
||||
{
|
||||
id: "different-org-id",
|
||||
name: "Different Organization",
|
||||
isOwner: true,
|
||||
} as Organization,
|
||||
]),
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.upgradeToOrganization(
|
||||
mockAccount,
|
||||
"Test Organization",
|
||||
mockPlanDetails,
|
||||
mockBillingAddress,
|
||||
),
|
||||
).rejects.toThrow("Failed to find newly created organization");
|
||||
});
|
||||
|
||||
it("should throw an error if no organizations are returned", async () => {
|
||||
organizationService.organizations$.mockReturnValue(of([]));
|
||||
|
||||
await expect(
|
||||
service.upgradeToOrganization(
|
||||
mockAccount,
|
||||
"Test Organization",
|
||||
mockPlanDetails,
|
||||
mockBillingAddress,
|
||||
),
|
||||
).rejects.toThrow("Failed to find newly created organization");
|
||||
});
|
||||
});
|
||||
|
||||
describe("previewProratedInvoice", () => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import {
|
||||
@@ -46,6 +48,7 @@ export class PremiumOrgUpgradeService {
|
||||
private previewInvoiceClient: PreviewInvoiceClient,
|
||||
private syncService: SyncService,
|
||||
private keyService: KeyService,
|
||||
private organizationService: OrganizationService,
|
||||
) {}
|
||||
|
||||
async previewProratedInvoice(
|
||||
@@ -71,7 +74,7 @@ export class PremiumOrgUpgradeService {
|
||||
organizationName: string,
|
||||
planDetails: PremiumOrgUpgradePlanDetails,
|
||||
billingAddress: BillingAddress,
|
||||
): Promise<void> {
|
||||
): Promise<string> {
|
||||
if (!organizationName) {
|
||||
throw new Error("Organization name is required for organization upgrade");
|
||||
}
|
||||
@@ -96,6 +99,17 @@ export class PremiumOrgUpgradeService {
|
||||
);
|
||||
|
||||
await this.syncService.fullSync(true);
|
||||
|
||||
// Get the newly created organization
|
||||
const organizations = await firstValueFrom(this.organizationService.organizations$(account.id));
|
||||
|
||||
const newOrg = organizations?.find((org) => org.name === organizationName && org.isOwner);
|
||||
|
||||
if (!newOrg) {
|
||||
throw new Error("Failed to find newly created organization");
|
||||
}
|
||||
|
||||
return newOrg.id;
|
||||
}
|
||||
|
||||
private ProductTierTypeFromSubscriptionTierId(
|
||||
|
||||
Reference in New Issue
Block a user