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: 14 additions & 0 deletions frontend/src/__tests__/purchase-modal-submit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,8 @@ describe('Issue #1903: purchase modal re-prices on Term/Payment change', () => {
test.each<[string, string, string, Record<string, unknown>, unknown]>([
['ec2', 'platform', 'm5.large', { instance_type: 'm5.large', platform: 'Linux/UNIX', tenancy: 'default', scope: 'Region' }, 'Windows'],
['ec2', 'tenancy', 'm5.large', { instance_type: 'm5.large', platform: 'Linux/UNIX', tenancy: 'default', scope: 'Region' }, 'dedicated'],
['ec2', 'availability_zone', 'm5.large', { instance_type: 'm5.large', platform: 'Linux/UNIX', tenancy: 'default', scope: 'Availability Zone', availability_zone: 'us-east-1a' }, 'us-east-1b'],
['compute', 'availability_zone', 'm5.large', { instance_type: 'm5.large', platform: 'Linux/UNIX', tenancy: 'default', scope: 'Availability Zone', availability_zone: 'us-east-1a' }, 'us-east-1b'],
['ec2', 'scope', 'm5.large', { instance_type: 'm5.large', platform: 'Linux/UNIX', tenancy: 'default', scope: 'Region' }, 'Availability Zone'],
['compute', 'platform', 'm5.large', { instance_type: 'm5.large', platform: 'Linux/UNIX', tenancy: 'default', scope: 'Region' }, 'Windows'],
['rds', 'az_config', 'db.r5.large', { engine: 'postgres', az_config: 'single-az' }, 'multi-az'],
Expand Down Expand Up @@ -547,6 +549,18 @@ describe('Issue #1903: purchase modal re-prices on Term/Payment change', () => {
]);
});

test('purchase identity keeps a regional EC2 rec (no AZ) matching its own variants', async () => {
const details = { instance_type: 'm5.large', platform: 'Linux/UNIX', tenancy: 'default', scope: 'Region' };
const original = { ...buildRows()[0]!, details };
const sibling = { ...original, id: 'regional-1-all', term: 1, upfront_cost: 12000 };
(state.getRecommendations as jest.Mock).mockReturnValue([sibling, original]);

await openPurchaseModal([original]);

expect(Array.from(document.querySelector<HTMLSelectElement>('.purchase-row-term')!.options, (option) => option.value).sort())
.toEqual(['1', '3']);
});

test.each([undefined, null, [], 'invalid', { platform: 1 }, {}])('purchase identity does not match populated EC2 details to %j', async (otherDetails) => {
const original = { ...buildRows()[0]!, details: { platform: 'Linux/UNIX', tenancy: 'default', scope: 'Region' } };
const other = { ...original, id: 'missing-identity', term: 1, details: otherDetails };
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/__tests__/recommendations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5155,6 +5155,17 @@ describe('Issues #225 + #226: cell grouping with savings range and collapse/expa
const groups = groupRecsByCell([rec1, rec2]);
expect(groups.size).toBe(2);
});

test('zonal EC2 recs differing only by AZ are separate cells; regional recs are unchanged (issue #792)', () => {
const a = mkRec({ availability_zone: 'us-east-1a' });
const b = mkRec({ availability_zone: 'us-east-1b' });
const a2 = mkRec({ availability_zone: 'us-east-1a', term: 3 });
const regional1 = mkRec({});
const regional2 = mkRec({ term: 3 });
const groups = groupRecsByCell([a, b, a2, regional1, regional2]);
expect(groups.size).toBe(3);
expect(Array.from(groups.keys())).toContain('aws||ec2|us-east-1|m5.large|');
});
});

describe('cellSummary (pure helper)', () => {
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ export interface Recommendation {
region: string;
resource_type: string;
engine?: string;
// Zonal EC2 reservation AZ name; absent for regional recs (issue #792).
availability_zone?: string;
// Opaque ServiceDetails payload from the backend (json.RawMessage).
// The frontend treats this as a pass-through blob: it is stashed when
// the GET /api/recommendations response is parsed and forwarded unchanged
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/recommendations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -988,7 +988,10 @@ const SORTABLE_STRING_COLUMNS: Record<string, (r: LocalRecommendation) => string
// Used by issue #224's radio enforcement: at most one variant per cell
// can be selected at any time.
function cellKey(rec: LocalRecommendation): string {
return `${rec.provider}|${rec.cloud_account_id ?? ''}|${rec.service}|${rec.region}|${rec.resource_type}|${rec.engine ?? ''}`;
const key = `${rec.provider}|${rec.cloud_account_id ?? ''}|${rec.service}|${rec.region}|${rec.resource_type}|${rec.engine ?? ''}`;
// Zonal EC2 recs in different AZs are distinct purchasable cells (issue #792);
// the suffix is added only for them so every other cell key is unchanged.
return rec.availability_zone ? `${key}|${rec.availability_zone}` : key;
}

// issues #225 + #226: cell-grouping helpers.
Expand Down Expand Up @@ -1020,7 +1023,7 @@ function samePurchaseVariantIdentity(a: LocalRecommendation, b: LocalRecommendat
switch (a.service) {
case 'ec2':
case 'compute':
fields = ['platform', 'tenancy', 'scope'];
fields = ['platform', 'tenancy', 'scope', 'availability_zone'];
break;
case 'rds':
case 'relational-db':
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export interface LocalRecommendation {
service: string;
resource_type: string;
engine?: string;
// Zonal EC2 reservation AZ name; absent for regional recs (issue #792).
availability_zone?: string;
// Opaque ServiceDetails payload from the backend (json.RawMessage).
// Stashed when the GET /api/recommendations response is parsed and
// forwarded unchanged in the POST /api/purchases/execute body so the
Expand Down
Loading