diff --git a/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/premium-org-upgrade-payment.component.spec.ts b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/premium-org-upgrade-payment.component.spec.ts
index 8796b750d4b..b306905a403 100644
--- a/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/premium-org-upgrade-payment.component.spec.ts
+++ b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/premium-org-upgrade-payment.component.spec.ts
@@ -10,8 +10,9 @@ import { FormControl, FormGroup, Validators } from "@angular/forms";
import { mock } from "jest-mock-extended";
import { of } from "rxjs";
+import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
-import { Account } from "@bitwarden/common/auth/abstractions/account.service";
+import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { SubscriptionPricingServiceAbstraction } from "@bitwarden/common/billing/abstractions/subscription-pricing.service.abstraction";
import {
BusinessSubscriptionPricingTier,
@@ -28,9 +29,10 @@ import { CartSummaryComponent } from "@bitwarden/pricing";
import { AccountBillingClient } from "../../../clients/account-billing.client";
import { PreviewInvoiceClient } from "../../../clients/preview-invoice.client";
+import { SubscriberBillingClient } from "../../../clients/subscriber-billing.client";
import {
EnterBillingAddressComponent,
- EnterPaymentMethodComponent,
+ DisplayPaymentMethodComponent,
} from "../../../payment/components";
import {
@@ -54,40 +56,19 @@ class MockCartSummaryComponent {
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
- selector: "app-enter-payment-method",
- template: `
Mock Enter Payment Method
`,
+ selector: "app-display-payment-method",
+ template: `Mock Display Payment Method
`,
providers: [
{
- provide: EnterPaymentMethodComponent,
- useClass: MockEnterPaymentMethodComponent,
+ provide: DisplayPaymentMethodComponent,
+ useClass: MockDisplayPaymentMethodComponent,
},
],
})
-class MockEnterPaymentMethodComponent {
- readonly group = input.required();
- readonly showBankAccount = input(true);
- readonly showPayPal = input(true);
- readonly showAccountCredit = input(false);
- readonly hasEnoughAccountCredit = input(true);
- readonly includeBillingAddress = input(false);
-
- tokenize = jest.fn().mockResolvedValue({ type: "card", token: "mock-token" });
- validate = jest.fn().mockReturnValue(true);
-
- static getFormGroup = () =>
- new FormGroup({
- type: new FormControl("card", { nonNullable: true }),
- bankAccount: new FormGroup({
- routingNumber: new FormControl("", { nonNullable: true }),
- accountNumber: new FormControl("", { nonNullable: true }),
- accountHolderName: new FormControl("", { nonNullable: true }),
- accountHolderType: new FormControl("", { nonNullable: true }),
- }),
- billingAddress: new FormGroup({
- country: new FormControl("", { nonNullable: true }),
- postalCode: new FormControl("", { nonNullable: true }),
- }),
- });
+class MockDisplayPaymentMethodComponent {
+ readonly subscriber = input.required();
+ readonly paymentMethod = input();
+ readonly hideHeader = input();
}
@Component({
@@ -148,6 +129,9 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
const mockPreviewInvoiceClient = mock();
const mockLogService = mock();
const mockOrganizationService = mock();
+ const mockSubscriberBillingClient = mock();
+ const mockApiService = mock();
+ const mockAccountService = mock();
const mockI18nService = { t: jest.fn((key: string, ...params: any[]) => key) };
const mockAccount = { id: "user-id", email: "test@bitwarden.com" } as Account;
@@ -191,6 +175,13 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
newPlanProratedMonths: 1,
});
mockOrganizationService.organizations$.mockReturnValue(of([]));
+ mockAccountService.activeAccount$ = of(mockAccount);
+ mockSubscriberBillingClient.getPaymentMethod.mockResolvedValue({
+ type: "card",
+ brand: "visa",
+ last4: "4242",
+ expiration: "12/2025",
+ });
mockSubscriptionPricingService.getBusinessSubscriptionPricingTiers$.mockReturnValue(
of([mockTeamsPlan]),
@@ -212,6 +203,9 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
{ provide: I18nService, useValue: mockI18nService },
{ provide: AccountBillingClient, useValue: mockAccountBillingClient },
{ provide: PreviewInvoiceClient, useValue: mockPreviewInvoiceClient },
+ { provide: SubscriberBillingClient, useValue: mockSubscriberBillingClient },
+ { provide: AccountService, useValue: mockAccountService },
+ { provide: ApiService, useValue: mockApiService },
{
provide: KeyService,
useValue: {
@@ -230,14 +224,14 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
add: {
imports: [
MockEnterBillingAddressComponent,
- MockEnterPaymentMethodComponent,
+ MockDisplayPaymentMethodComponent,
MockCartSummaryComponent,
],
},
remove: {
imports: [
EnterBillingAddressComponent,
- EnterPaymentMethodComponent,
+ DisplayPaymentMethodComponent,
CartSummaryComponent,
],
},
@@ -304,8 +298,7 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
it("should successfully upgrade to organization", async () => {
const completeSpy = jest.spyOn(component["complete"], "emit");
- // Mock isFormValid and processUpgrade to bypass form validation
- jest.spyOn(component as any, "isFormValid").mockReturnValue(true);
+ // Mock processUpgrade to bypass form validation
jest.spyOn(component as any, "processUpgrade").mockResolvedValue({
status: PremiumOrgUpgradePaymentStatus.UpgradedToTeams,
organizationId: null,
@@ -313,19 +306,6 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
component["formGroup"].setValue({
organizationName: "My New Org",
- paymentForm: {
- type: "card",
- bankAccount: {
- routingNumber: "",
- accountNumber: "",
- accountHolderName: "",
- accountHolderType: "",
- },
- billingAddress: {
- country: "",
- postalCode: "",
- },
- },
billingAddress: {
country: "US",
postalCode: "90210",
@@ -350,8 +330,6 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
});
it("should show an error toast if upgrade fails", async () => {
- // Mock isFormValid to return true
- jest.spyOn(component as any, "isFormValid").mockReturnValue(true);
// Mock processUpgrade to throw an error
jest
.spyOn(component as any, "processUpgrade")
@@ -359,19 +337,6 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
component["formGroup"].setValue({
organizationName: "My New Org",
- paymentForm: {
- type: "card",
- bankAccount: {
- routingNumber: "",
- accountNumber: "",
- accountHolderName: "",
- accountHolderType: "",
- },
- billingAddress: {
- country: "",
- postalCode: "",
- },
- },
billingAddress: {
country: "US",
postalCode: "90210",
@@ -428,7 +393,8 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
},
});
- tick(1000);
+ // Advance time to allow any async operations to complete
+ tick(1500);
fixture.detectChanges();
const estimatedInvoice = component["estimatedInvoice"]();
@@ -447,23 +413,6 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
component["formGroup"].patchValue({ organizationName: "My Organization" });
expect(component["formGroup"].get("organizationName")?.valid).toBe(true);
});
-
- it("should return false when payment component validation fails", () => {
- component["formGroup"].patchValue({
- organizationName: "Test Org",
- billingAddress: {
- country: "US",
- postalCode: "12345",
- },
- });
-
- const mockPaymentComponent = {
- validate: jest.fn().mockReturnValue(false),
- } as any;
- jest.spyOn(component, "paymentComponent").mockReturnValue(mockPaymentComponent);
-
- expect(component["isFormValid"]()).toBe(false);
- });
});
describe("Cart Calculation", () => {
@@ -516,6 +465,16 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
});
describe("processUpgrade", () => {
+ beforeEach(() => {
+ // Set paymentMethod signal for these tests
+ component["paymentMethod"].set({
+ type: "card",
+ brand: "visa",
+ last4: "4242",
+ expiration: "12/2025",
+ });
+ });
+
it("should throw error when billing address is incomplete", async () => {
component["formGroup"].patchValue({
organizationName: "Test Org",
@@ -525,11 +484,6 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
},
});
- const mockPaymentComponent = {
- tokenize: jest.fn().mockResolvedValue({ type: "card", token: "mock-token" }),
- } as any;
- jest.spyOn(component, "paymentComponent").mockReturnValue(mockPaymentComponent);
-
await expect(component["processUpgrade"]()).rejects.toThrow("Billing address is incomplete");
});
@@ -542,30 +496,8 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
},
});
- const mockPaymentComponent = {
- tokenize: jest.fn().mockResolvedValue({ type: "card", token: "mock-token" }),
- } as any;
- jest.spyOn(component, "paymentComponent").mockReturnValue(mockPaymentComponent);
-
await expect(component["processUpgrade"]()).rejects.toThrow("Organization name is required");
});
-
- it("should throw error when payment method tokenization fails", async () => {
- component["formGroup"].patchValue({
- organizationName: "Test Org",
- billingAddress: {
- country: "US",
- postalCode: "12345",
- },
- });
-
- const mockPaymentComponent = {
- tokenize: jest.fn().mockResolvedValue(null),
- } as any;
- jest.spyOn(component, "paymentComponent").mockReturnValue(mockPaymentComponent);
-
- await expect(component["processUpgrade"]()).rejects.toThrow("Payment method is required");
- });
});
describe("Plan Membership Messages", () => {
@@ -606,9 +538,21 @@ describe("PremiumOrgUpgradePaymentComponent", () => {
describe("Error Handling", () => {
it("should log error and continue when submit fails", async () => {
- jest.spyOn(component as any, "isFormValid").mockReturnValue(true);
jest.spyOn(component as any, "processUpgrade").mockRejectedValue(new Error("Network error"));
+ component["formGroup"].setValue({
+ organizationName: "My New Org",
+ billingAddress: {
+ country: "US",
+ postalCode: "90210",
+ line1: "123 Main St",
+ line2: "",
+ city: "Beverly Hills",
+ state: "CA",
+ taxId: "",
+ },
+ });
+
await component["submit"]();
expect(mockLogService.error).toHaveBeenCalledWith("Upgrade failed:", expect.any(Error));