Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 261 additions & 15 deletions apps/web/src/app/admin/coding-plans/CodingPlansOperationsContent.tsx

Large diffs are not rendered by default.

47 changes: 47 additions & 0 deletions apps/web/src/app/admin/coding-plans/coding-plan-operations.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import {
canSubmitExtensionDays,
canSubmitReduceCount,
getCancelSubscriptionDialogCopy,
getCodingPlanInsights,
getCodingPlanProviderDisplayName,
getExtendSubscriptionDialogCopy,
getInventoryReplacementCompleteToast,
getInventoryReplacementDialogCopy,
getPlanPerformanceRows,
getReduceInventoryCompleteToast,
getReduceInventoryDialogCopy,
getReplacementCompleteToast,
getReplacementDialogCopy,
getRevocationCompleteToast,
getRevocationDialogCopy,
getSubscriptionSummaryItems,
getSwapCredentialCompleteToast,
getSwapCredentialDialogCopy,
} from '@/app/admin/coding-plans/coding-plan-operations';

const CATALOG = [
Expand Down Expand Up @@ -107,6 +112,48 @@ describe('subscription action copy', () => {
'without charging credits'
);
});

it('names the user in the swap credential dialog and warns billing is unaffected', () => {
expect(getSwapCredentialDialogCopy('Ada').title).toBe("Swap Ada's credential?");
expect(getSwapCredentialDialogCopy('Ada').description).toContain('Billing');
expect(getSwapCredentialCompleteToast()).toBe('Subscription moved to a new pooled credential.');
});
});

describe('reduce inventory copy', () => {
it('names the plan and explains the manual provider-side step', () => {
const dialog = getReduceInventoryDialogCopy('minimax-token-plan-plus');
expect(dialog.title).toBe('Queue minimax-token-plan-plus inventory for removal?');
expect(dialog.description).toContain('Pending Key Removal queue');
expect(dialog.description).toContain('remove the key from the provider');
});

it('bounds the requested count to a positive integer no greater than what is available', () => {
expect(canSubmitReduceCount('3', 5)).toBe(true);
expect(canSubmitReduceCount('5', 5)).toBe(true);
expect(canSubmitReduceCount('6', 5)).toBe(false);
expect(canSubmitReduceCount('0', 5)).toBe(false);
expect(canSubmitReduceCount('1.5', 5)).toBe(false);
expect(canSubmitReduceCount('abc', 5)).toBe(false);
});

it('reports the exact queued count and upstream plan IDs on success', () => {
expect(getReduceInventoryCompleteToast(3, 3, ['plan-a', 'plan-b', 'plan-c'])).toBe(
'Queued 3 credentials for removal in Pending Key Rotation. Deprovision using upstream plan IDs: plan-a, plan-b, plan-c'
);
});

it('flags a shortfall when fewer credentials were queued than requested', () => {
expect(getReduceInventoryCompleteToast(2, 5, ['plan-a', 'plan-b'])).toBe(
'Queued 2 of 5 requested credentials for removal (the rest were claimed before queuing). Find them in Pending Key Rotation and deprovision using upstream plan IDs: plan-a, plan-b'
);
});

it('explains that nothing was queued when every candidate was already claimed', () => {
expect(getReduceInventoryCompleteToast(0, 2, [])).toBe(
'No credentials were queued. They may have just been claimed by a subscription; refresh and try again.'
);
});
});

describe('aggregate insight helpers', () => {
Expand Down
46 changes: 46 additions & 0 deletions apps/web/src/app/admin/coding-plans/coding-plan-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,52 @@ export function getExtendSubscriptionDialogCopy(userName: string): {
};
}

export function getSwapCredentialDialogCopy(userName: string): {
title: string;
description: string;
} {
return {
title: `Swap ${userName}'s credential?`,
description:
"Moves this subscription onto the next available credential from the pool and queues the current one for manual revocation. Billing, renewal dates, and status are unaffected, and the user doesn't need to do anything.",
};
}

export function getSwapCredentialCompleteToast(): string {
return 'Subscription moved to a new pooled credential.';
}

export function getReduceInventoryDialogCopy(planId: string): {
title: string;
description: string;
} {
return {
title: `Queue ${planId} inventory for removal?`,
description:
'This will add the desired number of plans to the Pending Key Removal queue. After this, the admin must remove the key from the provider, and then mark the key as revoked',
Comment thread
intentionally-left-nil marked this conversation as resolved.
};
}

export function canSubmitReduceCount(value: string, availableCount: number): boolean {
const count = Number(value);
return Number.isInteger(count) && count >= 1 && count <= availableCount;
}

export function getReduceInventoryCompleteToast(
queuedCount: number,
requestedCount: number,
upstreamPlanIds: readonly string[]
): string {
if (queuedCount === 0) {
return 'No credentials were queued. They may have just been claimed by a subscription; refresh and try again.';
}
const idList = upstreamPlanIds.join(', ');
if (queuedCount < requestedCount) {
return `Queued ${queuedCount} of ${requestedCount} requested credentials for removal (the rest were claimed before queuing). Find them in Pending Key Rotation and deprovision using upstream plan IDs: ${idList}`;
}
return `Queued ${queuedCount} credential${queuedCount === 1 ? '' : 's'} for removal in Pending Key Rotation. Deprovision using upstream plan IDs: ${idList}`;
}

export type InsightsRangeDays = 7 | 14 | 30;

export type AdminSubscriptionSummary = {
Expand Down
Loading