@getjones/test-infra 5.2.11 → 5.2.13
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/package.json +2 -2
- package/src/common/commonFlows/records/records.js +55 -3
- package/src/common/consts/projectSettings.js +7 -0
- package/src/common/errorCodes.js +2 -1
- package/src/index.js +4 -0
- package/src/saas/internal-api/accounts/api/core/accounts.core.api.js +8 -2
- package/src/saas/internal-api/accounts/api/core/accounts.core.service.js +10 -0
- package/src/saas/internal-api/auth/api/auth.service.js +46 -1
- package/src/saas/internal-api/certificial/api/certificial.api.js +15 -0
- package/src/saas/internal-api/certificial/api/certificial.service.js +24 -0
- package/src/saas/internal-api/certificial/consts.js +8 -0
- package/src/saas/internal-api/certificial/index.js +4 -0
- package/src/saas/internal-api/compliance/api/overrideGap/override.gap.api.js +25 -2
- package/src/saas/internal-api/compliance/api/overrideGap/override.gap.service.js +93 -2
- package/src/saas/internal-api/compliance/consts/overrideGapRequest/override.gap.request.js +8 -1
- package/src/saas/internal-api/compliance/models/overrideGapRequest/override.gap.request.model.js +16 -1
- package/src/saas/internal-api/dbEmails/api/db.emails.service.js +31 -0
- package/src/saas/internal-api/dbEmails/consts/consts.js +3 -0
- package/src/saas/internal-api/notificationCenter/consts.js +4 -1
- package/src/saas/internal-api/outlook/api/outlook.api.js +2 -2
- package/src/saas/internal-api/outlook/api/outlook.service.js +2 -2
- package/src/saas/internal-api/projects/models/settings/update.project.settings.model.js +17 -1
- package/src/saas/internal-api/requirements/consts/consts.js +3 -1
- package/src/saas/internal-api/schedulerJobRun/api/scheduler.job.run.api.js +31 -0
- package/src/saas/internal-api/schedulerJobRun/api/scheduler.job.run.service.js +56 -0
- package/src/saas/internal-api/schedulerJobRun/index.js +3 -0
- package/src/saas/internal-api/thread-scheduler/runner.js +17 -0
- package/src/saas/internal-api/thread-scheduler/schedulerJobs.js +2 -0
- package/src/saas/internal-api/uploadFiles/api/upload.file.service.js +19 -5
- package/src/saas/internal-api/users/models/notification.settings.model.js +2 -1
- package/src/saas/internal-api/users/utils/util.js +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getjones/test-infra",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.13",
|
|
4
4
|
"description": "Providing internal and openapi interface",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"license": "ISC",
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"axios": "1.8.1",
|
|
22
|
-
"chai": "
|
|
22
|
+
"chai": "6.3.0",
|
|
23
23
|
"dotenv": "16.5.0",
|
|
24
24
|
"enum": "3.0.4",
|
|
25
25
|
"form-data": "4.0.6",
|
|
@@ -20,7 +20,10 @@ const recordTypes = require('../../../common/consts/recordTypes');
|
|
|
20
20
|
const policyNames = require('../../../defaults/index').polices;
|
|
21
21
|
const { ExtractionRequestModel } = require('../../../saas/internal-api/requirements/models/extraction.request.model');
|
|
22
22
|
const { TabExtractionRequestModel } = require('../../../saas/internal-api/requirements/models/tab.extraction.request.model');
|
|
23
|
+
const { DEFAULT_LEASE_PATH } = require('../../../saas/internal-api/requirements/consts/consts');
|
|
24
|
+
const { RELATED_DOC_TYPES } = require('../../../saas/internal-api/uploadFiles/consts');
|
|
23
25
|
const { WAIVER_REASONS, EXPIRY_TYPES } = compliance.models.waive;
|
|
26
|
+
const { DEFAULT_PM_OVERRIDE_REASON } = require('../../../saas/internal-api/compliance/consts/overrideGapRequest/override.gap.request');
|
|
24
27
|
|
|
25
28
|
const makeRecordNotResponsive = async function(recordId, context) {
|
|
26
29
|
const { data: record } = await contractors.services.getContractorById(recordId, context);
|
|
@@ -326,6 +329,45 @@ const rejectOverrideGapRequest = async (overrideGapRequestId, rejectReason, reco
|
|
|
326
329
|
return compliance.services.overrideGap.rejectOverrideGapRequest(overrideGapRequestId, rejectReason, record.dateModified, context);
|
|
327
330
|
};
|
|
328
331
|
|
|
332
|
+
const createPmOverrideRequest = async (recordId, policyName, attributeName, assigneeUserIds, context, {
|
|
333
|
+
requestReason = DEFAULT_PM_OVERRIDE_REASON,
|
|
334
|
+
expiryType = EXPIRY_TYPES.NEVER,
|
|
335
|
+
expiryDate = null,
|
|
336
|
+
expectError
|
|
337
|
+
} = {}) => {
|
|
338
|
+
const { data: record } = await contractors.services.getContractorById(recordId, context);
|
|
339
|
+
const auditorContractorId = compliance.services.overrideGap.getAuditorContractorIdFromGap(record, policyName, attributeName);
|
|
340
|
+
return compliance.services.overrideGap.createPmOverrideRequest(auditorContractorId, context, {
|
|
341
|
+
requestReason,
|
|
342
|
+
expiryType,
|
|
343
|
+
expiryDate,
|
|
344
|
+
assigneeUserIds,
|
|
345
|
+
recordDateModified: record.dateModified,
|
|
346
|
+
expectError
|
|
347
|
+
});
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const editPmOverrideRequest = async (overrideGapRequestId, recordId, context, {
|
|
351
|
+
requestReason,
|
|
352
|
+
expiryType,
|
|
353
|
+
expiryDate = null,
|
|
354
|
+
assigneeUserIds,
|
|
355
|
+
expectError
|
|
356
|
+
} = {}) => {
|
|
357
|
+
const { data: record } = await contractors.services.getContractorById(recordId, context);
|
|
358
|
+
return compliance.services.overrideGap.editPmOverrideRequest(overrideGapRequestId, context, {
|
|
359
|
+
requestReason,
|
|
360
|
+
expiryType,
|
|
361
|
+
expiryDate,
|
|
362
|
+
assigneeUserIds,
|
|
363
|
+
recordDateModified: record.dateModified,
|
|
364
|
+
expectError
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const deletePmOverrideRequest = (overrideGapRequestId, context, { expectError } = {}) =>
|
|
369
|
+
compliance.services.overrideGap.deletePmOverrideRequest(overrideGapRequestId, context, { expectError });
|
|
370
|
+
|
|
329
371
|
const uploadMultipleCoiFiles = async (recordId, coiFilePaths, uploadedBy, uploaderEmail, context, { uploaderContext, duplicateHandling } = {}) => {
|
|
330
372
|
const recordResponse = await contractors.services.getContractorById(recordId, context);
|
|
331
373
|
const recordDateModified = recordResponse.data.dateModified;
|
|
@@ -392,18 +434,25 @@ const requestRequirementsExtraction = async (recordId, context, { leaseFilePath,
|
|
|
392
434
|
};
|
|
393
435
|
|
|
394
436
|
const requestTabRequirementsExtraction = async (tabId, context,
|
|
395
|
-
{ leaseFilePath, requirementName, notes, docType } = {}, { expectError, readContext = context } = {}) => {
|
|
437
|
+
{ leaseFilePath = DEFAULT_LEASE_PATH, requirementName, notes, docType = RELATED_DOC_TYPES.SERVICE_AGREEMENT } = {}, { expectError, readContext = context } = {}) => {
|
|
396
438
|
const stagedDocument = await uploadFile.services.stageDocumentToTempBucket(tabId, context, { filePath: leaseFilePath });
|
|
397
439
|
const tabExtractionRequestModel = new TabExtractionRequestModel({ ... stagedDocument, requirementName, docType, notes });
|
|
440
|
+
const existingLeases = expectError ? [] : await requirements.lease.services.getLeasesByTabId(tabId, readContext, { openOnly: false });
|
|
441
|
+
const existingLeaseIds = new Set(existingLeases.map(({ leaseId }) => leaseId));
|
|
398
442
|
const requestResponse = await requirements.lease.services.requestTabLeaseExtraction(tabId, tabExtractionRequestModel, context, { expectError });
|
|
399
443
|
|
|
400
444
|
if (expectError) {
|
|
401
445
|
return { requestResponse, stagedDocument };
|
|
402
446
|
}
|
|
403
447
|
|
|
404
|
-
const
|
|
448
|
+
const openLeases = await requirements.lease.services.getLeasesByTabId(tabId, readContext);
|
|
449
|
+
const createdLease = openLeases.find(({ leaseId }) => !existingLeaseIds.has(leaseId));
|
|
450
|
+
|
|
451
|
+
if (!createdLease) {
|
|
452
|
+
throw Error(`No new tab-level lease found for tab: ${tabId}`);
|
|
453
|
+
}
|
|
405
454
|
|
|
406
|
-
return
|
|
455
|
+
return createdLease.leaseId;
|
|
407
456
|
};
|
|
408
457
|
|
|
409
458
|
const cancelOpenTabRequests = async (tabId, icsContext) => {
|
|
@@ -438,6 +487,9 @@ module.exports = {
|
|
|
438
487
|
getActiveOverrides,
|
|
439
488
|
createOverrideGapRequest,
|
|
440
489
|
rejectOverrideGapRequest,
|
|
490
|
+
createPmOverrideRequest,
|
|
491
|
+
editPmOverrideRequest,
|
|
492
|
+
deletePmOverrideRequest,
|
|
441
493
|
uploadMultipleCoiFiles,
|
|
442
494
|
createTenantUser,
|
|
443
495
|
deleteRecordByName,
|
|
@@ -21,6 +21,13 @@ module.exports = {
|
|
|
21
21
|
onForTenants: { overrideGapRequestSettings: { isOverrideGapRequestEnabledForVendors: false, isOverrideGapRequestEnabledForTenants: true, isOverrideGapRequestEnabledForGuests: false } },
|
|
22
22
|
onForGuests: { overrideGapRequestSettings: { isOverrideGapRequestEnabledForVendors: false, isOverrideGapRequestEnabledForTenants: false, isOverrideGapRequestEnabledForGuests: true } }
|
|
23
23
|
},
|
|
24
|
+
userOverrideRequest: {
|
|
25
|
+
onForAll: { userOverrideRequestSettings: { isUserOverrideRequestEnabledForVendors: true, isUserOverrideRequestEnabledForTenants: true, isUserOverrideRequestEnabledForGuests: true } },
|
|
26
|
+
offForAll: { userOverrideRequestSettings: { isUserOverrideRequestEnabledForVendors: false, isUserOverrideRequestEnabledForTenants: false, isUserOverrideRequestEnabledForGuests: false } },
|
|
27
|
+
onForVendors: { userOverrideRequestSettings: { isUserOverrideRequestEnabledForVendors: true, isUserOverrideRequestEnabledForTenants: false, isUserOverrideRequestEnabledForGuests: false } },
|
|
28
|
+
onForTenants: { userOverrideRequestSettings: { isUserOverrideRequestEnabledForVendors: false, isUserOverrideRequestEnabledForTenants: true, isUserOverrideRequestEnabledForGuests: false } },
|
|
29
|
+
onForGuests: { userOverrideRequestSettings: { isUserOverrideRequestEnabledForVendors: false, isUserOverrideRequestEnabledForTenants: false, isUserOverrideRequestEnabledForGuests: true } }
|
|
30
|
+
},
|
|
24
31
|
onboardingPhase: {
|
|
25
32
|
on: { onboardingPhase: true },
|
|
26
33
|
off: { onboardingPhase: false }
|
package/src/common/errorCodes.js
CHANGED
|
@@ -8,5 +8,6 @@ module.exports = {
|
|
|
8
8
|
NOT_FOUND: 'NOT_FOUND',
|
|
9
9
|
NOT_ELIGIBLE: 'NOT_ELIGIBLE',
|
|
10
10
|
RECORD_NOT_IN_ACCOUNT: 'RECORD_NOT_IN_ACCOUNT',
|
|
11
|
-
GLOBAL_RECORD_NOT_IN_ACCOUNT: 'GLOBAL_RECORD_NOT_IN_ACCOUNT'
|
|
11
|
+
GLOBAL_RECORD_NOT_IN_ACCOUNT: 'GLOBAL_RECORD_NOT_IN_ACCOUNT',
|
|
12
|
+
IDENTITY_CHANGED: 'IDENTITY_CHANGED'
|
|
12
13
|
};
|
package/src/index.js
CHANGED
|
@@ -47,6 +47,8 @@ const getInternalAPI = () => {
|
|
|
47
47
|
const sampleCoi = require('./saas/internal-api/sampleCoi');
|
|
48
48
|
|
|
49
49
|
const threadScheduler = require('./saas/internal-api/thread-scheduler/runner');
|
|
50
|
+
const schedulerJobRun = require('./saas/internal-api/schedulerJobRun');
|
|
51
|
+
const certificial = require('./saas/internal-api/certificial');
|
|
50
52
|
const dbEmails = require('./saas/internal-api/dbEmails/index');
|
|
51
53
|
const agreement = require('./saas/internal-api/agreement/index');
|
|
52
54
|
const uploadFile = require('./saas/internal-api/uploadFiles/index');
|
|
@@ -86,6 +88,8 @@ const getInternalAPI = () => {
|
|
|
86
88
|
requestsTable,
|
|
87
89
|
sampleCoi,
|
|
88
90
|
threadScheduler,
|
|
91
|
+
schedulerJobRun,
|
|
92
|
+
certificial,
|
|
89
93
|
dbEmails,
|
|
90
94
|
emailCustomization,
|
|
91
95
|
factory,
|
|
@@ -5,6 +5,7 @@ const { backendUrl } = require('../../../../../env').getEnv();
|
|
|
5
5
|
|
|
6
6
|
const Endpoints = {
|
|
7
7
|
getAccount: `${backendUrl}/saas/accounts`,
|
|
8
|
+
getAccountList: `${backendUrl}/saas/accountList`,
|
|
8
9
|
create: `${backendUrl}/saas/createaccount`,
|
|
9
10
|
delete: `${backendUrl}/saas/account`,
|
|
10
11
|
edit: accountId => `${backendUrl}/saas/account/${accountId}/settings`,
|
|
@@ -24,9 +25,13 @@ const updateAccountSettings = (accountId, settings, context) => {
|
|
|
24
25
|
return requests.putRequestHandler(Endpoints.edit(accountId), { accountId, ... settings }, context);
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
const getByName = (accountName, context) => {
|
|
28
|
+
const getByName = (accountName, context, headers = {}) => {
|
|
28
29
|
const params = { name: accountName };
|
|
29
|
-
return requests.getRequestHandler(Endpoints.getAccount, params, context);
|
|
30
|
+
return requests.getRequestHandler(Endpoints.getAccount, params, context, headers);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const getAccountList = (context, headers = {}) => {
|
|
34
|
+
return requests.getRequestHandler(Endpoints.getAccountList, {}, context, headers);
|
|
30
35
|
};
|
|
31
36
|
|
|
32
37
|
const getFromCertificateHolder = certificateHolder => {
|
|
@@ -41,6 +46,7 @@ const getSupportAutomationAllowed = accountIds => {
|
|
|
41
46
|
module.exports = {
|
|
42
47
|
create,
|
|
43
48
|
getByName,
|
|
49
|
+
getAccountList,
|
|
44
50
|
deleteAccount,
|
|
45
51
|
updateAccountSettings,
|
|
46
52
|
getFromCertificateHolder,
|
|
@@ -18,6 +18,10 @@ const createAccount = async (createAccountModel, context) => {
|
|
|
18
18
|
return accountsAPI.create(createAccountModel, context);
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
const getAccountsResponse = (accountName, context, headers = {}) => {
|
|
22
|
+
return accountsAPI.getByName(accountName, context, headers);
|
|
23
|
+
};
|
|
24
|
+
|
|
21
25
|
const getAccount = async (accountName, context) => {
|
|
22
26
|
const response = await accountsAPI.getByName(accountName, context);
|
|
23
27
|
expect(response.status, 'Failed to get accounts by name').to.equal(httpResponseStatusCodes.OK);
|
|
@@ -35,6 +39,10 @@ const getAccount = async (accountName, context) => {
|
|
|
35
39
|
return existingAccounts[0];
|
|
36
40
|
};
|
|
37
41
|
|
|
42
|
+
const getAccountList = context => {
|
|
43
|
+
return accountsAPI.getAccountList(context);
|
|
44
|
+
};
|
|
45
|
+
|
|
38
46
|
const getOrCreateAccount = async (createAccountModel, context) => {
|
|
39
47
|
validateType(createAccountModel, CreateAccountModel);
|
|
40
48
|
const existingAccount = await getAccount(createAccountModel.companyName, context);
|
|
@@ -94,6 +102,8 @@ const getSupportAutomationAllowed = async (accountIds, { expectError } = {}) =>
|
|
|
94
102
|
|
|
95
103
|
module.exports = {
|
|
96
104
|
getAccount,
|
|
105
|
+
getAccountsResponse,
|
|
106
|
+
getAccountList,
|
|
97
107
|
getOrCreateAccount,
|
|
98
108
|
getAccountId,
|
|
99
109
|
createAccount,
|
|
@@ -4,6 +4,19 @@ const { credentials } = getEnv();
|
|
|
4
4
|
const httpResponseStatusCodes = require('../../../../common/httpResponseStatusCodes.json');
|
|
5
5
|
|
|
6
6
|
const authApi = require('./auth.api');
|
|
7
|
+
const timeUtils = require('../../../../common/utils/timeUtils');
|
|
8
|
+
|
|
9
|
+
const RETRYABLE_LOGIN_STATUSES = [
|
|
10
|
+
httpResponseStatusCodes.BAD_GATEWAY,
|
|
11
|
+
httpResponseStatusCodes.SERVICE_UNAVAILABLE,
|
|
12
|
+
httpResponseStatusCodes.GATEWAY_TIMEOUT
|
|
13
|
+
];
|
|
14
|
+
const MAX_LOGIN_ATTEMPTS = 3;
|
|
15
|
+
const LOGIN_RETRY_STEP_TIMEOUT = 2000;
|
|
16
|
+
const LOGIN_RETRY_RANGE_TIMEOUT = 5000;
|
|
17
|
+
|
|
18
|
+
// Parallel callers for the same user share one login request
|
|
19
|
+
const pendingLogins = new Map();
|
|
7
20
|
|
|
8
21
|
const getAuthCookie = async (email, password) => {
|
|
9
22
|
const systemUser = getSystemUserIdentity(email);
|
|
@@ -12,9 +25,32 @@ const getAuthCookie = async (email, password) => {
|
|
|
12
25
|
return systemUser.cookie;
|
|
13
26
|
}
|
|
14
27
|
|
|
15
|
-
|
|
28
|
+
if (!pendingLogins.has(email)) {
|
|
29
|
+
const login = loginAndCacheCookie(email, password).finally(() => pendingLogins.delete(email));
|
|
30
|
+
pendingLogins.set(email, login);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return pendingLogins.get(email);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const loginWithRetry = async (email, password) => {
|
|
37
|
+
for (let attempt = 1; ; attempt++) {
|
|
16
38
|
const response = await authApi.login(email, password);
|
|
17
39
|
|
|
40
|
+
if (!RETRYABLE_LOGIN_STATUSES.includes(response.status) || attempt === MAX_LOGIN_ATTEMPTS) {
|
|
41
|
+
return response;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.warn(`Login for ${email} failed with status ${response.status}, retrying`);
|
|
45
|
+
const retryRandomTimeout = attempt * LOGIN_RETRY_STEP_TIMEOUT + Math.random() * LOGIN_RETRY_RANGE_TIMEOUT;
|
|
46
|
+
await timeUtils.waitFor(retryRandomTimeout).mSeconds;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const loginAndCacheCookie = async (email, password) => {
|
|
51
|
+
try {
|
|
52
|
+
const response = await loginWithRetry(email, password);
|
|
53
|
+
|
|
18
54
|
if (response.status !== httpResponseStatusCodes.OK) {
|
|
19
55
|
throw Error(`Error: ${JSON.stringify(response.data)}`);
|
|
20
56
|
}
|
|
@@ -97,6 +133,14 @@ const getAuthenticatedUser = context => {
|
|
|
97
133
|
return authApi.getAuthenticatedUser(context);
|
|
98
134
|
};
|
|
99
135
|
|
|
136
|
+
// The id the backend stamps into the session on login — the same value
|
|
137
|
+
// getLoggedInUserId() resolves from the JWT. Available only after the email
|
|
138
|
+
// has logged in (getAuthCookie caches it); returns null otherwise.
|
|
139
|
+
const getUserId = email => {
|
|
140
|
+
const identity = getSystemUserIdentity(email);
|
|
141
|
+
return identity ? identity.id : null;
|
|
142
|
+
};
|
|
143
|
+
|
|
100
144
|
const isHubUser = async (email, { expectError } = {}) => {
|
|
101
145
|
const response = await authApi.isHubUser(email);
|
|
102
146
|
|
|
@@ -164,6 +208,7 @@ module.exports = {
|
|
|
164
208
|
twoFAResendCode,
|
|
165
209
|
twoFAVerify,
|
|
166
210
|
getAuthenticatedUser,
|
|
211
|
+
getUserId,
|
|
167
212
|
isHubUser,
|
|
168
213
|
resetPassword,
|
|
169
214
|
getLoginAttempts,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const requests = require('../../../../common/requests/cookieAuth/requests');
|
|
2
|
+
const { backendUrl, threadSchedulerSecret } = require('../../../../env').getEnv();
|
|
3
|
+
|
|
4
|
+
const Endpoints = {
|
|
5
|
+
enrollments: `${backendUrl}/schedulerJob/certificial/enrollments`
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// Authenticated by x-scheduler-secret, not a cookie, so no ics context is passed.
|
|
9
|
+
const getEnrollments = recordIds => {
|
|
10
|
+
const headers = { 'x-scheduler-secret': threadSchedulerSecret };
|
|
11
|
+
|
|
12
|
+
return requests.getRequestHandler(Endpoints.enrollments, { recordIds: recordIds.toString() }, null, headers);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
module.exports = { getEnrollments };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const certificialApi = require('./certificial.api');
|
|
2
|
+
const httpResponseStatusCodes = require('../../../../common/httpResponseStatusCodes.json');
|
|
3
|
+
|
|
4
|
+
// One row per requested record id. A record with no enrollment comes back with null fields, not a 404.
|
|
5
|
+
const getEnrollmentStates = async recordIds => {
|
|
6
|
+
const response = await certificialApi.getEnrollments(recordIds);
|
|
7
|
+
|
|
8
|
+
if (response.status !== httpResponseStatusCodes.OK) {
|
|
9
|
+
throw new Error(`Failed to read Certificial enrollment state for records ${recordIds}, response status - ${response.status}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return response.data;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const getEnrollmentState = async recordId => {
|
|
16
|
+
const [enrollmentState] = await getEnrollmentStates([recordId]);
|
|
17
|
+
|
|
18
|
+
return enrollmentState;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
getEnrollmentStates,
|
|
23
|
+
getEnrollmentState
|
|
24
|
+
};
|
|
@@ -13,7 +13,10 @@ const Endpoints = {
|
|
|
13
13
|
updateOverrideExpiryDate: auditorContractorId => `${backendUrl}/saas/integration-tests/override/${auditorContractorId}/expiry-date`,
|
|
14
14
|
getOverrideGapRequestById: overrideGapRequestId => `${backendUrl}/saas/gaps/override-gap-request/${overrideGapRequestId}`,
|
|
15
15
|
createOverrideGapRequest: `${backendUrl}/secured/vendor-app/record/override-gap-request`,
|
|
16
|
-
rejectOverrideGapRequest: `${backendUrl}/saas/gaps/override-gap-request/reject
|
|
16
|
+
rejectOverrideGapRequest: `${backendUrl}/saas/gaps/override-gap-request/reject`,
|
|
17
|
+
createPmOverrideRequest: `${backendUrl}/saas/gaps/override-gap-request`,
|
|
18
|
+
pmOverrideRequestById: overrideGapRequestId => `${backendUrl}/saas/gaps/override-gap-request/${overrideGapRequestId}`,
|
|
19
|
+
eligibleOverrideAssignees: requestId => `${backendUrl}/saas/gaps/override-gap-request/assignees/${requestId}`
|
|
17
20
|
};
|
|
18
21
|
|
|
19
22
|
const waive = async (contractorId, waiveRequestModel, context) => {
|
|
@@ -70,6 +73,22 @@ const rejectOverrideGapRequest = (rejectModel, context) => {
|
|
|
70
73
|
return requests.patchRequestHandler(Endpoints.rejectOverrideGapRequest, context, rejectModel);
|
|
71
74
|
};
|
|
72
75
|
|
|
76
|
+
const createPmOverrideRequest = (pmOverrideRequestModel, context) => {
|
|
77
|
+
return requests.postRequestHandler(Endpoints.createPmOverrideRequest, pmOverrideRequestModel, context);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const editPmOverrideRequest = (overrideGapRequestId, pmOverrideRequestModel, context) => {
|
|
81
|
+
return requests.patchRequestHandler(Endpoints.pmOverrideRequestById(overrideGapRequestId), context, pmOverrideRequestModel);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const deletePmOverrideRequest = (overrideGapRequestId, context) => {
|
|
85
|
+
return requests.deleteRequestHandler(Endpoints.pmOverrideRequestById(overrideGapRequestId), context);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const getEligibleOverrideAssignees = (requestId, context) => {
|
|
89
|
+
return requests.getRequestHandler(Endpoints.eligibleOverrideAssignees(requestId), {}, context);
|
|
90
|
+
};
|
|
91
|
+
|
|
73
92
|
module.exports = {
|
|
74
93
|
waive,
|
|
75
94
|
retract,
|
|
@@ -82,5 +101,9 @@ module.exports = {
|
|
|
82
101
|
updateOverrideExpiryDate,
|
|
83
102
|
getOverrideGapRequestById,
|
|
84
103
|
createOverrideGapRequest,
|
|
85
|
-
rejectOverrideGapRequest
|
|
104
|
+
rejectOverrideGapRequest,
|
|
105
|
+
createPmOverrideRequest,
|
|
106
|
+
editPmOverrideRequest,
|
|
107
|
+
deletePmOverrideRequest,
|
|
108
|
+
getEligibleOverrideAssignees
|
|
86
109
|
};
|
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
WAIVER_REASONS,
|
|
11
11
|
EXPIRY_TYPES
|
|
12
12
|
} = require('../../models/waive/waive.model');
|
|
13
|
-
const { OverrideGapRequestCreateModel, OverrideGapRequestRejectModel } = require('../../models/overrideGapRequest/override.gap.request.model');
|
|
13
|
+
const { OverrideGapRequestCreateModel, OverrideGapRequestRejectModel, PmOverrideRequestModel } = require('../../models/overrideGapRequest/override.gap.request.model');
|
|
14
14
|
const { validateType } = require('../../../../../common/validations/typeValidations');
|
|
15
15
|
const auditUtil = require('../../utils/auditUtil');
|
|
16
16
|
const httpResponseStatusCodes = require('../../../../../common/httpResponseStatusCodes.json');
|
|
@@ -277,6 +277,93 @@ const updateOverrideExpiryDate = async (auditorContractorId, expiryDate, context
|
|
|
277
277
|
return response;
|
|
278
278
|
};
|
|
279
279
|
|
|
280
|
+
const createPmOverrideRequest = async (auditorContractorId, context, {
|
|
281
|
+
requestReason,
|
|
282
|
+
expiryType,
|
|
283
|
+
expiryDate = null,
|
|
284
|
+
assigneeUserIds,
|
|
285
|
+
recordDateModified,
|
|
286
|
+
expectError
|
|
287
|
+
} = {}) => {
|
|
288
|
+
const model = new PmOverrideRequestModel({
|
|
289
|
+
auditorContractorId,
|
|
290
|
+
requestReason,
|
|
291
|
+
expiryType,
|
|
292
|
+
expiryDate,
|
|
293
|
+
assigneeUserIds,
|
|
294
|
+
recordDateModified
|
|
295
|
+
});
|
|
296
|
+
validateType(model, PmOverrideRequestModel);
|
|
297
|
+
const response = await overrideGapApi.createPmOverrideRequest(model, context);
|
|
298
|
+
|
|
299
|
+
if (expectError) {
|
|
300
|
+
return response;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (response.status !== httpResponseStatusCodes.CREATED) {
|
|
304
|
+
throw Error(`Failed to create PM override request. Status code: ${response.status}. Body: ${JSON.stringify(response.data)}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return response.data;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const editPmOverrideRequest = async (overrideGapRequestId, context, {
|
|
311
|
+
requestReason,
|
|
312
|
+
expiryType,
|
|
313
|
+
expiryDate = null,
|
|
314
|
+
assigneeUserIds,
|
|
315
|
+
recordDateModified,
|
|
316
|
+
expectError
|
|
317
|
+
} = {}) => {
|
|
318
|
+
const model = new PmOverrideRequestModel({
|
|
319
|
+
requestReason,
|
|
320
|
+
expiryType,
|
|
321
|
+
expiryDate,
|
|
322
|
+
assigneeUserIds,
|
|
323
|
+
recordDateModified
|
|
324
|
+
});
|
|
325
|
+
validateType(model, PmOverrideRequestModel);
|
|
326
|
+
const response = await overrideGapApi.editPmOverrideRequest(overrideGapRequestId, model, context);
|
|
327
|
+
|
|
328
|
+
if (expectError) {
|
|
329
|
+
return response;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (response.status !== httpResponseStatusCodes.NO_CONTENT) {
|
|
333
|
+
throw Error(`Failed to edit PM override request. Status code: ${response.status}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return response;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const deletePmOverrideRequest = async (overrideGapRequestId, context, { expectError } = {}) => {
|
|
340
|
+
const response = await overrideGapApi.deletePmOverrideRequest(overrideGapRequestId, context);
|
|
341
|
+
|
|
342
|
+
if (expectError) {
|
|
343
|
+
return response;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (response.status !== httpResponseStatusCodes.NO_CONTENT) {
|
|
347
|
+
throw Error(`Failed to delete PM override request. Status code: ${response.status}`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return response;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const getEligibleOverrideAssignees = async (requestId, context, { expectError } = {}) => {
|
|
354
|
+
const response = await overrideGapApi.getEligibleOverrideAssignees(requestId, context);
|
|
355
|
+
|
|
356
|
+
if (expectError) {
|
|
357
|
+
return response;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (response.status !== httpResponseStatusCodes.OK) {
|
|
361
|
+
throw Error(`Failed to get eligible override assignees. Status code: ${response.status}`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return response.data;
|
|
365
|
+
};
|
|
366
|
+
|
|
280
367
|
module.exports = {
|
|
281
368
|
waive,
|
|
282
369
|
waiveAll,
|
|
@@ -296,5 +383,9 @@ module.exports = {
|
|
|
296
383
|
getOverrideGapRequestById,
|
|
297
384
|
rejectOverrideGapRequest,
|
|
298
385
|
getAuditorContractorIdFromGap,
|
|
299
|
-
updateOverrideExpiryDate
|
|
386
|
+
updateOverrideExpiryDate,
|
|
387
|
+
createPmOverrideRequest,
|
|
388
|
+
editPmOverrideRequest,
|
|
389
|
+
deletePmOverrideRequest,
|
|
390
|
+
getEligibleOverrideAssignees
|
|
300
391
|
};
|
|
@@ -4,4 +4,11 @@ const OVERRIDE_GAP_REQUEST_STATUS = {
|
|
|
4
4
|
REJECTED: 'REJECTED'
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const OVERRIDE_REQUESTER_TYPE = {
|
|
8
|
+
HUB_USER: 'HUB_USER',
|
|
9
|
+
INVITER: 'INVITER'
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const DEFAULT_PM_OVERRIDE_REASON = 'PM override request reason for integration tests exceeding minimum length of 35 characters.';
|
|
13
|
+
|
|
14
|
+
module.exports = { OVERRIDE_GAP_REQUEST_STATUS, OVERRIDE_REQUESTER_TYPE, DEFAULT_PM_OVERRIDE_REASON };
|
package/src/saas/internal-api/compliance/models/overrideGapRequest/override.gap.request.model.js
CHANGED
|
@@ -13,4 +13,19 @@ class OverrideGapRequestRejectModel {
|
|
|
13
13
|
this.recordDateModified = recordDateModified;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
class PmOverrideRequestModel {
|
|
18
|
+
constructor({ auditorContractorId, requestReason, expiryType, expiryDate = null, assigneeUserIds, recordDateModified }) {
|
|
19
|
+
if (auditorContractorId !== undefined) {
|
|
20
|
+
this.auditorContractorId = auditorContractorId;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.requestReason = requestReason;
|
|
24
|
+
this.expiryType = expiryType;
|
|
25
|
+
this.expiryDate = expiryDate;
|
|
26
|
+
this.assigneeUserIds = assigneeUserIds;
|
|
27
|
+
this.recordDateModified = recordDateModified;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { OverrideGapRequestCreateModel, OverrideGapRequestRejectModel, PmOverrideRequestModel };
|
|
@@ -167,9 +167,40 @@ const waitForMail = async (emailModel, { maxSecondToWait = 30 } = {}) => {
|
|
|
167
167
|
return foundMails;
|
|
168
168
|
};
|
|
169
169
|
|
|
170
|
+
const waitForMails = async (emailModels, { maxSecondToWait = 60 } = {}) => {
|
|
171
|
+
const oneSecondInMilliseconds = 1000;
|
|
172
|
+
const started = new Date();
|
|
173
|
+
let timeFromTheStart = 0;
|
|
174
|
+
const foundMailsByModel = emailModels.map(() => []);
|
|
175
|
+
|
|
176
|
+
while (foundMailsByModel.some(found => found.length === 0) && timeFromTheStart < maxSecondToWait) {
|
|
177
|
+
for (let index = 0; index < emailModels.length; index++) {
|
|
178
|
+
if (foundMailsByModel[index].length > 0) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const { data } = await getAllMessages(emailModels[index]);
|
|
183
|
+
|
|
184
|
+
if (data.length > 0) {
|
|
185
|
+
foundMailsByModel[index] = data;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (foundMailsByModel.every(found => found.length > 0)) {
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
timeFromTheStart = (new Date() - started) / oneSecondInMilliseconds;
|
|
194
|
+
await timeUtils.waitFor(3).seconds;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return foundMailsByModel;
|
|
198
|
+
};
|
|
199
|
+
|
|
170
200
|
module.exports = {
|
|
171
201
|
util,
|
|
172
202
|
waitForMail,
|
|
203
|
+
waitForMails,
|
|
173
204
|
getAllMessages,
|
|
174
205
|
getLastMessage,
|
|
175
206
|
deleteMultipleMessages,
|
|
@@ -54,6 +54,9 @@ const EMAIL_SUBJECT = {
|
|
|
54
54
|
overrideGapRequestPending: ({ vendorCompanyName }) => `${vendorCompanyName} asked for an exception`,
|
|
55
55
|
overrideGapRequestRejected: ({ accountName }) => `${accountName} denied your exception request`,
|
|
56
56
|
overrideGapRequestApproved: ({ accountName }) => `${accountName} approved your exception request`,
|
|
57
|
+
pmOverrideRequestAssigned: ({ requesterFullName, companyName }) => `${requesterFullName} asked you to override a gap for ${companyName}`,
|
|
58
|
+
pmOverrideRequestDenied: ({ rejecterFullName, companyName }) => `${rejecterFullName} Rejected your override request for ${companyName}`,
|
|
59
|
+
pmOverrideRequestApproved: ({ approverFullName, companyName }) => `${approverFullName} Approved your override request for ${companyName}`,
|
|
57
60
|
timedOverrideExpired: ({ recordCompanyName, requesterName }) => `Override ended - ${recordCompanyName} in ${requesterName}`,
|
|
58
61
|
allRecordsExport: 'Jones - All Records Export'
|
|
59
62
|
};
|
|
@@ -3,7 +3,10 @@ const NOTIFICATION_TYPES = {
|
|
|
3
3
|
OVERRIDE_GAP_REQUEST_PENDING: 'NewOverrideGapRequest',
|
|
4
4
|
RELATED_DOC_UPDATED: 'RelatedDocUpdated',
|
|
5
5
|
GLOBAL_RECORD_RELATED_DOC_UPDATED: 'GlobalRecordRelatedDocUpdated',
|
|
6
|
-
TIMED_OVERRIDE_EXPIRED: 'TimedOverrideExpired'
|
|
6
|
+
TIMED_OVERRIDE_EXPIRED: 'TimedOverrideExpired',
|
|
7
|
+
PM_OVERRIDE_REQUEST: 'NewPmOverrideRequest',
|
|
8
|
+
PM_OVERRIDE_REQUEST_APPROVED: 'PmOverrideRequestApproved',
|
|
9
|
+
PM_OVERRIDE_REQUEST_REJECTED: 'PmOverrideRequestRejected'
|
|
7
10
|
};
|
|
8
11
|
|
|
9
12
|
module.exports = { NOTIFICATION_TYPES };
|
|
@@ -5,8 +5,8 @@ const Endpoints = {
|
|
|
5
5
|
getRequestIdByRecordIdBase64: base64RecordId => `${backendUrl}/secured/outlook/record/${base64RecordId}/compliance-report`
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
const getRequestIdByRecordIdBase64 = async (base64RecordId, context) => {
|
|
9
|
-
return requests.getRequestHandler(Endpoints.getRequestIdByRecordIdBase64(base64RecordId), {}, context);
|
|
8
|
+
const getRequestIdByRecordIdBase64 = async (base64RecordId, context, headers = {}) => {
|
|
9
|
+
return requests.getRequestHandler(Endpoints.getRequestIdByRecordIdBase64(base64RecordId), {}, context, headers);
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
module.exports = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const vendorProfileApi = require('./outlook.api');
|
|
2
2
|
|
|
3
|
-
const getRequestIdByRecordIdBase64 = async (base64RecordId, context) => {
|
|
4
|
-
return vendorProfileApi.getRequestIdByRecordIdBase64(base64RecordId, context);
|
|
3
|
+
const getRequestIdByRecordIdBase64 = async (base64RecordId, context, headers = {}) => {
|
|
4
|
+
return vendorProfileApi.getRequestIdByRecordIdBase64(base64RecordId, context, headers);
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
const generateRecordIdBase64 = recordId => {
|
|
@@ -8,7 +8,9 @@ class UpdateProjectSettingsModel {
|
|
|
8
8
|
projectId = null,
|
|
9
9
|
tenantSolution = false,
|
|
10
10
|
autocompleteSettings = {},
|
|
11
|
-
overrideGapRequestSettings = {}
|
|
11
|
+
overrideGapRequestSettings = {},
|
|
12
|
+
userOverrideRequestSettings = {},
|
|
13
|
+
relevantComplianceDocumentsSettings = {}
|
|
12
14
|
} = {}
|
|
13
15
|
) {
|
|
14
16
|
this.autoGaps = { ... DEFAULT_AUTOGAPS, ... autoGaps };
|
|
@@ -19,6 +21,8 @@ class UpdateProjectSettingsModel {
|
|
|
19
21
|
this.tenantSolution = tenantSolution;
|
|
20
22
|
this.autocompleteSettings = { ... DEFAULT_AUTOCOMPLETE_ADD_VENDORS, ... autocompleteSettings };
|
|
21
23
|
this.overrideGapRequestSettings = { ... DEFAULT_OVERRIDE_GAP_REQUEST, ... overrideGapRequestSettings };
|
|
24
|
+
this.userOverrideRequestSettings = { ... DEFAULT_USER_OVERRIDE_REQUEST, ... userOverrideRequestSettings };
|
|
25
|
+
this.relevantComplianceDocumentsSettings = { ... DEFAULT_RELEVANT_COMPLIANCE_DOCUMENTS, ... relevantComplianceDocumentsSettings };
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
|
|
@@ -46,3 +50,15 @@ const DEFAULT_OVERRIDE_GAP_REQUEST = {
|
|
|
46
50
|
isOverrideGapRequestEnabledForTenants: false,
|
|
47
51
|
isOverrideGapRequestEnabledForGuests: false
|
|
48
52
|
};
|
|
53
|
+
|
|
54
|
+
const DEFAULT_USER_OVERRIDE_REQUEST = {
|
|
55
|
+
isUserOverrideRequestEnabledForVendors: false,
|
|
56
|
+
isUserOverrideRequestEnabledForTenants: false,
|
|
57
|
+
isUserOverrideRequestEnabledForGuests: false
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const DEFAULT_RELEVANT_COMPLIANCE_DOCUMENTS = {
|
|
61
|
+
isRelevantComplianceDocumentsEnabledForVendors: false,
|
|
62
|
+
isRelevantComplianceDocumentsEnabledForTenants: false,
|
|
63
|
+
isRelevantComplianceDocumentsEnabledForGuests: false
|
|
64
|
+
};
|
|
@@ -12,9 +12,11 @@ const leaseExtractionStatus = {
|
|
|
12
12
|
closedReqSet: 'CLOSED_REQ_SET'
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
const DEFAULT_LEASE_PATH = 'packages/test-infra/src/resources/lease/defaultLease.pdf';
|
|
16
|
+
|
|
15
17
|
const LEASE_TRIGGER_TYPES = {
|
|
16
18
|
record: 'RECORD',
|
|
17
19
|
tab: 'TAB'
|
|
18
20
|
};
|
|
19
21
|
|
|
20
|
-
module.exports = { REQUIREMENT_SHARING_LEVELS, leaseExtractionStatus, LEASE_TRIGGER_TYPES };
|
|
22
|
+
module.exports = { REQUIREMENT_SHARING_LEVELS, leaseExtractionStatus, LEASE_TRIGGER_TYPES, DEFAULT_LEASE_PATH };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const requests = require('../../../../common/requests/cookieAuth/requests');
|
|
2
|
+
const { backendUrl } = require('../../../../env').getEnv();
|
|
3
|
+
|
|
4
|
+
const Endpoints = {
|
|
5
|
+
base: `${backendUrl}/saas/integration-tests/scheduler-job-run`,
|
|
6
|
+
byId: id => `${backendUrl}/saas/integration-tests/scheduler-job-run/${id}`,
|
|
7
|
+
reclaim: `${backendUrl}/saas/integration-tests/scheduler-job-run/reclaim`
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const seedJobRun = (body, context) => {
|
|
11
|
+
return requests.postRequestHandler(Endpoints.base, body, context);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const reclaimStaleJobRuns = (jobType, context) => {
|
|
15
|
+
return requests.postRequestHandler(Endpoints.reclaim, null, context, {}, { jobType });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const getJobRun = (id, context) => {
|
|
19
|
+
return requests.getRequestHandler(Endpoints.byId(id), {}, context);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const deleteJobRunsByType = (jobType, context) => {
|
|
23
|
+
return requests.deleteRequestHandler(Endpoints.base, context, {}, { jobType });
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
seedJobRun,
|
|
28
|
+
reclaimStaleJobRuns,
|
|
29
|
+
getJobRun,
|
|
30
|
+
deleteJobRunsByType
|
|
31
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const httpResponseStatusCodes = require('../../../../common/httpResponseStatusCodes.json');
|
|
2
|
+
|
|
3
|
+
const authService = require('../../auth/api/auth.service');
|
|
4
|
+
const schedulerJobRunApi = require('./scheduler.job.run.api');
|
|
5
|
+
|
|
6
|
+
const SUCCESS_STATUSES = [httpResponseStatusCodes.OK, httpResponseStatusCodes.NO_CONTENT];
|
|
7
|
+
|
|
8
|
+
// Seeds a job_run with backdated timestamps (applied with the DB clock) to simulate an orphaned run.
|
|
9
|
+
// heartbeatMinutesAgo: null seeds a NULL heartbeat. Returns the id.
|
|
10
|
+
const seedJobRun = async ({ jobType, status, startedMinutesAgo, heartbeatMinutesAgo = null }) => {
|
|
11
|
+
const response = await schedulerJobRunApi.seedJobRun(
|
|
12
|
+
{ jobType, status, startedMinutesAgo, heartbeatMinutesAgo }, await authService.getIcsCookie());
|
|
13
|
+
|
|
14
|
+
if (response.status !== httpResponseStatusCodes.OK) {
|
|
15
|
+
throw new Error(`seedJobRun failed for ${jobType}: ${JSON.stringify(response.data)}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return response.data;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Runs reclaim in isolation (no overlap check, no job submitted) so a test can assert it without starting a real job.
|
|
22
|
+
const reclaimStaleJobRuns = async jobType => {
|
|
23
|
+
const response = await schedulerJobRunApi.reclaimStaleJobRuns(jobType, await authService.getIcsCookie());
|
|
24
|
+
|
|
25
|
+
if (response.status !== httpResponseStatusCodes.OK) {
|
|
26
|
+
throw new Error(`reclaimStaleJobRuns failed for ${jobType}: ${JSON.stringify(response.data)}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return response.data;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getJobRun = async id => {
|
|
33
|
+
const response = await schedulerJobRunApi.getJobRun(id, await authService.getIcsCookie());
|
|
34
|
+
|
|
35
|
+
if (response.status !== httpResponseStatusCodes.OK) {
|
|
36
|
+
throw new Error(`getJobRun failed for id ${id}: ${JSON.stringify(response.data)}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return response.data;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Clears all job_run rows of a type between runs. Safe outside production, where only seeded rows exist.
|
|
43
|
+
const deleteJobRunsByType = async jobType => {
|
|
44
|
+
const response = await schedulerJobRunApi.deleteJobRunsByType(jobType, await authService.getIcsCookie());
|
|
45
|
+
|
|
46
|
+
if (!SUCCESS_STATUSES.includes(response.status)) {
|
|
47
|
+
throw new Error(`deleteJobRunsByType failed for ${jobType}: ${JSON.stringify(response.data)}`);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
seedJobRun,
|
|
53
|
+
reclaimStaleJobRuns,
|
|
54
|
+
getJobRun,
|
|
55
|
+
deleteJobRunsByType
|
|
56
|
+
};
|
|
@@ -58,11 +58,28 @@ module.exports = {
|
|
|
58
58
|
runJobForExpireTimedOverrides: function(entityIds) {
|
|
59
59
|
return runSchedulerJob({ jobType: schedulerJobs.EXPIRE_TIMED_OVERRIDES, entityIds });
|
|
60
60
|
},
|
|
61
|
+
runJobForCertificialEnrollment: function(entityIds) {
|
|
62
|
+
requireScope(entityIds, schedulerJobs.CERTIFICIAL_ENROLLMENT);
|
|
63
|
+
return runSchedulerJob({ jobType: schedulerJobs.CERTIFICIAL_ENROLLMENT, entityIds });
|
|
64
|
+
},
|
|
65
|
+
runJobForCertificialMonitoring: function(entityIds) {
|
|
66
|
+
requireScope(entityIds, schedulerJobs.CERTIFICIAL_MONITORING);
|
|
67
|
+
return runSchedulerJob({ jobType: schedulerJobs.CERTIFICIAL_MONITORING, entityIds });
|
|
68
|
+
},
|
|
61
69
|
runAllJobsNew: function(entityIds) {
|
|
62
70
|
return runSchedulerJob({ jobType: schedulerJobs.ALL, entityIds });
|
|
63
71
|
}
|
|
64
72
|
};
|
|
65
73
|
|
|
74
|
+
/* An unscoped enrollment run emails a real broker for every eligible record in the environment and
|
|
75
|
+
cannot be undone; an unscoped monitoring run polls and notifies on every active enrollment. A
|
|
76
|
+
since: token still counts as a scope. */
|
|
77
|
+
function requireScope(entityIds, jobType) {
|
|
78
|
+
if (!entityIds || !entityIds.length) {
|
|
79
|
+
throw new Error(`${jobType} must be scoped with entityIds`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
66
83
|
function isOverloadError(errorCode) {
|
|
67
84
|
const overloadResponseErrors = [httpResponseStatusCodes.TOO_MANY_REQUESTS, httpResponseStatusCodes.CONFLICT];
|
|
68
85
|
return overloadResponseErrors.includes(errorCode);
|
|
@@ -128,14 +128,28 @@ const createPolicyFiles = async (requestId, policyFileList, context, { shouldExp
|
|
|
128
128
|
return createPolicyFilesResponse;
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
-
const
|
|
131
|
+
const uploadPolicyModels = (policyTypeList, policyFilePath, requestId, context) => {
|
|
132
|
+
return Promise.all(policyTypeList.map(policyType => uploadPolicyFileAndGetPolicyModel(requestId, policyFilePath, policyType, context)));
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const uploadAndCreatePolicyFile = async (policyTypeList, policyFilePath, requestId, context, { markAsScanned, maxRetries = 3 } = {}) => {
|
|
132
136
|
validateArray(policyTypeList);
|
|
133
137
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
138
|
+
const policyModels = await uploadPolicyModels(policyTypeList, policyFilePath, requestId, context);
|
|
139
|
+
const policyFilesResponse = await createPolicyFiles(requestId, policyModels, context);
|
|
140
|
+
|
|
141
|
+
for (let retry = 1; retry <= maxRetries; retry++) {
|
|
142
|
+
const missingPolicyTypes = policyTypeList.filter(policyType => !policyFilesResponse.data[policyType]);
|
|
137
143
|
|
|
138
|
-
|
|
144
|
+
if (!missingPolicyTypes.length) {
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.warn(`Policy types ${missingPolicyTypes} were not created for request ${requestId}, retry`);
|
|
149
|
+
const missingPolicyModels = await uploadPolicyModels(missingPolicyTypes, policyFilePath, requestId, context);
|
|
150
|
+
const retryResponse = await createPolicyFiles(requestId, missingPolicyModels, context);
|
|
151
|
+
Object.assign(policyFilesResponse.data, retryResponse.data);
|
|
152
|
+
}
|
|
139
153
|
|
|
140
154
|
if (markAsScanned) {
|
|
141
155
|
const { data: request } = await requestsApi.getRequestById(requestId, context);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
class NotificationSettingsModel {
|
|
2
|
-
constructor({ aboutToExpire = true, auditCompliant = true, auditNotCompliant = true, missingCoi = true, recordOverrideRequest = true, timedOverrideExpired = true } = {}) {
|
|
2
|
+
constructor({ aboutToExpire = true, auditCompliant = true, auditNotCompliant = true, missingCoi = true, recordOverrideRequest = true, timedOverrideExpired = true, weeklySummary = true } = {}) {
|
|
3
3
|
this.aboutToExpire = aboutToExpire;
|
|
4
4
|
this.auditCompliant = auditCompliant;
|
|
5
5
|
this.auditNotCompliant = auditNotCompliant;
|
|
6
6
|
this.missingCoi = missingCoi;
|
|
7
7
|
this.recordOverrideRequest = recordOverrideRequest;
|
|
8
8
|
this.timedOverrideExpired = timedOverrideExpired;
|
|
9
|
+
this.weeklySummary = weeklySummary;
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -13,7 +13,8 @@ const attributesConvertor = {
|
|
|
13
13
|
'COI is compliant.': 'auditCompliant',
|
|
14
14
|
'Unresponsive contact.': 'missingCoi',
|
|
15
15
|
'Record Override Request': 'recordOverrideRequest',
|
|
16
|
-
'Timed override expired — record is not compliant.': 'timedOverrideExpired'
|
|
16
|
+
'Timed override expired — record is not compliant.': 'timedOverrideExpired',
|
|
17
|
+
'Weekly summary': 'weeklySummary'
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
const getUserEncryptedData = async (email, role, context) => {
|