Files
calendar/packages/features/ee/billing/api/webhook/hwm-webhook-utils.test.ts
T
sean-brydonGitHubClaude Opus 4.5Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1ad4ad6e4e feat: High Water Mark (HWM) billing for monthly subscriptions (#27559)
* wip

* fix: claue reveiew commentsd

* add seed test

* fix: seed test plus subscriptin undefined in webhook

* chore: udpate triggr

* Integration test + HWM seed script

* add feature flag for hmw

* Remove redudant docs

* feat: add hwm-seating feature flag for high water mark billing

- Add new hwm-seating feature flag to control HWM billing logic
- Update HighWaterMarkService to use hwm-seating flag
- Update SeatChangeTrackingService to use hwm-seating flag
- Update BillingPeriodService to use hwm-seating flag
- Add flag to migration SQL
- Enable flag in seed script and integration test

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: add hwm-seating to useFlags and fix test mocks

- Add hwm-seating to useFlags.ts initial data
- Add shouldApplyHighWaterMark mock to TeamBillingService tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix test

* feat: move to invoice paid subscription

* fix: transactions for transiants updates + reusable util

* fix: address Cubic AI review feedback for HWM billing

- Fix dotenv import hoisting in seed-hwm-test.ts by using side-effect import
- Remove logging of sensitive information (password/emails) in seed script
- Fix incorrect newHighWaterMark return value when update is not performed

Co-Authored-By: unknown <>

* add additional unit tests

* improve DI

* fix migration + restore package.json trigger update

* feat:move to featurerepo+update tests

* fix mocks

* fix DI calls

* revert faeturerepo to featuresrepository

* fix: pass featureRepository to BillingPeriodService and HighWaterMarkService

- Fix TeamBillingService.ts: pass featureRepository to BillingPeriodService constructor
- Fix _invoice.upcoming.ts: pass featureRepository to HighWaterMarkService constructor
- Fix hwm-webhook-utils.ts: pass featureRepository to HighWaterMarkService constructor

These services require featureRepository for feature flag checks but production
call sites were missing this required dependency, causing runtime errors.

Addresses Cubic AI review feedback on PR #27559

Co-Authored-By: unknown <>

* fix import

* update feature repo usage to features

* remove test thats mocking the wrong stuff

* revert RegularBookingService changes

* remove hide branding

* remove change in redudant file

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-02-05 09:56:31 +00:00

205 lines
7.1 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import {
extractPeriodStartFromInvoice,
validateInvoiceLinesForHwm,
handleHwmResetAfterRenewal,
} from "./hwm-webhook-utils";
// Mock dependencies
vi.mock("@calcom/ee/billing/di/containers/Billing", () => ({
getBillingProviderService: vi.fn(() => ({
handleSubscriptionUpdate: vi.fn(),
getSubscription: vi.fn(),
})),
}));
const mockResetSubscriptionAfterRenewal = vi.fn();
vi.mock("@calcom/features/ee/billing/service/highWaterMark/HighWaterMarkService", () => {
return {
HighWaterMarkService: class MockHighWaterMarkService {
resetSubscriptionAfterRenewal = mockResetSubscriptionAfterRenewal;
},
};
});
// Create a mock logger
const createMockLogger = () => ({
warn: vi.fn(),
info: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
});
describe("hwm-webhook-utils", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("extractPeriodStartFromInvoice", () => {
it("returns undefined for empty array", () => {
const result = extractPeriodStartFromInvoice([]);
expect(result).toBeUndefined();
});
it("returns undefined for null/undefined input", () => {
// @ts-expect-error - testing edge case
expect(extractPeriodStartFromInvoice(null)).toBeUndefined();
// @ts-expect-error - testing edge case
expect(extractPeriodStartFromInvoice(undefined)).toBeUndefined();
});
it("returns undefined when first item has no period", () => {
const result = extractPeriodStartFromInvoice([{}]);
expect(result).toBeUndefined();
});
it("returns undefined when period has no start", () => {
// @ts-expect-error - testing edge case
const result = extractPeriodStartFromInvoice([{ period: { end: 1234567890 } }]);
expect(result).toBeUndefined();
});
it("returns period start when valid", () => {
const timestamp = 1704067200; // 2024-01-01 00:00:00 UTC
const result = extractPeriodStartFromInvoice([
{ period: { start: timestamp, end: timestamp + 86400 } },
]);
expect(result).toBe(timestamp);
});
it("returns first item period start when multiple items exist", () => {
const firstTimestamp = 1704067200;
const secondTimestamp = 1704153600;
const result = extractPeriodStartFromInvoice([
{ period: { start: firstTimestamp, end: firstTimestamp + 86400 } },
{ period: { start: secondTimestamp, end: secondTimestamp + 86400 } },
]);
expect(result).toBe(firstTimestamp);
});
});
describe("validateInvoiceLinesForHwm", () => {
it("returns invalid for empty array and logs warning", () => {
const mockLog = createMockLogger();
// @ts-expect-error - mock logger
const result = validateInvoiceLinesForHwm([], "sub_123", mockLog);
expect(result.isValid).toBe(false);
expect(result.periodStart).toBeUndefined();
expect(mockLog.warn).toHaveBeenCalledWith(
"Invoice has no line items for subscription sub_123, cannot process HWM"
);
});
it("returns invalid for null input and logs warning", () => {
const mockLog = createMockLogger();
// @ts-expect-error - testing edge case
const result = validateInvoiceLinesForHwm(null, "sub_456", mockLog);
expect(result.isValid).toBe(false);
expect(mockLog.warn).toHaveBeenCalled();
});
it("returns invalid when period.start is missing and logs warning", () => {
const mockLog = createMockLogger();
// @ts-expect-error - mock logger
const result = validateInvoiceLinesForHwm([{ period: undefined }], "sub_789", mockLog);
expect(result.isValid).toBe(false);
expect(result.periodStart).toBeUndefined();
expect(mockLog.warn).toHaveBeenCalledWith(
"Invoice line item missing period.start for subscription sub_789, cannot process HWM"
);
});
it("returns valid with periodStart when data is correct", () => {
const mockLog = createMockLogger();
const timestamp = 1704067200;
const result = validateInvoiceLinesForHwm(
[{ period: { start: timestamp, end: timestamp + 86400 } }],
"sub_valid",
// @ts-expect-error - mock logger
mockLog
);
expect(result.isValid).toBe(true);
expect(result.periodStart).toBe(timestamp);
expect(mockLog.warn).not.toHaveBeenCalled();
});
});
describe("handleHwmResetAfterRenewal", () => {
it("returns failure when periodStartTimestamp is undefined", async () => {
const mockLog = createMockLogger();
// @ts-expect-error - mock logger
const result = await handleHwmResetAfterRenewal("sub_123", undefined, mockLog);
expect(result.success).toBe(false);
expect(result.error).toBe("No period start timestamp");
expect(mockLog.warn).toHaveBeenCalledWith(
"No period start timestamp for subscription sub_123, skipping HWM reset"
);
expect(mockResetSubscriptionAfterRenewal).not.toHaveBeenCalled();
});
it("returns success when reset succeeds", async () => {
const mockLog = createMockLogger();
mockResetSubscriptionAfterRenewal.mockResolvedValue(true);
const timestamp = 1704067200;
// @ts-expect-error - mock logger
const result = await handleHwmResetAfterRenewal("sub_456", timestamp, mockLog);
expect(result.success).toBe(true);
expect(result.updated).toBe(true);
expect(mockResetSubscriptionAfterRenewal).toHaveBeenCalledWith({
subscriptionId: "sub_456",
newPeriodStart: new Date(timestamp * 1000),
});
expect(mockLog.info).toHaveBeenCalled();
});
it("returns success with updated=false when no update needed", async () => {
const mockLog = createMockLogger();
mockResetSubscriptionAfterRenewal.mockResolvedValue(false);
const timestamp = 1704067200;
// @ts-expect-error - mock logger
const result = await handleHwmResetAfterRenewal("sub_789", timestamp, mockLog);
expect(result.success).toBe(true);
expect(result.updated).toBe(false);
});
it("returns failure when reset throws an error", async () => {
const mockLog = createMockLogger();
mockResetSubscriptionAfterRenewal.mockRejectedValue(new Error("Stripe API error"));
const timestamp = 1704067200;
// @ts-expect-error - mock logger
const result = await handleHwmResetAfterRenewal("sub_error", timestamp, mockLog);
expect(result.success).toBe(false);
expect(result.error).toBe("Stripe API error");
expect(mockLog.error).toHaveBeenCalledWith("Failed to reset HWM after invoice paid", {
subscriptionId: "sub_error",
error: "Stripe API error",
});
});
it("handles non-Error thrown values", async () => {
const mockLog = createMockLogger();
mockResetSubscriptionAfterRenewal.mockRejectedValue("string error");
const timestamp = 1704067200;
// @ts-expect-error - mock logger
const result = await handleHwmResetAfterRenewal("sub_string", timestamp, mockLog);
expect(result.success).toBe(false);
expect(result.error).toBe("string error");
});
});
});