Files
fotospiel-app/resources/js/admin/mobile/__tests__/billingUsage.test.ts
Codex Agent eeeca0eed5
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Show event-per-purchase for endcustomer packages
2026-01-16 14:00:12 +01:00

126 lines
3.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { TenantPackageSummary } from '../../api';
import { buildPackageUsageMetrics, formatPackageEventAllowance, getUsageState, usagePercent } from '../billingUsage';
const basePackage: TenantPackageSummary = {
id: 1,
package_id: 1,
package_name: 'Pro',
package_type: 'reseller',
included_package_slug: 'pro',
active: true,
used_events: 2,
remaining_events: 3,
price: 120,
currency: 'EUR',
purchased_at: null,
expires_at: null,
package_limits: {
max_events_per_year: 5,
max_guests: 150,
max_photos: 1000,
gallery_days: 90,
remaining_gallery_days: 30,
},
branding_allowed: true,
watermark_allowed: true,
features: null,
};
describe('buildPackageUsageMetrics', () => {
it('builds usage metrics for event, guest, and photo limits', () => {
const metrics = buildPackageUsageMetrics(basePackage);
const keys = metrics.map((metric) => metric.key);
expect(keys).toEqual(['events', 'guests', 'photos', 'gallery']);
const eventMetric = metrics.find((metric) => metric.key === 'events');
expect(eventMetric?.used).toBe(2);
expect(eventMetric?.limit).toBe(5);
expect(eventMetric?.remaining).toBe(3);
const galleryMetric = metrics.find((metric) => metric.key === 'gallery');
expect(galleryMetric?.remaining).toBe(30);
});
it('derives remaining when only usage is known', () => {
const metrics = buildPackageUsageMetrics({
...basePackage,
remaining_events: null,
package_limits: { max_events_per_year: 10, gallery_days: 60 },
used_events: 4,
});
expect(metrics[0].remaining).toBe(6);
});
it('filters metrics without limits', () => {
const metrics = buildPackageUsageMetrics({
...basePackage,
package_limits: { max_events_per_year: 0 },
});
expect(metrics).toHaveLength(0);
});
});
describe('usagePercent', () => {
it('calculates usage percent when usage is known', () => {
const metrics = buildPackageUsageMetrics(basePackage);
const eventMetric = metrics.find((metric) => metric.key === 'events');
expect(eventMetric).toBeTruthy();
expect(usagePercent(eventMetric!)).toBe(40);
});
it('defaults to full bar when usage is unknown', () => {
const metrics = buildPackageUsageMetrics({
...basePackage,
used_events: NaN,
remaining_events: null,
package_limits: { max_events_per_year: 10 },
});
expect(usagePercent(metrics[0])).toBe(100);
});
});
describe('getUsageState', () => {
it('returns warning and danger based on thresholds', () => {
const metrics = buildPackageUsageMetrics({
...basePackage,
used_events: 8,
remaining_events: null,
package_limits: { max_events_per_year: 10 },
});
expect(getUsageState(metrics[0])).toBe('warning');
expect(getUsageState({ ...metrics[0], used: 10, remaining: 0 })).toBe('danger');
});
});
describe('formatPackageEventAllowance', () => {
const t = (key: string, options?: Record<string, unknown>) => {
if (key === 'mobileBilling.eventPerPurchase') {
return String(options?.defaultValue ?? '1 event per purchase');
}
if (key === 'mobileBilling.remainingEventsZero') {
return String(options?.defaultValue ?? 'No events remaining');
}
if (key === 'mobileBilling.remainingEventsOf') {
return `${options?.remaining} of ${options?.limit} events remaining`;
}
if (key === 'mobileBilling.remainingEvents') {
return `${options?.count} events`;
}
return key;
};
it('returns event-per-purchase for endcustomer packages', () => {
const label = formatPackageEventAllowance(
{ ...basePackage, package_type: 'endcustomer', remaining_events: null },
t
);
expect(label).toBe('1 event per purchase');
});
it('returns remaining events for reseller packages', () => {
const label = formatPackageEventAllowance(basePackage, t);
expect(label).toBe('3 of 5 events remaining');
});
});