Skip to content

Commit f8194f4

Browse files
authored
Merge pull request #13 from aws-solutions/release/v1.0.3
release/v1.0.3
2 parents 0a48bf3 + ee277a6 commit f8194f4

File tree

10 files changed

+1621
-1386
lines changed

10 files changed

+1621
-1386
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.3] - 2026-02-09
9+
10+
### Security
11+
12+
- Update dependencies to mitigate [CVE-2026-25547](https://nvd.nist.gov/vuln/detail/CVE-2026-25547) and [CVE-2026-0775](https://nvd.nist.gov/vuln/detail/CVE-2026-0775).
13+
814
## [1.0.2] - 2026-02-05
915

1016
### Security

solution-manifest.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
id: SO0310
22
name: deepracer-on-aws
3-
version: v1.0.2
3+
version: v1.0.3
44
cloudformation_templates:
55
- template: deepracer-on-aws.template
66
main_template: true
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { describe, it, expect, vi, beforeEach } from 'vitest';
5+
6+
import { TRACKS } from '#constants/tracks';
7+
8+
import { SortByValue } from '../constants';
9+
import { getTrackTiles } from '../utils';
10+
11+
// Mock the tracks constant
12+
vi.mock('#constants/tracks', () => ({
13+
TRACKS: [
14+
{
15+
trackId: 'track1',
16+
name: 'Short Easy Track',
17+
description: 'A short and easy track',
18+
length: 10,
19+
difficulty: 1,
20+
},
21+
{
22+
trackId: 'track2',
23+
name: 'Medium Track',
24+
description: 'A medium difficulty track',
25+
length: 20,
26+
difficulty: 5,
27+
},
28+
{
29+
trackId: 'track3',
30+
name: 'Long Hard Track',
31+
description: 'A long and difficult track',
32+
length: 30,
33+
difficulty: 10,
34+
},
35+
],
36+
}));
37+
38+
describe('getTrackTiles', () => {
39+
beforeEach(() => {
40+
vi.clearAllMocks();
41+
});
42+
43+
describe('sorting functionality', () => {
44+
it('sorts tracks by length shortest to longest', () => {
45+
const result = getTrackTiles(SortByValue.LENGTH_SHORTEST, '');
46+
47+
expect(result).toHaveLength(3);
48+
expect(result[0].value).toBe('track1');
49+
expect(result[1].value).toBe('track2');
50+
expect(result[2].value).toBe('track3');
51+
});
52+
53+
it('sorts tracks by length longest to shortest', () => {
54+
const result = getTrackTiles(SortByValue.LENGTH_LONGEST, '');
55+
56+
expect(result).toHaveLength(3);
57+
expect(result[0].value).toBe('track3');
58+
expect(result[1].value).toBe('track2');
59+
expect(result[2].value).toBe('track1');
60+
});
61+
62+
it('sorts tracks by difficulty most to least', () => {
63+
const result = getTrackTiles(SortByValue.DIFFICULTY_MOST, '');
64+
65+
expect(result).toHaveLength(3);
66+
expect(result[0].value).toBe('track1');
67+
expect(result[1].value).toBe('track2');
68+
expect(result[2].value).toBe('track3');
69+
});
70+
71+
it('sorts tracks by difficulty least to most', () => {
72+
const result = getTrackTiles(SortByValue.DIFFICULTY_LEAST, '');
73+
74+
expect(result).toHaveLength(3);
75+
expect(result[0].value).toBe('track3');
76+
expect(result[1].value).toBe('track2');
77+
expect(result[2].value).toBe('track1');
78+
});
79+
80+
it('uses LENGTH_SHORTEST as default sort', () => {
81+
const result = getTrackTiles('invalid' as SortByValue, '');
82+
83+
expect(result).toHaveLength(3);
84+
expect(result[0].value).toBe('track1');
85+
expect(result[1].value).toBe('track2');
86+
expect(result[2].value).toBe('track3');
87+
});
88+
});
89+
90+
describe('filtering functionality', () => {
91+
it('filters tracks by name (case insensitive)', () => {
92+
const result = getTrackTiles(SortByValue.LENGTH_SHORTEST, 'short');
93+
94+
expect(result).toHaveLength(1);
95+
expect(result[0].value).toBe('track1');
96+
expect(result[0].label).toBe('Short Easy Track');
97+
});
98+
99+
it('filters tracks with partial match', () => {
100+
const result = getTrackTiles(SortByValue.LENGTH_SHORTEST, 'track');
101+
102+
expect(result).toHaveLength(3);
103+
});
104+
105+
it('returns empty array when no tracks match filter', () => {
106+
const result = getTrackTiles(SortByValue.LENGTH_SHORTEST, 'nonexistent');
107+
108+
expect(result).toHaveLength(0);
109+
});
110+
111+
it('handles empty filter string', () => {
112+
const result = getTrackTiles(SortByValue.LENGTH_SHORTEST, '');
113+
114+
expect(result).toHaveLength(3);
115+
});
116+
});
117+
118+
describe('output format', () => {
119+
it('returns correctly formatted track tiles', () => {
120+
const result = getTrackTiles(SortByValue.LENGTH_SHORTEST, '');
121+
122+
expect(result[0]).toHaveProperty('label');
123+
expect(result[0]).toHaveProperty('description');
124+
expect(result[0]).toHaveProperty('image');
125+
expect(result[0]).toHaveProperty('value');
126+
});
127+
128+
it('includes track name as label', () => {
129+
const result = getTrackTiles(SortByValue.LENGTH_SHORTEST, '');
130+
131+
expect(result[0].label).toBe('Short Easy Track');
132+
});
133+
134+
it('includes track description', () => {
135+
const result = getTrackTiles(SortByValue.LENGTH_SHORTEST, '');
136+
137+
expect(result[0].description).toBe('A short and easy track');
138+
});
139+
140+
it('includes track ID as value', () => {
141+
const result = getTrackTiles(SortByValue.LENGTH_SHORTEST, '');
142+
143+
expect(result[0].value).toBe('track1');
144+
});
145+
});
146+
147+
describe('immutability', () => {
148+
it('does not mutate the original TRACKS array', () => {
149+
const originalOrder = [...TRACKS];
150+
151+
getTrackTiles(SortByValue.LENGTH_LONGEST, '');
152+
153+
expect(TRACKS).toEqual(originalOrder);
154+
});
155+
156+
it('does not mutate TRACKS when sorting by difficulty', () => {
157+
const originalOrder = [...TRACKS];
158+
159+
getTrackTiles(SortByValue.DIFFICULTY_MOST, '');
160+
161+
expect(TRACKS).toEqual(originalOrder);
162+
});
163+
});
164+
165+
describe('combined sorting and filtering', () => {
166+
it('applies both sorting and filtering correctly', () => {
167+
const result = getTrackTiles(SortByValue.LENGTH_LONGEST, 'track');
168+
169+
expect(result).toHaveLength(3);
170+
expect(result[0].value).toBe('track3');
171+
expect(result[2].value).toBe('track1');
172+
});
173+
});
174+
});

source/apps/website/src/pages/CreateModel/components/ModelInfo/utils.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ export const getTrackTiles = (sortBy: SortByValue, filteringText: string) => {
1010

1111
switch (sortBy) {
1212
case SortByValue.LENGTH_LONGEST:
13-
sortedTracks = TRACKS.sort((a, b) => b.length - a.length);
13+
sortedTracks = [...TRACKS].sort((a, b) => b.length - a.length);
1414
break;
1515
case SortByValue.DIFFICULTY_MOST:
16-
sortedTracks = TRACKS.sort((a, b) => a.difficulty - b.difficulty);
16+
sortedTracks = [...TRACKS].sort((a, b) => a.difficulty - b.difficulty);
1717
break;
1818
case SortByValue.DIFFICULTY_LEAST:
19-
sortedTracks = TRACKS.sort((a, b) => b.difficulty - a.difficulty);
19+
sortedTracks = [...TRACKS].sort((a, b) => b.difficulty - a.difficulty);
2020
break;
2121
case SortByValue.LENGTH_SHORTEST:
2222
default:
23-
sortedTracks = TRACKS.sort((a, b) => a.length - b.length);
23+
sortedTracks = [...TRACKS].sort((a, b) => a.length - b.length);
2424
break;
2525
}
2626

source/apps/website/src/services/deepRacer/__tests__/middleware.spec.ts

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { MiddlewareStack } from '@smithy/types';
88
import { type AuthSession, fetchAuthSession } from 'aws-amplify/auth';
99
import { vi } from 'vitest';
1010

11-
import { httpSigningMiddleware, httpSigningPlugin } from '../middleware';
11+
import { httpSigningMiddleware, httpSigningPlugin, signRequest } from '../middleware';
1212

1313
// Mock dependencies
1414
vi.mock('@aws-crypto/sha256-browser', () => ({
@@ -23,7 +23,7 @@ vi.mock('aws-amplify/auth', () => ({
2323
fetchAuthSession: vi.fn(),
2424
}));
2525

26-
vi.mock('#utils/envUtils', () => ({
26+
vi.mock('../../utils/envUtils.js', () => ({
2727
environmentConfig: {
2828
region: 'us-east-1',
2929
},
@@ -36,13 +36,14 @@ describe('middleware', () => {
3636
operation: { name: 'TestOperation' },
3737
};
3838
const mockSignedRequest = { headers: { Authorization: 'AWS4-HMAC-SHA256...' } };
39+
const mockSignatureV4Instance = {
40+
sign: vi.fn().mockResolvedValue(mockSignedRequest),
41+
};
3942

4043
beforeEach(() => {
4144
vi.clearAllMocks();
4245
// Mock SignatureV4 instance
43-
(SignatureV4 as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => ({
44-
sign: vi.fn().mockResolvedValue(mockSignedRequest),
45-
}));
46+
(SignatureV4 as unknown as ReturnType<typeof vi.fn>).mockImplementation(() => mockSignatureV4Instance);
4647
});
4748

4849
describe('httpSigningMiddleware', () => {
@@ -135,4 +136,102 @@ describe('middleware', () => {
135136
});
136137
});
137138
});
139+
140+
describe('signRequest', () => {
141+
it('should sign request successfully with valid credentials', async () => {
142+
const mockSession: AuthSession = {
143+
credentials: {
144+
accessKeyId: 'test-access-key-id',
145+
secretAccessKey: 'test-secret-access-key',
146+
sessionToken: 'test-session-token',
147+
},
148+
identityId: 'test-identity-id',
149+
tokens: {
150+
accessToken: {
151+
toString: () => 'test-access-token',
152+
payload: {},
153+
},
154+
idToken: {
155+
toString: () => 'test-id-token',
156+
payload: {},
157+
},
158+
},
159+
};
160+
161+
const mockRequest = new HttpRequest({
162+
method: 'POST',
163+
protocol: 'https:',
164+
hostname: 'api.example.com',
165+
path: '/test',
166+
headers: {
167+
'Content-Type': 'application/json',
168+
},
169+
});
170+
171+
const result = await signRequest(mockSession, mockRequest);
172+
173+
expect(result.headers.authorization).toBeDefined();
174+
expect(result.headers['Content-Type']).toBeDefined();
175+
expect(result.headers['x-amz-content-sha256']).toBeDefined();
176+
expect(result.headers['x-amz-date']).toBeDefined();
177+
expect(result.headers['x-amz-security-token']).toBeDefined();
178+
expect(mockRequest).toBeDefined();
179+
});
180+
181+
it('should throw error when session has no credentials', async () => {
182+
const mockSession: AuthSession = {
183+
credentials: undefined,
184+
identityId: 'test-identity-id',
185+
tokens: {
186+
accessToken: {
187+
toString: () => 'test-access-token',
188+
payload: {},
189+
},
190+
idToken: {
191+
toString: () => 'test-id-token',
192+
payload: {},
193+
},
194+
},
195+
};
196+
197+
const mockRequest = new HttpRequest({
198+
method: 'GET',
199+
protocol: 'https:',
200+
hostname: 'api.example.com',
201+
path: '/test',
202+
});
203+
204+
await expect(signRequest(mockSession, mockRequest)).rejects.toThrow('No credentials found');
205+
206+
expect(SignatureV4).not.toHaveBeenCalled();
207+
});
208+
209+
it('should throw error when session credentials are undefined', async () => {
210+
const mockSession: AuthSession = {
211+
credentials: undefined,
212+
identityId: 'test-identity-id',
213+
tokens: {
214+
accessToken: {
215+
toString: () => 'test-access-token',
216+
payload: {},
217+
},
218+
idToken: {
219+
toString: () => 'test-id-token',
220+
payload: {},
221+
},
222+
},
223+
};
224+
225+
const mockRequest = new HttpRequest({
226+
method: 'PUT',
227+
protocol: 'https:',
228+
hostname: 'api.example.com',
229+
path: '/test',
230+
});
231+
232+
await expect(signRequest(mockSession, mockRequest)).rejects.toThrow('No credentials found');
233+
234+
expect(SignatureV4).not.toHaveBeenCalled();
235+
});
236+
});
138237
});

0 commit comments

Comments
 (0)