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
14 changes: 12 additions & 2 deletions frontend/src/__tests__/execute-mode-toggle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,21 @@ jest.mock('../toast', () => ({
}));

import * as state from '../state';

import { ADMINISTRATORS_GROUP_ID, PURCHASER_GROUP_ID } from '../permissions';

// 'admin' represents a fully-capable admin: Administrators + Purchaser group
// membership (mirrors the auto-migration that adds existing admins to the
// Purchaser group on first deploy of issue #923). Both groups are needed:
// ADMINISTRATORS_GROUP_ID for isAdmin() (gates execute-any/execute-own), and
// PURCHASER_GROUP_ID for isPurchaser() (gates the carved-out execute:purchases
// / approve-any:purchases / retry-any:purchases verbs from issue #923).
// 'user' and 'readonly' have no group memberships and thus no execute access.
type UserRole = 'admin' | 'user' | 'readonly';
const mockUser = (role: UserRole | null) => {
(state.getCurrentUser as jest.Mock).mockReturnValue(
role === null ? null : { id: 'u', email: 'u@example.com', role },
role === null
? null
: { id: 'u', email: 'u@example.com', groups: role === 'admin' ? [ADMINISTRATORS_GROUP_ID, PURCHASER_GROUP_ID] : [] },
);
};

Expand Down
6 changes: 6 additions & 0 deletions frontend/src/__tests__/purchase-execution-toast.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ jest.mock('../recommendations', () => ({
clearPurchaseModalRecommendations: jest.fn(),
getFanOutBuckets: jest.fn(),
clearFanOutBuckets: jest.fn(),
// app.ts calls getExecuteMode() and clearExecuteMode() in
// handleExecutePurchase (issue #289 / PR #924 carve-out).
// Default getExecuteMode to '' (approval path) so existing toast tests
// that don't exercise the direct-execute path are unaffected.
getExecuteMode: jest.fn().mockReturnValue(''),
clearExecuteMode: jest.fn(),
}));

jest.mock('../plans', () => ({
Expand Down
29 changes: 29 additions & 0 deletions frontend/src/__tests__/recommendations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1393,12 +1393,21 @@ describe('Recommendations Module', () => {
});

test('shows purchase summary', async () => {
// Use a non-admin session so the approval-required note renders instead
// of the execute-mode toggle (issue #923 carve-out: execute-any/own is
// gated on isAdmin(), which requires the Administrators group; a regular
// user without group membership sees the approval-required note).
// Restore the default admin mock after this test so sibling tests are
// unaffected; the outer beforeEach only calls clearAllMocks() which does
// not reset return values.
(state.getCurrentUser as jest.Mock).mockReturnValue({ id: 'u-reg', email: 'user@example.com', groups: [] });
const recommendations = [
{ id: 'rec-2', provider: 'aws' as const, service: 'ec2', resource_type: 't3.medium', region: 'us-east-1', count: 5, term: 1, savings: 100, upfront_cost: 500 },
{ id: 'rec-3', provider: 'aws' as const, service: 'rds', resource_type: 'db.r5.large', region: 'us-east-1', count: 2, term: 1, savings: 200, upfront_cost: 1000 }
];

await openPurchaseModal(recommendations);
(state.getCurrentUser as jest.Mock).mockReturnValue({ id: 'u-admin', email: 'admin@example.com', groups: ['00000000-0000-5000-8000-000000000001'] });

const details = document.getElementById('purchase-details');
// Issue #320: the modal now renders a full breakdown table with column
Expand Down Expand Up @@ -1433,6 +1442,26 @@ describe('Recommendations Module', () => {
// wording so a regression that reverts to the misleading "Execute
// Purchase" framing fails this suite.
describe('approval-required messaging (issue #288)', () => {
beforeEach(() => {
// These tests exercise the approval-required path that renders the
// explanatory note. After issue #923 / PR #924, the execute-mode
// toggle renders only for sessions with execute-any:purchases or
// execute-own:purchases (both gated on isAdmin()). Using a regular
// user here ensures the approval-required note is rendered and the
// execute-mode toggle is absent, which is what the assertions below
// expect. The admin execute-mode toggle path is covered in
// execute-mode-toggle.test.ts.
(state.getCurrentUser as jest.Mock).mockReturnValue({ id: 'u-reg', email: 'user@example.com', groups: [] });
});

afterEach(() => {
// Restore the default admin mock so tests in sibling describe blocks
// are not affected by this scope's non-admin override. The outer
// beforeEach only calls clearAllMocks() which does not reset return
// values, so we must restore explicitly.
(state.getCurrentUser as jest.Mock).mockReturnValue({ id: 'u-admin', email: 'admin@example.com', groups: ['00000000-0000-5000-8000-000000000001'] });
});

const baseRec = {
id: 'rec-288',
provider: 'aws' as const,
Expand Down
Loading