@forge/events 2.0.0 → 2.0.1-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/package.json +3 -3
  3. package/out/__test__/appEvents.test.d.ts +0 -2
  4. package/out/__test__/appEvents.test.d.ts.map +0 -1
  5. package/out/__test__/appEvents.test.js +0 -137
  6. package/out/__test__/invocationError.test.d.ts +0 -2
  7. package/out/__test__/invocationError.test.d.ts.map +0 -1
  8. package/out/__test__/invocationError.test.js +0 -36
  9. package/out/__test__/jobProgress.test.d.ts +0 -2
  10. package/out/__test__/jobProgress.test.d.ts.map +0 -1
  11. package/out/__test__/jobProgress.test.js +0 -118
  12. package/out/__test__/queue.test.d.ts +0 -2
  13. package/out/__test__/queue.test.d.ts.map +0 -1
  14. package/out/__test__/queue.test.js +0 -237
  15. package/out/__test__/queueResponse.test.d.ts +0 -2
  16. package/out/__test__/queueResponse.test.d.ts.map +0 -1
  17. package/out/__test__/queueResponse.test.js +0 -14
  18. package/out/__test__/utils.d.ts +0 -8
  19. package/out/__test__/utils.d.ts.map +0 -1
  20. package/out/__test__/utils.js +0 -41
  21. package/src/__test__/appEvents.test.ts +0 -201
  22. package/src/__test__/invocationError.test.ts +0 -40
  23. package/src/__test__/jobProgress.test.ts +0 -184
  24. package/src/__test__/queue.test.ts +0 -306
  25. package/src/__test__/queueResponse.test.ts +0 -14
  26. package/src/__test__/utils.ts +0 -47
  27. package/src/appEvents.ts +0 -107
  28. package/src/errors.ts +0 -46
  29. package/src/index.ts +0 -28
  30. package/src/invocationError.ts +0 -22
  31. package/src/invocationErrorCode.ts +0 -8
  32. package/src/jobProgress.ts +0 -51
  33. package/src/queries.ts +0 -17
  34. package/src/queue.ts +0 -39
  35. package/src/queueResponse.ts +0 -13
  36. package/src/retryOptions.ts +0 -14
  37. package/src/text.ts +0 -17
  38. package/src/types.ts +0 -62
  39. package/src/validators.ts +0 -160
  40. package/tsconfig.json +0 -14
  41. package/tsconfig.tsbuildinfo +0 -1
@@ -1,41 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.verifyApiClientCalledPushPathWith = exports.verifyApiClientCalledWith = exports.getApiClientMockWithoutResponseBody = exports.getMockFetchMethod = void 0;
4
- const getMockFetchMethod = (response, statusCode = 201) => {
5
- return jest.fn().mockReturnValue({
6
- created: statusCode === 201,
7
- status: statusCode,
8
- statusText: 'Status Text',
9
- json: jest.fn().mockResolvedValue(response)
10
- });
11
- };
12
- exports.getMockFetchMethod = getMockFetchMethod;
13
- const getApiClientMockWithoutResponseBody = (statusCode, statusText) => {
14
- return jest.fn().mockReturnValue({
15
- ok: statusCode === 200,
16
- status: statusCode,
17
- statusText: statusText
18
- });
19
- };
20
- exports.getApiClientMockWithoutResponseBody = getApiClientMockWithoutResponseBody;
21
- const verifyApiClientCalledWith = (apiClientMock, path, expectedBody) => {
22
- expect(apiClientMock).toHaveBeenCalledWith(path, expect.objectContaining({
23
- method: 'POST',
24
- body: expect.any(String),
25
- headers: {
26
- 'content-type': 'application/json'
27
- }
28
- }));
29
- const [, { body }] = apiClientMock.mock.calls[0];
30
- expect(JSON.parse(body)).toEqual(expect.objectContaining(expectedBody));
31
- };
32
- exports.verifyApiClientCalledWith = verifyApiClientCalledWith;
33
- const verifyApiClientCalledPushPathWith = (apiClientMock, payloads, name) => {
34
- (0, exports.verifyApiClientCalledWith)(apiClientMock, '/webhook/queue/publish/{contextAri}/{environmentId}/{appId}/{appVersion}', {
35
- queueName: name,
36
- schema: 'ari:cloud:ecosystem::forge/app-event-2',
37
- type: 'avi:forge:app:event',
38
- payload: payloads
39
- });
40
- };
41
- exports.verifyApiClientCalledPushPathWith = verifyApiClientCalledPushPathWith;
@@ -1,201 +0,0 @@
1
- import { appEvents, AppEvent } from '../appEvents';
2
- import { post } from '../queries';
3
- import { __requestAtlassianAsApp } from '@forge/api';
4
-
5
- // Mock the dependencies
6
- jest.mock('../queries', () => ({
7
- post: jest.fn()
8
- }));
9
-
10
- jest.mock('@forge/api', () => ({
11
- __requestAtlassianAsApp: jest.fn()
12
- }));
13
-
14
- describe('appEvents', () => {
15
- beforeEach(() => {
16
- // Clear all mocks before each test
17
- jest.clearAllMocks();
18
- });
19
-
20
- describe('publishEvent', () => {
21
- it('should call post with the correct parameters', async () => {
22
- // Arrange
23
- const event: AppEvent = { key: 'test-event' };
24
- const mockResponse = {
25
- ok: true,
26
- json: jest.fn().mockResolvedValue({
27
- failedEvents: []
28
- })
29
- };
30
-
31
- // Mock the post function to return a successful response
32
- (post as jest.Mock).mockResolvedValue(mockResponse);
33
-
34
- // Act
35
- const result = await appEvents.publish(event);
36
-
37
- // Assert
38
- expect(post).toHaveBeenCalledTimes(1);
39
- expect(post).toHaveBeenCalledWith(
40
- '/forge/events/v1/app-events',
41
- {
42
- events: [
43
- {
44
- key: event.key
45
- }
46
- ]
47
- },
48
- __requestAtlassianAsApp
49
- );
50
- expect(result).toEqual({ type: 'success', failedEvents: [] });
51
- });
52
-
53
- it('should return a success result for successful requests', async () => {
54
- // Arrange
55
- const event: AppEvent = { key: 'test-event' };
56
- const mockResponse = {
57
- ok: true,
58
- json: jest.fn().mockResolvedValue({
59
- failedEvents: []
60
- })
61
- };
62
-
63
- // Mock the post function to return a successful response
64
- (post as jest.Mock).mockResolvedValue(mockResponse);
65
-
66
- // Act
67
- const result = await appEvents.publish(event);
68
-
69
- // Assert
70
- expect(result).toEqual({ type: 'success', failedEvents: [] });
71
- });
72
-
73
- it('should return an error result for failed requests', async () => {
74
- // Arrange
75
- const event: AppEvent = { key: 'test-event' };
76
- const mockResponse = {
77
- ok: false,
78
- status: 500,
79
- json: jest.fn().mockResolvedValue({
80
- errorMessages: ['Internal server error']
81
- })
82
- };
83
-
84
- // Mock the post function to return a failed response
85
- (post as jest.Mock).mockResolvedValue(mockResponse);
86
-
87
- // Act
88
- const result = await appEvents.publish(event);
89
-
90
- // Assert
91
- expect(result).toEqual({
92
- type: 'error',
93
- errorType: 'SERVICE_ERROR',
94
- errorMessage: 'Internal server error'
95
- });
96
- });
97
-
98
- it('should handle validation errors', async () => {
99
- // Arrange
100
- const event: AppEvent = { key: 'test-event' };
101
- const mockResponse = {
102
- ok: false,
103
- status: 400,
104
- json: jest.fn().mockResolvedValue({
105
- errorMessages: ['Invalid event type']
106
- })
107
- };
108
-
109
- // Mock the post function to return a validation error response
110
- (post as jest.Mock).mockResolvedValue(mockResponse);
111
-
112
- // Act
113
- const result = await appEvents.publish(event);
114
-
115
- // Assert
116
- expect(result).toEqual({
117
- type: 'error',
118
- errorType: 'VALIDATION_ERROR',
119
- errorMessage: 'Invalid event type'
120
- });
121
- });
122
-
123
- it('should handle errors object in the error response', async () => {
124
- // Arrange
125
- const event: AppEvent = { key: 'test-event' };
126
- const errorResponse = {
127
- errorMessages: [],
128
- errors: { key: 'Event key is required' }
129
- };
130
-
131
- const mockResponse = {
132
- ok: false,
133
- status: 400,
134
- json: jest.fn().mockResolvedValue(errorResponse)
135
- };
136
-
137
- // Mock the post function to return an error response with errors object
138
- (post as jest.Mock).mockResolvedValue(mockResponse);
139
-
140
- // Act
141
- const result = await appEvents.publish(event);
142
-
143
- // Assert
144
- expect(result).toEqual({
145
- type: 'error',
146
- errorType: 'VALIDATION_ERROR',
147
- errorMessage: 'key: Event key is required'
148
- });
149
- });
150
-
151
- it('should use OTHER as errorType for unknown status codes', async () => {
152
- // Arrange
153
- const event: AppEvent = { key: 'test-event' };
154
- const mockResponse = {
155
- ok: false,
156
- status: 418, // I'm a teapot
157
- json: jest.fn().mockResolvedValue({
158
- errorMessages: ['I refuse to brew coffee']
159
- })
160
- };
161
-
162
- // Mock the post function to return a response with unknown status code
163
- (post as jest.Mock).mockResolvedValue(mockResponse);
164
-
165
- // Act
166
- const result = await appEvents.publish(event);
167
-
168
- // Assert
169
- expect(result).toEqual({
170
- type: 'error',
171
- errorType: 'OTHER',
172
- errorMessage: 'I refuse to brew coffee'
173
- });
174
- });
175
-
176
- it('should handle empty error messages', async () => {
177
- // Arrange
178
- const event: AppEvent = { key: 'test-event' };
179
- const mockResponse = {
180
- ok: false,
181
- status: 500,
182
- json: jest.fn().mockResolvedValue({
183
- errorMessages: []
184
- })
185
- };
186
-
187
- // Mock the post function to return a response with empty error messages
188
- (post as jest.Mock).mockResolvedValue(mockResponse);
189
-
190
- // Act
191
- const result = await appEvents.publish(event);
192
-
193
- // Assert
194
- expect(result).toEqual({
195
- type: 'error',
196
- errorType: 'SERVICE_ERROR',
197
- errorMessage: '{"errorMessages":[]}'
198
- });
199
- });
200
- });
201
- });
@@ -1,40 +0,0 @@
1
- import { InvocationError } from '../invocationError';
2
- import { InvocationErrorCode } from '../invocationErrorCode';
3
- import { DEFAULT_RETRY_OPTIONS, MIN_RETRY_AFTER, RetryOptions } from '../retryOptions';
4
-
5
- describe('InvocationError tests', () => {
6
- let target = new InvocationError();
7
-
8
- it('Populate invocationError with default retryOptions and expect default retry option', async () => {
9
- expect(target.retryOptions.retryAfter).toEqual(DEFAULT_RETRY_OPTIONS.retryAfter);
10
- expect(target.retryOptions.retryReason).toEqual(InvocationErrorCode.FUNCTION_RETRY_REQUEST);
11
- expect((target as any).hasOwnProperty('_retry')).toBe(true);
12
- });
13
-
14
- it('Populate InvocationError with custom RetryOptions and expect the custom value to set.', () => {
15
- const retryOptions: RetryOptions = {
16
- retryAfter: 10,
17
- retryReason: InvocationErrorCode.FUNCTION_OUT_OF_MEMORY,
18
- retryData: {
19
- eventsToSplit: 5
20
- }
21
- };
22
- target = new InvocationError(retryOptions);
23
-
24
- expect(target.retryOptions).toEqual(retryOptions);
25
- expect((target as any).hasOwnProperty('_retry')).toBe(true);
26
- });
27
-
28
- it('Use MIN_RETRY_AFTER when retryAfter <= 0', () => {
29
- const retryOptions: RetryOptions = {
30
- retryAfter: 0,
31
- retryReason: InvocationErrorCode.FUNCTION_OUT_OF_MEMORY,
32
- retryData: 'some string data'
33
- };
34
- target = new InvocationError(retryOptions);
35
-
36
- expect(target.retryOptions.retryAfter).toEqual(MIN_RETRY_AFTER);
37
- expect(target.retryOptions.retryData).toEqual('some string data');
38
- expect((target as any).hasOwnProperty('_retry')).toBe(true);
39
- });
40
- });
@@ -1,184 +0,0 @@
1
- import { getMockFetchMethod, verifyApiClientCalledWith } from './utils';
2
- import { InternalServerError, InvalidQueueNameError, JobDoesNotExistError, RateLimitError } from '../errors';
3
- import { JobProgress } from '../jobProgress';
4
- import { __requestAtlassianAsApp } from '@forge/api';
5
- import { QueueParams } from '../types';
6
-
7
- jest.mock('@forge/api', () => ({
8
- __requestAtlassianAsApp: getMockFetchMethod({ done: true }, 200)
9
- }));
10
-
11
- const queueParams: QueueParams = { key: 'test-queue-name' };
12
- const getJobProgress = (jobId: string, apiClientMock?: any) => new JobProgress(queueParams, jobId, apiClientMock);
13
-
14
- describe('JobProgress methods', () => {
15
- describe('getStats', () => {
16
- it('should call the queue/stats endpoint', async () => {
17
- const apiClientMock = getMockFetchMethod(
18
- {
19
- success: 100,
20
- inProgress: 50,
21
- failed: 1
22
- },
23
- 200
24
- );
25
- const jobProgress = getJobProgress('test-job-id', apiClientMock);
26
- const { success, inProgress, failed } = await jobProgress.getStats();
27
- verifyApiClientCalledWith(
28
- apiClientMock,
29
- '/webhook/queue/stats/{contextAri}/{environmentId}/{appId}/{appVersion}',
30
- {
31
- queueName: 'test-queue-name',
32
- jobId: 'test-job-id'
33
- }
34
- );
35
- expect(success).toEqual(100);
36
- expect(inProgress).toEqual(50);
37
- expect(failed).toEqual(1);
38
- });
39
-
40
- it('should throw JobDoesNotExistError', async () => {
41
- const apiClientMock = getMockFetchMethod(
42
- {
43
- message: 'Job Not Found',
44
- code: 404
45
- },
46
- 404
47
- );
48
- const jobProgress = getJobProgress('test-job-id', apiClientMock);
49
- await expect(jobProgress.getStats()).rejects.toThrow(
50
- new JobDoesNotExistError(`The job test-job-id was not found for the queue test-queue-name.`)
51
- );
52
- verifyApiClientCalledWith(
53
- apiClientMock,
54
- '/webhook/queue/stats/{contextAri}/{environmentId}/{appId}/{appVersion}',
55
- {
56
- queueName: 'test-queue-name',
57
- jobId: 'test-job-id'
58
- }
59
- );
60
- });
61
-
62
- it('should throw RateLimitError', async () => {
63
- const apiClientMock = getMockFetchMethod({}, 429);
64
- const jobProgress = getJobProgress('test-job-id', apiClientMock);
65
- await expect(jobProgress.getStats()).rejects.toThrow(new RateLimitError(`Too many requests.`));
66
- verifyApiClientCalledWith(
67
- apiClientMock,
68
- '/webhook/queue/stats/{contextAri}/{environmentId}/{appId}/{appVersion}',
69
- {
70
- queueName: 'test-queue-name',
71
- jobId: 'test-job-id'
72
- }
73
- );
74
- });
75
-
76
- it('should throw InternalServerError', async () => {
77
- const apiClientMock = getMockFetchMethod(
78
- {
79
- message: 'Service is not available',
80
- code: 513,
81
- details: 'The request processing has failed because of an unknown error, exception or failure'
82
- },
83
- 513
84
- );
85
- const jobProgress = getJobProgress('test-job-id', apiClientMock);
86
- await expect(jobProgress.getStats()).rejects.toThrow(
87
- new InternalServerError(`513 Status Text: Service is not available`, 513)
88
- );
89
- verifyApiClientCalledWith(
90
- apiClientMock,
91
- '/webhook/queue/stats/{contextAri}/{environmentId}/{appId}/{appVersion}',
92
- {
93
- queueName: 'test-queue-name',
94
- jobId: 'test-job-id'
95
- }
96
- );
97
- });
98
- });
99
-
100
- describe('cancel', () => {
101
- it('should call the queue/cancel endpoint', async () => {
102
- const apiClientMock = getMockFetchMethod({}, 204);
103
- const jobProgress = getJobProgress('test-job-id', apiClientMock);
104
- await jobProgress.cancel();
105
- verifyApiClientCalledWith(
106
- apiClientMock,
107
- '/webhook/queue/cancel/{contextAri}/{environmentId}/{appId}/{appVersion}',
108
- {
109
- queueName: 'test-queue-name',
110
- jobId: 'test-job-id'
111
- }
112
- );
113
- });
114
-
115
- it('should throw JobDoesNotExistError', async () => {
116
- const apiClientMock = getMockFetchMethod(
117
- {
118
- message: 'Job Not Found',
119
- code: 404
120
- },
121
- 404
122
- );
123
- const jobProgress = getJobProgress('test-job-id', apiClientMock);
124
- await expect(jobProgress.cancel()).rejects.toThrow(
125
- new JobDoesNotExistError(`The job test-job-id was not found for the queue test-queue-name.`)
126
- );
127
- verifyApiClientCalledWith(
128
- apiClientMock,
129
- '/webhook/queue/cancel/{contextAri}/{environmentId}/{appId}/{appVersion}',
130
- {
131
- queueName: 'test-queue-name',
132
- jobId: 'test-job-id'
133
- }
134
- );
135
- });
136
- });
137
-
138
- it('should throw InternalServerError when WHP returns 422 response', async () => {
139
- const apiClientMock = getMockFetchMethod(
140
- {
141
- errors: ['jobId must not be null', 'queueName must not be null']
142
- },
143
- 422
144
- );
145
- const jobProgress = getJobProgress('test-job-id', apiClientMock);
146
- await expect(jobProgress.getStats()).rejects.toThrow(
147
- new InternalServerError(`422 Status Text: jobId must not be null, queueName must not be null`)
148
- );
149
-
150
- verifyApiClientCalledWith(apiClientMock, '/webhook/queue/stats/{contextAri}/{environmentId}/{appId}/{appVersion}', {
151
- queueName: 'test-queue-name',
152
- jobId: 'test-job-id'
153
- });
154
- });
155
-
156
- it('should throw errors when queueName or jobId is empty', async () => {
157
- const apiClientMock = getMockFetchMethod(
158
- {
159
- success: 100,
160
- inProgress: 50,
161
- failed: 1
162
- },
163
- 200
164
- );
165
- const jobProgress1 = getJobProgress('', apiClientMock);
166
- await expect(jobProgress1.getStats()).rejects.toThrow(new JobDoesNotExistError(`jobId cannot be empty.`));
167
-
168
- const jobProgress2 = new JobProgress({ key: '!' }, 'test-job-id', apiClientMock);
169
- await expect(jobProgress2.getStats()).rejects.toThrow(
170
- new InvalidQueueNameError('Queue names can only contain alphanumeric characters, dashes and underscores.')
171
- );
172
-
173
- const jobProgress3 = getJobProgress('', apiClientMock);
174
- await expect(jobProgress3.getStats()).rejects.toThrow(new JobDoesNotExistError(`jobId cannot be empty.`));
175
-
176
- expect(apiClientMock).toHaveBeenCalledTimes(0);
177
- });
178
-
179
- it('requests stargate if no api client is provided', async () => {
180
- const jobProgress = getJobProgress('job-id');
181
- await jobProgress.getStats();
182
- expect(__requestAtlassianAsApp).toHaveBeenCalledTimes(1);
183
- });
184
- });