@squiz/job-runner-lib 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/domains/job/constants/QueueBacklog.constants.d.ts +11 -0
  3. package/lib/domains/job/constants/RetryConfig.constants.d.ts +6 -1
  4. package/lib/domains/job/constants/RuntimeImage.constants.d.ts +5 -0
  5. package/lib/domains/job/errors/RuntimeImageTaskDefinitionError.d.ts +7 -0
  6. package/lib/domains/job/errors/index.d.ts +1 -0
  7. package/lib/domains/job/index.d.ts +1 -0
  8. package/lib/domains/job/manifest/JobManifestTypes.d.ts +13 -1
  9. package/lib/domains/job/model/Job/Job.d.ts +12 -0
  10. package/lib/domains/job/repository/JobExecutionRepository/JobExecutionRepository.d.ts +15 -0
  11. package/lib/domains/job/repository/JobManifestRepository/JobManifestRepository.d.ts +1 -1
  12. package/lib/domains/job/repository/JobRepository/JobRepository.d.ts +2 -2
  13. package/lib/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.d.ts +1 -0
  14. package/lib/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.defs.d.ts +1 -0
  15. package/lib/domains/job/utils/ResolveRuntimeImageTaskDefinitionArn/ResolveRuntimeImageTaskDefinitionArn.d.ts +6 -0
  16. package/lib/domains/job/utils/ResolveRuntimeImageTaskDefinitionArn/ResolveRuntimeImageTaskDefinitionArn.types.d.ts +9 -0
  17. package/lib/domains/job/utils/index.d.ts +2 -0
  18. package/lib/domains/manifest/index.d.ts +3 -2
  19. package/lib/domains/manifest/utils/defaultAutoRetry.d.ts +5 -0
  20. package/lib/domains/manifest/utils/defaultAutoRetry.spec.d.ts +5 -0
  21. package/lib/index.js +9 -9
  22. package/lib/index.js.map +4 -4
  23. package/package.json +2 -2
  24. package/src/domains/job/constants/QueueBacklog.constants.ts +12 -0
  25. package/src/domains/job/constants/RetryConfig.constants.ts +8 -2
  26. package/src/domains/job/constants/RuntimeImage.constants.ts +8 -2
  27. package/src/domains/job/errors/RuntimeImageTaskDefinitionError.ts +10 -0
  28. package/src/domains/job/errors/index.ts +1 -0
  29. package/src/domains/job/index.ts +1 -0
  30. package/src/domains/job/manifest/JobManifestTypes.ts +16 -1
  31. package/src/domains/job/manifest/mocks/JobManifest.mock.ts +1 -0
  32. package/src/domains/job/manifest/mocks/JobManifestDto.mock.ts +3 -0
  33. package/src/domains/job/manifest/mocks/JobManifestRecord.mock.ts +1 -0
  34. package/src/domains/job/manifest/mocks/JobManifestRequest.mock.ts +1 -0
  35. package/src/domains/job/model/Job/Job.ts +14 -0
  36. package/src/domains/job/model/Job/mocks/Job.mock.ts +2 -0
  37. package/src/domains/job/model/Job/mocks/JobDto.mock.ts +4 -0
  38. package/src/domains/job/model/Job/mocks/JobRecord.mock.ts +2 -0
  39. package/src/domains/job/model/Job/mocks/JobRequest.mock.ts +2 -0
  40. package/src/domains/job/repository/JobExecutionRepository/JobExecutionRepository.spec.ts +65 -0
  41. package/src/domains/job/repository/JobExecutionRepository/JobExecutionRepository.ts +53 -0
  42. package/src/domains/job/repository/JobManifestRepository/JobManifestRepository.spec.ts +56 -4
  43. package/src/domains/job/repository/JobManifestRepository/JobManifestRepository.ts +14 -4
  44. package/src/domains/job/repository/JobRepository/JobRepository.spec.ts +36 -4
  45. package/src/domains/job/repository/JobRepository/JobRepository.ts +10 -5
  46. package/src/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.defs.ts +1 -0
  47. package/src/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.spec.ts +53 -3
  48. package/src/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.ts +19 -8
  49. package/src/domains/job/services/JobManifestService/JobManifestService.spec.ts +9 -8
  50. package/src/domains/job/utils/ResolveRuntimeImageTaskDefinitionArn/ResolveRuntimeImageTaskDefinitionArn.ts +39 -0
  51. package/src/domains/job/utils/ResolveRuntimeImageTaskDefinitionArn/ResolveRuntimeImageTaskDefinitionArn.types.ts +10 -0
  52. package/src/domains/job/utils/index.ts +2 -0
  53. package/src/domains/manifest/index.ts +3 -2
  54. package/src/domains/manifest/utils/defaultAutoRetry.spec.ts +16 -0
  55. package/src/domains/manifest/utils/defaultAutoRetry.ts +6 -0
  56. package/src/domains/manifest/utils/validateRuntimeImage.spec.ts +11 -12
@@ -11,18 +11,19 @@ import {
11
11
  QueryCommandOutput,
12
12
  } from '@aws-sdk/client-dynamodb';
13
13
  import { inject } from 'inversify';
14
- import { provideTransient } from '../../transientProvider';
15
- import { Repository } from '../AbstractRepository';
14
+ import { InjectTokens } from '../../constants/InjectTokens';
16
15
  import { ENTITY_TYPES } from '../../constants/Repository.constants';
17
- import { parsePartitionKey } from '../../ParsePartitionKey';
18
- import { CreateJobRequest, Job, JobDto, JobSk, ListJobParameters } from '../../model/Job';
16
+ import { getRuntimeImageOrDefault } from '../../constants/RuntimeImage.constants';
19
17
  import {
20
18
  decodeTokenString,
21
19
  PAGINATION_DIRECTION_TYPE_BEFORE,
22
20
  PaginationInfo,
23
21
  PaginationQueryResponse,
24
22
  } from '../../core/pagination';
25
- import { InjectTokens } from '../../constants/InjectTokens';
23
+ import { CreateJobRequest, Job, JobDto, JobSk, ListJobParameters } from '../../model/Job';
24
+ import { parsePartitionKey } from '../../ParsePartitionKey';
25
+ import { provideTransient } from '../../transientProvider';
26
+ import { Repository } from '../AbstractRepository';
26
27
 
27
28
  @provideTransient(JobRepository)
28
29
  export class JobRepository extends Repository<Job, JobDto, JobSk> {
@@ -37,6 +38,7 @@ export class JobRepository extends Repository<Job, JobDto, JobSk> {
37
38
  public async create(input: CreateJobRequest): Promise<Job> {
38
39
  const newJob: Job = {
39
40
  ...input,
41
+ image: getRuntimeImageOrDefault(input.image),
40
42
  type: ENTITY_TYPES.job,
41
43
  };
42
44
 
@@ -157,11 +159,13 @@ export class JobRepository extends Repository<Job, JobDto, JobSk> {
157
159
  name: { S: item.name },
158
160
  description: item.description ? { S: item.description } : { NULL: true },
159
161
  version: { S: item.version },
162
+ image: { S: item.image },
160
163
  };
161
164
  }
162
165
 
163
166
  convertToItem(dbItem: JobDto): Job {
164
167
  const { pk, name, version } = dbItem;
168
+ const image = (dbItem as Partial<JobDto>).image?.S;
165
169
  let description;
166
170
  // TODO https://jira.squiz.net/browse/CONN-609 remove this check if we tidy up
167
171
  // jobs added before this field was implemented
@@ -175,6 +179,7 @@ export class JobRepository extends Repository<Job, JobDto, JobSk> {
175
179
  description: description.S ? (description.S as string) : '',
176
180
  type: parsePartitionKey(pk.S as string).entityType,
177
181
  version: version.S as string,
182
+ image: getRuntimeImageOrDefault(image),
178
183
  };
179
184
  }
180
185
 
@@ -17,5 +17,6 @@ export interface RunJobTaskParams {
17
17
  shortEnvironment: string;
18
18
  clusterArn: string;
19
19
  taskDefinitionArn: string;
20
+ taskDefinitionArnsByRuntimeImage: Record<string, string>;
20
21
  tenantId: string;
21
22
  }
@@ -4,6 +4,7 @@ import { AwsClientStub, mockClient } from 'aws-sdk-client-mock';
4
4
  import 'aws-sdk-client-mock-jest';
5
5
  import { JobDetailsRepository, JobUploadsRepository } from '../../S3Repository';
6
6
  import { JOB_EXECUTION_STATUS } from '../../constants/JobExecutionStatus.constants';
7
+ import { RuntimeImageTaskDefinitionError } from '../../errors';
7
8
  import { createMockJobManifest } from '../../manifest';
8
9
  import { createMockJobContext } from '../../model/JobContext';
9
10
  import { createMockJobExecution } from '../../model/JobExecution';
@@ -30,7 +31,11 @@ describe('JobExecutionTaskService', () => {
30
31
  shortRegion: 'au',
31
32
  shortEnvironment: 'dev',
32
33
  clusterArn: `clusterArn`,
33
- taskDefinitionArn: `taskDefinitionArn`,
34
+ taskDefinitionArn: `legacyTaskDefinitionArn`,
35
+ taskDefinitionArnsByRuntimeImage: {
36
+ 'node:20': 'node20TaskDefinitionArn',
37
+ 'node:24': 'node24TaskDefinitionArn',
38
+ },
34
39
  tenantId: 'some_tenant',
35
40
  };
36
41
 
@@ -104,7 +109,7 @@ describe('JobExecutionTaskService', () => {
104
109
  const mockArn = `mock-arn`;
105
110
  const mockTask = { taskArn: mockArn };
106
111
  const jobContext = createMockJobContext();
107
- const jobManifest = createMockJobManifest();
112
+ const jobManifest = createMockJobManifest({ image: 'node:24' });
108
113
  const jobExecution = createMockJobExecution({
109
114
  id: `20250917T005949395Z-9602417782`,
110
115
  jobName: 'jobName',
@@ -173,7 +178,7 @@ describe('JobExecutionTaskService', () => {
173
178
  { key: 'map-migrated', value: 'migUJ1IX3DUXG' },
174
179
  { key: 'cmp:tenant', value: 'some_tenant' },
175
180
  ],
176
- taskDefinition: defaultRunJobTaskParams.taskDefinitionArn,
181
+ taskDefinition: defaultRunJobTaskParams.taskDefinitionArnsByRuntimeImage['node:24'],
177
182
  // sha1 of execution job name/id + tenant + retryCount.
178
183
  clientToken: '4725755e08138edde031a177c36a269ecdbed5a3',
179
184
  });
@@ -207,6 +212,51 @@ describe('JobExecutionTaskService', () => {
207
212
  null,
208
213
  defaultRunJobTaskParams.jobExecution,
209
214
  );
215
+ expect(mockEcsClient).toHaveReceivedCommandWith(RunTaskCommand, {
216
+ taskDefinition: defaultRunJobTaskParams.taskDefinitionArnsByRuntimeImage['node:20'],
217
+ });
218
+ });
219
+
220
+ it('should use the legacy task definition ARN when the node:20 map entry is missing', async () => {
221
+ const mockArn = `mock-arn`;
222
+
223
+ mockEcsClient.on(RunTaskCommand).resolves({ tasks: [{ taskArn: mockArn }] });
224
+ mockJobContextService.findJobContext.mockResolvedValue(createMockJobContext());
225
+ mockJobManifestService.findJobManifest.mockResolvedValue(createMockJobManifest({ image: 'node:20' }));
226
+
227
+ await jobExecutionTaskService.runJobExecutionTask({
228
+ ...defaultRunJobTaskParams,
229
+ taskDefinitionArnsByRuntimeImage: {},
230
+ });
231
+
232
+ expect(mockEcsClient).toHaveReceivedCommandWith(RunTaskCommand, {
233
+ taskDefinition: defaultRunJobTaskParams.taskDefinitionArn,
234
+ });
235
+ });
236
+
237
+ it('should throw before creating an ECS task when the runtime image is unsupported', async () => {
238
+ mockJobManifestService.findJobManifest.mockResolvedValue(createMockJobManifest({ image: 'node:26' }));
239
+
240
+ await expect(jobExecutionTaskService.runJobExecutionTask(defaultRunJobTaskParams)).rejects.toThrow(
241
+ RuntimeImageTaskDefinitionError,
242
+ );
243
+ expect(mockEcsClient).not.toHaveReceivedCommand(RunTaskCommand);
244
+ expect(mockJobDetailsRepository.createJobDetailsFile).not.toHaveBeenCalled();
245
+ });
246
+
247
+ it('should throw before creating an ECS task when the runtime image has no task definition ARN', async () => {
248
+ mockJobManifestService.findJobManifest.mockResolvedValue(createMockJobManifest({ image: 'node:24' }));
249
+
250
+ await expect(
251
+ jobExecutionTaskService.runJobExecutionTask({
252
+ ...defaultRunJobTaskParams,
253
+ taskDefinitionArnsByRuntimeImage: {
254
+ 'node:20': 'node20TaskDefinitionArn',
255
+ },
256
+ }),
257
+ ).rejects.toThrow(RuntimeImageTaskDefinitionError);
258
+ expect(mockEcsClient).not.toHaveReceivedCommand(RunTaskCommand);
259
+ expect(mockJobDetailsRepository.createJobDetailsFile).not.toHaveBeenCalled();
210
260
  });
211
261
 
212
262
  it('should handle ConflictException with a previously started task arn', async () => {
@@ -7,9 +7,10 @@ import { ConflictException, ECSClient, RunTaskCommand } from '@aws-sdk/client-ec
7
7
  import { createHash } from 'node:crypto';
8
8
  import { JobDetailsRepository, JobUploadsRepository } from '../../S3Repository';
9
9
  import { JOB_EXECUTION_STATUS } from '../../constants/JobExecutionStatus.constants';
10
+ import { JobManifest } from '../../manifest';
10
11
  import { JobExecution } from '../../model/JobExecution';
11
12
  import { JobExecutionRepository } from '../../repository';
12
- import { getAwsTags } from '../../utils';
13
+ import { getAwsTags, resolveRuntimeImageTaskDefinitionArn } from '../../utils';
13
14
  import { JobContextService } from '../JobContextService/JobContextService';
14
15
  import { JobManifestService } from '../JobManifestService/JobManifestService';
15
16
  import { HasReachedMaxTenantConcurrencyParams, RunJobTaskParams } from './JobExecutionTaskService.defs';
@@ -56,7 +57,13 @@ export class JobExecutionTaskService {
56
57
  }
57
58
 
58
59
  public async runJobExecutionTask(params: RunJobTaskParams): Promise<string | undefined> {
59
- const jobDetailsFileUrl = await this.createJobDetailsFile(params.tenantId, params.jobExecution);
60
+ const jobManifest = await this.findJobManifest(params.jobExecution);
61
+ const taskDefinitionArn = resolveRuntimeImageTaskDefinitionArn({
62
+ image: jobManifest?.image,
63
+ taskDefinitionArnsByRuntimeImage: params.taskDefinitionArnsByRuntimeImage,
64
+ legacyTaskDefinitionArn: params.taskDefinitionArn,
65
+ });
66
+ const jobDetailsFileUrl = await this.createJobDetailsFile(params.tenantId, params.jobExecution, jobManifest);
60
67
  const tags = getAwsTags({
61
68
  tenant: params.tenantId,
62
69
  region: params.awsRegion,
@@ -104,7 +111,7 @@ export class JobExecutionTaskService {
104
111
  const result = await this.ecsClient.send(
105
112
  new RunTaskCommand({
106
113
  cluster: params.clusterArn,
107
- taskDefinition: params.taskDefinitionArn,
114
+ taskDefinition: taskDefinitionArn,
108
115
  overrides: {
109
116
  containerOverrides: [
110
117
  {
@@ -183,11 +190,11 @@ export class JobExecutionTaskService {
183
190
  return createHash('sha1').update(key).digest('hex');
184
191
  }
185
192
 
186
- private async createJobDetailsFile(tenantId: string, executionToStart: JobExecution): Promise<string> {
187
- const jobManifest = await this.jobManifestService.findJobManifest(
188
- executionToStart.jobName,
189
- executionToStart.version,
190
- );
193
+ private async createJobDetailsFile(
194
+ tenantId: string,
195
+ executionToStart: JobExecution,
196
+ jobManifest: JobManifest | null,
197
+ ): Promise<string> {
191
198
  const jobContext = await this.jobContextService.findJobContext(executionToStart.context);
192
199
 
193
200
  const jobDetailsFileName = await this.jobDetailsRepository.createJobDetailsFile(
@@ -198,4 +205,8 @@ export class JobExecutionTaskService {
198
205
  );
199
206
  return await this.jobDetailsRepository.getS3SignedUrl(jobDetailsFileName);
200
207
  }
208
+
209
+ private async findJobManifest(executionToStart: JobExecution): Promise<JobManifest | null> {
210
+ return await this.jobManifestService.findJobManifest(executionToStart.jobName, executionToStart.version);
211
+ }
201
212
  }
@@ -2,7 +2,6 @@
2
2
  * @license
3
3
  * Copyright Squiz Australia Pty Ltd. All Rights Reserved.
4
4
  */
5
- import { initIocContainer, iocContainer } from '../../../../mocks/mockIoc';
6
5
  import {
7
6
  DynamoDBClient,
8
7
  DynamoDBClientConfig,
@@ -10,7 +9,13 @@ import {
10
9
  PutItemCommandOutput,
11
10
  ScanCommandOutput,
12
11
  } from '@aws-sdk/client-dynamodb';
13
- import { JobManifestService } from './JobManifestService';
12
+ import { initIocContainer, iocContainer } from '../../../../mocks/mockIoc';
13
+ import { ENTITY_TYPES } from '../../constants/Repository.constants';
14
+ import {
15
+ createMockClientGetResponse,
16
+ createMockClientPutResponse,
17
+ createMockClientScanResponse,
18
+ } from '../../core/dynamoClient.mock';
14
19
  import {
15
20
  createMockCreateJobManifestRequest,
16
21
  createMockJobManifest,
@@ -19,12 +24,7 @@ import {
19
24
  createMockJobManifestRecord,
20
25
  JobManifest,
21
26
  } from '../../manifest';
22
- import {
23
- createMockClientGetResponse,
24
- createMockClientPutResponse,
25
- createMockClientScanResponse,
26
- } from '../../core/dynamoClient.mock';
27
- import { ENTITY_TYPES } from '../../constants/Repository.constants';
27
+ import { JobManifestService } from './JobManifestService';
28
28
 
29
29
  initIocContainer();
30
30
 
@@ -88,6 +88,7 @@ describe('JobManifestService', (): void => {
88
88
  version: mockJobManifestItem.version.S as string,
89
89
  timeoutSeconds: parseInt(mockJobManifestItem.timeoutSeconds.N as string),
90
90
  image: mockJobManifestItem.image.S as string,
91
+ autoRetry: false,
91
92
  type: `manifest`,
92
93
  } as JobManifest);
93
94
  expect(mockSend).toBeCalledTimes(1);
@@ -0,0 +1,39 @@
1
+ /*!
2
+ * @license
3
+ * Copyright Squiz Australia Pty Ltd. All Rights Reserved.
4
+ */
5
+
6
+ import {
7
+ LEGACY_RUNTIME_IMAGE,
8
+ SUPPORTED_RUNTIME_IMAGES,
9
+ getRuntimeImageOrDefault,
10
+ isSupportedRuntimeImage,
11
+ } from '../../constants/RuntimeImage.constants';
12
+ import { RuntimeImageTaskDefinitionError } from '../../errors';
13
+ import { ResolveRuntimeImageTaskDefinitionArnParams } from './ResolveRuntimeImageTaskDefinitionArn.types';
14
+
15
+ export const resolveRuntimeImageTaskDefinitionArn = ({
16
+ image,
17
+ taskDefinitionArnsByRuntimeImage,
18
+ legacyTaskDefinitionArn,
19
+ }: ResolveRuntimeImageTaskDefinitionArnParams): string => {
20
+ const runtimeImage = getRuntimeImageOrDefault(image);
21
+
22
+ if (!isSupportedRuntimeImage(runtimeImage)) {
23
+ throw new RuntimeImageTaskDefinitionError(
24
+ `Unsupported runtime image "${runtimeImage}". Supported images are: ${SUPPORTED_RUNTIME_IMAGES.join(', ')}`,
25
+ );
26
+ }
27
+
28
+ const taskDefinitionArn =
29
+ taskDefinitionArnsByRuntimeImage[runtimeImage] ||
30
+ (runtimeImage === LEGACY_RUNTIME_IMAGE ? legacyTaskDefinitionArn : undefined);
31
+
32
+ if (!taskDefinitionArn) {
33
+ throw new RuntimeImageTaskDefinitionError(
34
+ `No ECS task definition ARN configured for runtime image "${runtimeImage}"`,
35
+ );
36
+ }
37
+
38
+ return taskDefinitionArn;
39
+ };
@@ -0,0 +1,10 @@
1
+ /*!
2
+ * @license
3
+ * Copyright Squiz Australia Pty Ltd. All Rights Reserved.
4
+ */
5
+
6
+ export interface ResolveRuntimeImageTaskDefinitionArnParams {
7
+ image: string | null | undefined;
8
+ taskDefinitionArnsByRuntimeImage: Record<string, string>;
9
+ legacyTaskDefinitionArn: string;
10
+ }
@@ -3,3 +3,5 @@ export * from './GetAwsTags/GetAwsTags.types';
3
3
  export * from './SlackClient/SlackClient';
4
4
  export * from './SlackClient/types/SlackClient.types';
5
5
  export * from './SortByTimeQueued/SortByTimeQueued';
6
+ export * from './ResolveRuntimeImageTaskDefinitionArn/ResolveRuntimeImageTaskDefinitionArn';
7
+ export * from './ResolveRuntimeImageTaskDefinitionArn/ResolveRuntimeImageTaskDefinitionArn.types';
@@ -1,10 +1,11 @@
1
- export * from './services/ManifestServiceForDev';
2
1
  export * from './services/JobFunctionService';
2
+ export * from './services/ManifestServiceForDev';
3
3
 
4
+ export * from './utils/defaultAutoRetry';
4
5
  export * from './utils/getAndAssertJobManifestVersion';
5
6
  export * from './utils/isValidManifestVersion';
6
7
  export * from './utils/validateRuntimeImage';
7
8
 
8
- export * from './ManifestModel';
9
9
  export * from '../job/manifest/JobManifestV1';
10
10
  export * from './errors/ManifestValidationError';
11
+ export * from './ManifestModel';
@@ -0,0 +1,16 @@
1
+ /*!
2
+ * @license
3
+ * Copyright Squiz Australia Pty Ltd. All Rights Reserved.
4
+ */
5
+
6
+ import { defaultAutoRetry } from './defaultAutoRetry';
7
+
8
+ describe('defaultAutoRetry', () => {
9
+ it('returns true when autoRetry is true', () => {
10
+ expect(defaultAutoRetry(true)).toEqual(true);
11
+ });
12
+
13
+ it.each([false, null, undefined])('returns false when autoRetry is %s', (autoRetry) => {
14
+ expect(defaultAutoRetry(autoRetry)).toEqual(false);
15
+ });
16
+ });
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * @license
3
+ * Copyright Squiz Australia Pty Ltd. All Rights Reserved.
4
+ */
5
+
6
+ export const defaultAutoRetry = (autoRetry: boolean | null | undefined): boolean => autoRetry === true;
@@ -3,16 +3,15 @@
3
3
  * Copyright Squiz Australia Pty Ltd. All Rights Reserved.
4
4
  */
5
5
 
6
- import * as runtimeImageConstants from '../../job/constants/RuntimeImage.constants';
7
- import { RECOMMENDED_RUNTIME_IMAGE, SUPPORTED_RUNTIME_IMAGES } from '../../job/constants/RuntimeImage.constants';
6
+ import {
7
+ DEPRECATED_RUNTIME_IMAGES,
8
+ RECOMMENDED_RUNTIME_IMAGE,
9
+ SUPPORTED_RUNTIME_IMAGES,
10
+ } from '../../job/constants/RuntimeImage.constants';
8
11
  import { ManifestValidationError } from '../errors/ManifestValidationError';
9
12
  import { validateAndDefaultRuntimeImage } from './validateRuntimeImage';
10
13
 
11
14
  describe('validateAndDefaultRuntimeImage', () => {
12
- afterEach(() => {
13
- jest.restoreAllMocks();
14
- });
15
-
16
15
  it('returns the recommended runtime image when image is undefined', () => {
17
16
  expect(validateAndDefaultRuntimeImage(undefined)).toEqual(RECOMMENDED_RUNTIME_IMAGE);
18
17
  });
@@ -21,9 +20,12 @@ describe('validateAndDefaultRuntimeImage', () => {
21
20
  expect(validateAndDefaultRuntimeImage(null)).toEqual(RECOMMENDED_RUNTIME_IMAGE);
22
21
  });
23
22
 
24
- it.each(SUPPORTED_RUNTIME_IMAGES)('returns "%s" when it is in the supported list', (supportedImage) => {
25
- expect(validateAndDefaultRuntimeImage(supportedImage)).toEqual(supportedImage);
26
- });
23
+ it.each(SUPPORTED_RUNTIME_IMAGES.filter((image) => !DEPRECATED_RUNTIME_IMAGES.includes(image)))(
24
+ 'returns "%s" when it is supported and not deprecated',
25
+ (supportedImage) => {
26
+ expect(validateAndDefaultRuntimeImage(supportedImage)).toEqual(supportedImage);
27
+ },
28
+ );
27
29
 
28
30
  it('throws ManifestValidationError when the image is not supported', () => {
29
31
  expect(() => validateAndDefaultRuntimeImage('node:18')).toThrow(ManifestValidationError);
@@ -31,9 +33,6 @@ describe('validateAndDefaultRuntimeImage', () => {
31
33
  });
32
34
 
33
35
  it('throws ManifestValidationError when the image is deprecated', () => {
34
- jest.spyOn(runtimeImageConstants, 'isSupportedRuntimeImage').mockReturnValue(true);
35
- jest.spyOn(runtimeImageConstants, 'isDeprecatedRuntimeImage').mockReturnValue(true);
36
-
37
36
  expect(() => validateAndDefaultRuntimeImage('node:20')).toThrow(ManifestValidationError);
38
37
  expect(() => validateAndDefaultRuntimeImage('node:20')).toThrow(/deprecated/);
39
38
  });