@forge/events
Advanced tools
Comparing version 0.2.0 to 0.3.0-next.0
# @forge/events | ||
## 0.3.0-next.0 | ||
### Minor Changes | ||
- 767d40d: Explicitly add uuid dependency and whitelist WHP getStats endpoint | ||
## 0.2.0 | ||
@@ -4,0 +10,0 @@ |
@@ -61,2 +61,27 @@ "use strict"; | ||
}); | ||
it('should throw InternalServerError when WHP returns 422 response', async () => { | ||
const apiClientMock = utils_1.getApiClientMock({ | ||
errors: ['jobId must not be null', 'queueName must not be null'] | ||
}, 422); | ||
const jobProgress = getJobProgress(apiClientMock, 'test-queue-name#test-job-id'); | ||
await expect(jobProgress.getStats()).rejects.toThrow(new errors_1.InternalServerError(`422 Status Text: jobId must not be null, queueName must not be null`)); | ||
utils_1.verifyApiClientCalledWith(apiClientMock, '/webhook/queue/stats/{cloudId}/{environmentId}/{appId}/{appVersion}', { | ||
queueName: 'test-queue-name', | ||
jobId: 'test-job-id' | ||
}); | ||
}); | ||
it('should throw errors when queueName or jobId is empty', async () => { | ||
const apiClientMock = utils_1.getApiClientMock({ | ||
success: 100, | ||
inProgress: 50, | ||
failed: 1 | ||
}, 200); | ||
const jobProgress1 = getJobProgress(apiClientMock, 'test-queue-name#'); | ||
await expect(jobProgress1.getStats()).rejects.toThrow(new errors_1.JobDoesNotExistError(`jobId must not be empty`)); | ||
const jobProgress2 = getJobProgress(apiClientMock, '#test-job-id'); | ||
await expect(jobProgress2.getStats()).rejects.toThrow(new errors_1.InvalidQueueNameError('Queue names can only contain alphanumeric characters and the dash and underscore characters.')); | ||
const jobProgress3 = getJobProgress(apiClientMock, ''); | ||
await expect(jobProgress3.getStats()).rejects.toThrow(new errors_1.JobDoesNotExistError(`jobId must not be empty`)); | ||
expect(apiClientMock).toHaveBeenCalledTimes(0); | ||
}); | ||
}); |
@@ -156,2 +156,9 @@ "use strict"; | ||
}); | ||
it('should throw InternalServerError for error response without response body', async () => { | ||
const apiClientMock = utils_1.getApiClientMockWithoutResponseBody(504, 'Gateway Timeout'); | ||
const queue = getQueue(apiClientMock, 'name'); | ||
const payload = [1]; | ||
await expect(queue.push(1)).rejects.toThrow(new errors_1.InternalServerError(`504 Gateway Timeout`, 504)); | ||
utils_1.verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name'); | ||
}); | ||
}); | ||
@@ -158,0 +165,0 @@ describe('getJob', () => { |
/// <reference types="jest" /> | ||
import { Payload } from '../types'; | ||
export declare const getApiClientMock: (response?: any, statusCode?: number) => jest.Mock<any, any>; | ||
export declare const getApiClientMockWithoutResponseBody: (statusCode: number, statusText: string) => jest.Mock<any, any>; | ||
export declare const verifyApiClientCalledWith: (apiClientMock: jest.Mock, path: string, expectedBody: any) => void; | ||
export declare const verifyApiClientCalledPushPathWith: (apiClientMock: jest.Mock, payloads: Payload | Payload[], name: string) => void; | ||
//# sourceMappingURL=utils.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.verifyApiClientCalledPushPathWith = exports.verifyApiClientCalledWith = exports.getApiClientMock = void 0; | ||
exports.verifyApiClientCalledPushPathWith = exports.verifyApiClientCalledWith = exports.getApiClientMockWithoutResponseBody = exports.getApiClientMock = void 0; | ||
exports.getApiClientMock = (response, statusCode = 201) => { | ||
@@ -12,2 +12,9 @@ return jest.fn().mockReturnValue({ | ||
}; | ||
exports.getApiClientMockWithoutResponseBody = (statusCode, statusText) => { | ||
return jest.fn().mockReturnValue({ | ||
ok: statusCode === 200, | ||
status: statusCode, | ||
statusText: statusText | ||
}); | ||
}; | ||
exports.verifyApiClientCalledWith = (apiClientMock, path, expectedBody) => { | ||
@@ -14,0 +21,0 @@ expect(apiClientMock).toHaveBeenCalledWith(path, expect.objectContaining({ |
export { Queue } from './queue'; | ||
export { InvalidQueueNameError, TooManyEventsError, PayloadTooBigError, NoEventsToPushError, RateLimitError, PartialSuccessError, InternalServerError, JobDoesNotExistError } from './errors'; | ||
export { InvalidQueueNameError, TooManyEventsError, PayloadTooBigError, NoEventsToPushError, RateLimitError, PartialSuccessError, InternalServerError, JobDoesNotExistError, InvalidPushSettingsError } from './errors'; | ||
export { JobProgress } from './jobProgress'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -14,3 +14,4 @@ "use strict"; | ||
Object.defineProperty(exports, "JobDoesNotExistError", { enumerable: true, get: function () { return errors_1.JobDoesNotExistError; } }); | ||
Object.defineProperty(exports, "InvalidPushSettingsError", { enumerable: true, get: function () { return errors_1.InvalidPushSettingsError; } }); | ||
var jobProgress_1 = require("./jobProgress"); | ||
Object.defineProperty(exports, "JobProgress", { enumerable: true, get: function () { return jobProgress_1.JobProgress; } }); |
@@ -18,2 +18,3 @@ "use strict"; | ||
}; | ||
validators_1.validateGetStatsPayload(getStatsRequest); | ||
const response = await queries_1.post(queries_1.GET_STATS_PATH, getStatsRequest, this.apiClient); | ||
@@ -20,0 +21,0 @@ await validators_1.validateGetStatsAPIResponse(response, getStatsRequest); |
@@ -16,3 +16,3 @@ "use strict"; | ||
async push(payloads, pushSettings) { | ||
validators_1.validatePayloads(payloads); | ||
validators_1.validatePushPayloads(payloads); | ||
const queueName = this.queueParams.key; | ||
@@ -19,0 +19,0 @@ const jobId = v4_1.default(); |
import { APIResponse, GetStatsRequest, Payload, PushRequest, PushSettings } from './types'; | ||
export declare const validateQueueKey: (queueName: string) => void; | ||
export declare const validatePushSettings: (settings: PushSettings) => void; | ||
export declare const validatePayloads: (payloads: Payload | Payload[]) => void; | ||
export declare const validatePushPayloads: (payloads: Payload | Payload[]) => void; | ||
export declare const validateGetStatsPayload: (getStatsRequest: GetStatsRequest) => void; | ||
export declare const validateAPIResponse: (response: APIResponse, expectedSuccessStatus: number) => Promise<void>; | ||
@@ -6,0 +7,0 @@ export declare const validatePushAPIResponse: (response: APIResponse, requestBody: PushRequest) => Promise<void>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.validateGetStatsAPIResponse = exports.validatePushAPIResponse = exports.validateAPIResponse = exports.validatePayloads = exports.validatePushSettings = exports.validateQueueKey = void 0; | ||
exports.validateGetStatsAPIResponse = exports.validatePushAPIResponse = exports.validateAPIResponse = exports.validateGetStatsPayload = exports.validatePushPayloads = exports.validatePushSettings = exports.validateQueueKey = void 0; | ||
const errors_1 = require("./errors"); | ||
@@ -18,3 +18,3 @@ const VALID_QUEUE_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/; | ||
}; | ||
exports.validatePayloads = (payloads) => { | ||
exports.validatePushPayloads = (payloads) => { | ||
if (!payloads || (Array.isArray(payloads) && payloads.length === 0)) { | ||
@@ -31,2 +31,8 @@ throw new errors_1.NoEventsToPushError(`No events pushed`); | ||
}; | ||
exports.validateGetStatsPayload = (getStatsRequest) => { | ||
if (!getStatsRequest.jobId) { | ||
throw new errors_1.JobDoesNotExistError(`jobId must not be empty`); | ||
} | ||
exports.validateQueueKey(getStatsRequest.queueName); | ||
}; | ||
exports.validateAPIResponse = async (response, expectedSuccessStatus) => { | ||
@@ -37,5 +43,13 @@ if (response.status === 429) { | ||
if (response.status != expectedSuccessStatus && response.status) { | ||
const responseBody = await response.json(); | ||
const errorMessage = responseBody.message ? `: ${responseBody.message}` : ''; | ||
throw new errors_1.InternalServerError(`${response.status} ${response.statusText}${errorMessage}`, responseBody.code, responseBody.details); | ||
let internalServerError; | ||
try { | ||
const responseBody = await response.json(); | ||
const errorMessage = responseBody.message ? `: ${responseBody.message}` : ''; | ||
const errors = responseBody.errors ? `: ${responseBody.errors.join(', ')}` : ''; | ||
internalServerError = new errors_1.InternalServerError(`${response.status} ${response.statusText}${errorMessage}${errors}`, responseBody.code, responseBody.details); | ||
} | ||
catch (ignore) { | ||
internalServerError = new errors_1.InternalServerError(`${response.status} ${response.statusText}`, response.status); | ||
} | ||
throw internalServerError; | ||
} | ||
@@ -42,0 +56,0 @@ }; |
{ | ||
"name": "@forge/events", | ||
"version": "0.2.0", | ||
"version": "0.3.0-next.0", | ||
"description": "Forge Async Event methods", | ||
@@ -15,4 +15,8 @@ "author": "Atlassian", | ||
"devDependencies": { | ||
"@types/uuid": "^3.4.7", | ||
"@types/node": "^12.12.63" | ||
}, | ||
"dependencies": { | ||
"uuid": "^3.4.0" | ||
} | ||
} |
import { getApiClientMock, verifyApiClientCalledWith } from './utils'; | ||
import { InternalServerError, JobDoesNotExistError, RateLimitError } from '../errors'; | ||
import { InternalServerError, InvalidQueueNameError, JobDoesNotExistError, RateLimitError } from '../errors'; | ||
import { JobProgress } from '../jobProgress'; | ||
@@ -77,2 +77,45 @@ | ||
}); | ||
it('should throw InternalServerError when WHP returns 422 response', async () => { | ||
const apiClientMock = getApiClientMock( | ||
{ | ||
errors: ['jobId must not be null', 'queueName must not be null'] | ||
}, | ||
422 | ||
); | ||
const jobProgress = getJobProgress(apiClientMock, 'test-queue-name#test-job-id'); | ||
await expect(jobProgress.getStats()).rejects.toThrow( | ||
new InternalServerError(`422 Status Text: jobId must not be null, queueName must not be null`) | ||
); | ||
verifyApiClientCalledWith(apiClientMock, '/webhook/queue/stats/{cloudId}/{environmentId}/{appId}/{appVersion}', { | ||
queueName: 'test-queue-name', | ||
jobId: 'test-job-id' | ||
}); | ||
}); | ||
it('should throw errors when queueName or jobId is empty', async () => { | ||
const apiClientMock = getApiClientMock( | ||
{ | ||
success: 100, | ||
inProgress: 50, | ||
failed: 1 | ||
}, | ||
200 | ||
); | ||
const jobProgress1 = getJobProgress(apiClientMock, 'test-queue-name#'); | ||
await expect(jobProgress1.getStats()).rejects.toThrow(new JobDoesNotExistError(`jobId must not be empty`)); | ||
const jobProgress2 = getJobProgress(apiClientMock, '#test-job-id'); | ||
await expect(jobProgress2.getStats()).rejects.toThrow( | ||
new InvalidQueueNameError( | ||
'Queue names can only contain alphanumeric characters and the dash and underscore characters.' | ||
) | ||
); | ||
const jobProgress3 = getJobProgress(apiClientMock, ''); | ||
await expect(jobProgress3.getStats()).rejects.toThrow(new JobDoesNotExistError(`jobId must not be empty`)); | ||
expect(apiClientMock).toHaveBeenCalledTimes(0); | ||
}); | ||
}); |
@@ -11,3 +11,3 @@ import { | ||
} from '../errors'; | ||
import { getApiClientMock, verifyApiClientCalledPushPathWith } from './utils'; | ||
import { getApiClientMock, getApiClientMockWithoutResponseBody, verifyApiClientCalledPushPathWith } from './utils'; | ||
import { Queue } from '../queue'; | ||
@@ -210,2 +210,10 @@ import { JobProgress } from '../jobProgress'; | ||
}); | ||
it('should throw InternalServerError for error response without response body', async () => { | ||
const apiClientMock = getApiClientMockWithoutResponseBody(504, 'Gateway Timeout'); | ||
const queue = getQueue(apiClientMock, 'name'); | ||
const payload = [1]; | ||
await expect(queue.push(1)).rejects.toThrow(new InternalServerError(`504 Gateway Timeout`, 504)); | ||
verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name'); | ||
}); | ||
}); | ||
@@ -212,0 +220,0 @@ |
@@ -12,2 +12,10 @@ import { Payload } from '../types'; | ||
export const getApiClientMockWithoutResponseBody = (statusCode: number, statusText: string) => { | ||
return jest.fn().mockReturnValue({ | ||
ok: statusCode === 200, | ||
status: statusCode, | ||
statusText: statusText | ||
}); | ||
}; | ||
export const verifyApiClientCalledWith = (apiClientMock: jest.Mock, path: string, expectedBody: any) => { | ||
@@ -14,0 +22,0 @@ expect(apiClientMock).toHaveBeenCalledWith( |
@@ -10,4 +10,5 @@ export { Queue } from './queue'; | ||
InternalServerError, | ||
JobDoesNotExistError | ||
JobDoesNotExistError, | ||
InvalidPushSettingsError | ||
} from './errors'; | ||
export { JobProgress } from './jobProgress'; |
import { GET_STATS_PATH, post } from './queries'; | ||
import { validateGetStatsAPIResponse } from './validators'; | ||
import { validateGetStatsAPIResponse, validateGetStatsPayload } from './validators'; | ||
import { APIResponse, FetchMethod, GetStatsRequest } from './types'; | ||
@@ -22,2 +22,4 @@ | ||
validateGetStatsPayload(getStatsRequest); | ||
const response = await post(GET_STATS_PATH, getStatsRequest, this.apiClient); | ||
@@ -24,0 +26,0 @@ await validateGetStatsAPIResponse(response, getStatsRequest); |
import { PUSH_PATH, post } from './queries'; | ||
import { validatePushAPIResponse, validatePayloads, validateQueueKey, validatePushSettings } from './validators'; | ||
import { validatePushAPIResponse, validatePushPayloads, validateQueueKey, validatePushSettings } from './validators'; | ||
import { FetchMethod, Payload, QueueParams, PushSettings, PushRequest } from './types'; | ||
@@ -18,3 +18,3 @@ import uuid from 'uuid/v4'; | ||
async push(payloads: Payload | Payload[], pushSettings?: PushSettings): Promise<string> { | ||
validatePayloads(payloads); | ||
validatePushPayloads(payloads); | ||
const queueName = this.queueParams.key; | ||
@@ -21,0 +21,0 @@ const jobId = uuid(); |
@@ -32,3 +32,3 @@ import { | ||
export const validatePayloads = (payloads: Payload | Payload[]) => { | ||
export const validatePushPayloads = (payloads: Payload | Payload[]) => { | ||
if (!payloads || (Array.isArray(payloads) && payloads.length === 0)) { | ||
@@ -48,2 +48,9 @@ throw new NoEventsToPushError(`No events pushed`); | ||
export const validateGetStatsPayload = (getStatsRequest: GetStatsRequest) => { | ||
if (!getStatsRequest.jobId) { | ||
throw new JobDoesNotExistError(`jobId must not be empty`); | ||
} | ||
validateQueueKey(getStatsRequest.queueName); | ||
}; | ||
export const validateAPIResponse = async (response: APIResponse, expectedSuccessStatus: number) => { | ||
@@ -56,9 +63,19 @@ if (response.status === 429) { | ||
//Catch all errors from server that we have not handled | ||
const responseBody = await response.json(); | ||
const errorMessage = responseBody.message ? `: ${responseBody.message}` : ''; | ||
throw new InternalServerError( | ||
`${response.status} ${response.statusText}${errorMessage}`, | ||
responseBody.code, | ||
responseBody.details | ||
); | ||
let internalServerError; | ||
try { | ||
const responseBody = await response.json(); | ||
const errorMessage = responseBody.message ? `: ${responseBody.message}` : ''; | ||
const errors = responseBody.errors ? `: ${responseBody.errors.join(', ')}` : ''; //Dropwizard returns an array of errors when request body validation failed | ||
internalServerError = new InternalServerError( | ||
`${response.status} ${response.statusText}${errorMessage}${errors}`, | ||
responseBody.code, | ||
responseBody.details | ||
); | ||
} catch (ignore) { | ||
//response body is not a json | ||
internalServerError = new InternalServerError(`${response.status} ${response.statusText}`, response.status); | ||
} | ||
throw internalServerError; | ||
} | ||
@@ -65,0 +82,0 @@ }; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
114218
1284
1
2
+ Addeduuid@^3.4.0
+ Addeduuid@3.4.0(transitive)