@squiz/job-runner-lib 2.3.0 → 2.4.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 (137) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/lib/domains/component/errors/JobError.spec.d.ts +1 -0
  3. package/lib/domains/job/ParsePartitionKey/ParsePartitionKey.d.ts +2 -2
  4. package/lib/domains/job/ParsePartitionKey/ParsePartitionKey.types.d.ts +5 -0
  5. package/lib/domains/job/S3Repository/JobDetailsRepository.d.ts +1 -2
  6. package/lib/domains/job/S3Repository/JobDetailsRepository.spec.d.ts +1 -1
  7. package/lib/domains/job/S3Repository/JobUploadsRepository.d.ts +16 -0
  8. package/lib/domains/job/{services/JobQueueService/JobQueueService.spec.d.ts → S3Repository/JobUploadsRepository.spec.d.ts} +1 -1
  9. package/lib/domains/job/S3Repository/S3RepositoryBase.d.ts +12 -5
  10. package/lib/domains/job/S3Repository/index.d.ts +2 -1
  11. package/lib/domains/job/constants/InjectTokens.d.ts +8 -0
  12. package/lib/domains/job/constants/JobExecutionStatus.constants.d.ts +2 -1
  13. package/lib/domains/job/constants/Repository.constants.d.ts +3 -3
  14. package/lib/domains/job/constants/RetryConfig.constants.d.ts +21 -0
  15. package/lib/domains/job/index.d.ts +6 -4
  16. package/lib/domains/job/manifest/JobManifestTypes.d.ts +7 -6
  17. package/lib/domains/job/model/Base/Base.d.ts +10 -5
  18. package/lib/domains/job/model/Job/Job.d.ts +9 -6
  19. package/lib/domains/job/model/JobContext/JobContext.d.ts +6 -5
  20. package/lib/domains/job/model/JobExecution/JobExecution.d.ts +64 -9
  21. package/lib/domains/job/model/LogPayload/LogPayload.d.ts +21 -12
  22. package/lib/domains/job/model/LogPayload/index.d.ts +1 -0
  23. package/lib/domains/job/model/LogPayload/mocks/LogPayload.mock.d.ts +6 -0
  24. package/lib/domains/job/repository/AbstractRepository.d.ts +20 -11
  25. package/lib/domains/job/repository/JobContextRepository/JobContextRepository.d.ts +6 -7
  26. package/lib/domains/job/repository/JobExecutionRepository/JobExecutionRepository.d.ts +7 -17
  27. package/lib/domains/job/repository/JobExecutionRepository/JobExecutionRepository.spec.d.ts +1 -1
  28. package/lib/domains/job/repository/JobExecutionRepository/utils/convertDynamoDbItemToJobExecution/convertDynamoDbItemToJobExecution.d.ts +2 -0
  29. package/lib/domains/job/repository/JobExecutionRepository/utils/index.d.ts +1 -0
  30. package/lib/domains/job/repository/JobManifestRepository/JobManifestRepository.d.ts +5 -6
  31. package/lib/domains/job/repository/JobRepository/JobRepository.d.ts +6 -7
  32. package/lib/domains/job/repository/MultiTenantJobExecutionRepository/MultiTenantJobExecutionRepository.d.ts +9 -0
  33. package/lib/domains/job/repository/MultiTenantJobExecutionRepository/MultiTenantJobExecutionRepository.spec.d.ts +1 -0
  34. package/lib/domains/job/repository/index.d.ts +1 -0
  35. package/lib/domains/job/services/JobContextService/JobContextService.d.ts +6 -3
  36. package/lib/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.d.ts +10 -6
  37. package/lib/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.defs.d.ts +1 -3
  38. package/lib/domains/job/services/JobManifestService/JobManifestService.d.ts +0 -1
  39. package/lib/domains/job/services/JobStartQueueService/JobStartQueueService.d.ts +17 -0
  40. package/lib/domains/job/services/JobStartQueueService/JobStartQueueService.defs.d.ts +25 -0
  41. package/lib/domains/job/services/JobUploadService/utils/manifestValidator.d.ts +1 -2
  42. package/lib/domains/job/services/index.d.ts +2 -1
  43. package/lib/domains/manifest/services/ManifestServiceForDev.d.ts +0 -3
  44. package/lib/index.d.ts +1 -0
  45. package/lib/index.js +13 -13
  46. package/lib/index.js.map +4 -4
  47. package/lib/jest-global-setup.d.ts +6 -1
  48. package/lib/mocks/mockIoc.d.ts +3 -3
  49. package/lib/utils/buildJobRunnerModule/buildJobRunnerModule.d.ts +2 -0
  50. package/lib/utils/buildJobRunnerModule/buildJobRunnerModule.types.d.ts +14 -0
  51. package/lib/utils/index.d.ts +2 -0
  52. package/package.json +12 -11
  53. package/src/domains/component/errors/JobError.spec.ts +30 -0
  54. package/src/domains/component/errors/JobError.ts +8 -1
  55. package/src/domains/job/ParsePartitionKey/ParsePartitionKey.spec.ts +17 -8
  56. package/src/domains/job/ParsePartitionKey/ParsePartitionKey.ts +13 -16
  57. package/src/domains/job/ParsePartitionKey/ParsePartitionKey.types.ts +6 -0
  58. package/src/domains/job/S3Repository/JobDetailsRepository.spec.ts +47 -31
  59. package/src/domains/job/S3Repository/JobDetailsRepository.ts +7 -8
  60. package/src/domains/job/S3Repository/JobUploadsRepository.spec.ts +66 -0
  61. package/src/domains/job/S3Repository/JobUploadsRepository.ts +35 -0
  62. package/src/domains/job/S3Repository/S3RepositoryBase.spec.ts +13 -19
  63. package/src/domains/job/S3Repository/S3RepositoryBase.ts +24 -9
  64. package/src/domains/job/S3Repository/index.ts +2 -1
  65. package/src/domains/job/constants/InjectTokens.ts +8 -0
  66. package/src/domains/job/constants/JobExecutionStatus.constants.ts +1 -0
  67. package/src/domains/job/constants/Repository.constants.ts +3 -3
  68. package/src/domains/job/constants/RetryConfig.constants.ts +29 -0
  69. package/src/domains/job/core/pagination/services/PaginationService.spec.ts +3 -5
  70. package/src/domains/job/index.ts +6 -4
  71. package/src/domains/job/manifest/JobManifestTypes.ts +8 -6
  72. package/src/domains/job/manifest/mocks/JobManifest.mock.ts +2 -2
  73. package/src/domains/job/manifest/mocks/JobManifestDto.mock.ts +3 -2
  74. package/src/domains/job/manifest/mocks/JobManifestRecord.mock.ts +4 -3
  75. package/src/domains/job/model/Base/Base.ts +11 -5
  76. package/src/domains/job/model/Job/Job.ts +11 -6
  77. package/src/domains/job/model/Job/mocks/Job.mock.ts +2 -2
  78. package/src/domains/job/model/Job/mocks/JobDto.mock.ts +3 -2
  79. package/src/domains/job/model/Job/mocks/JobRecord.mock.ts +3 -2
  80. package/src/domains/job/model/JobContext/JobContext.ts +7 -5
  81. package/src/domains/job/model/JobContext/mocks/JobContext.mock.ts +2 -2
  82. package/src/domains/job/model/JobContext/mocks/JobContextDto.mock.ts +3 -2
  83. package/src/domains/job/model/JobContext/mocks/JobContextRecord.mock.ts +3 -2
  84. package/src/domains/job/model/JobExecution/JobExecution.ts +74 -9
  85. package/src/domains/job/model/JobExecution/mocks/JobExecution.mock.ts +7 -4
  86. package/src/domains/job/model/JobExecution/mocks/JobExecutionDto.mock.ts +32 -16
  87. package/src/domains/job/model/JobExecution/mocks/JobExecutionRecord.mock.ts +10 -4
  88. package/src/domains/job/model/LogPayload/LogPayload.ts +32 -11
  89. package/src/domains/job/model/LogPayload/index.ts +1 -0
  90. package/src/domains/job/model/LogPayload/mocks/LogPayload.mock.ts +21 -0
  91. package/src/domains/job/repository/AbstractRepository.ts +91 -59
  92. package/src/domains/job/repository/JobContextRepository/JobContextRepository.spec.ts +39 -49
  93. package/src/domains/job/repository/JobContextRepository/JobContextRepository.ts +33 -21
  94. package/src/domains/job/repository/JobExecutionRepository/JobExecutionRepository.spec.ts +343 -169
  95. package/src/domains/job/repository/JobExecutionRepository/JobExecutionRepository.ts +112 -141
  96. package/src/domains/job/repository/JobExecutionRepository/utils/convertDynamoDbItemToJobExecution/convertDynamoDbItemToJobExecution.ts +22 -0
  97. package/src/domains/job/repository/JobExecutionRepository/utils/index.ts +1 -0
  98. package/src/domains/job/repository/JobManifestRepository/JobManifestRepository.spec.ts +23 -38
  99. package/src/domains/job/repository/JobManifestRepository/JobManifestRepository.ts +29 -36
  100. package/src/domains/job/repository/JobRepository/JobRepository.spec.ts +72 -80
  101. package/src/domains/job/repository/JobRepository/JobRepository.ts +66 -70
  102. package/src/domains/job/repository/MultiTenantJobExecutionRepository/MultiTenantJobExecutionRepository.spec.ts +60 -0
  103. package/src/domains/job/repository/MultiTenantJobExecutionRepository/MultiTenantJobExecutionRepository.ts +42 -0
  104. package/src/domains/job/repository/index.ts +1 -0
  105. package/src/domains/job/services/JobContextService/JobContextService.spec.ts +73 -39
  106. package/src/domains/job/services/JobContextService/JobContextService.ts +37 -6
  107. package/src/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.defs.ts +1 -3
  108. package/src/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.spec.ts +113 -31
  109. package/src/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.ts +101 -49
  110. package/src/domains/job/services/JobManifestService/JobManifestService.spec.ts +4 -41
  111. package/src/domains/job/services/JobManifestService/JobManifestService.ts +2 -8
  112. package/src/domains/job/services/JobStartQueueService/JobStartQueueService.defs.ts +27 -0
  113. package/src/domains/job/services/JobStartQueueService/JobStartQueueService.ts +42 -0
  114. package/src/domains/job/services/JobUploadService/JobUploadService.spec.ts +1 -1
  115. package/src/domains/job/services/JobUploadService/JobUploadService.ts +1 -1
  116. package/src/domains/job/services/JobUploadService/utils/manifestValidator.spec.ts +12 -25
  117. package/src/domains/job/services/JobUploadService/utils/manifestValidator.ts +2 -7
  118. package/src/domains/job/services/index.ts +2 -1
  119. package/src/domains/job/utils/SlackClient/SlackClient.spec.ts +0 -2
  120. package/src/domains/manifest/services/ManifestServiceForDev.spec.ts +1 -2
  121. package/src/domains/manifest/services/ManifestServiceForDev.ts +0 -4
  122. package/src/index.ts +1 -0
  123. package/src/jest-global-setup.ts +8 -4
  124. package/src/mocks/mockIoc.ts +18 -43
  125. package/src/utils/buildJobRunnerModule/buildJobRunnerModule.ts +81 -0
  126. package/src/utils/buildJobRunnerModule/buildJobRunnerModule.types.ts +13 -0
  127. package/src/utils/index.ts +2 -0
  128. package/lib/domains/job/S3Repository/S3Repository.d.ts +0 -15
  129. package/lib/domains/job/S3Repository/S3Repository.spec.d.ts +0 -5
  130. package/lib/domains/job/services/JobQueueService/JobQueueService.d.ts +0 -9
  131. package/lib/domains/job/services/JobQueueService/JobQueueService.defs.d.ts +0 -9
  132. package/src/custom-types.d.ts +0 -7
  133. package/src/domains/job/S3Repository/S3Repository.spec.ts +0 -50
  134. package/src/domains/job/S3Repository/S3Repository.ts +0 -27
  135. package/src/domains/job/services/JobQueueService/JobQueueService.defs.ts +0 -10
  136. package/src/domains/job/services/JobQueueService/JobQueueService.spec.ts +0 -190
  137. package/src/domains/job/services/JobQueueService/JobQueueService.ts +0 -60
@@ -1,52 +1,27 @@
1
- import { Container, decorate, injectable } from 'inversify';
2
- import { Controller } from 'tsoa';
1
+ import { faker } from '@faker-js/faker';
2
+ import { Logger } from '@squiz/optimization-logger';
3
+ import { Container } from 'inversify';
3
4
  import { buildProviderModule } from 'inversify-binding-decorators';
4
- import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
5
-
6
- import { Logger } from '@squiz/dx-logger-lib';
7
- import { S3Client } from '@aws-sdk/client-s3';
8
-
9
- // Makes tsoa's Controller injectable
10
- decorate(injectable(), Controller);
5
+ import { testLogger } from '../jest-global-setup';
6
+ import { buildJobRunnerModule } from '../utils';
11
7
 
12
8
  let iocContainer: Container;
13
9
 
14
- function initIocContainer(log: Logger): void {
10
+ function initIocContainer(logger?: Logger): void {
11
+ const tenant = faker.string.uuid();
12
+ const jobRunnerModule = buildJobRunnerModule({
13
+ local: true,
14
+ dynamoTableName: faker.lorem.words(),
15
+ jobUploadsBucket: faker.lorem.words(),
16
+ jobDetailsBucket: faker.lorem.words(),
17
+ awsRegion: faker.lorem.words(),
18
+ });
19
+
15
20
  // Create a new container tsoa can use
16
- iocContainer = new Container();
21
+ iocContainer = new Container({ skipBaseClassChecks: true });
17
22
 
18
23
  // make inversify aware of inversify-binding-decorators
19
24
  iocContainer.load(buildProviderModule());
20
-
21
- // Init DynamoDB and S3 clients
22
- let dynamoDbClient: DynamoDBClient;
23
- let s3Client: S3Client;
24
-
25
- if (process.env.ENVIRONMENT !== 'local') {
26
- dynamoDbClient = new DynamoDBClient({ region: 'ap-southeast-2' });
27
- s3Client = new S3Client({ region: 'ap-southeast-2' });
28
- } else {
29
- // eslint-disable-next-line no-console
30
- console.log(`environment: '${process.env.ENVIRONMENT}'`);
31
-
32
- // eslint-disable-next-line no-console
33
- console.log(`table: '${process.env.DYNAMODB_TABLE}'`);
34
- dynamoDbClient = new DynamoDBClient({
35
- credentials: { accessKeyId: 'key', secretAccessKey: 'key' },
36
- endpoint: 'http://localhost:8000',
37
- });
38
- s3Client = new S3Client({
39
- credentials: { accessKeyId: 'key', secretAccessKey: 'key' },
40
- endpoint: 'http://localhost:8000',
41
- });
42
- }
43
-
44
- iocContainer.bind<DynamoDBClient>(DynamoDBClient).toConstantValue(dynamoDbClient);
45
- iocContainer.bind<Logger>('Logger').toConstantValue(log);
46
- iocContainer.bind<S3Client>(S3Client).toConstantValue(s3Client);
47
-
48
- iocContainer.bind<string>('JobDetailsBucketName').toConstantValue('configObj.jobDetailsBucketName');
49
- iocContainer.bind<string>('S3RepositoryBucketName').toConstantValue('configObj.bucketName');
25
+ iocContainer.load(jobRunnerModule({ tenant, logger: logger || testLogger }));
50
26
  }
51
-
52
- export { iocContainer, initIocContainer };
27
+ export { initIocContainer, iocContainer };
@@ -0,0 +1,81 @@
1
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
2
+ import { ECSClient } from '@aws-sdk/client-ecs';
3
+ import { S3Client } from '@aws-sdk/client-s3';
4
+ import { SQSClient } from '@aws-sdk/client-sqs';
5
+ import { Logger } from '@squiz/optimization-logger';
6
+ import { ContainerModule, interfaces } from 'inversify';
7
+ import {
8
+ InjectTokens,
9
+ JobContextService,
10
+ JobDetailsRepository,
11
+ JobExecutionRepository,
12
+ JobExecutionTaskService,
13
+ JobManifestService,
14
+ JobStartQueueService,
15
+ JobUploadsRepository,
16
+ } from '../../domains/job';
17
+ import { CreateJobRunnerModuleProps, CreateJobRunnerModuleResult } from './buildJobRunnerModule.types';
18
+
19
+ export const buildJobRunnerModule = (props: CreateJobRunnerModuleProps): CreateJobRunnerModuleResult => {
20
+ let dynamoDbClient: DynamoDBClient;
21
+ let s3Client: S3Client;
22
+ let ecsClient: ECSClient;
23
+ let sqsClient: SQSClient;
24
+
25
+ if (props.local) {
26
+ dynamoDbClient = new DynamoDBClient({
27
+ credentials: { accessKeyId: 'key', secretAccessKey: 'key' },
28
+ endpoint: 'http://localhost:8000',
29
+ });
30
+ s3Client = new S3Client({
31
+ credentials: { accessKeyId: 'key', secretAccessKey: 'key' },
32
+ endpoint: 'http://localhost:4566', // This is the localstack EDGE_PORT
33
+ forcePathStyle: true,
34
+ });
35
+ ecsClient = new ECSClient({});
36
+ sqsClient = new SQSClient({
37
+ credentials: { accessKeyId: 'key', secretAccessKey: 'key' },
38
+ endpoint: 'http://localhost:4566',
39
+ });
40
+ } else {
41
+ dynamoDbClient = new DynamoDBClient({ region: props.awsRegion });
42
+ s3Client = new S3Client({ region: props.awsRegion });
43
+ ecsClient = new ECSClient({ region: props.awsRegion });
44
+ sqsClient = new SQSClient({ region: props.awsRegion });
45
+ }
46
+
47
+ return ({ tenant, logger }: { tenant?: string; logger: Logger }) => {
48
+ return new ContainerModule((bind: interfaces.Bind) => {
49
+ bind(DynamoDBClient).toConstantValue(dynamoDbClient);
50
+ bind(S3Client).toConstantValue(s3Client);
51
+ bind(ECSClient).toConstantValue(ecsClient);
52
+ bind(SQSClient).toConstantValue(sqsClient);
53
+
54
+ bind(InjectTokens.Logger).toConstantValue(logger);
55
+ bind(InjectTokens.Tenant).toConstantValue(tenant);
56
+ bind(InjectTokens.DynamoTableName).toConstantValue(props.dynamoTableName);
57
+ bind(InjectTokens.JobUploadsBucket).toConstantValue(props.jobUploadsBucket);
58
+ bind(InjectTokens.JobDetailsBucket).toConstantValue(props.jobDetailsBucket);
59
+
60
+ if (props.jobStartQueueUrl) {
61
+ bind(InjectTokens.JobStartQueueUrl).toConstantValue(props.jobStartQueueUrl);
62
+ bind<JobStartQueueService>(JobStartQueueService).toDynamicValue(
63
+ (context: interfaces.Context) =>
64
+ new JobStartQueueService(sqsClient, context.container.get(InjectTokens.JobStartQueueUrl)),
65
+ );
66
+ }
67
+
68
+ bind<JobExecutionTaskService>(JobExecutionTaskService).toDynamicValue(
69
+ (context: interfaces.Context) =>
70
+ new JobExecutionTaskService(
71
+ ecsClient,
72
+ context.container.get(JobManifestService),
73
+ context.container.get(JobContextService),
74
+ context.container.get(JobExecutionRepository),
75
+ context.container.get(JobDetailsRepository),
76
+ context.container.get(JobUploadsRepository),
77
+ ),
78
+ );
79
+ });
80
+ };
81
+ };
@@ -0,0 +1,13 @@
1
+ import { Logger } from '@squiz/optimization-logger';
2
+ import { ContainerModule } from 'inversify';
3
+
4
+ export type CreateJobRunnerModuleProps = {
5
+ dynamoTableName: string;
6
+ jobUploadsBucket: string;
7
+ jobDetailsBucket: string;
8
+ awsRegion: string;
9
+ local: boolean;
10
+ jobStartQueueUrl?: string;
11
+ };
12
+
13
+ export type CreateJobRunnerModuleResult = (props: { tenant?: string; logger: Logger }) => ContainerModule;
@@ -0,0 +1,2 @@
1
+ export * from './buildJobRunnerModule/buildJobRunnerModule';
2
+ export * from './buildJobRunnerModule/buildJobRunnerModule.types';
@@ -1,15 +0,0 @@
1
- /*!
2
- * @license
3
- * Copyright Squiz Australia Pty Ltd. All Rights Reserved.
4
- */
5
- import { S3Client } from '@aws-sdk/client-s3';
6
- import { S3RepositoryBase } from './S3RepositoryBase';
7
- /**
8
- * Repository class for interacting with S3.
9
- * @export
10
- * @class S3Repository
11
- */
12
- export declare class S3Repository extends S3RepositoryBase {
13
- protected s3Client: S3Client;
14
- constructor(s3Client: S3Client, bucketName: string);
15
- }
@@ -1,5 +0,0 @@
1
- /*!
2
- * @license
3
- * Copyright Squiz Australia Pty Ltd. All Rights Reserved.
4
- */
5
- export {};
@@ -1,9 +0,0 @@
1
- import { JobExecutionTaskService } from '../JobExecutionTaskService/JobExecutionTaskService';
2
- import { CheckAndStartQueuedJobTaskParams } from './JobQueueService.defs';
3
- import { JobExecutionRepository } from '../../repository';
4
- export declare class JobQueueService {
5
- private readonly jobExecutionTaskService;
6
- private readonly jobExecutionRepository;
7
- constructor(jobExecutionTaskService: JobExecutionTaskService, jobExecutionRepository: JobExecutionRepository);
8
- checkAndStartQueuedJob(params: CheckAndStartQueuedJobTaskParams): Promise<string | null>;
9
- }
@@ -1,9 +0,0 @@
1
- import { RunJobTaskParams } from '../JobExecutionTaskService/JobExecutionTaskService.defs';
2
- /**
3
- * The parameters required for running a job as an ECS task.
4
- */
5
- export interface CheckAndStartQueuedJobTaskParams extends Omit<RunJobTaskParams, 'jobExecution'> {
6
- jobName: string;
7
- runtimeTaskFamily: string;
8
- maxTenantConcurrentTasks: number;
9
- }
@@ -1,7 +0,0 @@
1
- import { type Logger } from 'winston';
2
-
3
- declare module 'express-serve-static-core' {
4
- interface Request {
5
- log: Logger;
6
- }
7
- }
@@ -1,50 +0,0 @@
1
- /*!
2
- * @license
3
- * Copyright Squiz Australia Pty Ltd. All Rights Reserved.
4
- */
5
-
6
- import { S3Client, S3ClientConfig } from '@aws-sdk/client-s3';
7
- import { S3Repository } from './S3Repository';
8
- import { initIocContainer, iocContainer } from '../../../mocks/mockIoc';
9
- import { testLogger } from '../../../jest-global-setup';
10
-
11
- // Create a new container tsoa can use
12
- initIocContainer(testLogger);
13
-
14
- // Mock S3 Client
15
- const mockOptions = {} as S3ClientConfig;
16
- const mockClient = new S3Client(mockOptions);
17
-
18
- const mockSend = jest.fn().mockResolvedValue({
19
- Body: { transformToString: jest.fn().mockResolvedValue('file') },
20
- $metadata: {},
21
- });
22
- mockClient.send = mockSend;
23
-
24
- // Inject the S3Repository with a mocked client
25
- iocContainer.rebind<S3Client>(S3Client).toConstantValue(mockClient);
26
-
27
- let s3Repository: S3Repository;
28
-
29
- describe('S3Repository', (): void => {
30
- beforeAll((): void => {
31
- s3Repository = iocContainer.get(S3Repository);
32
- });
33
-
34
- describe(`get`, (): void => {
35
- beforeEach(() => {
36
- jest.resetAllMocks();
37
- });
38
-
39
- it(`should return a response for get`, async (): Promise<void> => {
40
- const mockKey = `mock`;
41
- mockSend.mockResolvedValueOnce({
42
- Body: { transformToString: jest.fn().mockResolvedValue('file') },
43
- });
44
-
45
- const response = await s3Repository.get(mockKey);
46
- expect(mockSend).toBeCalledTimes(1);
47
- expect(response).toStrictEqual('file');
48
- });
49
- });
50
- });
@@ -1,27 +0,0 @@
1
- /*!
2
- * @license
3
- * Copyright Squiz Australia Pty Ltd. All Rights Reserved.
4
- */
5
-
6
- import * as dotenv from 'dotenv';
7
- dotenv.config();
8
-
9
- import { S3Client } from '@aws-sdk/client-s3';
10
- import { inject } from 'inversify';
11
- import { S3RepositoryBase } from './S3RepositoryBase';
12
- import { provideTransient } from '../transientProvider';
13
-
14
- /**
15
- * Repository class for interacting with S3.
16
- * @export
17
- * @class S3Repository
18
- */
19
- @provideTransient(S3Repository)
20
- export class S3Repository extends S3RepositoryBase {
21
- public constructor(
22
- @inject(S3Client) protected s3Client: S3Client,
23
- @inject('S3RepositoryBucketName') bucketName: string,
24
- ) {
25
- super(s3Client, bucketName);
26
- }
27
- }
@@ -1,10 +0,0 @@
1
- import { RunJobTaskParams } from '../JobExecutionTaskService/JobExecutionTaskService.defs';
2
-
3
- /**
4
- * The parameters required for running a job as an ECS task.
5
- */
6
- export interface CheckAndStartQueuedJobTaskParams extends Omit<RunJobTaskParams, 'jobExecution'> {
7
- jobName: string;
8
- runtimeTaskFamily: string;
9
- maxTenantConcurrentTasks: number;
10
- }
@@ -1,190 +0,0 @@
1
- /*!
2
- * @license
3
- * Copyright Squiz Australia Pty Ltd. All Rights Reserved.
4
- */
5
-
6
- import { ECSClient } from '@aws-sdk/client-ecs';
7
- import { faker } from '@faker-js/faker/locale/en';
8
- import { S3Client } from '@aws-sdk/client-s3';
9
- import { JobContextRepository, JobExecutionRepository, JobManifestRepository } from '../../repository';
10
- import { JobContextService } from '../JobContextService/JobContextService';
11
- import { JobManifestService } from '../JobManifestService/JobManifestService';
12
- import { JobQueueService } from './JobQueueService';
13
- import { JobExecutionTaskService } from '../JobExecutionTaskService/JobExecutionTaskService';
14
- import { PaginationService } from '../../core/pagination';
15
- import { JobExecution } from '../../model/JobExecution';
16
- import { JOB_EXECUTION_STATUS } from '../../constants/JobExecutionStatus.constants';
17
- import { createMockJobManifest } from '../../manifest';
18
- import { createMockJobContext } from '../../model/JobContext';
19
- import { CheckAndStartQueuedJobTaskParams } from './JobQueueService.defs';
20
- import { JobDetailsRepository, S3Repository } from '../../S3Repository';
21
-
22
- describe('JobQueueService', () => {
23
- const mockJobDetailsBucketName = faker.lorem.word();
24
- const mockJobUploadBucketName = faker.lorem.word();
25
- const jobExecutionBase = {
26
- context: ``,
27
- ecsTaskId: ``,
28
- input: ``,
29
- jobName: `mockJobName`,
30
- timeFinished: null,
31
- timeStarted: null,
32
- type: 'jobExecution',
33
- version: '1.2.3',
34
- };
35
- const mockExecutionOld: JobExecution = {
36
- ...jobExecutionBase,
37
- id: 'old',
38
- status: JOB_EXECUTION_STATUS.queued,
39
- timeQueued: faker.date.past().toISOString(),
40
- };
41
- const mockExecutionNew: JobExecution = {
42
- ...jobExecutionBase,
43
- id: 'new',
44
- status: JOB_EXECUTION_STATUS.queued,
45
- timeQueued: faker.date.future().toISOString(),
46
- };
47
- const mockCheckAndStartQueuedJobParams: CheckAndStartQueuedJobTaskParams = {
48
- jobName: faker.lorem.word(),
49
- tenantId: faker.string.uuid(),
50
- shortEnvironment: faker.lorem.word(),
51
- awsRegion: faker.lorem.word(),
52
- clusterArn: faker.lorem.word(),
53
- subnets: [],
54
- maxTenantConcurrentTasks: 20,
55
- shortRegion: faker.lorem.word(),
56
- runtimeTaskFamily: faker.lorem.word(),
57
- taskDefinitionArn: faker.lorem.word(),
58
- };
59
-
60
- let mockJobExecutionRepository: jest.Mocked<JobExecutionRepository>;
61
- let mockEcsClient: jest.Mocked<ECSClient>;
62
- let mockS3Client: jest.Mocked<S3Client>;
63
-
64
- let jobManifestService: JobManifestService;
65
- let jobContextService: JobContextService;
66
- let jobExecutionTaskService: JobExecutionTaskService;
67
- let jobQueueService: JobQueueService;
68
- let jobDetailsRepository: JobDetailsRepository;
69
- let jobUploadRepository: S3Repository;
70
-
71
- let mockJobContextRepository: JobContextRepository;
72
- let mockJobManifestRepository: JobManifestRepository;
73
-
74
- beforeEach(() => {
75
- mockJobExecutionRepository = {
76
- list: jest.fn(),
77
- findOne: jest.fn(),
78
- update: jest.fn(),
79
- } as unknown as jest.Mocked<JobExecutionRepository>;
80
-
81
- mockEcsClient = {
82
- send: jest.fn(),
83
- } as unknown as jest.Mocked<ECSClient>;
84
-
85
- mockS3Client = {
86
- send: jest.fn().mockResolvedValue({
87
- Body: { transformToString: jest.fn().mockResolvedValue('file') },
88
- $metadata: {},
89
- }),
90
- } as unknown as jest.Mocked<S3Client>;
91
-
92
- jobDetailsRepository = new JobDetailsRepository(mockS3Client, mockJobDetailsBucketName);
93
- jobUploadRepository = new S3Repository(mockS3Client, mockJobUploadBucketName);
94
-
95
- mockJobContextRepository = {
96
- findOne: jest.fn(),
97
- } as unknown as jest.Mocked<JobContextRepository>;
98
- jobContextService = new JobContextService(mockJobContextRepository, new PaginationService());
99
-
100
- mockJobManifestRepository = {
101
- findOne: jest.fn(),
102
- } as unknown as jest.Mocked<JobManifestRepository>;
103
- jobManifestService = new JobManifestService(mockJobManifestRepository);
104
-
105
- jobExecutionTaskService = new JobExecutionTaskService(
106
- mockEcsClient,
107
- jobManifestService,
108
- jobContextService,
109
- jobDetailsRepository,
110
- jobUploadRepository,
111
- );
112
-
113
- jobQueueService = new JobQueueService(jobExecutionTaskService, mockJobExecutionRepository);
114
- });
115
-
116
- describe('checkAndStartQueuedJob', () => {
117
- it('should not start any jobs if no pending executions exist', async (): Promise<void> => {
118
- mockJobExecutionRepository.list.mockResolvedValue([]);
119
- jest.spyOn(jobExecutionTaskService, `hasReachedMaxTenantConcurrency`).mockResolvedValueOnce(false);
120
- jest.spyOn(jobExecutionTaskService, `runJobExecutionTask`).mockResolvedValueOnce('');
121
-
122
- await jobQueueService.checkAndStartQueuedJob(mockCheckAndStartQueuedJobParams);
123
- expect(jobExecutionTaskService.runJobExecutionTask).not.toBeCalled();
124
- });
125
-
126
- it('should start the oldest queued job', async (): Promise<void> => {
127
- mockJobExecutionRepository.list
128
- .mockResolvedValueOnce([mockExecutionNew, mockExecutionOld])
129
- .mockResolvedValueOnce([mockExecutionOld, mockExecutionNew])
130
- .mockResolvedValueOnce([mockExecutionOld, mockExecutionOld]);
131
-
132
- jest
133
- .spyOn(jobExecutionTaskService, `hasReachedMaxTenantConcurrency`)
134
- .mockResolvedValueOnce(false)
135
- .mockResolvedValueOnce(false)
136
- .mockResolvedValueOnce(false);
137
-
138
- jest.spyOn(jobExecutionTaskService, `runJobExecutionTask`).mockResolvedValue('');
139
-
140
- await jobQueueService.checkAndStartQueuedJob(mockCheckAndStartQueuedJobParams);
141
- expect(jobExecutionTaskService.runJobExecutionTask).toBeCalledTimes(1);
142
- let executionStarted = (jobExecutionTaskService.runJobExecutionTask as jest.Mock).mock.calls[0][0];
143
- expect(executionStarted.jobExecution).toBe(mockExecutionOld);
144
-
145
- await jobQueueService.checkAndStartQueuedJob(mockCheckAndStartQueuedJobParams);
146
- expect(jobExecutionTaskService.runJobExecutionTask).toBeCalledTimes(2);
147
- executionStarted = (jobExecutionTaskService.runJobExecutionTask as jest.Mock).mock.calls[1][0];
148
- expect(executionStarted.jobExecution).toBe(mockExecutionOld);
149
-
150
- await jobQueueService.checkAndStartQueuedJob(mockCheckAndStartQueuedJobParams);
151
- expect(jobExecutionTaskService.runJobExecutionTask).toBeCalledTimes(3);
152
- executionStarted = (jobExecutionTaskService.runJobExecutionTask as jest.Mock).mock.calls[2][0];
153
- expect(executionStarted.jobExecution).toBe(mockExecutionOld);
154
- });
155
-
156
- it('should create job details file when file is not found', async (): Promise<void> => {
157
- mockJobExecutionRepository.list.mockResolvedValue([mockExecutionOld]);
158
-
159
- jest.spyOn(jobExecutionTaskService, `hasReachedMaxTenantConcurrency`).mockResolvedValue(false);
160
- jest.spyOn(jobExecutionTaskService, `runJobExecutionTask`).mockResolvedValue('mock-task-id');
161
-
162
- // Mock job manifest and context services
163
- const mockJobManifest = createMockJobManifest();
164
- const mockJobContext = createMockJobContext();
165
-
166
- jest.spyOn(jobManifestService, 'findJobManifest').mockResolvedValue(mockJobManifest);
167
- jest.spyOn(jobContextService, 'findJobContext').mockResolvedValue(mockJobContext);
168
-
169
- await jobQueueService.checkAndStartQueuedJob(mockCheckAndStartQueuedJobParams);
170
-
171
- // Verify that the job execution task was started
172
- expect(jobExecutionTaskService.runJobExecutionTask).toHaveBeenCalledWith(
173
- expect.objectContaining({
174
- jobExecution: mockExecutionOld,
175
- }),
176
- );
177
-
178
- // Verify that the execution status was updated
179
- expect(mockJobExecutionRepository.update).toHaveBeenCalledWith(
180
- mockExecutionOld.id,
181
- {
182
- ecsTaskId: 'mock-task-id',
183
- status: JOB_EXECUTION_STATUS.running,
184
- timeStarted: expect.any(String),
185
- },
186
- mockExecutionOld.jobName,
187
- );
188
- });
189
- });
190
- });
@@ -1,60 +0,0 @@
1
- import { JobExecutionTaskService } from '../JobExecutionTaskService/JobExecutionTaskService';
2
- import { CheckAndStartQueuedJobTaskParams } from './JobQueueService.defs';
3
- import { JOB_EXECUTION_STATUS } from '../../constants/JobExecutionStatus.constants';
4
- import { JobExecutionRepository } from '../../repository';
5
- import { sortByTimeQueued } from '../../utils';
6
- import { provideSingleton } from '../../singletonProvider';
7
- import { inject } from 'inversify';
8
-
9
- @provideSingleton(JobQueueService)
10
- export class JobQueueService {
11
- public constructor(
12
- @inject(JobExecutionTaskService) private readonly jobExecutionTaskService: JobExecutionTaskService,
13
- @inject(JobExecutionRepository) private readonly jobExecutionRepository: JobExecutionRepository,
14
- ) {}
15
-
16
- public async checkAndStartQueuedJob(params: CheckAndStartQueuedJobTaskParams): Promise<string | null> {
17
- const hasReachedMaxConcurrencyForTenant = await this.jobExecutionTaskService.hasReachedMaxTenantConcurrency({
18
- clusterArn: params.clusterArn,
19
- runtimeTaskFamily: params.runtimeTaskFamily,
20
- maxTenantConcurrentTasks: params.maxTenantConcurrentTasks,
21
- });
22
-
23
- if (!hasReachedMaxConcurrencyForTenant) {
24
- const executions = await this.jobExecutionRepository.list({
25
- jobName: params.jobName,
26
- status: JOB_EXECUTION_STATUS.queued,
27
- });
28
-
29
- if (executions.length > 0) {
30
- const executionToStart = executions.sort(sortByTimeQueued)[0];
31
- const ecsTaskId = await this.jobExecutionTaskService.runJobExecutionTask({
32
- jobExecution: executionToStart,
33
-
34
- awsRegion: params.awsRegion,
35
- clusterArn: params.clusterArn,
36
- subnets: params.subnets,
37
- taskDefinitionArn: params.taskDefinitionArn,
38
-
39
- tenantId: params.tenantId,
40
- shortEnvironment: params.shortEnvironment,
41
- shortRegion: params.shortRegion,
42
- });
43
-
44
- await this.jobExecutionRepository.update(
45
- executionToStart.id,
46
- {
47
- ecsTaskId,
48
- status: JOB_EXECUTION_STATUS.running,
49
- timeStarted: new Date().toISOString(),
50
- },
51
- executionToStart.jobName,
52
- );
53
-
54
- return executionToStart.id;
55
- }
56
- }
57
-
58
- return null;
59
- }
60
- }