pdfdancer-client-typescript 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/image-builder.d.ts +1 -0
- package/dist/image-builder.d.ts.map +1 -1
- package/dist/image-builder.js +4 -0
- package/dist/image-builder.js.map +1 -1
- package/dist/pdfdancer_v1.d.ts +6 -0
- package/dist/pdfdancer_v1.d.ts.map +1 -1
- package/dist/pdfdancer_v1.js +52 -3
- package/dist/pdfdancer_v1.js.map +1 -1
- package/package.json +18 -2
- package/.claude/commands/discuss.md +0 -4
- package/.eslintrc.js +0 -27
- package/.github/workflows/ci.yml +0 -86
- package/.github/workflows/daily-tests.yml +0 -54
- package/NOTICE +0 -8
- package/docs/openapi.yml +0 -2640
- package/fixtures/DancingScript-Regular.ttf +0 -0
- package/fixtures/Empty.pdf +0 -0
- package/fixtures/JetBrainsMono-Regular.ttf +0 -0
- package/fixtures/ObviouslyAwesome.pdf +0 -0
- package/fixtures/README.md +0 -23
- package/fixtures/Showcase.pdf +0 -0
- package/fixtures/basic-paths.pdf +0 -0
- package/fixtures/form-xobject-example.pdf +0 -0
- package/fixtures/logo-80.png +0 -0
- package/fixtures/mixed-form-types.pdf +0 -0
- package/jest.config.js +0 -27
- package/scripts/release.js +0 -91
- package/scripts/test-release.js +0 -59
- package/src/__tests__/assertions.ts +0 -12
- package/src/__tests__/client-v1.test.ts +0 -70
- package/src/__tests__/e2e/acroform.test.ts +0 -166
- package/src/__tests__/e2e/context-manager-showcase.test.ts +0 -267
- package/src/__tests__/e2e/create-new.test.ts +0 -133
- package/src/__tests__/e2e/form_x_object.test.ts +0 -87
- package/src/__tests__/e2e/image-showcase.test.ts +0 -133
- package/src/__tests__/e2e/image.test.ts +0 -147
- package/src/__tests__/e2e/line-showcase.test.ts +0 -113
- package/src/__tests__/e2e/line.test.ts +0 -189
- package/src/__tests__/e2e/page-showcase.test.ts +0 -154
- package/src/__tests__/e2e/page.test.ts +0 -47
- package/src/__tests__/e2e/paragraph-showcase.test.ts +0 -515
- package/src/__tests__/e2e/paragraph.test.ts +0 -683
- package/src/__tests__/e2e/path.test.ts +0 -108
- package/src/__tests__/e2e/pdf-assertions.ts +0 -248
- package/src/__tests__/e2e/pdfdancer-showcase.test.ts +0 -40
- package/src/__tests__/e2e/snapshot-showcase.test.ts +0 -158
- package/src/__tests__/e2e/snapshot.test.ts +0 -296
- package/src/__tests__/e2e/test-helpers.ts +0 -152
- package/src/__tests__/e2e/token_from_env.test.ts +0 -80
- package/src/__tests__/fingerprint.test.ts +0 -36
- package/src/__tests__/retry-mechanism.test.ts +0 -420
- package/src/__tests__/standard-fonts.test.ts +0 -87
- package/src/__tests__/url-builder.test.ts +0 -44
- package/src/exceptions.ts +0 -63
- package/src/fingerprint.ts +0 -182
- package/src/image-builder.ts +0 -59
- package/src/index.ts +0 -49
- package/src/models.ts +0 -1051
- package/src/page-builder.ts +0 -130
- package/src/paragraph-builder.ts +0 -644
- package/src/pdfdancer_v1.ts +0 -2224
- package/src/types.ts +0 -407
- package/tsconfig.json +0 -20
- package/update-api-spec.sh +0 -3
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the retry mechanism in REST API calls.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {PDFDancer} from '../pdfdancer_v1';
|
|
6
|
-
import {RetryConfig} from '../pdfdancer_v1';
|
|
7
|
-
|
|
8
|
-
// Mock the fetch function
|
|
9
|
-
global.fetch = jest.fn();
|
|
10
|
-
|
|
11
|
-
// Helper to create a mock response
|
|
12
|
-
function createMockResponse(status: number, body: unknown = {}): Response {
|
|
13
|
-
const bodyString = typeof body === 'string' ? body : JSON.stringify(body);
|
|
14
|
-
return {
|
|
15
|
-
ok: status >= 200 && status < 300,
|
|
16
|
-
status,
|
|
17
|
-
statusText: status === 200 ? 'OK' : 'Error',
|
|
18
|
-
headers: new Headers(),
|
|
19
|
-
text: async () => bodyString,
|
|
20
|
-
json: async () => typeof body === 'object' ? body : JSON.parse(body as string),
|
|
21
|
-
arrayBuffer: async () => new ArrayBuffer(0),
|
|
22
|
-
blob: async () => new Blob(),
|
|
23
|
-
formData: async () => new FormData(),
|
|
24
|
-
clone: function() {
|
|
25
|
-
return createMockResponse(status, body);
|
|
26
|
-
},
|
|
27
|
-
body: null,
|
|
28
|
-
bodyUsed: false,
|
|
29
|
-
redirected: false,
|
|
30
|
-
type: 'basic',
|
|
31
|
-
url: ''
|
|
32
|
-
} as Response;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
describe('Retry Mechanism', () => {
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
jest.clearAllMocks();
|
|
38
|
-
(global.fetch as jest.Mock).mockReset();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('RetryConfig', () => {
|
|
42
|
-
test('should use default retry config when none provided', async () => {
|
|
43
|
-
// Mock successful responses
|
|
44
|
-
(global.fetch as jest.Mock)
|
|
45
|
-
.mockResolvedValueOnce(createMockResponse(200, {token: 'test-token'}))
|
|
46
|
-
.mockResolvedValueOnce(createMockResponse(200, 'session-123'));
|
|
47
|
-
|
|
48
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]); // PDF header
|
|
49
|
-
|
|
50
|
-
// Should use default retry config
|
|
51
|
-
await expect(PDFDancer.open(pdfData, 'test-token')).resolves.toBeDefined();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('should accept custom retry config', async () => {
|
|
55
|
-
const customRetryConfig: RetryConfig = {
|
|
56
|
-
maxRetries: 5,
|
|
57
|
-
initialDelay: 500,
|
|
58
|
-
maxDelay: 5000,
|
|
59
|
-
retryableStatusCodes: [429, 503],
|
|
60
|
-
retryOnNetworkError: true,
|
|
61
|
-
backoffMultiplier: 3,
|
|
62
|
-
useJitter: false
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
(global.fetch as jest.Mock)
|
|
66
|
-
.mockResolvedValueOnce(createMockResponse(200, {token: 'test-token'}))
|
|
67
|
-
.mockResolvedValueOnce(createMockResponse(200, 'session-123'));
|
|
68
|
-
|
|
69
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
70
|
-
|
|
71
|
-
await expect(
|
|
72
|
-
PDFDancer.open(pdfData, 'test-token', undefined, undefined, customRetryConfig)
|
|
73
|
-
).resolves.toBeDefined();
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe('Retryable Status Codes', () => {
|
|
78
|
-
test('should retry on 429 (rate limit)', async () => {
|
|
79
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
80
|
-
|
|
81
|
-
// First call returns 429, second call succeeds
|
|
82
|
-
// When token is provided, no token fetch call is made
|
|
83
|
-
mockFetch
|
|
84
|
-
.mockResolvedValueOnce(createMockResponse(429, 'Rate limit exceeded'))
|
|
85
|
-
.mockResolvedValueOnce(createMockResponse(200, 'session-123'));
|
|
86
|
-
|
|
87
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
88
|
-
const retryConfig: RetryConfig = {
|
|
89
|
-
maxRetries: 2,
|
|
90
|
-
initialDelay: 10, // Use short delay for tests
|
|
91
|
-
useJitter: false
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
await expect(
|
|
95
|
-
PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig)
|
|
96
|
-
).resolves.toBeDefined();
|
|
97
|
-
|
|
98
|
-
// Should have made 2 fetch calls: 1 failed session + 1 retry
|
|
99
|
-
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('should retry on 500 (server error)', async () => {
|
|
103
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
104
|
-
|
|
105
|
-
mockFetch
|
|
106
|
-
.mockResolvedValueOnce(createMockResponse(500, 'Internal server error'))
|
|
107
|
-
.mockResolvedValueOnce(createMockResponse(200, 'session-123'));
|
|
108
|
-
|
|
109
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
110
|
-
const retryConfig: RetryConfig = {
|
|
111
|
-
maxRetries: 2,
|
|
112
|
-
initialDelay: 10,
|
|
113
|
-
useJitter: false
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
await expect(
|
|
117
|
-
PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig)
|
|
118
|
-
).resolves.toBeDefined();
|
|
119
|
-
|
|
120
|
-
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('should retry on 502, 503, 504', async () => {
|
|
124
|
-
for (const statusCode of [502, 503, 504]) {
|
|
125
|
-
jest.clearAllMocks();
|
|
126
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
127
|
-
|
|
128
|
-
mockFetch
|
|
129
|
-
.mockResolvedValueOnce(createMockResponse(statusCode, 'Service unavailable'))
|
|
130
|
-
.mockResolvedValueOnce(createMockResponse(200, 'session-123'));
|
|
131
|
-
|
|
132
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
133
|
-
const retryConfig: RetryConfig = {
|
|
134
|
-
maxRetries: 2,
|
|
135
|
-
initialDelay: 10,
|
|
136
|
-
useJitter: false
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
await expect(
|
|
140
|
-
PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig)
|
|
141
|
-
).resolves.toBeDefined();
|
|
142
|
-
|
|
143
|
-
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test('should NOT retry on 400 (bad request)', async () => {
|
|
148
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
149
|
-
|
|
150
|
-
mockFetch
|
|
151
|
-
.mockResolvedValueOnce(createMockResponse(400, 'Bad request'));
|
|
152
|
-
|
|
153
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
154
|
-
const retryConfig: RetryConfig = {
|
|
155
|
-
maxRetries: 3,
|
|
156
|
-
initialDelay: 10,
|
|
157
|
-
useJitter: false
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
await expect(
|
|
161
|
-
PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig)
|
|
162
|
-
).rejects.toThrow();
|
|
163
|
-
|
|
164
|
-
// Should only make 1 call (no retries for 400)
|
|
165
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test('should NOT retry on 404 (not found)', async () => {
|
|
169
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
170
|
-
|
|
171
|
-
mockFetch
|
|
172
|
-
.mockResolvedValueOnce(createMockResponse(404, 'Not found'));
|
|
173
|
-
|
|
174
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
175
|
-
const retryConfig: RetryConfig = {
|
|
176
|
-
maxRetries: 3,
|
|
177
|
-
initialDelay: 10,
|
|
178
|
-
useJitter: false
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
await expect(
|
|
182
|
-
PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig)
|
|
183
|
-
).rejects.toThrow();
|
|
184
|
-
|
|
185
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
describe('Network Errors', () => {
|
|
190
|
-
test('should retry on network errors when retryOnNetworkError is true', async () => {
|
|
191
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
192
|
-
|
|
193
|
-
mockFetch
|
|
194
|
-
.mockRejectedValueOnce(new Error('Network error'))
|
|
195
|
-
.mockResolvedValueOnce(createMockResponse(200, 'session-123'));
|
|
196
|
-
|
|
197
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
198
|
-
const retryConfig: RetryConfig = {
|
|
199
|
-
maxRetries: 2,
|
|
200
|
-
initialDelay: 10,
|
|
201
|
-
retryOnNetworkError: true,
|
|
202
|
-
useJitter: false
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
await expect(
|
|
206
|
-
PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig)
|
|
207
|
-
).resolves.toBeDefined();
|
|
208
|
-
|
|
209
|
-
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test('should NOT retry on network errors when retryOnNetworkError is false', async () => {
|
|
213
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
214
|
-
|
|
215
|
-
mockFetch
|
|
216
|
-
.mockRejectedValueOnce(new Error('Network error'));
|
|
217
|
-
|
|
218
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
219
|
-
const retryConfig: RetryConfig = {
|
|
220
|
-
maxRetries: 3,
|
|
221
|
-
initialDelay: 10,
|
|
222
|
-
retryOnNetworkError: false,
|
|
223
|
-
useJitter: false
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
await expect(
|
|
227
|
-
PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig)
|
|
228
|
-
).rejects.toThrow('Network error');
|
|
229
|
-
|
|
230
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
describe('Max Retries', () => {
|
|
235
|
-
test('should respect maxRetries limit', async () => {
|
|
236
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
237
|
-
|
|
238
|
-
// All calls fail with 503
|
|
239
|
-
mockFetch
|
|
240
|
-
.mockResolvedValue(createMockResponse(503, 'Service unavailable'));
|
|
241
|
-
|
|
242
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
243
|
-
const retryConfig: RetryConfig = {
|
|
244
|
-
maxRetries: 3,
|
|
245
|
-
initialDelay: 10,
|
|
246
|
-
useJitter: false
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
await expect(
|
|
250
|
-
PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig)
|
|
251
|
-
).rejects.toThrow();
|
|
252
|
-
|
|
253
|
-
// Should make: 1 initial session call + 3 retries = 4 total
|
|
254
|
-
expect(mockFetch).toHaveBeenCalledTimes(4);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test('should not retry when maxRetries is 0', async () => {
|
|
258
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
259
|
-
|
|
260
|
-
mockFetch
|
|
261
|
-
.mockResolvedValueOnce(createMockResponse(503, 'Service unavailable'));
|
|
262
|
-
|
|
263
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
264
|
-
const retryConfig: RetryConfig = {
|
|
265
|
-
maxRetries: 0,
|
|
266
|
-
initialDelay: 10,
|
|
267
|
-
useJitter: false
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
await expect(
|
|
271
|
-
PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig)
|
|
272
|
-
).rejects.toThrow();
|
|
273
|
-
|
|
274
|
-
// Should only make 1 call (no retries)
|
|
275
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
describe('Exponential Backoff', () => {
|
|
280
|
-
test('should apply exponential backoff between retries', async () => {
|
|
281
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
282
|
-
const delays: number[] = [];
|
|
283
|
-
const originalSetTimeout = global.setTimeout;
|
|
284
|
-
|
|
285
|
-
// Mock setTimeout to capture delays
|
|
286
|
-
global.setTimeout = jest.fn((callback: () => void, delay?: number) => {
|
|
287
|
-
if (delay) delays.push(delay);
|
|
288
|
-
return originalSetTimeout(callback, 0);
|
|
289
|
-
}) as unknown as typeof setTimeout;
|
|
290
|
-
|
|
291
|
-
mockFetch
|
|
292
|
-
.mockResolvedValueOnce(createMockResponse(503, 'Unavailable'))
|
|
293
|
-
.mockResolvedValueOnce(createMockResponse(503, 'Unavailable'))
|
|
294
|
-
.mockResolvedValueOnce(createMockResponse(200, 'session-123'));
|
|
295
|
-
|
|
296
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
297
|
-
const retryConfig: RetryConfig = {
|
|
298
|
-
maxRetries: 3,
|
|
299
|
-
initialDelay: 100,
|
|
300
|
-
backoffMultiplier: 2,
|
|
301
|
-
useJitter: false
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
await PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig);
|
|
305
|
-
|
|
306
|
-
// Restore original setTimeout
|
|
307
|
-
global.setTimeout = originalSetTimeout;
|
|
308
|
-
|
|
309
|
-
// Should have 2 delays (for 2 retries that eventually succeeded)
|
|
310
|
-
expect(delays.length).toBeGreaterThanOrEqual(2);
|
|
311
|
-
|
|
312
|
-
// First retry delay should be around initialDelay (100ms)
|
|
313
|
-
expect(delays[0]).toBeGreaterThanOrEqual(100);
|
|
314
|
-
expect(delays[0]).toBeLessThan(110);
|
|
315
|
-
|
|
316
|
-
// Second retry delay should be around initialDelay * backoffMultiplier (200ms)
|
|
317
|
-
expect(delays[1]).toBeGreaterThanOrEqual(200);
|
|
318
|
-
expect(delays[1]).toBeLessThan(210);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
test('should cap delay at maxDelay', async () => {
|
|
322
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
323
|
-
const delays: number[] = [];
|
|
324
|
-
const originalSetTimeout = global.setTimeout;
|
|
325
|
-
|
|
326
|
-
global.setTimeout = jest.fn((callback: () => void, delay?: number) => {
|
|
327
|
-
if (delay) delays.push(delay);
|
|
328
|
-
return originalSetTimeout(callback, 0);
|
|
329
|
-
}) as unknown as typeof setTimeout;
|
|
330
|
-
|
|
331
|
-
mockFetch
|
|
332
|
-
.mockResolvedValueOnce(createMockResponse(503, 'Unavailable'))
|
|
333
|
-
.mockResolvedValueOnce(createMockResponse(503, 'Unavailable'))
|
|
334
|
-
.mockResolvedValueOnce(createMockResponse(503, 'Unavailable'))
|
|
335
|
-
.mockResolvedValueOnce(createMockResponse(200, 'session-123'));
|
|
336
|
-
|
|
337
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
338
|
-
const retryConfig: RetryConfig = {
|
|
339
|
-
maxRetries: 4,
|
|
340
|
-
initialDelay: 1000,
|
|
341
|
-
maxDelay: 2000,
|
|
342
|
-
backoffMultiplier: 2,
|
|
343
|
-
useJitter: false
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
await PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig);
|
|
347
|
-
|
|
348
|
-
global.setTimeout = originalSetTimeout;
|
|
349
|
-
|
|
350
|
-
// All delays should be capped at maxDelay (2000ms)
|
|
351
|
-
delays.forEach(delay => {
|
|
352
|
-
expect(delay).toBeLessThanOrEqual(2000);
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
describe('Jitter', () => {
|
|
358
|
-
test('should apply jitter when useJitter is true', async () => {
|
|
359
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
360
|
-
const delays: number[] = [];
|
|
361
|
-
const originalSetTimeout = global.setTimeout;
|
|
362
|
-
|
|
363
|
-
global.setTimeout = jest.fn((callback: () => void, delay?: number) => {
|
|
364
|
-
if (delay) delays.push(delay);
|
|
365
|
-
return originalSetTimeout(callback, 0);
|
|
366
|
-
}) as unknown as typeof setTimeout;
|
|
367
|
-
|
|
368
|
-
mockFetch
|
|
369
|
-
.mockResolvedValueOnce(createMockResponse(503, 'Unavailable'))
|
|
370
|
-
.mockResolvedValueOnce(createMockResponse(200, 'session-123'));
|
|
371
|
-
|
|
372
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
373
|
-
const retryConfig: RetryConfig = {
|
|
374
|
-
maxRetries: 2,
|
|
375
|
-
initialDelay: 1000,
|
|
376
|
-
backoffMultiplier: 2,
|
|
377
|
-
useJitter: true
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
await PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig);
|
|
381
|
-
|
|
382
|
-
global.setTimeout = originalSetTimeout;
|
|
383
|
-
|
|
384
|
-
// With jitter, delay should be between 50% and 100% of calculated delay
|
|
385
|
-
// For first retry: should be between 500 (50% of 1000) and 1000
|
|
386
|
-
expect(delays[0]).toBeGreaterThanOrEqual(500);
|
|
387
|
-
expect(delays[0]).toBeLessThanOrEqual(1000);
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
test('should not apply jitter when useJitter is false', async () => {
|
|
391
|
-
const mockFetch = global.fetch as jest.Mock;
|
|
392
|
-
const delays: number[] = [];
|
|
393
|
-
const originalSetTimeout = global.setTimeout;
|
|
394
|
-
|
|
395
|
-
global.setTimeout = jest.fn((callback: () => void, delay?: number) => {
|
|
396
|
-
if (delay) delays.push(delay);
|
|
397
|
-
return originalSetTimeout(callback, 0);
|
|
398
|
-
}) as unknown as typeof setTimeout;
|
|
399
|
-
|
|
400
|
-
mockFetch
|
|
401
|
-
.mockResolvedValueOnce(createMockResponse(503, 'Unavailable'))
|
|
402
|
-
.mockResolvedValueOnce(createMockResponse(200, 'session-123'));
|
|
403
|
-
|
|
404
|
-
const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
|
|
405
|
-
const retryConfig: RetryConfig = {
|
|
406
|
-
maxRetries: 2,
|
|
407
|
-
initialDelay: 1000,
|
|
408
|
-
backoffMultiplier: 2,
|
|
409
|
-
useJitter: false
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
await PDFDancer.open(pdfData, 'test-token', undefined, undefined, retryConfig);
|
|
413
|
-
|
|
414
|
-
global.setTimeout = originalSetTimeout;
|
|
415
|
-
|
|
416
|
-
// Without jitter, delay should be exactly the calculated value
|
|
417
|
-
expect(delays[0]).toBe(1000);
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
});
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for StandardFonts enum.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { StandardFonts, Font } from '../models';
|
|
6
|
-
|
|
7
|
-
describe('StandardFonts', () => {
|
|
8
|
-
test('should have all 14 standard PDF fonts', () => {
|
|
9
|
-
const standardFonts = Object.values(StandardFonts);
|
|
10
|
-
expect(standardFonts).toHaveLength(14);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
test('should have Times family fonts', () => {
|
|
14
|
-
expect(StandardFonts.TIMES_ROMAN).toBe('Times-Roman');
|
|
15
|
-
expect(StandardFonts.TIMES_BOLD).toBe('Times-Bold');
|
|
16
|
-
expect(StandardFonts.TIMES_ITALIC).toBe('Times-Italic');
|
|
17
|
-
expect(StandardFonts.TIMES_BOLD_ITALIC).toBe('Times-BoldItalic');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('should have Helvetica family fonts', () => {
|
|
21
|
-
expect(StandardFonts.HELVETICA).toBe('Helvetica');
|
|
22
|
-
expect(StandardFonts.HELVETICA_BOLD).toBe('Helvetica-Bold');
|
|
23
|
-
expect(StandardFonts.HELVETICA_OBLIQUE).toBe('Helvetica-Oblique');
|
|
24
|
-
expect(StandardFonts.HELVETICA_BOLD_OBLIQUE).toBe('Helvetica-BoldOblique');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('should have Courier family fonts', () => {
|
|
28
|
-
expect(StandardFonts.COURIER).toBe('Courier');
|
|
29
|
-
expect(StandardFonts.COURIER_BOLD).toBe('Courier-Bold');
|
|
30
|
-
expect(StandardFonts.COURIER_OBLIQUE).toBe('Courier-Oblique');
|
|
31
|
-
expect(StandardFonts.COURIER_BOLD_OBLIQUE).toBe('Courier-BoldOblique');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('should have Symbol font', () => {
|
|
35
|
-
expect(StandardFonts.SYMBOL).toBe('Symbol');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('should have ZapfDingbats font', () => {
|
|
39
|
-
expect(StandardFonts.ZAPF_DINGBATS).toBe('ZapfDingbats');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('should work with Font class', () => {
|
|
43
|
-
const font = new Font(StandardFonts.HELVETICA, 12);
|
|
44
|
-
expect(font.name).toBe('Helvetica');
|
|
45
|
-
expect(font.size).toBe(12);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('should work with all standard fonts in Font class', () => {
|
|
49
|
-
for (const standardFont of Object.values(StandardFonts)) {
|
|
50
|
-
const font = new Font(standardFont, 10);
|
|
51
|
-
expect(font.name).toBe(standardFont);
|
|
52
|
-
expect(font.size).toBe(10);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('should have correct font name formats', () => {
|
|
57
|
-
expect(StandardFonts.TIMES_BOLD_ITALIC).toMatch(/^Times-[A-Za-z]+$/);
|
|
58
|
-
expect(StandardFonts.HELVETICA_BOLD_OBLIQUE).toMatch(/^Helvetica-[A-Za-z]+$/);
|
|
59
|
-
expect(StandardFonts.COURIER_BOLD_OBLIQUE).toMatch(/^Courier-[A-Za-z]+$/);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('should have unique font names', () => {
|
|
63
|
-
const fontNames = Object.values(StandardFonts);
|
|
64
|
-
const uniqueFontNames = new Set(fontNames);
|
|
65
|
-
expect(uniqueFontNames.size).toBe(fontNames.length);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('enum keys should match convention', () => {
|
|
69
|
-
expect(Object.keys(StandardFonts)).toContain('TIMES_ROMAN');
|
|
70
|
-
expect(Object.keys(StandardFonts)).toContain('HELVETICA');
|
|
71
|
-
expect(Object.keys(StandardFonts)).toContain('COURIER');
|
|
72
|
-
expect(Object.keys(StandardFonts)).toContain('SYMBOL');
|
|
73
|
-
expect(Object.keys(StandardFonts)).toContain('ZAPF_DINGBATS');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('should be compatible with string type', () => {
|
|
77
|
-
const fontName: string = StandardFonts.HELVETICA;
|
|
78
|
-
expect(typeof fontName).toBe('string');
|
|
79
|
-
expect(fontName).toBe('Helvetica');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('standard fonts should not have spaces', () => {
|
|
83
|
-
for (const standardFont of Object.values(StandardFonts)) {
|
|
84
|
-
expect(standardFont).not.toContain(' ');
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for URL building helper
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
describe('URL Building', () => {
|
|
6
|
-
// Helper function to simulate the _buildUrl logic
|
|
7
|
-
function buildUrl(baseUrl: string, path: string): string {
|
|
8
|
-
const base = baseUrl.replace(/\/+$/, '');
|
|
9
|
-
const endpoint = path.replace(/^\/+/, '');
|
|
10
|
-
return `${base}/${endpoint}`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
test('handles base URL with trailing slash', () => {
|
|
14
|
-
expect(buildUrl('http://localhost:8080/', '/session/create')).toBe('http://localhost:8080/session/create');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test('handles base URL without trailing slash', () => {
|
|
18
|
-
expect(buildUrl('http://localhost:8080', '/session/create')).toBe('http://localhost:8080/session/create');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test('handles path without leading slash', () => {
|
|
22
|
-
expect(buildUrl('http://localhost:8080', 'session/create')).toBe('http://localhost:8080/session/create');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('handles both with slashes', () => {
|
|
26
|
-
expect(buildUrl('http://localhost:8080/', '/session/create')).toBe('http://localhost:8080/session/create');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('handles neither with slashes', () => {
|
|
30
|
-
expect(buildUrl('http://localhost:8080', 'session/create')).toBe('http://localhost:8080/session/create');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('handles multiple trailing slashes', () => {
|
|
34
|
-
expect(buildUrl('http://localhost:8080///', '///session/create')).toBe('http://localhost:8080/session/create');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('handles production URL', () => {
|
|
38
|
-
expect(buildUrl('https://api.pdfdancer.com/', '/pdf/find')).toBe('https://api.pdfdancer.com/pdf/find');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('handles base URL with path', () => {
|
|
42
|
-
expect(buildUrl('http://localhost:8080/api/', '/session/create')).toBe('http://localhost:8080/api/session/create');
|
|
43
|
-
});
|
|
44
|
-
});
|
package/src/exceptions.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exception classes for the PDFDancer TypeScript client.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Base exception for all PDFDancer client errors.
|
|
7
|
-
*/
|
|
8
|
-
export class PdfDancerException extends Error {
|
|
9
|
-
public readonly cause?: Error;
|
|
10
|
-
|
|
11
|
-
constructor(message: string, cause?: Error) {
|
|
12
|
-
super(message);
|
|
13
|
-
this.name = 'PdfDancerException';
|
|
14
|
-
this.cause = cause;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Exception raised when a required font is not found or available.
|
|
20
|
-
*/
|
|
21
|
-
export class FontNotFoundException extends PdfDancerException {
|
|
22
|
-
constructor(message: string) {
|
|
23
|
-
super(`Font not found: ${message}`);
|
|
24
|
-
this.name = 'FontNotFoundException';
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Exception raised for HTTP client errors during API communication.
|
|
30
|
-
* Wraps fetch exceptions and HTTP errors from the API.
|
|
31
|
-
*/
|
|
32
|
-
export class HttpClientException extends PdfDancerException {
|
|
33
|
-
public readonly response?: Response;
|
|
34
|
-
public readonly statusCode?: number;
|
|
35
|
-
|
|
36
|
-
constructor(message: string, response?: Response, cause?: Error) {
|
|
37
|
-
super(message, cause);
|
|
38
|
-
this.name = 'HttpClientException';
|
|
39
|
-
this.response = response;
|
|
40
|
-
this.statusCode = response?.status;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Exception raised for session-related errors.
|
|
46
|
-
* Occurs when session creation fails or session is invalid.
|
|
47
|
-
*/
|
|
48
|
-
export class SessionException extends PdfDancerException {
|
|
49
|
-
constructor(message: string, cause?: Error) {
|
|
50
|
-
super(message, cause);
|
|
51
|
-
this.name = 'SessionException';
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Exception raised for input validation errors.
|
|
57
|
-
*/
|
|
58
|
-
export class ValidationException extends PdfDancerException {
|
|
59
|
-
constructor(message: string) {
|
|
60
|
-
super(message);
|
|
61
|
-
this.name = 'ValidationException';
|
|
62
|
-
}
|
|
63
|
-
}
|