@devrev/ts-adaas 1.7.2-beta.0 → 1.8.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ import { NormalizedAttachment, ExternalSystemAttachmentStreamingFunction, ProcessAttachmentReturnType } from '../types';
2
+ import { WorkerAdapter } from '../workers/worker-adapter';
3
+ export declare class AttachmentsStreamingPool<ConnectorState> {
4
+ private adapter;
5
+ private attachments;
6
+ private batchSize;
7
+ private delay;
8
+ private stream;
9
+ constructor({ adapter, attachments, batchSize, stream, }: {
10
+ adapter: WorkerAdapter<ConnectorState>;
11
+ attachments: NormalizedAttachment[];
12
+ batchSize?: number;
13
+ stream: ExternalSystemAttachmentStreamingFunction;
14
+ });
15
+ streamAll(): Promise<ProcessAttachmentReturnType>;
16
+ startPoolWorker(): Promise<void>;
17
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AttachmentsStreamingPool = void 0;
4
+ class AttachmentsStreamingPool {
5
+ constructor({ adapter, attachments, batchSize = 10, stream, }) {
6
+ this.adapter = adapter;
7
+ this.attachments = [...attachments]; // Create a copy we can mutate
8
+ this.batchSize = batchSize;
9
+ this.delay = undefined;
10
+ this.stream = stream;
11
+ }
12
+ async streamAll() {
13
+ console.log(`Starting download of ${this.attachments.length} attachments with ${this.batchSize} workers.`);
14
+ if (!this.adapter.state.toDevRev) {
15
+ const error = new Error('toDevRev state is not initialized');
16
+ console.error(error);
17
+ return { error };
18
+ }
19
+ // Get the list of successfully processed attachments in previous (possibly incomplete) batch extraction.
20
+ // If no such list exists, create an empty one.
21
+ if (!this.adapter.state.toDevRev.attachmentsMetadata
22
+ .lastProcessedAttachmentsIdsList) {
23
+ this.adapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList =
24
+ [];
25
+ }
26
+ // Start initial batch of workers up to batchSize limit
27
+ const initialBatchSize = Math.min(this.batchSize, this.attachments.length);
28
+ const initialPromises = [];
29
+ for (let i = 0; i < initialBatchSize; i++) {
30
+ initialPromises.push(this.startPoolWorker());
31
+ }
32
+ // Wait for all workers to complete
33
+ await Promise.all(initialPromises);
34
+ if (this.delay) {
35
+ return { delay: this.delay };
36
+ }
37
+ return {};
38
+ }
39
+ async startPoolWorker() {
40
+ var _a, _b, _c, _d;
41
+ // Worker keeps taking jobs until the attachments array is empty
42
+ while (this.attachments.length > 0) {
43
+ if (this.delay) {
44
+ break; // Exit if we have a delay
45
+ }
46
+ // Check if we can start a new worker
47
+ const attachment = this.attachments.shift();
48
+ if (!attachment) {
49
+ break; // Exit if no more attachments
50
+ }
51
+ if (this.adapter.state.toDevRev &&
52
+ ((_a = this.adapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList) === null || _a === void 0 ? void 0 : _a.includes(attachment.id))) {
53
+ console.log(`Attachment with ID ${attachment.id} has already been processed. Skipping.`);
54
+ continue; // Skip if the attachment ID is already processed
55
+ }
56
+ try {
57
+ const response = await this.adapter.processAttachment(attachment, this.stream);
58
+ // Check if rate limit was hit
59
+ if (response === null || response === void 0 ? void 0 : response.delay) {
60
+ this.delay = response.delay; // Set the delay for rate limiting
61
+ return;
62
+ }
63
+ // No rate limiting, process normally
64
+ if ((_c = (_b = this.adapter.state.toDevRev) === null || _b === void 0 ? void 0 : _b.attachmentsMetadata) === null || _c === void 0 ? void 0 : _c.lastProcessedAttachmentsIdsList) {
65
+ (_d = this.adapter.state.toDevRev) === null || _d === void 0 ? void 0 : _d.attachmentsMetadata.lastProcessedAttachmentsIdsList.push(attachment.id);
66
+ console.log(`Successfully processed attachment: ${attachment.id}`);
67
+ }
68
+ }
69
+ catch (error) {
70
+ console.warn(`Skipping attachment with ID ${attachment.id} due to error: ${error}`);
71
+ }
72
+ }
73
+ }
74
+ }
75
+ exports.AttachmentsStreamingPool = AttachmentsStreamingPool;
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const attachments_streaming_pool_1 = require("./attachments-streaming-pool");
4
+ describe('AttachmentsStreamingPool', () => {
5
+ let mockAdapter;
6
+ let mockStream;
7
+ let mockAttachments;
8
+ beforeEach(() => {
9
+ // Create mock adapter
10
+ mockAdapter = {
11
+ state: {
12
+ attachments: { completed: false },
13
+ toDevRev: {
14
+ attachmentsMetadata: {
15
+ lastProcessedAttachmentsIdsList: []
16
+ }
17
+ }
18
+ },
19
+ processAttachment: jest.fn().mockResolvedValue({})
20
+ };
21
+ // Create mock stream function
22
+ mockStream = jest.fn().mockResolvedValue({ success: true });
23
+ // Create mock attachments
24
+ mockAttachments = [
25
+ {
26
+ id: 'attachment-1',
27
+ url: 'https://example.com/file1.pdf',
28
+ file_name: 'file1.pdf',
29
+ parent_id: 'parent-1'
30
+ },
31
+ {
32
+ id: 'attachment-2',
33
+ url: 'https://example.com/file2.jpg',
34
+ file_name: 'file2.jpg',
35
+ parent_id: 'parent-2'
36
+ },
37
+ {
38
+ id: 'attachment-3',
39
+ url: 'https://example.com/file3.doc',
40
+ file_name: 'file3.doc',
41
+ parent_id: 'parent-3'
42
+ }
43
+ ];
44
+ // Mock console methods
45
+ jest.spyOn(console, 'log').mockImplementation();
46
+ jest.spyOn(console, 'error').mockImplementation();
47
+ jest.spyOn(console, 'warn').mockImplementation();
48
+ });
49
+ afterEach(() => {
50
+ jest.clearAllMocks();
51
+ jest.restoreAllMocks();
52
+ });
53
+ describe('constructor', () => {
54
+ it('should initialize with default values', () => {
55
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
56
+ adapter: mockAdapter,
57
+ attachments: mockAttachments,
58
+ stream: mockStream
59
+ });
60
+ expect(pool).toBeDefined();
61
+ expect(pool['adapter']).toBe(mockAdapter);
62
+ expect(pool['attachments']).toEqual(mockAttachments);
63
+ expect(pool['batchSize']).toBe(10);
64
+ expect(pool['stream']).toBe(mockStream);
65
+ });
66
+ it('should initialize with custom batch size', () => {
67
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
68
+ adapter: mockAdapter,
69
+ attachments: mockAttachments,
70
+ batchSize: 5,
71
+ stream: mockStream
72
+ });
73
+ expect(pool['batchSize']).toBe(5);
74
+ });
75
+ it('should create a copy of attachments array', () => {
76
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
77
+ adapter: mockAdapter,
78
+ attachments: mockAttachments,
79
+ stream: mockStream
80
+ });
81
+ expect(pool['attachments']).toEqual(mockAttachments);
82
+ expect(pool['attachments']).not.toBe(mockAttachments); // Different reference
83
+ });
84
+ });
85
+ describe('streamAll', () => {
86
+ it('should initialize lastProcessedAttachmentsIdsList if it does not exist', async () => {
87
+ mockAdapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList = undefined;
88
+ mockAdapter.processAttachment.mockResolvedValue({});
89
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
90
+ adapter: mockAdapter,
91
+ attachments: mockAttachments,
92
+ stream: mockStream
93
+ });
94
+ // Mock startPoolWorker to avoid actual processing
95
+ jest.spyOn(pool, 'startPoolWorker').mockResolvedValue(undefined);
96
+ await pool.streamAll();
97
+ expect(mockAdapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList).toEqual([]);
98
+ });
99
+ it('should process all attachments successfully', async () => {
100
+ mockAdapter.processAttachment.mockResolvedValue({});
101
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
102
+ adapter: mockAdapter,
103
+ attachments: mockAttachments,
104
+ stream: mockStream
105
+ });
106
+ const result = await pool.streamAll();
107
+ expect(result).toEqual({});
108
+ expect(mockAdapter.processAttachment).toHaveBeenCalledTimes(3);
109
+ });
110
+ it('should handle empty attachments array', async () => {
111
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
112
+ adapter: mockAdapter,
113
+ attachments: [],
114
+ stream: mockStream
115
+ });
116
+ const result = await pool.streamAll();
117
+ expect(result).toEqual({});
118
+ expect(mockAdapter.processAttachment).not.toHaveBeenCalled();
119
+ expect(console.log).toHaveBeenCalledWith('Starting download of 0 attachments with 10 workers.');
120
+ });
121
+ it('should return delay when rate limit is hit', async () => {
122
+ const delayResponse = { delay: 5000 };
123
+ mockAdapter.processAttachment.mockResolvedValue(delayResponse);
124
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
125
+ adapter: mockAdapter,
126
+ attachments: mockAttachments,
127
+ stream: mockStream
128
+ });
129
+ const result = await pool.streamAll();
130
+ expect(result).toEqual({ delay: 5000 });
131
+ });
132
+ });
133
+ describe('startPoolWorker', () => {
134
+ it('should skip already processed attachments', async () => {
135
+ mockAdapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList = ['attachment-1'];
136
+ mockAdapter.processAttachment.mockResolvedValue({});
137
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
138
+ adapter: mockAdapter,
139
+ attachments: mockAttachments,
140
+ stream: mockStream
141
+ });
142
+ await pool.streamAll();
143
+ expect(console.log).toHaveBeenCalledWith('Attachment with ID attachment-1 has already been processed. Skipping.');
144
+ expect(mockAdapter.processAttachment).toHaveBeenCalledTimes(2); // Only 2 out of 3
145
+ });
146
+ it('should add successfully processed attachment IDs to the list', async () => {
147
+ mockAdapter.processAttachment.mockResolvedValue({});
148
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
149
+ adapter: mockAdapter,
150
+ attachments: mockAttachments,
151
+ stream: mockStream
152
+ });
153
+ await pool.streamAll();
154
+ expect(mockAdapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList).toEqual([
155
+ 'attachment-1',
156
+ 'attachment-2',
157
+ 'attachment-3'
158
+ ]);
159
+ expect(console.log).toHaveBeenCalledWith('Successfully processed attachment: attachment-1');
160
+ expect(console.log).toHaveBeenCalledWith('Successfully processed attachment: attachment-2');
161
+ expect(console.log).toHaveBeenCalledWith('Successfully processed attachment: attachment-3');
162
+ });
163
+ it('should handle processing errors gracefully', async () => {
164
+ const error = new Error('Processing failed');
165
+ mockAdapter.processAttachment
166
+ .mockResolvedValueOnce({}) // First attachment succeeds
167
+ .mockRejectedValueOnce(error) // Second attachment fails
168
+ .mockResolvedValueOnce({}); // Third attachment succeeds
169
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
170
+ adapter: mockAdapter,
171
+ attachments: mockAttachments,
172
+ stream: mockStream
173
+ });
174
+ await pool.streamAll();
175
+ expect(console.warn).toHaveBeenCalledWith('Skipping attachment with ID attachment-2 due to error: Error: Processing failed');
176
+ expect(mockAdapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList).toEqual([
177
+ 'attachment-1',
178
+ 'attachment-3'
179
+ ]);
180
+ });
181
+ it('should stop processing when rate limit delay is encountered', async () => {
182
+ mockAdapter.processAttachment
183
+ .mockResolvedValueOnce({}) // First attachment succeeds
184
+ .mockResolvedValueOnce({ delay: 5000 }) // Second attachment triggers rate limit
185
+ .mockResolvedValueOnce({}); // Third attachment succeeds
186
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
187
+ adapter: mockAdapter,
188
+ attachments: mockAttachments,
189
+ stream: mockStream
190
+ });
191
+ await pool.streamAll();
192
+ expect(mockAdapter.processAttachment).toHaveBeenCalledTimes(3);
193
+ expect(mockAdapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList).toEqual([
194
+ 'attachment-1',
195
+ 'attachment-3'
196
+ ]);
197
+ });
198
+ it('should pass correct parameters to processAttachment', async () => {
199
+ mockAdapter.processAttachment.mockResolvedValue({});
200
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
201
+ adapter: mockAdapter,
202
+ attachments: [mockAttachments[0]],
203
+ stream: mockStream
204
+ });
205
+ await pool.streamAll();
206
+ expect(mockAdapter.processAttachment).toHaveBeenCalledWith(mockAttachments[0], mockStream);
207
+ });
208
+ });
209
+ describe('edge cases', () => {
210
+ it('should handle single attachment', async () => {
211
+ mockAdapter.processAttachment.mockResolvedValue({});
212
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
213
+ adapter: mockAdapter,
214
+ attachments: [mockAttachments[0]],
215
+ stream: mockStream
216
+ });
217
+ const result = await pool.streamAll();
218
+ expect(result).toEqual({});
219
+ expect(mockAdapter.processAttachment).toHaveBeenCalledTimes(1);
220
+ });
221
+ it('should handle batch size larger than attachments array', async () => {
222
+ mockAdapter.processAttachment.mockResolvedValue({});
223
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
224
+ adapter: mockAdapter,
225
+ attachments: mockAttachments,
226
+ batchSize: 100,
227
+ stream: mockStream
228
+ });
229
+ await pool.streamAll();
230
+ expect(mockAdapter.processAttachment).toHaveBeenCalledTimes(3);
231
+ });
232
+ it('should handle batch size of 1', async () => {
233
+ mockAdapter.processAttachment.mockResolvedValue({});
234
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
235
+ adapter: mockAdapter,
236
+ attachments: mockAttachments,
237
+ batchSize: 1,
238
+ stream: mockStream
239
+ });
240
+ await pool.streamAll();
241
+ expect(mockAdapter.processAttachment).toHaveBeenCalledTimes(3);
242
+ });
243
+ });
244
+ describe('concurrency behavior', () => {
245
+ it('should process attachments concurrently within batch size', async () => {
246
+ let processCallCount = 0;
247
+ const processPromises = [];
248
+ mockAdapter.processAttachment.mockImplementation(() => {
249
+ const promise = new Promise((resolve) => {
250
+ setTimeout(() => {
251
+ processCallCount++;
252
+ resolve({});
253
+ }, 100);
254
+ });
255
+ processPromises.push(promise);
256
+ return promise;
257
+ });
258
+ const pool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
259
+ adapter: mockAdapter,
260
+ attachments: mockAttachments,
261
+ batchSize: 2,
262
+ stream: mockStream
263
+ });
264
+ const startTime = Date.now();
265
+ await pool.streamAll();
266
+ const endTime = Date.now();
267
+ expect(mockAdapter.processAttachment).toHaveBeenCalledTimes(3);
268
+ expect(processCallCount).toBe(3);
269
+ // Should complete in roughly 200ms (2 batches of 100ms each) rather than 300ms (sequential)
270
+ expect(endTime - startTime).toBeLessThan(250);
271
+ });
272
+ });
273
+ });
@@ -1,5 +1,5 @@
1
1
  export { ErrorLevel, ErrorRecord, LogRecord, AdapterUpdateParams, InitialDomainMapping, } from './common';
2
- export { EventType, ExtractorEventType, ExtractionMode, ExternalSyncUnit, EventContextIn, EventContextOut, ConnectionData, EventData, DomainObjectState, AirdropEvent, AirdropMessage, ExtractorEvent, SyncMode, ExternalSystemAttachmentStreamingParams, ExternalSystemAttachmentStreamingResponse, ExternalSystemAttachmentStreamingFunction, ExternalProcessAttachmentFunction, ExternalSystemAttachmentReducerFunction, ExternalSystemAttachmentIteratorFunction, } from './extraction';
2
+ export { EventType, ExtractorEventType, ExtractionMode, ExternalSyncUnit, EventContextIn, EventContextOut, ConnectionData, EventData, DomainObjectState, AirdropEvent, AirdropMessage, ExtractorEvent, SyncMode, ExternalSystemAttachmentStreamingParams, ExternalSystemAttachmentStreamingResponse, ExternalSystemAttachmentStreamingFunction, ExternalProcessAttachmentFunction, ExternalSystemAttachmentReducerFunction, ExternalSystemAttachmentIteratorFunction, ProcessAttachmentReturnType, } from './extraction';
3
3
  export { LoaderEventType, ExternalSystemItem, ExternalSystemItemLoadingResponse, ExternalSystemItemLoadingParams, ExternalSystemAttachment, } from './loading';
4
4
  export { NormalizedItem, NormalizedAttachment, RepoInterface, } from '../repo/repo.interfaces';
5
5
  export { AdapterState } from '../state/state.interfaces';
@@ -68,27 +68,6 @@ export declare class WorkerAdapter<ConnectorState> {
68
68
  item: ExternalSystemAttachment;
69
69
  create: ExternalSystemLoadingFunction<ExternalSystemAttachment>;
70
70
  }): Promise<LoadItemResponse>;
71
- /**
72
- * Transforms an array of attachments into array of batches of the specified size.
73
- *
74
- * @param {Object} parameters - The parameters object
75
- * @param {NormalizedAttachment[]} parameters.attachments - Array of attachments to be processed
76
- * @param {number} [parameters.batchSize=1] - The size of each batch (defaults to 1)
77
- * @param {ConnectorState} parameters.adapter - The adapter instance
78
- * @returns {NormalizedAttachment[][]} An array of attachment batches
79
- */
80
- private defaultAttachmentsReducer;
81
- /**
82
- * This iterator function processes attachments batch by batch, saves progress to state, and handles rate limiting.
83
- *
84
- * @param {Object} parameters - The parameters object
85
- * @param {NormalizedAttachment[][]} parameters.reducedAttachments - Array of attachment batches to process
86
- * @param {Object} parameters.adapter - The connector adapter that contains state and processing methods
87
- * @param {Object} parameters.stream - Stream object for logging or progress reporting
88
- * @returns {Promise<{delay?: number} | void>} Returns an object with delay information if rate-limited, otherwise void
89
- * @throws Will not throw exceptions but will log warnings for processing failures
90
- */
91
- private defaultAttachmentsIterator;
92
71
  /**
93
72
  * Streams the attachments to the DevRev platform.
94
73
  * The attachments are streamed to the platform and the artifact information is returned.
@@ -15,7 +15,7 @@ const mappers_1 = require("../mappers/mappers");
15
15
  const uploader_1 = require("../uploader/uploader");
16
16
  const logger_1 = require("../logger/logger");
17
17
  const mappers_interface_1 = require("../mappers/mappers.interface");
18
- const helpers_2 = require("../common/helpers");
18
+ const attachments_streaming_pool_1 = require("../attachments-streaming/attachments-streaming-pool");
19
19
  function createWorkerAdapter({ event, adapterState, options, }) {
20
20
  return new WorkerAdapter({
21
21
  event,
@@ -40,108 +40,6 @@ function createWorkerAdapter({ event, adapterState, options, }) {
40
40
  class WorkerAdapter {
41
41
  constructor({ event, adapterState, options, }) {
42
42
  this.repos = [];
43
- /**
44
- * Transforms an array of attachments into array of batches of the specified size.
45
- *
46
- * @param {Object} parameters - The parameters object
47
- * @param {NormalizedAttachment[]} parameters.attachments - Array of attachments to be processed
48
- * @param {number} [parameters.batchSize=1] - The size of each batch (defaults to 1)
49
- * @param {ConnectorState} parameters.adapter - The adapter instance
50
- * @returns {NormalizedAttachment[][]} An array of attachment batches
51
- */
52
- this.defaultAttachmentsReducer = ({ attachments, batchSize = 1 }) => {
53
- // Transform the attachments array into smaller batches
54
- const batches = attachments.reduce((result, item, index) => {
55
- // Determine the index of the current batch
56
- const batchIndex = Math.floor(index / batchSize);
57
- // Initialize a new batch if it doesn't already exist
58
- if (!result[batchIndex]) {
59
- result[batchIndex] = [];
60
- }
61
- // Append the current item to the current batch
62
- result[batchIndex].push(item);
63
- return result;
64
- }, []);
65
- // Return the array of batches
66
- return batches;
67
- };
68
- /**
69
- * This iterator function processes attachments batch by batch, saves progress to state, and handles rate limiting.
70
- *
71
- * @param {Object} parameters - The parameters object
72
- * @param {NormalizedAttachment[][]} parameters.reducedAttachments - Array of attachment batches to process
73
- * @param {Object} parameters.adapter - The connector adapter that contains state and processing methods
74
- * @param {Object} parameters.stream - Stream object for logging or progress reporting
75
- * @returns {Promise<{delay?: number} | void>} Returns an object with delay information if rate-limited, otherwise void
76
- * @throws Will not throw exceptions but will log warnings for processing failures
77
- */
78
- this.defaultAttachmentsIterator = async ({ reducedAttachments, adapter, stream }) => {
79
- if (!adapter.state.toDevRev) {
80
- const error = new Error(`toDevRev state is not defined.`);
81
- console.error(error.message);
82
- return { error };
83
- }
84
- // Get index of the last processed batch of this artifact
85
- const lastProcessedBatchIndex = adapter.state.toDevRev.attachmentsMetadata.lastProcessed || 0;
86
- // Get the list of successfully processed attachments in previous (possibly incomplete) batch extraction.
87
- // If no such list exists, create an empty one.
88
- if (!adapter.state.toDevRev.attachmentsMetadata
89
- .lastProcessedAttachmentsIdsList) {
90
- adapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList =
91
- [];
92
- }
93
- // Loop through the batches of attachments
94
- for (let i = lastProcessedBatchIndex; i < reducedAttachments.length; i++) {
95
- // Check if we hit timeout
96
- if (adapter.isTimeout) {
97
- await (0, helpers_2.sleep)(constants_1.DEFAULT_SLEEP_DELAY_MS);
98
- }
99
- const attachmentsBatch = reducedAttachments[i];
100
- // Create a list of promises for parallel processing
101
- const promises = [];
102
- for (const attachment of attachmentsBatch) {
103
- if (adapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList.includes(attachment.id)) {
104
- console.log(`Attachment with ID ${attachment.id} has already been processed. Skipping.`);
105
- continue; // Skip if the attachment ID is already processed
106
- }
107
- const promise = adapter
108
- .processAttachment(attachment, stream)
109
- .then((response) => {
110
- var _a, _b, _c;
111
- // Check if rate limit was hit
112
- if (response === null || response === void 0 ? void 0 : response.delay) {
113
- // Store this promise result to be checked later
114
- return { delay: response.delay };
115
- }
116
- // No rate limiting, process normally
117
- if ((_b = (_a = adapter.state.toDevRev) === null || _a === void 0 ? void 0 : _a.attachmentsMetadata) === null || _b === void 0 ? void 0 : _b.lastProcessedAttachmentsIdsList) {
118
- (_c = adapter.state.toDevRev) === null || _c === void 0 ? void 0 : _c.attachmentsMetadata.lastProcessedAttachmentsIdsList.push(attachment.id);
119
- }
120
- return null; // Return null for successful processing
121
- })
122
- .catch((error) => {
123
- console.warn(`Skipping attachment with ID ${attachment.id} due to error: ${error}`);
124
- return null; // Return null for errors too
125
- });
126
- promises.push(promise);
127
- }
128
- // Wait for all promises to settle and check for rate limiting
129
- const results = await Promise.all(promises);
130
- // Check if any of the results indicate rate limiting
131
- const rateLimit = results.find((result) => result === null || result === void 0 ? void 0 : result.delay);
132
- if (rateLimit) {
133
- // Return the delay information to the caller
134
- return { delay: rateLimit.delay };
135
- }
136
- if (adapter.state.toDevRev) {
137
- // Update the last processed batch index
138
- adapter.state.toDevRev.attachmentsMetadata.lastProcessed = i + 1;
139
- // Reset successfullyProcessedAttachments list
140
- adapter.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList.length = 0;
141
- }
142
- }
143
- return {};
144
- };
145
43
  this.event = event;
146
44
  this.options = options;
147
45
  this.adapterState = adapterState;
@@ -728,38 +626,41 @@ class WorkerAdapter {
728
626
  continue;
729
627
  }
730
628
  console.log(`Found ${attachments.length} attachments for artifact ID: ${attachmentsMetadataArtifactId}.`);
731
- // Use the reducer to split into batches.
732
- let reducer;
733
- // Use the iterator to process each batch, streaming all attachments inside one batch in parallel.
734
- let iterator;
629
+ let response;
735
630
  if (processors) {
736
631
  console.log(`Using custom processors for attachments.`);
737
- reducer = processors.reducer;
738
- iterator = processors.iterator;
632
+ const reducer = processors.reducer;
633
+ const iterator = processors.iterator;
634
+ const reducedAttachments = reducer({
635
+ attachments,
636
+ adapter: this,
637
+ batchSize,
638
+ });
639
+ response = await iterator({
640
+ reducedAttachments,
641
+ adapter: this,
642
+ stream,
643
+ });
739
644
  }
740
645
  else {
741
- console.log(`Using default processors for attachments.`);
742
- reducer = this
743
- .defaultAttachmentsReducer;
744
- iterator = this
745
- .defaultAttachmentsIterator;
646
+ console.log(`Using attachments streaming pool for attachments streaming.`);
647
+ const attachmentsPool = new attachments_streaming_pool_1.AttachmentsStreamingPool({
648
+ adapter: this,
649
+ attachments,
650
+ batchSize,
651
+ stream,
652
+ });
653
+ response = await attachmentsPool.streamAll();
746
654
  }
747
- const reducedAttachments = reducer({
748
- attachments,
749
- adapter: this,
750
- batchSize,
751
- });
752
- const response = await iterator({
753
- reducedAttachments,
754
- adapter: this,
755
- stream,
756
- });
757
655
  if ((response === null || response === void 0 ? void 0 : response.delay) || (response === null || response === void 0 ? void 0 : response.error)) {
758
656
  return response;
759
657
  }
760
658
  console.log(`Finished processing all attachments for artifact ID: ${attachmentsMetadataArtifactId}.`);
761
659
  this.state.toDevRev.attachmentsMetadata.artifactIds.shift();
762
660
  this.state.toDevRev.attachmentsMetadata.lastProcessed = 0;
661
+ if (this.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList) {
662
+ this.state.toDevRev.attachmentsMetadata.lastProcessedAttachmentsIdsList.length = 0;
663
+ }
763
664
  }
764
665
  return;
765
666
  }
@@ -4,6 +4,7 @@ const worker_adapter_1 = require("./worker-adapter");
4
4
  const state_1 = require("../state/state");
5
5
  const test_helpers_1 = require("../tests/test-helpers");
6
6
  const types_1 = require("../types");
7
+ const attachments_streaming_pool_1 = require("../attachments-streaming/attachments-streaming-pool");
7
8
  // Mock dependencies
8
9
  jest.mock('../common/control-protocol', () => ({
9
10
  emit: jest.fn().mockResolvedValue({}),
@@ -19,6 +20,15 @@ jest.mock('node:worker_threads', () => ({
19
20
  postMessage: jest.fn(),
20
21
  },
21
22
  }));
23
+ jest.mock('../attachments-streaming/attachments-streaming-pool', () => {
24
+ return {
25
+ AttachmentsStreamingPool: jest.fn().mockImplementation(() => {
26
+ return {
27
+ streamAll: jest.fn().mockResolvedValue(undefined),
28
+ };
29
+ }),
30
+ };
31
+ });
22
32
  describe('WorkerAdapter', () => {
23
33
  let adapter;
24
34
  let mockEvent;
@@ -51,269 +61,6 @@ describe('WorkerAdapter', () => {
51
61
  adapterState: mockAdapterState,
52
62
  });
53
63
  });
54
- describe('defaultAttachmentsReducer', () => {
55
- it('should correctly batch attachments based on the batchSize', () => {
56
- // Arrange
57
- const attachments = [
58
- { url: 'http://example.com/file1.pdf', id: 'attachment1', file_name: 'file1.pdf', parent_id: 'parent1' },
59
- { url: 'http://example.com/file2.pdf', id: 'attachment2', file_name: 'file2.pdf', parent_id: 'parent2' },
60
- { url: 'http://example.com/file3.pdf', id: 'attachment3', file_name: 'file3.pdf', parent_id: 'parent3' },
61
- { url: 'http://example.com/file4.pdf', id: 'attachment4', file_name: 'file4.pdf', parent_id: 'parent4' },
62
- { url: 'http://example.com/file5.pdf', id: 'attachment5', file_name: 'file5.pdf', parent_id: 'parent5' },
63
- ];
64
- // Act - call the private method using function call notation
65
- const result = adapter['defaultAttachmentsReducer']({
66
- attachments,
67
- adapter,
68
- batchSize: 2,
69
- });
70
- // Assert
71
- expect(result).toHaveLength(3);
72
- expect(result[0]).toHaveLength(2);
73
- expect(result[1]).toHaveLength(2);
74
- expect(result[2]).toHaveLength(1);
75
- expect(result[0][0].id).toBe('attachment1');
76
- expect(result[0][1].id).toBe('attachment2');
77
- expect(result[1][0].id).toBe('attachment3');
78
- expect(result[1][1].id).toBe('attachment4');
79
- expect(result[2][0].id).toBe('attachment5');
80
- });
81
- it('should return a single batch when batchSize equals the number of attachments', () => {
82
- // Arrange
83
- const attachments = [
84
- { url: 'http://example.com/file1.pdf', id: 'attachment1', file_name: 'file1.pdf', parent_id: 'parent1' },
85
- { url: 'http://example.com/file2.pdf', id: 'attachment2', file_name: 'file2.pdf', parent_id: 'parent2' },
86
- ];
87
- // Act
88
- const result = adapter['defaultAttachmentsReducer']({
89
- attachments,
90
- adapter,
91
- batchSize: 2,
92
- });
93
- // Assert
94
- expect(result).toHaveLength(1);
95
- expect(result[0]).toHaveLength(2);
96
- expect(result[0][0].id).toBe('attachment1');
97
- expect(result[0][1].id).toBe('attachment2');
98
- });
99
- it('should return a single batch when batchSize is bigger than the number of attachments', () => {
100
- // Arrange
101
- const attachments = [
102
- { url: 'http://example.com/file1.pdf', id: 'attachment1', file_name: 'file1.pdf', parent_id: 'parent1' },
103
- { url: 'http://example.com/file2.pdf', id: 'attachment2', file_name: 'file2.pdf', parent_id: 'parent2' },
104
- ];
105
- // Act
106
- const result = adapter['defaultAttachmentsReducer']({
107
- attachments,
108
- adapter,
109
- batchSize: 10,
110
- });
111
- // Assert
112
- expect(result).toHaveLength(1);
113
- expect(result[0]).toHaveLength(2);
114
- expect(result[0][0].id).toBe('attachment1');
115
- expect(result[0][1].id).toBe('attachment2');
116
- });
117
- it('should handle empty attachments array', () => {
118
- // Arrange
119
- const attachments = [];
120
- // Act
121
- const result = adapter['defaultAttachmentsReducer']({
122
- attachments,
123
- adapter,
124
- batchSize: 2,
125
- });
126
- // Assert
127
- expect(result).toHaveLength(0);
128
- });
129
- it('should default to batchSize of 1 if not provided', () => {
130
- // Arrange
131
- const attachments = [
132
- { url: 'http://example.com/file1.pdf', id: 'attachment1', file_name: 'file1.pdf', parent_id: 'parent1' },
133
- { url: 'http://example.com/file2.pdf', id: 'attachment2', file_name: 'file2.pdf', parent_id: 'parent2' },
134
- ];
135
- // Act
136
- const result = adapter['defaultAttachmentsReducer']({
137
- attachments,
138
- adapter
139
- });
140
- // Assert
141
- expect(result).toHaveLength(2);
142
- expect(result[0]).toHaveLength(1);
143
- expect(result[1]).toHaveLength(1);
144
- });
145
- });
146
- describe('defaultAttachmentsIterator', () => {
147
- it('should process all batches of attachments', async () => {
148
- var _a, _b;
149
- // Arrange
150
- const mockAttachments = [
151
- [
152
- { url: 'http://example.com/file1.pdf', id: 'attachment1', file_name: 'file1.pdf', parent_id: 'parent1' },
153
- { url: 'http://example.com/file2.pdf', id: 'attachment2', file_name: 'file2.pdf', parent_id: 'parent2' },
154
- ],
155
- [
156
- { url: 'http://example.com/file3.pdf', id: 'attachment3', file_name: 'file3.pdf', parent_id: 'parent3' },
157
- ],
158
- ];
159
- const mockStream = jest.fn();
160
- // Mock the processAttachment method
161
- adapter.processAttachment = jest.fn().mockResolvedValue(null);
162
- // Act
163
- const result = await adapter['defaultAttachmentsIterator']({
164
- reducedAttachments: mockAttachments,
165
- adapter: adapter,
166
- stream: mockStream,
167
- });
168
- // Assert
169
- expect(adapter.processAttachment).toHaveBeenCalledTimes(3);
170
- expect(adapter.processAttachment).toHaveBeenCalledWith(mockAttachments[0][0], mockStream);
171
- expect(adapter.processAttachment).toHaveBeenCalledWith(mockAttachments[0][1], mockStream);
172
- expect(adapter.processAttachment).toHaveBeenCalledWith(mockAttachments[1][0], mockStream);
173
- // Verify the state was updated correctly
174
- expect((_a = adapter.state.toDevRev) === null || _a === void 0 ? void 0 : _a.attachmentsMetadata.lastProcessed).toBe(2);
175
- expect((_b = adapter.state.toDevRev) === null || _b === void 0 ? void 0 : _b.attachmentsMetadata.lastProcessedAttachmentsIdsList).toEqual([]);
176
- expect(result).toEqual({});
177
- });
178
- it('should handle rate limiting during processing', async () => {
179
- var _a;
180
- // Arrange
181
- const mockAttachments = [
182
- [
183
- { url: 'http://example.com/file1.pdf', id: 'attachment1', file_name: 'file1.pdf', parent_id: 'parent1' },
184
- { url: 'http://example.com/file2.pdf', id: 'attachment2', file_name: 'file2.pdf', parent_id: 'parent2' },
185
- ],
186
- [
187
- { url: 'http://example.com/file3.pdf', id: 'attachment3', file_name: 'file3.pdf', parent_id: 'parent3' },
188
- ],
189
- ];
190
- const mockStream = jest.fn();
191
- // Mock the processAttachment method to simulate rate limiting on the second attachment
192
- adapter.processAttachment = jest.fn()
193
- .mockResolvedValueOnce(null) // First attachment processes successfully
194
- .mockResolvedValueOnce({ delay: 30 }); // Second attachment hits rate limit
195
- // Set up adapter state
196
- adapter.state.toDevRev = {
197
- attachmentsMetadata: {
198
- lastProcessed: 0,
199
- artifactIds: [],
200
- lastProcessedAttachmentsIdsList: [],
201
- },
202
- };
203
- // Act
204
- const result = await adapter['defaultAttachmentsIterator']({
205
- reducedAttachments: mockAttachments,
206
- adapter: adapter,
207
- stream: mockStream,
208
- });
209
- // Assert
210
- expect(adapter.processAttachment).toHaveBeenCalledTimes(2);
211
- expect(adapter.processAttachment).toHaveBeenCalledWith(mockAttachments[0][0], mockStream);
212
- expect(adapter.processAttachment).toHaveBeenCalledWith(mockAttachments[0][1], mockStream);
213
- // Verify the delay was returned
214
- expect(result).toEqual({ delay: 30 });
215
- // And lastProcessed wasn't updated yet
216
- expect((_a = adapter.state.toDevRev) === null || _a === void 0 ? void 0 : _a.attachmentsMetadata.lastProcessed).toBe(0);
217
- });
218
- it('should skip already processed attachments', async () => {
219
- // Arrange
220
- const mockAttachments = [
221
- [
222
- { url: 'http://example.com/file1.pdf', id: 'attachment1', file_name: 'file1.pdf', parent_id: 'parent1' },
223
- { url: 'http://example.com/file2.pdf', id: 'attachment2', file_name: 'file2.pdf', parent_id: 'parent2' },
224
- ],
225
- ];
226
- const mockStream = jest.fn();
227
- // Mock the processAttachment method
228
- adapter.processAttachment = jest.fn().mockResolvedValue(null);
229
- // Set up adapter state to indicate attachment1 was already processed
230
- adapter.state.toDevRev = {
231
- attachmentsMetadata: {
232
- lastProcessed: 0,
233
- artifactIds: [],
234
- lastProcessedAttachmentsIdsList: ['attachment1'],
235
- },
236
- };
237
- // Act
238
- await adapter['defaultAttachmentsIterator']({
239
- reducedAttachments: mockAttachments,
240
- adapter: adapter,
241
- stream: mockStream,
242
- });
243
- // Assert
244
- expect(adapter.processAttachment).toHaveBeenCalledTimes(1);
245
- expect(adapter.processAttachment).toHaveBeenCalledWith(mockAttachments[0][1], mockStream);
246
- expect(adapter.processAttachment).not.toHaveBeenCalledWith(mockAttachments[0][0], mockStream);
247
- });
248
- it('should continue from last processed batch', async () => {
249
- // Arrange
250
- const mockAttachments = [
251
- [
252
- { url: 'http://example.com/file1.pdf', id: 'attachment1', file_name: 'file1.pdf', parent_id: 'parent1' },
253
- ],
254
- [
255
- { url: 'http://example.com/file2.pdf', id: 'attachment2', file_name: 'file2.pdf', parent_id: 'parent2' },
256
- ],
257
- ];
258
- const mockStream = jest.fn();
259
- // Mock the processAttachment method
260
- adapter.processAttachment = jest.fn().mockResolvedValue(null);
261
- // Set up adapter state to indicate we already processed the first batch
262
- adapter.state.toDevRev = {
263
- attachmentsMetadata: {
264
- lastProcessed: 1, // Skip first batch (index 0)
265
- artifactIds: [],
266
- lastProcessedAttachmentsIdsList: [],
267
- },
268
- };
269
- // Act
270
- await adapter['defaultAttachmentsIterator']({
271
- reducedAttachments: mockAttachments,
272
- adapter: adapter,
273
- stream: mockStream,
274
- });
275
- // Assert
276
- expect(adapter.processAttachment).toHaveBeenCalledTimes(1);
277
- expect(adapter.processAttachment).toHaveBeenCalledWith(mockAttachments[1][0], mockStream);
278
- expect(adapter.processAttachment).not.toHaveBeenCalledWith(mockAttachments[0][0], mockStream);
279
- });
280
- it('should handle errors during processing and continue', async () => {
281
- // Arrange
282
- const mockAttachments = [
283
- [
284
- { url: 'http://example.com/file1.pdf', id: 'attachment1', file_name: 'file1.pdf', parent_id: 'parent1' },
285
- { url: 'http://example.com/file2.pdf', id: 'attachment2', file_name: 'file2.pdf', parent_id: 'parent2' },
286
- ],
287
- ];
288
- const mockStream = jest.fn();
289
- // Mock processAttachment to throw an error for the first attachment
290
- adapter.processAttachment = jest.fn()
291
- .mockRejectedValueOnce(new Error('Processing error'))
292
- .mockResolvedValueOnce(null);
293
- // Mock console.warn to avoid test output noise
294
- const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
295
- // Set up adapter state
296
- adapter.state.toDevRev = {
297
- attachmentsMetadata: {
298
- lastProcessed: 0,
299
- artifactIds: [],
300
- lastProcessedAttachmentsIdsList: [],
301
- },
302
- };
303
- // Act
304
- const result = await adapter['defaultAttachmentsIterator']({
305
- reducedAttachments: mockAttachments,
306
- adapter: adapter,
307
- stream: mockStream,
308
- });
309
- // Assert - both attachments should have been processed
310
- expect(adapter.processAttachment).toHaveBeenCalledTimes(2);
311
- expect(consoleWarnSpy).toHaveBeenCalled();
312
- expect(result).toEqual({});
313
- // Restore console.warn
314
- consoleWarnSpy.mockRestore();
315
- });
316
- });
317
64
  describe('streamAttachments', () => {
318
65
  it('should process all artifact batches successfully', async () => {
319
66
  // Arrange
@@ -341,11 +88,6 @@ describe('WorkerAdapter', () => {
341
88
  });
342
89
  // Mock the initializeRepos method
343
90
  adapter.initializeRepos = jest.fn();
344
- // Mock the defaultAttachmentsReducer and defaultAttachmentsIterator
345
- const mockReducedAttachments = [['batch1']];
346
- adapter['defaultAttachmentsReducer'] = jest.fn().mockReturnValue(mockReducedAttachments);
347
- adapter['defaultAttachmentsIterator'] = jest.fn().mockResolvedValue({});
348
- // Act
349
91
  const result = await adapter.streamAttachments({
350
92
  stream: mockStream,
351
93
  });
@@ -353,9 +95,8 @@ describe('WorkerAdapter', () => {
353
95
  expect(adapter.initializeRepos).toHaveBeenCalledWith([
354
96
  { itemType: 'ssor_attachment' },
355
97
  ]);
98
+ expect(adapter.initializeRepos).toHaveBeenCalledTimes(1);
356
99
  expect(adapter['uploader'].getAttachmentsFromArtifactId).toHaveBeenCalledTimes(2);
357
- expect(adapter['defaultAttachmentsReducer']).toHaveBeenCalledTimes(2);
358
- expect(adapter['defaultAttachmentsIterator']).toHaveBeenCalledTimes(2);
359
100
  // Verify state was updated correctly
360
101
  expect(adapter.state.toDevRev.attachmentsMetadata.artifactIds).toEqual([]);
361
102
  expect(adapter.state.toDevRev.attachmentsMetadata.lastProcessed).toBe(0);
@@ -379,11 +120,7 @@ describe('WorkerAdapter', () => {
379
120
  { url: 'http://example.com/file1.pdf', id: 'attachment1', file_name: 'file1.pdf', parent_id: 'parent1' },
380
121
  ],
381
122
  });
382
- // Mock the required methods
383
123
  adapter.initializeRepos = jest.fn();
384
- const mockReducedAttachments = [['batch1']];
385
- adapter['defaultAttachmentsReducer'] = jest.fn().mockReturnValue(mockReducedAttachments);
386
- adapter['defaultAttachmentsIterator'] = jest.fn().mockResolvedValue({});
387
124
  // Act
388
125
  const result = await adapter.streamAttachments({
389
126
  stream: mockStream,
@@ -391,12 +128,6 @@ describe('WorkerAdapter', () => {
391
128
  });
392
129
  // Assert
393
130
  expect(consoleWarnSpy).toHaveBeenCalledWith('The specified batch size (0) is invalid. Using 1 instead.');
394
- // Verify that the reducer was called with batchSize 50 (not 100)
395
- expect(adapter['defaultAttachmentsReducer']).toHaveBeenCalledWith({
396
- attachments: expect.any(Array),
397
- adapter: adapter,
398
- batchSize: 1,
399
- });
400
131
  expect(result).toBeUndefined();
401
132
  // Restore console.warn
402
133
  consoleWarnSpy.mockRestore();
@@ -421,9 +152,6 @@ describe('WorkerAdapter', () => {
421
152
  });
422
153
  // Mock the required methods
423
154
  adapter.initializeRepos = jest.fn();
424
- const mockReducedAttachments = [['batch1']];
425
- adapter['defaultAttachmentsReducer'] = jest.fn().mockReturnValue(mockReducedAttachments);
426
- adapter['defaultAttachmentsIterator'] = jest.fn().mockResolvedValue({});
427
155
  // Act
428
156
  const result = await adapter.streamAttachments({
429
157
  stream: mockStream,
@@ -431,12 +159,6 @@ describe('WorkerAdapter', () => {
431
159
  });
432
160
  // Assert
433
161
  expect(consoleWarnSpy).toHaveBeenCalledWith('The specified batch size (100) is too large. Using 50 instead.');
434
- // Verify that the reducer was called with batchSize 50 (not 100)
435
- expect(adapter['defaultAttachmentsReducer']).toHaveBeenCalledWith({
436
- attachments: expect.any(Array),
437
- adapter: adapter,
438
- batchSize: 50, // Should be capped at 50
439
- });
440
162
  expect(result).toBeUndefined();
441
163
  // Restore console.warn
442
164
  consoleWarnSpy.mockRestore();
@@ -568,6 +290,12 @@ describe('WorkerAdapter', () => {
568
290
  it('should handle rate limiting from iterator', async () => {
569
291
  // Arrange
570
292
  const mockStream = jest.fn();
293
+ attachments_streaming_pool_1.AttachmentsStreamingPool.mockImplementationOnce(() => {
294
+ return {
295
+ // Return an object with a `streamAll` method that resolves to your desired value.
296
+ streamAll: jest.fn().mockResolvedValue({ delay: 30 }),
297
+ };
298
+ });
571
299
  // Set up adapter state with artifact IDs
572
300
  adapter.state.toDevRev = {
573
301
  attachmentsMetadata: {
@@ -582,10 +310,6 @@ describe('WorkerAdapter', () => {
582
310
  });
583
311
  // Mock methods
584
312
  adapter.initializeRepos = jest.fn();
585
- adapter['defaultAttachmentsReducer'] = jest.fn().mockReturnValue([]);
586
- adapter['defaultAttachmentsIterator'] = jest.fn().mockResolvedValue({
587
- delay: 30,
588
- });
589
313
  // Act
590
314
  const result = await adapter.streamAttachments({
591
315
  stream: mockStream,
@@ -600,6 +324,14 @@ describe('WorkerAdapter', () => {
600
324
  it('should handle error from iterator', async () => {
601
325
  // Arrange
602
326
  const mockStream = jest.fn();
327
+ attachments_streaming_pool_1.AttachmentsStreamingPool.mockImplementationOnce(() => {
328
+ return {
329
+ // Return an object with a `streamAll` method that resolves to your desired value.
330
+ streamAll: jest.fn().mockResolvedValue({
331
+ error: "Mock error",
332
+ }),
333
+ };
334
+ });
603
335
  // Set up adapter state with artifact IDs
604
336
  adapter.state.toDevRev = {
605
337
  attachmentsMetadata: {
@@ -614,51 +346,17 @@ describe('WorkerAdapter', () => {
614
346
  });
615
347
  // Mock methods
616
348
  adapter.initializeRepos = jest.fn();
617
- adapter['defaultAttachmentsReducer'] = jest.fn().mockReturnValue([]);
618
- const mockError = new Error('Iterator error');
619
- adapter['defaultAttachmentsIterator'] = jest.fn().mockResolvedValue({
620
- error: mockError,
621
- });
622
349
  // Act
623
350
  const result = await adapter.streamAttachments({
624
351
  stream: mockStream,
625
352
  });
626
353
  // Assert
627
354
  expect(result).toEqual({
628
- error: mockError,
355
+ error: "Mock error",
629
356
  });
630
357
  // The artifactIds array should remain unchanged
631
358
  expect(adapter.state.toDevRev.attachmentsMetadata.artifactIds).toEqual(['artifact1']);
632
359
  });
633
- it('should continue processing from last processed attachment for the current artifact', async () => {
634
- const mockStream = jest.fn();
635
- adapter.state.toDevRev = {
636
- attachmentsMetadata: {
637
- artifactIds: ['artifact1'],
638
- lastProcessed: 0,
639
- lastProcessedAttachmentsIdsList: ['attachment1', 'attachment2'],
640
- },
641
- };
642
- adapter['uploader'].getAttachmentsFromArtifactId = jest.fn().mockResolvedValue({
643
- attachments: [
644
- { url: 'http://example.com/file1.pdf', id: 'attachment1', file_name: 'file1.pdf', parent_id: 'parent1' },
645
- { url: 'http://example.com/file2.pdf', id: 'attachment2', file_name: 'file2.pdf', parent_id: 'parent2' },
646
- { url: 'http://example.com/file3.pdf', id: 'attachment3', file_name: 'file3.pdf', parent_id: 'parent3' },
647
- ],
648
- });
649
- adapter.processAttachment = jest.fn().mockResolvedValue(null);
650
- await adapter.streamAttachments({
651
- stream: mockStream,
652
- batchSize: 3,
653
- });
654
- expect(adapter.processAttachment).toHaveBeenCalledTimes(1);
655
- expect(adapter.processAttachment).toHaveBeenCalledWith({
656
- url: 'http://example.com/file3.pdf',
657
- id: 'attachment3',
658
- file_name: 'file3.pdf',
659
- parent_id: 'parent3'
660
- }, mockStream);
661
- });
662
360
  it('should reset lastProcessed and attachment IDs list after processing all artifacts', async () => {
663
361
  const mockStream = jest.fn();
664
362
  adapter.state.toDevRev = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devrev/ts-adaas",
3
- "version": "1.7.2-beta.0",
3
+ "version": "1.8.0-beta.0",
4
4
  "description": "Typescript library containing the ADaaS(AirDrop as a Service) control protocol.",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",