@squiz/job-runner-lib 2.2.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.
- package/CHANGELOG.md +12 -0
- package/lib/domains/component/errors/JobError.spec.d.ts +1 -0
- package/lib/domains/job/ParsePartitionKey/ParsePartitionKey.d.ts +2 -2
- package/lib/domains/job/ParsePartitionKey/ParsePartitionKey.types.d.ts +5 -0
- package/lib/domains/job/S3Repository/JobDetailsRepository.d.ts +1 -2
- package/lib/domains/job/S3Repository/JobDetailsRepository.spec.d.ts +1 -1
- package/lib/domains/job/S3Repository/JobUploadsRepository.d.ts +16 -0
- package/lib/domains/job/{services/JobQueueService/JobQueueService.spec.d.ts → S3Repository/JobUploadsRepository.spec.d.ts} +1 -1
- package/lib/domains/job/S3Repository/S3RepositoryBase.d.ts +12 -5
- package/lib/domains/job/S3Repository/index.d.ts +2 -1
- package/lib/domains/job/constants/InjectTokens.d.ts +8 -0
- package/lib/domains/job/constants/JobExecutionStatus.constants.d.ts +2 -1
- package/lib/domains/job/constants/Repository.constants.d.ts +3 -3
- package/lib/domains/job/constants/RetryConfig.constants.d.ts +21 -0
- package/lib/domains/job/index.d.ts +6 -4
- package/lib/domains/job/manifest/JobManifestTypes.d.ts +7 -6
- package/lib/domains/job/model/Base/Base.d.ts +10 -5
- package/lib/domains/job/model/Job/Job.d.ts +9 -6
- package/lib/domains/job/model/JobContext/JobContext.d.ts +6 -5
- package/lib/domains/job/model/JobExecution/JobExecution.d.ts +64 -9
- package/lib/domains/job/model/LogPayload/LogPayload.d.ts +21 -12
- package/lib/domains/job/model/LogPayload/index.d.ts +1 -0
- package/lib/domains/job/model/LogPayload/mocks/LogPayload.mock.d.ts +6 -0
- package/lib/domains/job/repository/AbstractRepository.d.ts +20 -11
- package/lib/domains/job/repository/JobContextRepository/JobContextRepository.d.ts +6 -7
- package/lib/domains/job/repository/JobExecutionRepository/JobExecutionRepository.d.ts +7 -17
- package/lib/domains/job/repository/JobExecutionRepository/JobExecutionRepository.spec.d.ts +1 -1
- package/lib/domains/job/repository/JobExecutionRepository/utils/convertDynamoDbItemToJobExecution/convertDynamoDbItemToJobExecution.d.ts +2 -0
- package/lib/domains/job/repository/JobExecutionRepository/utils/index.d.ts +1 -0
- package/lib/domains/job/repository/JobManifestRepository/JobManifestRepository.d.ts +5 -6
- package/lib/domains/job/repository/JobRepository/JobRepository.d.ts +6 -7
- package/lib/domains/job/repository/MultiTenantJobExecutionRepository/MultiTenantJobExecutionRepository.d.ts +9 -0
- package/lib/domains/job/repository/MultiTenantJobExecutionRepository/MultiTenantJobExecutionRepository.spec.d.ts +1 -0
- package/lib/domains/job/repository/index.d.ts +1 -0
- package/lib/domains/job/services/JobContextService/JobContextService.d.ts +6 -3
- package/lib/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.d.ts +10 -6
- package/lib/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.defs.d.ts +1 -3
- package/lib/domains/job/services/JobManifestService/JobManifestService.d.ts +0 -1
- package/lib/domains/job/services/JobStartQueueService/JobStartQueueService.d.ts +17 -0
- package/lib/domains/job/services/JobStartQueueService/JobStartQueueService.defs.d.ts +25 -0
- package/lib/domains/job/services/JobUploadService/utils/manifestValidator.d.ts +1 -2
- package/lib/domains/job/services/index.d.ts +2 -1
- package/lib/domains/manifest/services/ManifestServiceForDev.d.ts +0 -3
- package/lib/index.d.ts +1 -0
- package/lib/index.js +13 -13
- package/lib/index.js.map +4 -4
- package/lib/jest-global-setup.d.ts +6 -1
- package/lib/mocks/mockIoc.d.ts +3 -3
- package/lib/utils/buildJobRunnerModule/buildJobRunnerModule.d.ts +2 -0
- package/lib/utils/buildJobRunnerModule/buildJobRunnerModule.types.d.ts +14 -0
- package/lib/utils/index.d.ts +2 -0
- package/package.json +12 -11
- package/src/domains/component/errors/JobError.spec.ts +30 -0
- package/src/domains/component/errors/JobError.ts +8 -1
- package/src/domains/job/ParsePartitionKey/ParsePartitionKey.spec.ts +17 -8
- package/src/domains/job/ParsePartitionKey/ParsePartitionKey.ts +13 -16
- package/src/domains/job/ParsePartitionKey/ParsePartitionKey.types.ts +6 -0
- package/src/domains/job/S3Repository/JobDetailsRepository.spec.ts +47 -31
- package/src/domains/job/S3Repository/JobDetailsRepository.ts +7 -8
- package/src/domains/job/S3Repository/JobUploadsRepository.spec.ts +66 -0
- package/src/domains/job/S3Repository/JobUploadsRepository.ts +35 -0
- package/src/domains/job/S3Repository/S3RepositoryBase.spec.ts +13 -19
- package/src/domains/job/S3Repository/S3RepositoryBase.ts +24 -9
- package/src/domains/job/S3Repository/index.ts +2 -1
- package/src/domains/job/constants/InjectTokens.ts +8 -0
- package/src/domains/job/constants/JobExecutionStatus.constants.ts +1 -0
- package/src/domains/job/constants/Repository.constants.ts +3 -3
- package/src/domains/job/constants/RetryConfig.constants.ts +29 -0
- package/src/domains/job/core/pagination/services/PaginationService.spec.ts +3 -5
- package/src/domains/job/index.ts +6 -4
- package/src/domains/job/manifest/JobManifestTypes.ts +8 -6
- package/src/domains/job/manifest/mocks/JobManifest.mock.ts +2 -2
- package/src/domains/job/manifest/mocks/JobManifestDto.mock.ts +3 -2
- package/src/domains/job/manifest/mocks/JobManifestRecord.mock.ts +4 -3
- package/src/domains/job/model/Base/Base.ts +11 -5
- package/src/domains/job/model/Job/Job.ts +11 -6
- package/src/domains/job/model/Job/mocks/Job.mock.ts +2 -2
- package/src/domains/job/model/Job/mocks/JobDto.mock.ts +3 -2
- package/src/domains/job/model/Job/mocks/JobRecord.mock.ts +3 -2
- package/src/domains/job/model/JobContext/JobContext.ts +7 -5
- package/src/domains/job/model/JobContext/mocks/JobContext.mock.ts +2 -2
- package/src/domains/job/model/JobContext/mocks/JobContextDto.mock.ts +3 -2
- package/src/domains/job/model/JobContext/mocks/JobContextRecord.mock.ts +3 -2
- package/src/domains/job/model/JobExecution/JobExecution.ts +74 -9
- package/src/domains/job/model/JobExecution/mocks/JobExecution.mock.ts +7 -4
- package/src/domains/job/model/JobExecution/mocks/JobExecutionDto.mock.ts +32 -16
- package/src/domains/job/model/JobExecution/mocks/JobExecutionRecord.mock.ts +10 -4
- package/src/domains/job/model/LogPayload/LogPayload.ts +32 -11
- package/src/domains/job/model/LogPayload/index.ts +1 -0
- package/src/domains/job/model/LogPayload/mocks/LogPayload.mock.ts +21 -0
- package/src/domains/job/repository/AbstractRepository.ts +91 -59
- package/src/domains/job/repository/JobContextRepository/JobContextRepository.spec.ts +39 -49
- package/src/domains/job/repository/JobContextRepository/JobContextRepository.ts +33 -21
- package/src/domains/job/repository/JobExecutionRepository/JobExecutionRepository.spec.ts +343 -169
- package/src/domains/job/repository/JobExecutionRepository/JobExecutionRepository.ts +112 -141
- package/src/domains/job/repository/JobExecutionRepository/utils/convertDynamoDbItemToJobExecution/convertDynamoDbItemToJobExecution.ts +22 -0
- package/src/domains/job/repository/JobExecutionRepository/utils/index.ts +1 -0
- package/src/domains/job/repository/JobManifestRepository/JobManifestRepository.spec.ts +23 -38
- package/src/domains/job/repository/JobManifestRepository/JobManifestRepository.ts +29 -36
- package/src/domains/job/repository/JobRepository/JobRepository.spec.ts +72 -80
- package/src/domains/job/repository/JobRepository/JobRepository.ts +66 -70
- package/src/domains/job/repository/MultiTenantJobExecutionRepository/MultiTenantJobExecutionRepository.spec.ts +60 -0
- package/src/domains/job/repository/MultiTenantJobExecutionRepository/MultiTenantJobExecutionRepository.ts +42 -0
- package/src/domains/job/repository/index.ts +1 -0
- package/src/domains/job/services/JobContextService/JobContextService.spec.ts +73 -39
- package/src/domains/job/services/JobContextService/JobContextService.ts +37 -6
- package/src/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.defs.ts +1 -3
- package/src/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.spec.ts +113 -31
- package/src/domains/job/services/JobExecutionTaskService/JobExecutionTaskService.ts +101 -49
- package/src/domains/job/services/JobManifestService/JobManifestService.spec.ts +4 -41
- package/src/domains/job/services/JobManifestService/JobManifestService.ts +2 -8
- package/src/domains/job/services/JobStartQueueService/JobStartQueueService.defs.ts +27 -0
- package/src/domains/job/services/JobStartQueueService/JobStartQueueService.ts +42 -0
- package/src/domains/job/services/JobUploadService/JobUploadService.spec.ts +1 -1
- package/src/domains/job/services/JobUploadService/JobUploadService.ts +1 -1
- package/src/domains/job/services/JobUploadService/utils/manifestValidator.spec.ts +12 -25
- package/src/domains/job/services/JobUploadService/utils/manifestValidator.ts +2 -7
- package/src/domains/job/services/index.ts +2 -1
- package/src/domains/job/utils/SlackClient/SlackClient.spec.ts +0 -2
- package/src/domains/manifest/services/ManifestServiceForDev.spec.ts +1 -2
- package/src/domains/manifest/services/ManifestServiceForDev.ts +0 -4
- package/src/index.ts +1 -0
- package/src/jest-global-setup.ts +8 -4
- package/src/mocks/mockIoc.ts +18 -43
- package/src/utils/buildJobRunnerModule/buildJobRunnerModule.ts +81 -0
- package/src/utils/buildJobRunnerModule/buildJobRunnerModule.types.ts +13 -0
- package/src/utils/index.ts +2 -0
- package/lib/domains/job/S3Repository/S3Repository.d.ts +0 -15
- package/lib/domains/job/S3Repository/S3Repository.spec.d.ts +0 -5
- package/lib/domains/job/services/JobQueueService/JobQueueService.d.ts +0 -9
- package/lib/domains/job/services/JobQueueService/JobQueueService.defs.d.ts +0 -9
- package/src/custom-types.d.ts +0 -7
- package/src/domains/job/S3Repository/S3Repository.spec.ts +0 -50
- package/src/domains/job/S3Repository/S3Repository.ts +0 -27
- package/src/domains/job/services/JobQueueService/JobQueueService.defs.ts +0 -10
- package/src/domains/job/services/JobQueueService/JobQueueService.spec.ts +0 -190
- package/src/domains/job/services/JobQueueService/JobQueueService.ts +0 -60
|
@@ -3,56 +3,46 @@
|
|
|
3
3
|
* Copyright Squiz Australia Pty Ltd. All Rights Reserved.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
dotenv.config();
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
AttributeValue,
|
|
11
|
-
DynamoDBClient,
|
|
12
|
-
PutItemCommand,
|
|
13
|
-
UpdateItemCommand,
|
|
14
|
-
UpdateItemCommandInput,
|
|
15
|
-
} from '@aws-sdk/client-dynamodb';
|
|
6
|
+
import { AttributeValue, DynamoDBClient, UpdateItemCommand, UpdateItemCommandInput } from '@aws-sdk/client-dynamodb';
|
|
16
7
|
import { inject } from 'inversify';
|
|
17
8
|
import { provideTransient } from '../../transientProvider';
|
|
18
9
|
import { Repository } from '../AbstractRepository';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
10
|
+
import { ENTITY_TYPES } from '../../constants/Repository.constants';
|
|
11
|
+
import {
|
|
12
|
+
CreateJobManifestRequest,
|
|
13
|
+
JobManifest,
|
|
14
|
+
JobManifestDto,
|
|
15
|
+
JobManifestSk,
|
|
16
|
+
UpdateJobManifestRequest,
|
|
17
|
+
} from '../../manifest';
|
|
18
|
+
import { InjectTokens } from '../../constants/InjectTokens';
|
|
22
19
|
import { parsePartitionKey } from '../../ParsePartitionKey';
|
|
23
20
|
|
|
24
21
|
@provideTransient(JobManifestRepository)
|
|
25
|
-
export class JobManifestRepository extends Repository<JobManifest, JobManifestDto> {
|
|
26
|
-
public constructor(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
export class JobManifestRepository extends Repository<JobManifest, JobManifestDto, JobManifestSk> {
|
|
23
|
+
public constructor(
|
|
24
|
+
@inject(DynamoDBClient) dynamoDbClient: DynamoDBClient,
|
|
25
|
+
@inject(InjectTokens.DynamoTableName) tableName: string,
|
|
26
|
+
@inject(InjectTokens.Tenant) tenant: string,
|
|
27
|
+
) {
|
|
28
|
+
super(dynamoDbClient, tableName, `${ENTITY_TYPES.manifest}~${tenant}`);
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
public async create(input: CreateJobManifestRequest): Promise<JobManifest> {
|
|
33
32
|
const newJobManifest: JobManifest = {
|
|
34
33
|
...input,
|
|
35
|
-
type:
|
|
34
|
+
type: ENTITY_TYPES.manifest,
|
|
36
35
|
};
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
await this.dynamoDbClient.send(
|
|
41
|
-
new PutItemCommand({
|
|
42
|
-
Item: dynamoDbRecord,
|
|
43
|
-
TableName: this.tableName,
|
|
44
|
-
}),
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
return newJobManifest;
|
|
37
|
+
return super.create(newJobManifest);
|
|
48
38
|
}
|
|
49
39
|
|
|
50
|
-
async update(sk:
|
|
40
|
+
async update(sk: JobManifestSk, input: UpdateJobManifestRequest): Promise<JobManifest> {
|
|
51
41
|
// Initialise update command
|
|
52
42
|
const updateParams: UpdateItemCommandInput = {
|
|
53
43
|
Key: {
|
|
54
44
|
pk: { S: this.pk },
|
|
55
|
-
sk: { S: sk },
|
|
45
|
+
sk: { S: this.buildSk(sk) },
|
|
56
46
|
},
|
|
57
47
|
ReturnValues: 'ALL_NEW',
|
|
58
48
|
TableName: this.tableName,
|
|
@@ -88,8 +78,7 @@ export class JobManifestRepository extends Repository<JobManifest, JobManifestDt
|
|
|
88
78
|
const updateItemCommand = new UpdateItemCommand(updateParams);
|
|
89
79
|
const updatedJob = await this.dynamoDbClient.send(updateItemCommand);
|
|
90
80
|
|
|
91
|
-
|
|
92
|
-
return updatedJob.Attributes ? this.convertToItem(updatedJob.Attributes as JobManifestDto) : null;
|
|
81
|
+
return this.convertToItem(updatedJob.Attributes as JobManifestDto);
|
|
93
82
|
}
|
|
94
83
|
|
|
95
84
|
convertToDynamoDbItem(item: JobManifest): JobManifestDto {
|
|
@@ -99,8 +88,8 @@ export class JobManifestRepository extends Repository<JobManifest, JobManifestDt
|
|
|
99
88
|
entryFunction: { S: JSON.stringify(item.entryFunction) },
|
|
100
89
|
name: { S: item.name },
|
|
101
90
|
version: { S: item.version },
|
|
102
|
-
pk: { S:
|
|
103
|
-
sk: { S: `${item.name}
|
|
91
|
+
pk: { S: this.pk },
|
|
92
|
+
sk: { S: `${item.name}~${item.version}` },
|
|
104
93
|
timeoutSeconds: { N: item.timeoutSeconds.toString() },
|
|
105
94
|
};
|
|
106
95
|
}
|
|
@@ -114,7 +103,11 @@ export class JobManifestRepository extends Repository<JobManifest, JobManifestDt
|
|
|
114
103
|
name: name.S as string,
|
|
115
104
|
version: version.S as string,
|
|
116
105
|
timeoutSeconds: parseInt(timeoutSeconds.N as string),
|
|
117
|
-
type: parsePartitionKey(pk.S as string),
|
|
106
|
+
type: parsePartitionKey(pk.S as string).entityType,
|
|
118
107
|
};
|
|
119
108
|
}
|
|
109
|
+
|
|
110
|
+
protected buildSk(sk: JobManifestSk): string {
|
|
111
|
+
return `${sk.name}~${sk.version}`;
|
|
112
|
+
}
|
|
120
113
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
AttributeValue,
|
|
8
|
+
DeleteItemCommandOutput,
|
|
8
9
|
DynamoDBClient,
|
|
9
10
|
DynamoDBClientConfig,
|
|
10
11
|
GetItemCommandOutput,
|
|
@@ -13,13 +14,12 @@ import {
|
|
|
13
14
|
ScanCommandOutput,
|
|
14
15
|
} from '@aws-sdk/client-dynamodb';
|
|
15
16
|
import { initIocContainer, iocContainer } from '../../../../mocks/mockIoc';
|
|
16
|
-
import { testLogger } from '../../../../jest-global-setup';
|
|
17
17
|
import {
|
|
18
18
|
createMockClientPutResponse,
|
|
19
19
|
createMockClientScanResponse,
|
|
20
20
|
createMockClientGetResponse,
|
|
21
21
|
} from '../../core/dynamoClient.mock';
|
|
22
|
-
import {
|
|
22
|
+
import { ENTITY_TYPES } from '../../constants/Repository.constants';
|
|
23
23
|
import { PaginationInfo } from '../../core/pagination/model/PaginationInfo';
|
|
24
24
|
import { PAGINATION_DIRECTION_TYPE_BEFORE } from '../../core/pagination/model/PaginationTypes';
|
|
25
25
|
import * as PaginationUtils from '../../core/pagination/utils/PaginationUtils';
|
|
@@ -27,8 +27,10 @@ import { Job } from '../../model/Job/Job';
|
|
|
27
27
|
import { createMockJobDto, createMockJobDtos } from '../../model/Job/mocks/JobDto.mock';
|
|
28
28
|
import { createMockCreateJobRequest } from '../../model/Job/mocks/JobRequest.mock';
|
|
29
29
|
import { JobRepository } from './JobRepository';
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
import { createMockJob, createMockJobRecord } from '../../model/Job';
|
|
31
|
+
import { InjectTokens } from '../../constants/InjectTokens';
|
|
32
|
+
|
|
33
|
+
initIocContainer();
|
|
32
34
|
|
|
33
35
|
// Mock DynamoDB Client
|
|
34
36
|
const mockOptions = {} as DynamoDBClientConfig;
|
|
@@ -39,18 +41,18 @@ mockClient.send = mockSend;
|
|
|
39
41
|
// Inject JobRepository with a mocked client
|
|
40
42
|
iocContainer.rebind<DynamoDBClient>(DynamoDBClient).toConstantValue(mockClient);
|
|
41
43
|
|
|
42
|
-
let jobRepository: JobRepository;
|
|
43
|
-
|
|
44
44
|
describe('JobRepository', (): void => {
|
|
45
|
+
let jobRepository: JobRepository;
|
|
46
|
+
let tenant: string;
|
|
47
|
+
let tableName: string;
|
|
48
|
+
|
|
45
49
|
beforeAll(() => {
|
|
46
50
|
jobRepository = iocContainer.get(JobRepository);
|
|
51
|
+
tenant = iocContainer.get(InjectTokens.Tenant);
|
|
52
|
+
tableName = iocContainer.get(InjectTokens.DynamoTableName);
|
|
47
53
|
});
|
|
48
54
|
|
|
49
55
|
describe('list', () => {
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
jest.resetAllMocks();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
56
|
it(`should return an empty list`, async (): Promise<void> => {
|
|
55
57
|
mockSend.mockImplementationOnce((): ScanCommandOutput => createMockClientScanResponse());
|
|
56
58
|
|
|
@@ -87,7 +89,9 @@ describe('JobRepository', (): void => {
|
|
|
87
89
|
}),
|
|
88
90
|
);
|
|
89
91
|
|
|
90
|
-
const response = await jobRepository.list({
|
|
92
|
+
const response = await jobRepository.list({
|
|
93
|
+
filters: { name: mockName },
|
|
94
|
+
});
|
|
91
95
|
expect(mockSend).toBeCalledTimes(1);
|
|
92
96
|
expect(response).toHaveLength(mockRecords.length);
|
|
93
97
|
});
|
|
@@ -104,14 +108,16 @@ describe('JobRepository', (): void => {
|
|
|
104
108
|
}),
|
|
105
109
|
);
|
|
106
110
|
|
|
107
|
-
const response = await jobRepository.list({
|
|
111
|
+
const response = await jobRepository.list({
|
|
112
|
+
filters: { version: mockVersion },
|
|
113
|
+
});
|
|
108
114
|
expect(mockSend).toBeCalledTimes(1);
|
|
109
115
|
expect(response).toHaveLength(mockRecords.length);
|
|
110
116
|
});
|
|
111
117
|
|
|
112
118
|
it(`should paginate results to return a list of existing jobs`, async (): Promise<void> => {
|
|
113
|
-
const mockRecordsPage1 = createMockJobDtos();
|
|
114
|
-
const mockRecordsPage2 = createMockJobDtos();
|
|
119
|
+
const mockRecordsPage1 = createMockJobDtos(2);
|
|
120
|
+
const mockRecordsPage2 = createMockJobDtos(2);
|
|
115
121
|
|
|
116
122
|
mockSend
|
|
117
123
|
.mockImplementationOnce(
|
|
@@ -129,9 +135,11 @@ describe('JobRepository', (): void => {
|
|
|
129
135
|
}),
|
|
130
136
|
);
|
|
131
137
|
|
|
132
|
-
const response = await jobRepository.list(
|
|
138
|
+
const response = await jobRepository.list({
|
|
139
|
+
limit: 3,
|
|
140
|
+
});
|
|
133
141
|
expect(mockSend).toBeCalledTimes(2);
|
|
134
|
-
expect(response).toHaveLength(
|
|
142
|
+
expect(response).toHaveLength(3);
|
|
135
143
|
});
|
|
136
144
|
|
|
137
145
|
it(`should stop paginating if unexpect result is returned`, async (): Promise<void> => {
|
|
@@ -144,10 +152,6 @@ describe('JobRepository', (): void => {
|
|
|
144
152
|
});
|
|
145
153
|
|
|
146
154
|
describe('findOne', () => {
|
|
147
|
-
beforeEach(() => {
|
|
148
|
-
jest.resetAllMocks();
|
|
149
|
-
});
|
|
150
|
-
|
|
151
155
|
it(`should return job by name`, async (): Promise<void> => {
|
|
152
156
|
const mockJobRecord = createMockJobDto();
|
|
153
157
|
mockSend.mockImplementationOnce(
|
|
@@ -157,7 +161,10 @@ describe('JobRepository', (): void => {
|
|
|
157
161
|
}),
|
|
158
162
|
);
|
|
159
163
|
|
|
160
|
-
const response = await jobRepository.findOne(
|
|
164
|
+
const response = await jobRepository.findOne({
|
|
165
|
+
name: mockJobRecord.name.S as string,
|
|
166
|
+
version: mockJobRecord.version.S as string,
|
|
167
|
+
});
|
|
161
168
|
expect(response).toStrictEqual({
|
|
162
169
|
name: mockJobRecord.name.S as string,
|
|
163
170
|
type: `job`,
|
|
@@ -175,22 +182,18 @@ describe('JobRepository', (): void => {
|
|
|
175
182
|
}),
|
|
176
183
|
);
|
|
177
184
|
|
|
178
|
-
const response = await jobRepository.findOne(
|
|
185
|
+
const response = await jobRepository.findOne({ name: 'unknown', version: '0.0.1' });
|
|
179
186
|
expect(mockSend).toBeCalledTimes(1);
|
|
180
187
|
expect(response).toBe(null);
|
|
181
188
|
});
|
|
182
189
|
});
|
|
183
190
|
|
|
184
191
|
describe('create', () => {
|
|
185
|
-
beforeEach(() => {
|
|
186
|
-
jest.resetAllMocks();
|
|
187
|
-
});
|
|
188
|
-
|
|
189
192
|
it(`should create a new job record and return job in the response`, async (): Promise<void> => {
|
|
190
193
|
const newJobToCreate = createMockCreateJobRequest();
|
|
191
194
|
const mockReponse = createMockClientPutResponse({
|
|
192
195
|
Attributes: {
|
|
193
|
-
pk: { S:
|
|
196
|
+
pk: { S: ENTITY_TYPES.job },
|
|
194
197
|
sk: { S: `${newJobToCreate.name}-${newJobToCreate.version}` },
|
|
195
198
|
name: { S: newJobToCreate.name },
|
|
196
199
|
version: { S: newJobToCreate.version },
|
|
@@ -203,7 +206,7 @@ describe('JobRepository', (): void => {
|
|
|
203
206
|
expect(mockSend).toBeCalledTimes(1);
|
|
204
207
|
expect(response).toStrictEqual({
|
|
205
208
|
...newJobToCreate,
|
|
206
|
-
type:
|
|
209
|
+
type: ENTITY_TYPES.job,
|
|
207
210
|
});
|
|
208
211
|
});
|
|
209
212
|
});
|
|
@@ -215,7 +218,7 @@ describe('JobRepository', (): void => {
|
|
|
215
218
|
|
|
216
219
|
beforeEach(() => {
|
|
217
220
|
mockExclusiveStartKey = {
|
|
218
|
-
pk: { S:
|
|
221
|
+
pk: { S: `${ENTITY_TYPES.job}~${tenant}` },
|
|
219
222
|
sk: { S: 'mockSk' },
|
|
220
223
|
};
|
|
221
224
|
mockPaginationInfo = {
|
|
@@ -227,11 +230,11 @@ describe('JobRepository', (): void => {
|
|
|
227
230
|
mockParams = {
|
|
228
231
|
ExclusiveStartKey: mockExclusiveStartKey,
|
|
229
232
|
ExpressionAttributeValues: {
|
|
230
|
-
':pk': { S:
|
|
233
|
+
':pk': { S: `${ENTITY_TYPES.job}~${tenant}` },
|
|
231
234
|
},
|
|
232
235
|
KeyConditionExpression: 'pk = :pk',
|
|
233
236
|
ScanIndexForward: false,
|
|
234
|
-
TableName:
|
|
237
|
+
TableName: tableName,
|
|
235
238
|
};
|
|
236
239
|
|
|
237
240
|
jest.spyOn(PaginationUtils, 'decodeTokenString').mockReturnValue(mockExclusiveStartKey);
|
|
@@ -261,7 +264,7 @@ describe('JobRepository', (): void => {
|
|
|
261
264
|
it('should return an array of jobs if found', async () => {
|
|
262
265
|
const mockJobRecords = createMockJobDtos();
|
|
263
266
|
const mockLastEvaluatedKey = {
|
|
264
|
-
pk: { S:
|
|
267
|
+
pk: { S: ENTITY_TYPES.job },
|
|
265
268
|
sk: { S: 'mockSk' },
|
|
266
269
|
};
|
|
267
270
|
|
|
@@ -286,52 +289,41 @@ describe('JobRepository', (): void => {
|
|
|
286
289
|
});
|
|
287
290
|
});
|
|
288
291
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
// mockSend.mockImplementationOnce((): DeleteItemCommandOutput => {
|
|
327
|
-
// return {
|
|
328
|
-
// $metadata: {},
|
|
329
|
-
// };
|
|
330
|
-
// });
|
|
331
|
-
|
|
332
|
-
// const response = await jobRepository.delete(`name`);
|
|
333
|
-
// expect(response).toBe(undefined);
|
|
334
|
-
// expect(mockSend).toBeCalledTimes(1);
|
|
335
|
-
// });
|
|
336
|
-
// });
|
|
292
|
+
describe('update', () => {
|
|
293
|
+
it(`should update a job record`, async (): Promise<void> => {
|
|
294
|
+
const jobToUpdate = createMockJob();
|
|
295
|
+
const updatedJob: Job = {
|
|
296
|
+
...jobToUpdate,
|
|
297
|
+
description: `updated description`,
|
|
298
|
+
};
|
|
299
|
+
const updatedJobRecord = createMockJobRecord(updatedJob);
|
|
300
|
+
const updatedJobRecordDto = createMockJobDto(updatedJobRecord);
|
|
301
|
+
const putResponse = createMockClientPutResponse({
|
|
302
|
+
Attributes: { ...updatedJobRecordDto },
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
mockSend.mockImplementation((): PutItemCommandOutput => putResponse);
|
|
306
|
+
|
|
307
|
+
const response = await jobRepository.update(
|
|
308
|
+
{ name: jobToUpdate.name, version: jobToUpdate.version },
|
|
309
|
+
updatedJobRecord,
|
|
310
|
+
);
|
|
311
|
+
expect(response).toStrictEqual(updatedJob);
|
|
312
|
+
expect(mockSend).toBeCalledTimes(1);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe('delete', () => {
|
|
317
|
+
it(`should return undefined when calling the delete operation`, async (): Promise<void> => {
|
|
318
|
+
mockSend.mockImplementationOnce((): DeleteItemCommandOutput => {
|
|
319
|
+
return {
|
|
320
|
+
$metadata: {},
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const response = await jobRepository.delete({ name: `name`, version: '1.0.0' });
|
|
325
|
+
expect(response).toBe(undefined);
|
|
326
|
+
expect(mockSend).toBeCalledTimes(1);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
337
329
|
});
|
|
@@ -3,53 +3,44 @@
|
|
|
3
3
|
* Copyright Squiz Australia Pty Ltd. All Rights Reserved.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import * as dotenv from 'dotenv';
|
|
7
|
-
dotenv.config();
|
|
8
|
-
|
|
9
6
|
import {
|
|
10
7
|
AttributeValue,
|
|
11
8
|
DynamoDBClient,
|
|
12
|
-
PutItemCommand,
|
|
13
9
|
QueryCommand,
|
|
14
10
|
QueryCommandInput,
|
|
15
|
-
|
|
16
|
-
ScanCommandOutput,
|
|
11
|
+
QueryCommandOutput,
|
|
17
12
|
} from '@aws-sdk/client-dynamodb';
|
|
18
13
|
import { inject } from 'inversify';
|
|
19
14
|
import { provideTransient } from '../../transientProvider';
|
|
20
|
-
import { Repository } from '
|
|
21
|
-
import {
|
|
15
|
+
import { Repository } from '../AbstractRepository';
|
|
16
|
+
import { ENTITY_TYPES } from '../../constants/Repository.constants';
|
|
22
17
|
import { parsePartitionKey } from '../../ParsePartitionKey';
|
|
23
|
-
import { CreateJobRequest, Job, JobDto, ListJobParameters } from '../../model/Job
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
import { CreateJobRequest, Job, JobDto, JobSk, ListJobParameters } from '../../model/Job';
|
|
19
|
+
import {
|
|
20
|
+
decodeTokenString,
|
|
21
|
+
PAGINATION_DIRECTION_TYPE_BEFORE,
|
|
22
|
+
PaginationInfo,
|
|
23
|
+
PaginationQueryResponse,
|
|
24
|
+
} from '../../core/pagination';
|
|
25
|
+
import { InjectTokens } from '../../constants/InjectTokens';
|
|
28
26
|
|
|
29
27
|
@provideTransient(JobRepository)
|
|
30
|
-
export class JobRepository extends Repository<Job, JobDto> {
|
|
31
|
-
public constructor(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
export class JobRepository extends Repository<Job, JobDto, JobSk> {
|
|
29
|
+
public constructor(
|
|
30
|
+
@inject(DynamoDBClient) dynamoDbClient: DynamoDBClient,
|
|
31
|
+
@inject(InjectTokens.DynamoTableName) tableName: string,
|
|
32
|
+
@inject(InjectTokens.Tenant) tenant: string,
|
|
33
|
+
) {
|
|
34
|
+
super(dynamoDbClient, tableName, `${ENTITY_TYPES.job}~${tenant}`);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
public async create(input: CreateJobRequest): Promise<Job> {
|
|
38
38
|
const newJob: Job = {
|
|
39
39
|
...input,
|
|
40
|
-
type:
|
|
40
|
+
type: ENTITY_TYPES.job,
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
await this.dynamoDbClient.send(
|
|
46
|
-
new PutItemCommand({
|
|
47
|
-
Item: dynamoDbRecord,
|
|
48
|
-
TableName: this.tableName,
|
|
49
|
-
}),
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
return newJob;
|
|
43
|
+
return super.create(newJob);
|
|
53
44
|
}
|
|
54
45
|
|
|
55
46
|
/**
|
|
@@ -58,7 +49,9 @@ export class JobRepository extends Repository<Job, JobDto> {
|
|
|
58
49
|
* @returns {Promise<Array<T>>} The retrieved records.
|
|
59
50
|
* @memberof Repository
|
|
60
51
|
*/
|
|
61
|
-
async list(parameters
|
|
52
|
+
async list(parameters: ListJobParameters = {}): Promise<Array<Job>> {
|
|
53
|
+
const keyConditionExpressions = ['#pk = :pk'];
|
|
54
|
+
const filterExpressions: Array<string> = [];
|
|
62
55
|
const expressionAttributeNames: Record<string, string> = {
|
|
63
56
|
'#pk': 'pk',
|
|
64
57
|
};
|
|
@@ -66,26 +59,21 @@ export class JobRepository extends Repository<Job, JobDto> {
|
|
|
66
59
|
':pk': { S: this.pk },
|
|
67
60
|
};
|
|
68
61
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (version) {
|
|
86
|
-
params.ExpressionAttributeNames['#version'] = 'version';
|
|
87
|
-
params.ExpressionAttributeValues[':version'] = { S: version };
|
|
88
|
-
params.FilterExpression += ` and #version = :version`;
|
|
62
|
+
if (parameters.filters) {
|
|
63
|
+
const { name, version } = parameters.filters;
|
|
64
|
+
|
|
65
|
+
if (name && version) {
|
|
66
|
+
expressionAttributeNames['#sk'] = 'sk';
|
|
67
|
+
expressionAttributeValues[':sk'] = { S: `${name}~${version}` };
|
|
68
|
+
keyConditionExpressions.push(`#sk = :sk`);
|
|
69
|
+
} else if (name) {
|
|
70
|
+
expressionAttributeNames['#sk'] = 'sk';
|
|
71
|
+
expressionAttributeValues[':skPrefix'] = { S: `${name}~` };
|
|
72
|
+
keyConditionExpressions.push(`begins_with(#sk, :skPrefix)`);
|
|
73
|
+
} else if (version) {
|
|
74
|
+
expressionAttributeNames['#version'] = 'version';
|
|
75
|
+
expressionAttributeValues[':version'] = { S: version };
|
|
76
|
+
filterExpressions.push(`#version = :version`);
|
|
89
77
|
}
|
|
90
78
|
}
|
|
91
79
|
|
|
@@ -93,29 +81,33 @@ export class JobRepository extends Repository<Job, JobDto> {
|
|
|
93
81
|
// By default, the Scan operation processes data sequentially.
|
|
94
82
|
// DynamoDB returns data in increments of 1 MB increments.
|
|
95
83
|
|
|
96
|
-
let startKey; /* Indicator of where to paginate from. */
|
|
84
|
+
let startKey: Record<string, AttributeValue> | undefined = undefined; /* Indicator of where to paginate from. */
|
|
97
85
|
const results: Array<Record<string, AttributeValue>> = [];
|
|
98
86
|
|
|
99
87
|
// Iterate through pages until no more results are found.
|
|
100
|
-
|
|
101
|
-
const res:
|
|
102
|
-
new
|
|
88
|
+
do {
|
|
89
|
+
const res: QueryCommandOutput = await this.dynamoDbClient.send(
|
|
90
|
+
new QueryCommand({
|
|
91
|
+
ExpressionAttributeNames: expressionAttributeNames,
|
|
92
|
+
ExpressionAttributeValues: expressionAttributeValues,
|
|
93
|
+
KeyConditionExpression: keyConditionExpressions.join(' and '),
|
|
94
|
+
FilterExpression: filterExpressions.length > 0 ? filterExpressions.join(' and ') : undefined,
|
|
95
|
+
TableName: this.tableName,
|
|
96
|
+
ExclusiveStartKey: startKey,
|
|
97
|
+
Limit: parameters.limit,
|
|
98
|
+
ConsistentRead: parameters.consistentRead,
|
|
99
|
+
}),
|
|
103
100
|
);
|
|
104
101
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
[]),
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
// If no more results, set key to null to terminate loop
|
|
116
|
-
startKey = res.LastEvaluatedKey ?? null;
|
|
102
|
+
results.push(...(res?.Items || []));
|
|
103
|
+
startKey = res?.LastEvaluatedKey;
|
|
104
|
+
|
|
105
|
+
if (parameters.limit !== undefined && results.length >= parameters.limit) {
|
|
106
|
+
// Truncate list to max requested items, stop querying.
|
|
107
|
+
results.length = parameters.limit;
|
|
108
|
+
break;
|
|
117
109
|
}
|
|
118
|
-
}
|
|
110
|
+
} while (startKey);
|
|
119
111
|
|
|
120
112
|
return results.length ? results.map((item) => this.convertToItem(item as JobDto)) : [];
|
|
121
113
|
}
|
|
@@ -160,8 +152,8 @@ export class JobRepository extends Repository<Job, JobDto> {
|
|
|
160
152
|
|
|
161
153
|
convertToDynamoDbItem(item: Job): JobDto {
|
|
162
154
|
return {
|
|
163
|
-
pk: { S:
|
|
164
|
-
sk: { S:
|
|
155
|
+
pk: { S: this.pk },
|
|
156
|
+
sk: { S: this.buildSk(item) },
|
|
165
157
|
name: { S: item.name },
|
|
166
158
|
description: item.description ? { S: item.description } : { NULL: true },
|
|
167
159
|
version: { S: item.version },
|
|
@@ -181,8 +173,12 @@ export class JobRepository extends Repository<Job, JobDto> {
|
|
|
181
173
|
return {
|
|
182
174
|
name: name.S as string,
|
|
183
175
|
description: description.S ? (description.S as string) : '',
|
|
184
|
-
type: parsePartitionKey(pk.S as string),
|
|
176
|
+
type: parsePartitionKey(pk.S as string).entityType,
|
|
185
177
|
version: version.S as string,
|
|
186
178
|
};
|
|
187
179
|
}
|
|
180
|
+
|
|
181
|
+
protected buildSk(sk: JobSk): string {
|
|
182
|
+
return `${sk.name}~${sk.version}`;
|
|
183
|
+
}
|
|
188
184
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import 'aws-sdk-client-mock-jest';
|
|
2
|
+
import { mockClient } from 'aws-sdk-client-mock';
|
|
3
|
+
import { DynamoDBClient, QueryCommand, QueryCommandInput } from '@aws-sdk/client-dynamodb';
|
|
4
|
+
import { faker } from '@faker-js/faker';
|
|
5
|
+
import { createMockJobExecution, createMockJobExecutionDto } from '../../model/JobExecution';
|
|
6
|
+
import { MultiTenantJobExecutionRepository } from './MultiTenantJobExecutionRepository';
|
|
7
|
+
import { JOB_EXECUTION_STATUS } from '../../constants/JobExecutionStatus.constants';
|
|
8
|
+
import { ENTITY_TYPES } from '../../constants/Repository.constants';
|
|
9
|
+
|
|
10
|
+
describe('MultiTenantJobExecutionRepository', () => {
|
|
11
|
+
const mockDynamoClient = mockClient(DynamoDBClient);
|
|
12
|
+
const tableName = faker.lorem.words();
|
|
13
|
+
let repository: MultiTenantJobExecutionRepository;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mockDynamoClient.reset();
|
|
17
|
+
repository = new MultiTenantJobExecutionRepository(new DynamoDBClient(), tableName);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('findByStatus', () => {
|
|
21
|
+
it('Queries job executions by status', async () => {
|
|
22
|
+
const tenant1 = faker.string.uuid();
|
|
23
|
+
const tenant2 = faker.string.uuid();
|
|
24
|
+
const executions = [createMockJobExecution(), createMockJobExecution(), createMockJobExecution()];
|
|
25
|
+
const expectedQueryInput: QueryCommandInput = {
|
|
26
|
+
KeyConditionExpression: 'gsi1pk = :gsi1pk',
|
|
27
|
+
ExpressionAttributeValues: {
|
|
28
|
+
':gsi1pk': { S: `${ENTITY_TYPES.jobExecution}~running` },
|
|
29
|
+
},
|
|
30
|
+
TableName: tableName,
|
|
31
|
+
IndexName: 'gsi1',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
mockDynamoClient
|
|
35
|
+
.on(QueryCommand)
|
|
36
|
+
.resolvesOnce({
|
|
37
|
+
Items: [createMockJobExecutionDto({ pk: `${ENTITY_TYPES.jobExecution}~${tenant1}`, ...executions[0] })],
|
|
38
|
+
LastEvaluatedKey: { pk: { S: 'page-2' } },
|
|
39
|
+
})
|
|
40
|
+
.resolvesOnce({
|
|
41
|
+
Items: [
|
|
42
|
+
createMockJobExecutionDto({ pk: `${ENTITY_TYPES.jobExecution}~${tenant1}`, ...executions[1] }),
|
|
43
|
+
createMockJobExecutionDto({ pk: `${ENTITY_TYPES.jobExecution}~${tenant2}`, ...executions[2] }),
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const results = await repository.findByStatus(JOB_EXECUTION_STATUS.running);
|
|
48
|
+
|
|
49
|
+
expect(results).toEqual({
|
|
50
|
+
[tenant1]: [executions[0], executions[1]],
|
|
51
|
+
[tenant2]: [executions[2]],
|
|
52
|
+
});
|
|
53
|
+
expect(mockDynamoClient).toHaveReceivedNthCommandWith(1, QueryCommand, expectedQueryInput);
|
|
54
|
+
expect(mockDynamoClient).toHaveReceivedNthCommandWith(2, QueryCommand, {
|
|
55
|
+
...expectedQueryInput,
|
|
56
|
+
ExclusiveStartKey: { pk: { S: 'page-2' } },
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|