From 5d395cfabee232d82c1d6716c91f96ad31937bb0 Mon Sep 17 00:00:00 2001 From: rayangler <27821750+rayangler@users.noreply.github.com> Date: Fri, 14 Jul 2023 10:37:12 -0400 Subject: [PATCH] DOP-3769: Create webhook to handle post-build operations from Gatsby Cloud builds (#852) --- .github/workflows/deploy-stg-ecs.yml | 1 - api/config/custom-environment-variables.json | 1 + api/config/default.json | 1 + api/controllers/v1/jobs.ts | 149 +++++++++++++++++- .../config/custom-environment-variables.json | 1 + cdk-infra/static/api/config/default.json | 1 + cdk-infra/utils/ssm.ts | 2 + serverless.yml | 12 ++ src/repositories/jobRepository.ts | 14 +- tests/unit/job/api/jobs.test.ts | 121 +++++++++++++- tests/unit/repositories/jobRepository.test.ts | 43 ++--- 11 files changed, 313 insertions(+), 33 deletions(-) diff --git a/.github/workflows/deploy-stg-ecs.yml b/.github/workflows/deploy-stg-ecs.yml index 67f8d15cd..55c5630b3 100644 --- a/.github/workflows/deploy-stg-ecs.yml +++ b/.github/workflows/deploy-stg-ecs.yml @@ -3,7 +3,6 @@ on: branches: - "master" - "integration" - - "close-lambda-mdb-clients" concurrency: group: environment-stg-${{ github.ref }} cancel-in-progress: true diff --git a/api/config/custom-environment-variables.json b/api/config/custom-environment-variables.json index d4cb2d615..d9c72614d 100644 --- a/api/config/custom-environment-variables.json +++ b/api/config/custom-environment-variables.json @@ -13,6 +13,7 @@ "slackSecret": "SLACK_SECRET", "slackAuthToken": "SLACK_TOKEN", "slackViewOpenUrl": "https://slack.com/api/views.open", + "snootySecret": "SNOOTY_SECRET", "jobQueueCollection": "JOB_QUEUE_COL_NAME", "entitlementCollection": "USER_ENTITLEMENT_COL_NAME", "dashboardUrl": "DASHBOARD_URL", diff --git a/api/config/default.json b/api/config/default.json index cf9b3c8d8..010cf6fe7 100644 --- a/api/config/default.json +++ b/api/config/default.json @@ -13,6 +13,7 @@ "slackSecret": "SLACK_SECRET", "slackAuthToken": "SLACK_TOKEN", "slackViewOpenUrl": "https://slack.com/api/views.open", + "snootySecret": "SNOOTY_SECRET", "jobQueueCollection": "JOB_QUEUE_COL_NAME", "entitlementCollection": "USER_ENTITLEMENT_COL_NAME", "repoBranchesCollection": "REPO_BRANCHES_COL_NAME", diff --git a/api/controllers/v1/jobs.ts b/api/controllers/v1/jobs.ts index 982475a8a..b677c98fa 100644 --- a/api/controllers/v1/jobs.ts +++ b/api/controllers/v1/jobs.ts @@ -1,6 +1,8 @@ import * as c from 'config'; +import crypto from 'crypto'; import * as mongodb from 'mongodb'; import { IConfig } from 'config'; +import { APIGatewayEvent, APIGatewayProxyResult } from 'aws-lambda'; import { RepoEntitlementsRepository } from '../../../src/repositories/repoEntitlementsRepository'; import { BranchRepository } from '../../../src/repositories/branchRepository'; import { ConsoleLogger } from '../../../src/services/logger'; @@ -13,6 +15,19 @@ import { ECSContainer } from '../../../src/services/containerServices'; import { SQSConnector } from '../../../src/services/queue'; import { Batch } from '../../../src/services/batch'; +// Although data in payload should always be present, it's not guaranteed from +// external callers +interface SnootyPayload { + jobId?: string; +} + +// These options should only be defined if the build summary is being called after +// a Gatsby Cloud job +interface BuildSummaryOptions { + mongoClient?: mongodb.MongoClient; + previewUrl?: string; +} + export const TriggerLocalBuild = async (event: any = {}, context: any = {}): Promise => { const client = new mongodb.MongoClient(c.get('dbUrl')); await client.connect(); @@ -160,9 +175,11 @@ async function retry(message: JobQueueMessage, consoleLogger: ConsoleLogger, url consoleLogger.error(message['jobId'], err); } } -async function NotifyBuildSummary(jobId: string): Promise { + +async function NotifyBuildSummary(jobId: string, options: BuildSummaryOptions = {}): Promise { + const { mongoClient, previewUrl } = options; const consoleLogger = new ConsoleLogger(); - const client = new mongodb.MongoClient(c.get('dbUrl')); + const client: mongodb.MongoClient = mongoClient ?? new mongodb.MongoClient(c.get('dbUrl')); await client.connect(); const db = client.db(c.get('dbName')); const env = c.get('env'); @@ -187,6 +204,11 @@ async function NotifyBuildSummary(jobId: string): Promise { const prCommentId = await githubCommenter.getPullRequestCommentId(fullDocument.payload, pr); const fullJobDashboardUrl = c.get('dashboardUrl') + jobId; + // We currently avoid posting the Gatsby Cloud preview url on GitHub to avoid + // potentially conflicting behavior with the S3 staging link with parallel + // frontend builds. This is in case the GC build finishing first causes the + // initial comment to be made with a nullish S3 url, while subsequent comment + // updates only append the list of build logs. if (prCommentId !== undefined) { const ghMessage = prepGithubComment(fullDocument, fullJobDashboardUrl, true); await githubCommenter.updateComment(fullDocument.payload, prCommentId, ghMessage); @@ -213,7 +235,8 @@ async function NotifyBuildSummary(jobId: string): Promise { repoName, c.get('dashboardUrl'), jobId, - fullDocument.status == 'failed' + fullDocument.status == 'failed', + previewUrl ), entitlement['slack_user_id'] ); @@ -247,22 +270,26 @@ async function prepSummaryMessage( repoName: string, jobUrl: string, jobId: string, - failed = false + failed = false, + previewUrl?: string ): Promise { const urls = extractUrlFromMessage(fullDocument); - let mms_urls = [null, null]; + let mms_urls: Array = [null, null]; // mms-docs needs special handling as it builds two sites (cloudmanager & ops manager) // so we need to extract both URLs if (repoName === 'mms-docs') { if (urls.length >= 2) { - // TODO: Type 'string[]' is not assignable to type 'null[]'. mms_urls = urls.slice(-2); } } + let url = ''; - if (urls.length > 0) { + if (previewUrl) { + url = previewUrl; + } else if (urls.length > 0) { url = urls[urls.length - 1]; } + let msg = ''; if (failed) { msg = `Your Job <${jobUrl}${jobId}|Failed>! Please check the build log for any errors.\n- Repo: *${repoName}*\n- Branch: *${fullDocument.payload.branchName}*\n- urlSlug: *${fullDocument.payload.urlSlug}*\n- Env: *${env}*\n Check logs for more errors!!\nSorry :disappointed:! `; @@ -385,3 +412,111 @@ async function SubmitArchiveJob(jobId: string) { consoleLogger.info('submit archive job', JSON.stringify({ jobId: jobId, batchJobId: response.jobId })); await client.close(); } + +/** + * Checks the signature payload as a rough validation that the request was made by + * the Snooty frontend. + * @param payload - stringified JSON payload + * @param signature - the Snooty signature included in the header + */ +function validateSnootyPayload(payload: string, signature: string) { + const secret = c.get('snootySecret'); + const expectedSignature = crypto.createHmac('sha256', secret).update(payload).digest('hex'); + return signature === expectedSignature; +} + +/** + * Performs post-build operations such as notifications and db updates for job ID + * provided in its payload. This is typically expected to only be called by + * Snooty's Gatsby Cloud source plugin. + * @param event + * @returns + */ +export async function SnootyBuildComplete(event: APIGatewayEvent): Promise { + const consoleLogger = new ConsoleLogger(); + const defaultHeaders = { 'Content-Type': 'text/plain' }; + + if (!event.body) { + const err = 'SnootyBuildComplete does not have a body in event payload'; + consoleLogger.error('SnootyBuildCompleteError', err); + return { + statusCode: 400, + headers: defaultHeaders, + body: err, + }; + } + + // Keep lowercase in case header is automatically converted to lowercase + // The Snooty frontend should be mindful of using a lowercase header + const snootySignature = event.headers['x-snooty-signature']; + if (!snootySignature) { + const err = 'SnootyBuildComplete does not have a signature in event payload'; + consoleLogger.error('SnootyBuildCompleteError', err); + return { + statusCode: 400, + headers: defaultHeaders, + body: err, + }; + } + + if (!validateSnootyPayload(event.body, snootySignature)) { + const errMsg = 'Payload signature is incorrect'; + consoleLogger.error('SnootyBuildCompleteError', errMsg); + return { + statusCode: 401, + headers: defaultHeaders, + body: errMsg, + }; + } + + let payload: SnootyPayload | undefined; + try { + payload = JSON.parse(event.body) as SnootyPayload; + } catch (e) { + const errMsg = 'Payload is not valid JSON'; + return { + statusCode: 400, + headers: defaultHeaders, + body: errMsg, + }; + } + + const { jobId } = payload; + if (!jobId) { + const errMsg = 'Payload missing job ID'; + consoleLogger.error('SnootyBuildCompleteError', errMsg); + return { + statusCode: 400, + headers: defaultHeaders, + body: errMsg, + }; + } + + const client = new mongodb.MongoClient(c.get('dbUrl')); + + try { + await client.connect(); + const db = client.db(c.get('dbName')); + const jobRepository = new JobRepository(db, c, consoleLogger); + await jobRepository.updateWithCompletionStatus(jobId, null, false); + // Placeholder preview URL until we iron out the Gatsby Cloud site URLs. + // This would probably involve fetching the URLs in the db on a per project basis + const previewUrl = 'https://www.mongodb.com/docs/'; + await NotifyBuildSummary(jobId, { mongoClient: client, previewUrl }); + } catch (e) { + consoleLogger.error('SnootyBuildCompleteError', e); + return { + statusCode: 500, + headers: defaultHeaders, + body: e, + }; + } finally { + await client.close(); + } + + return { + statusCode: 200, + headers: defaultHeaders, + body: `Snooty build ${jobId} completed`, + }; +} diff --git a/cdk-infra/static/api/config/custom-environment-variables.json b/cdk-infra/static/api/config/custom-environment-variables.json index 100337204..003d2f7c0 100644 --- a/cdk-infra/static/api/config/custom-environment-variables.json +++ b/cdk-infra/static/api/config/custom-environment-variables.json @@ -12,6 +12,7 @@ "slackSecret": "SLACK_SECRET", "slackAuthToken": "SLACK_TOKEN", "slackViewOpenUrl": "https://slack.com/api/views.open", + "snootySecret": "SNOOTY_SECRET", "jobQueueCollection": "JOB_QUEUE_COL_NAME", "entitlementCollection": "USER_ENTITLEMENT_COL_NAME", "dashboardUrl": "DASHBOARD_URL", diff --git a/cdk-infra/static/api/config/default.json b/cdk-infra/static/api/config/default.json index 7cdac4dd8..7000b4370 100644 --- a/cdk-infra/static/api/config/default.json +++ b/cdk-infra/static/api/config/default.json @@ -12,6 +12,7 @@ "slackSecret": "SLACK_SECRET", "slackAuthToken": "SLACK_TOKEN", "slackViewOpenUrl": "https://slack.com/api/views.open", + "snootySecret": "SNOOTY_SECRET", "jobQueueCollection": "JOB_QUEUE_COL_NAME", "entitlementCollection": "USER_ENTITLEMENT_COL_NAME", "repoBranchesCollection": "REPO_BRANCHES_COL_NAME", diff --git a/cdk-infra/utils/ssm.ts b/cdk-infra/utils/ssm.ts index 42b3c0310..27b4c1c3c 100644 --- a/cdk-infra/utils/ssm.ts +++ b/cdk-infra/utils/ssm.ts @@ -110,6 +110,7 @@ const webhookSecureStrings = [ '/cdn/client/secret', '/slack/webhook/secret', '/slack/auth/token', + '/snooty/webhook/secret', ] as const; type WebhookSecureString = typeof webhookSecureStrings[number]; @@ -125,6 +126,7 @@ webhookParamPathToEnvName.set('/cdn/client/id', 'CDN_CLIENT_ID'); webhookParamPathToEnvName.set('/cdn/client/secret', 'CDN_CLIENT_SECRET'); webhookParamPathToEnvName.set('/slack/auth/token', 'SLACK_TOKEN'); webhookParamPathToEnvName.set('/slack/webhook/secret', 'SLACK_SECRET'); +webhookParamPathToEnvName.set('/snooty/webhook/secret', 'SNOOTY_SECRET'); export async function getWebhookSecureStrings(ssmPrefix: string): Promise> { return getSecureStrings(ssmPrefix, webhookSecureStrings, webhookParamPathToEnvName, 'webhookParamPathToEnvName'); diff --git a/serverless.yml b/serverless.yml index b9d4f403c..cb53159be 100644 --- a/serverless.yml +++ b/serverless.yml @@ -65,6 +65,7 @@ custom: githubBotPW: ${ssm:/env/${self:provider.stage}/docs/worker_pool/github/bot/password} slackSecret: ${ssm:/env/${self:provider.stage}/docs/worker_pool/slack/webhook/secret} slackAuthToken: ${ssm:/env/${self:provider.stage}/docs/worker_pool/slack/auth/token} + snootySecret: ${ssm:/env/${self:provider.stage}/docs/worker_pool/snooty/webhook/secret} JobsQueueName: autobuilder-jobs-queue-${self:provider.stage} JobsDLQueueName: autobuilder-jobs-dlqueue-${self:provider.stage} JobUpdatesQueueName: autobuilder-job-updates-queue-${self:provider.stage} @@ -112,6 +113,7 @@ webhook-env-core: &webhook-env-core REPO_BRANCHES_COL_NAME: ${self:custom.repoBranchesCollection} SLACK_SECRET: ${self:custom.slackSecret} SLACK_TOKEN: ${self:custom.slackAuthToken} + SNOOTY_SECRET: ${self:custom.snootySecret} DASHBOARD_URL: ${self:custom.dashboardUrl.${self:provider.stage}} NODE_CONFIG_DIR: './api/config' TASK_DEFINITION_FAMILY: docs-worker-pool-${self:provider.stage} @@ -261,6 +263,16 @@ functions: environment: <<: *webhook-env-core + v1SnootyBuildComplete: + handler: api/controllers/v1/jobs.SnootyBuildComplete + events: + - http: + path: /webhook/snooty/trigger/complete + method: POST + cors: true + environment: + <<: *webhook-env-core + Outputs: JobsQueueURL: Description: Jobs Queue Url diff --git a/src/repositories/jobRepository.ts b/src/repositories/jobRepository.ts index b14d6e6c6..f9a155f3b 100644 --- a/src/repositories/jobRepository.ts +++ b/src/repositories/jobRepository.ts @@ -21,8 +21,14 @@ export class JobRepository extends BaseRepository { this._queueConnector = new SQSConnector(logger, config); } - async updateWithCompletionStatus(id: string, result: any): Promise { - const query = { _id: id }; + async updateWithCompletionStatus( + id: string | mongodb.ObjectId, + result: any, + shouldNotifySqs = true + ): Promise { + // Safely convert to object ID + const objectId = new mongodb.ObjectId(id); + const query = { _id: objectId }; const update = { $set: { status: 'completed', @@ -35,8 +41,8 @@ export class JobRepository extends BaseRepository { update, `Mongo Timeout Error: Timed out while updating success status for jobId: ${id}` ); - if (bRet) { - await this.notify(id, c.get('jobUpdatesQueueUrl'), JobStatus.completed, 0); + if (bRet && shouldNotifySqs) { + await this.notify(objectId.toString(), c.get('jobUpdatesQueueUrl'), JobStatus.completed, 0); } return bRet; } diff --git a/tests/unit/job/api/jobs.test.ts b/tests/unit/job/api/jobs.test.ts index 6a9ba07a2..f9cb57315 100644 --- a/tests/unit/job/api/jobs.test.ts +++ b/tests/unit/job/api/jobs.test.ts @@ -1,8 +1,80 @@ -import { extractUrlFromMessage } from '../../../../api/controllers/v1/jobs'; -import { Job } from '../../../../src/entities/job'; +import crypto from 'crypto'; +import * as mongodb from 'mongodb'; +import { APIGatewayProxyEventHeaders } from 'aws-lambda'; +import { SnootyBuildComplete, extractUrlFromMessage } from '../../../../api/controllers/v1/jobs'; import validURLFormatDoc from '../../../data/fullDoc'; import invalidURLFormatDoc from '../../../data/fullDoc'; +jest.mock('config', () => ({ + get: () => 'SNOOTY_SECRET', +})); + +jest.mock('../../../../src/repositories/jobRepository', () => ({ + JobRepository: jest.fn().mockImplementation(() => ({ + updateWithCompletionStatus: jest.fn(), + getJobById: jest.fn(), + })), +})); + +/** + * Mocks the creation of a signature as if it was from Snooty + * @param payloadString + * @param secret + */ +const createSnootySignature = (payloadString: string, secret: string) => { + return crypto.createHmac('sha256', secret).update(payloadString).digest('hex'); +}; + +/** + * Mocks the API Gateway Event object for API calls + * @param payloadString - the payload for the event.body + * @param headers - the headers to be included as part of the event + * @returns a barebones API Gateway Event object + */ +const createMockAPIGatewayEvent = (payloadString: string, headers: APIGatewayProxyEventHeaders = {}) => ({ + body: payloadString, + headers, + multiValueHeaders: {}, + httpMethod: 'POST', + isBase64Encoded: false, + path: '/foo', + pathParameters: {}, + queryStringParameters: {}, + multiValueQueryStringParameters: {}, + stageVariables: {}, + resource: '', + requestContext: { + accountId: '', + apiId: '', + authorizer: {}, + protocol: 'HTTP/1.1', + httpMethod: 'POST', + identity: { + accessKey: '', + accountId: '', + apiKey: '', + apiKeyId: '', + caller: '', + clientCert: null, + cognitoAuthenticationProvider: '', + cognitoAuthenticationType: '', + cognitoIdentityId: '', + cognitoIdentityPoolId: '', + principalOrgId: '', + sourceIp: '', + user: '', + userAgent: '', + userArn: '', + }, + path: '/foo', + stage: '', + requestId: '', + requestTimeEpoch: 0, + resourceId: '', + resourcePath: '', + }, +}); + describe('JobValidator Tests', () => { test('valid urls doc returns list of valid urls', () => { const job = Object.assign({}, validURLFormatDoc); @@ -15,3 +87,48 @@ describe('JobValidator Tests', () => { expect(urls[urls.length - 1]).toEqual('https://docs.mongodb.com/drivers/java/sync/upcoming'); }); }); + +describe('Post-build webhook tests', () => { + const payload = { + jobId: '64ad959b423952aeb9341fae', + }; + const payloadString = JSON.stringify(payload); + + beforeAll(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(mongodb, 'MongoClient').mockReturnValue({ + connect: jest.fn(), + db: jest.fn(), + close: jest.fn(), + }); + }); + + test('successfully completes job and performs notification', async () => { + const signature = createSnootySignature(payloadString, 'SNOOTY_SECRET'); + const res = await SnootyBuildComplete( + createMockAPIGatewayEvent(payloadString, { 'x-snooty-signature': signature }) + ); + expect(res.statusCode).toBe(200); + }); + + test('invalid signatures gracefully error', async () => { + const signature = createSnootySignature(payloadString, 'NOT_SNOOTY_SECRET'); + let res = await SnootyBuildComplete(createMockAPIGatewayEvent(payloadString, { 'x-snooty-signature': signature })); + expect(res.statusCode).toBe(401); + + // Missing signature + res = await SnootyBuildComplete(createMockAPIGatewayEvent(payloadString)); + expect(res.statusCode).toBe(400); + }); + + test('invalid payloads gracefully error', async () => { + const invalidPayload = { jobId: undefined }; + const payloadString = JSON.stringify(invalidPayload); + const signature = createSnootySignature(payloadString, 'SNOOTY_SECRET'); + const res = await SnootyBuildComplete( + createMockAPIGatewayEvent(payloadString, { 'x-snooty-signature': signature }) + ); + expect(res.statusCode).toBe(400); + }); +}); diff --git a/tests/unit/repositories/jobRepository.test.ts b/tests/unit/repositories/jobRepository.test.ts index 3bd59bddd..fbfd1cb45 100644 --- a/tests/unit/repositories/jobRepository.test.ts +++ b/tests/unit/repositories/jobRepository.test.ts @@ -19,25 +19,25 @@ describe('Job Repository Tests', () => { describe('Job Repository updateWithCompletionStatus Tests', () => { test('Update with completion status throws DB Error as result is undefined', async () => { const testData = TestDataProvider.getStatusUpdateQueryAndUpdateObject( - 'Test_Job', + '64ad959b423952aeb9341fad', 'completed', 'All good', new Date() ); - await expect(jobRepo.updateWithCompletionStatus('Test_Job', 'All good')).rejects.toThrow( + await expect(jobRepo.updateWithCompletionStatus('64ad959b423952aeb9341fad', 'All good')).rejects.toThrow( `Failed to update job (${JSON.stringify(testData.query)}) for ${JSON.stringify(testData.update)}` ); }); test('Update with completion status throws DB Error as result length < 1', async () => { const testData = TestDataProvider.getStatusUpdateQueryAndUpdateObject( - 'Test_Job', + '64ad959b423952aeb9341fad', 'completed', 'All good', new Date() ); dbRepoHelper.collection.updateOne.mockReturnValue({ modifiedCount: -1 }); - await expect(jobRepo.updateWithCompletionStatus('Test_Job', 'All good')).rejects.toThrow( + await expect(jobRepo.updateWithCompletionStatus('64ad959b423952aeb9341fad', 'All good')).rejects.toThrow( `Failed to update job (${JSON.stringify(testData.query)}) for ${JSON.stringify(testData.update)}` ); expect(dbRepoHelper.collection.updateOne).toBeCalledTimes(1); @@ -45,13 +45,13 @@ describe('Job Repository Tests', () => { test('Update with completion status fails as there is no modifiedCount in results', async () => { const testData = TestDataProvider.getStatusUpdateQueryAndUpdateObject( - 'Test_Job', + '64ad959b423952aeb9341fad', 'completed', 'All good', new Date() ); dbRepoHelper.collection.updateOne.mockReturnValueOnce({ result: { sn: -1 } }); - await expect(jobRepo.updateWithCompletionStatus('Test_Job', 'All good')).rejects.toThrow( + await expect(jobRepo.updateWithCompletionStatus('64ad959b423952aeb9341fad', 'All good')).rejects.toThrow( `Failed to update job (${JSON.stringify(testData.query)}) for ${JSON.stringify(testData.update)}` ); expect(dbRepoHelper.collection.updateOne).toBeCalledTimes(1); @@ -60,7 +60,7 @@ describe('Job Repository Tests', () => { test('Update with completion status succeeds', async () => { setupForUpdateOneSuccess(); - await expect(jobRepo.updateWithCompletionStatus('Test_Job', 'All good')).resolves.toEqual(true); + await expect(jobRepo.updateWithCompletionStatus('64ad959b423952aeb9341fad', 'All good')).resolves.toEqual(true); expect(dbRepoHelper.collection.updateOne).toBeCalledTimes(1); expect(dbRepoHelper.logger.error).toBeCalledTimes(0); }); @@ -73,10 +73,10 @@ describe('Job Repository Tests', () => { }); }); try { - jobRepo.updateWithCompletionStatus('Test_Job', 'All good').catch((error) => { + jobRepo.updateWithCompletionStatus('64ad959b423952aeb9341fad', 'All good').catch((error) => { expect(dbRepoHelper.logger.error).toBeCalledTimes(1); expect(error.message).toContain( - `Mongo Timeout Error: Timed out while updating success status for jobId: Test_Job` + `Mongo Timeout Error: Timed out while updating success status for jobId: 64ad959b423952aeb9341fad` ); }); jest.runAllTimers(); @@ -87,7 +87,7 @@ describe('Job Repository Tests', () => { describe('Job Repository updateWithFailureStatus Tests', () => { test('updateWithFailureStatus succeeds', async () => { const testData = TestDataProvider.getStatusUpdateQueryAndUpdateObject( - 'Test_Job', + '64ad959b423952aeb9341fad', 'failed', 'All good', new Date(), @@ -95,7 +95,7 @@ describe('Job Repository Tests', () => { 'wierd reason' ); setupForUpdateOneSuccess(); - await expect(jobRepo.updateWithErrorStatus('Test_Job', 'wierd reason')).resolves.toEqual(true); + await expect(jobRepo.updateWithErrorStatus('64ad959b423952aeb9341fad', 'wierd reason')).resolves.toEqual(true); expect(dbRepoHelper.collection.updateOne).toBeCalledTimes(1); expect(dbRepoHelper.collection.updateOne).toBeCalledWith(testData.query, testData.update); @@ -143,27 +143,29 @@ describe('Job Repository Tests', () => { describe('insertLogStatement Tests', () => { test('insertLogStatement succeeds', async () => { - const testData = TestDataProvider.getInsertLogStatementInfo('Test_Job', ['msg1', 'msg2']); + const testData = TestDataProvider.getInsertLogStatementInfo('64ad959b423952aeb9341fad', ['msg1', 'msg2']); setupForUpdateOneSuccess(); - await expect(jobRepo.insertLogStatement('Test_Job', ['msg1', 'msg2'])).resolves.toEqual(true); + await expect(jobRepo.insertLogStatement('64ad959b423952aeb9341fad', ['msg1', 'msg2'])).resolves.toEqual(true); validateSuccessfulUpdate(testData); }); }); describe('insertNotificationMessages Tests', () => { test('insertNotificationMessages succeeds', async () => { - const testData = TestDataProvider.getInsertComMessageInfo('Test_Job', 'Successfully tested'); + const testData = TestDataProvider.getInsertComMessageInfo('64ad959b423952aeb9341fad', 'Successfully tested'); setupForUpdateOneSuccess(); - await expect(jobRepo.insertNotificationMessages('Test_Job', 'Successfully tested')).resolves.toEqual(true); + await expect( + jobRepo.insertNotificationMessages('64ad959b423952aeb9341fad', 'Successfully tested') + ).resolves.toEqual(true); validateSuccessfulUpdate(testData); }); }); describe('insertPurgedUrls Tests', () => { test('insertPurgedUrls succeeds', async () => { - const testData = TestDataProvider.getInsertPurgedUrls('Test_Job', ['url1', 'url2']); + const testData = TestDataProvider.getInsertPurgedUrls('64ad959b423952aeb9341fad', ['url1', 'url2']); setupForUpdateOneSuccess(); - await expect(jobRepo.insertPurgedUrls('Test_Job', ['url1', 'url2'])).resolves.toEqual(true); + await expect(jobRepo.insertPurgedUrls('64ad959b423952aeb9341fad', ['url1', 'url2'])).resolves.toEqual(true); validateSuccessfulUpdate(testData); }); }); @@ -171,10 +173,13 @@ describe('Job Repository Tests', () => { // TODO: Fix failing test describe('resetJobStatus Tests', () => { test('resetJobStatus succeeds', async () => { - const testData = TestDataProvider.getJobResetInfo('Test_Job', 'reset job status for testing reasons '); + const testData = TestDataProvider.getJobResetInfo( + '64ad959b423952aeb9341fad', + 'reset job status for testing reasons ' + ); setupForUpdateOneSuccess(); await expect( - jobRepo.resetJobStatus('Test_Job', 'inQueue', 'reset job status for testing reasons ') + jobRepo.resetJobStatus('64ad959b423952aeb9341fad', 'inQueue', 'reset job status for testing reasons ') ).resolves.toEqual(true); validateSuccessfulUpdate(testData); });