@bernierllc/contentful-cma-client 1.0.2
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/.eslintrc.cjs +34 -0
- package/README.md +441 -0
- package/dist/ContentfulCMAClient.d.ts +119 -0
- package/dist/ContentfulCMAClient.js +538 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.js +9 -0
- package/jest.config.cjs +31 -0
- package/package.json +44 -0
- package/src/ContentfulCMAClient.ts +694 -0
- package/src/__tests__/ContentfulCMAClient.test.ts +841 -0
- package/src/index.ts +16 -0
- package/src/types.ts +51 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ContentfulCMAClient } from '../ContentfulCMAClient';
|
|
10
|
+
import * as indexExports from '../index';
|
|
11
|
+
import contentfulManagement from 'contentful-management';
|
|
12
|
+
import {
|
|
13
|
+
ContentfulEntry,
|
|
14
|
+
ContentfulAsset,
|
|
15
|
+
ContentfulContentType
|
|
16
|
+
} from '@bernierllc/contentful-types';
|
|
17
|
+
|
|
18
|
+
// Mock contentful-management
|
|
19
|
+
jest.mock('contentful-management', () => ({
|
|
20
|
+
__esModule: true,
|
|
21
|
+
default: {
|
|
22
|
+
createClient: jest.fn()
|
|
23
|
+
}
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Mock logger
|
|
27
|
+
jest.mock('@bernierllc/logger', () => ({
|
|
28
|
+
Logger: jest.fn().mockImplementation(() => ({
|
|
29
|
+
info: jest.fn(),
|
|
30
|
+
debug: jest.fn(),
|
|
31
|
+
warn: jest.fn(),
|
|
32
|
+
error: jest.fn()
|
|
33
|
+
})),
|
|
34
|
+
LogLevel: {
|
|
35
|
+
INFO: 'info',
|
|
36
|
+
DEBUG: 'debug',
|
|
37
|
+
WARN: 'warn',
|
|
38
|
+
ERROR: 'error'
|
|
39
|
+
},
|
|
40
|
+
ConsoleTransport: jest.fn()
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
describe('Index Exports', () => {
|
|
44
|
+
it('should export ContentfulCMAClient', () => {
|
|
45
|
+
expect(indexExports.ContentfulCMAClient).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should export types', () => {
|
|
49
|
+
expect(indexExports).toHaveProperty('ContentfulCMAClient');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('ContentfulCMAClient', () => {
|
|
54
|
+
let client: ContentfulCMAClient;
|
|
55
|
+
let mockPlainClient: jest.Mocked<any>;
|
|
56
|
+
|
|
57
|
+
const mockConfig = {
|
|
58
|
+
accessToken: 'test-token',
|
|
59
|
+
spaceId: 'test-space',
|
|
60
|
+
environmentId: 'master'
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const mockEntry: Partial<ContentfulEntry> = {
|
|
64
|
+
sys: {
|
|
65
|
+
type: 'Entry',
|
|
66
|
+
id: 'entry-123',
|
|
67
|
+
version: 1,
|
|
68
|
+
space: { sys: { type: 'Link', linkType: 'Space', id: 'test-space' } },
|
|
69
|
+
environment: { sys: { type: 'Link', linkType: 'Environment', id: 'master' } },
|
|
70
|
+
contentType: { sys: { type: 'Link', linkType: 'ContentType', id: 'blogPost' } },
|
|
71
|
+
createdAt: '2025-01-01T00:00:00Z',
|
|
72
|
+
updatedAt: '2025-01-01T00:00:00Z'
|
|
73
|
+
},
|
|
74
|
+
fields: {
|
|
75
|
+
title: { 'en-US': 'Test Entry' }
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const mockAsset: Partial<ContentfulAsset> = {
|
|
80
|
+
sys: {
|
|
81
|
+
type: 'Asset',
|
|
82
|
+
id: 'asset-123',
|
|
83
|
+
version: 1,
|
|
84
|
+
space: { sys: { type: 'Link', linkType: 'Space', id: 'test-space' } },
|
|
85
|
+
environment: { sys: { type: 'Link', linkType: 'Environment', id: 'master' } },
|
|
86
|
+
createdAt: '2025-01-01T00:00:00Z',
|
|
87
|
+
updatedAt: '2025-01-01T00:00:00Z'
|
|
88
|
+
},
|
|
89
|
+
fields: {
|
|
90
|
+
title: { 'en-US': 'Test Asset' },
|
|
91
|
+
file: {
|
|
92
|
+
'en-US': {
|
|
93
|
+
url: 'https://example.com/test.jpg',
|
|
94
|
+
details: { size: 1024 },
|
|
95
|
+
fileName: 'test.jpg',
|
|
96
|
+
contentType: 'image/jpeg'
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const mockContentType: Partial<ContentfulContentType> = {
|
|
103
|
+
sys: {
|
|
104
|
+
type: 'ContentType',
|
|
105
|
+
id: 'blogPost',
|
|
106
|
+
version: 1,
|
|
107
|
+
space: { sys: { type: 'Link', linkType: 'Space', id: 'test-space' } },
|
|
108
|
+
createdAt: '2025-01-01T00:00:00Z',
|
|
109
|
+
updatedAt: '2025-01-01T00:00:00Z'
|
|
110
|
+
},
|
|
111
|
+
name: 'Blog Post',
|
|
112
|
+
fields: [
|
|
113
|
+
{
|
|
114
|
+
id: 'title',
|
|
115
|
+
name: 'Title',
|
|
116
|
+
type: 'Symbol',
|
|
117
|
+
required: true,
|
|
118
|
+
localized: false,
|
|
119
|
+
disabled: false,
|
|
120
|
+
omitted: false
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
jest.clearAllMocks();
|
|
127
|
+
|
|
128
|
+
// Create mock plain client
|
|
129
|
+
mockPlainClient = {
|
|
130
|
+
entry: {
|
|
131
|
+
get: jest.fn(),
|
|
132
|
+
getMany: jest.fn(),
|
|
133
|
+
create: jest.fn(),
|
|
134
|
+
update: jest.fn(),
|
|
135
|
+
publish: jest.fn(),
|
|
136
|
+
unpublish: jest.fn(),
|
|
137
|
+
archive: jest.fn(),
|
|
138
|
+
unarchive: jest.fn(),
|
|
139
|
+
delete: jest.fn()
|
|
140
|
+
},
|
|
141
|
+
asset: {
|
|
142
|
+
get: jest.fn(),
|
|
143
|
+
getMany: jest.fn(),
|
|
144
|
+
create: jest.fn(),
|
|
145
|
+
processForLocale: jest.fn(),
|
|
146
|
+
publish: jest.fn(),
|
|
147
|
+
unpublish: jest.fn(),
|
|
148
|
+
delete: jest.fn()
|
|
149
|
+
},
|
|
150
|
+
contentType: {
|
|
151
|
+
get: jest.fn(),
|
|
152
|
+
getMany: jest.fn()
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Mock contentfulManagement.createClient
|
|
157
|
+
(contentfulManagement.createClient as jest.Mock).mockReturnValue(mockPlainClient);
|
|
158
|
+
|
|
159
|
+
// Create client
|
|
160
|
+
client = new ContentfulCMAClient(mockConfig);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('Constructor', () => {
|
|
164
|
+
it('should initialize with provided config', () => {
|
|
165
|
+
expect(contentfulManagement.createClient).toHaveBeenCalledWith(
|
|
166
|
+
{
|
|
167
|
+
accessToken: 'test-token',
|
|
168
|
+
host: 'api.contentful.com'
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
type: 'plain',
|
|
172
|
+
defaults: {
|
|
173
|
+
spaceId: 'test-space',
|
|
174
|
+
environmentId: 'master'
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should use default values for optional config', () => {
|
|
181
|
+
const minimalConfig = {
|
|
182
|
+
accessToken: 'test-token',
|
|
183
|
+
spaceId: 'test-space'
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const minimalClient = new ContentfulCMAClient(minimalConfig);
|
|
187
|
+
|
|
188
|
+
expect(minimalClient.getEnvironmentId()).toBe('master');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('Entry Operations', () => {
|
|
193
|
+
describe('getEntry', () => {
|
|
194
|
+
it('should get entry by ID', async () => {
|
|
195
|
+
mockPlainClient.entry.get.mockResolvedValue(mockEntry);
|
|
196
|
+
|
|
197
|
+
const result = await client.getEntry('entry-123');
|
|
198
|
+
|
|
199
|
+
expect(mockPlainClient.entry.get).toHaveBeenCalledWith({
|
|
200
|
+
entryId: 'entry-123'
|
|
201
|
+
});
|
|
202
|
+
expect(result).toEqual(mockEntry);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should handle errors when getting entry', async () => {
|
|
206
|
+
mockPlainClient.entry.get.mockRejectedValue(new Error('Not found'));
|
|
207
|
+
|
|
208
|
+
await expect(client.getEntry('entry-123')).rejects.toThrow('Not found');
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('getEntries', () => {
|
|
213
|
+
it('should get entries with query', async () => {
|
|
214
|
+
mockPlainClient.entry.getMany.mockResolvedValue({
|
|
215
|
+
items: [mockEntry],
|
|
216
|
+
total: 1,
|
|
217
|
+
skip: 0,
|
|
218
|
+
limit: 100
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const result = await client.getEntries({ content_type: 'blogPost' });
|
|
222
|
+
|
|
223
|
+
expect(mockPlainClient.entry.getMany).toHaveBeenCalledWith({
|
|
224
|
+
query: { content_type: 'blogPost' }
|
|
225
|
+
});
|
|
226
|
+
expect(result).toEqual([mockEntry]);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should get entries without query', async () => {
|
|
230
|
+
mockPlainClient.entry.getMany.mockResolvedValue({
|
|
231
|
+
items: [mockEntry],
|
|
232
|
+
total: 1,
|
|
233
|
+
skip: 0,
|
|
234
|
+
limit: 100
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const result = await client.getEntries();
|
|
238
|
+
|
|
239
|
+
expect(mockPlainClient.entry.getMany).toHaveBeenCalledWith({
|
|
240
|
+
query: undefined
|
|
241
|
+
});
|
|
242
|
+
expect(result).toEqual([mockEntry]);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('getAllEntries', () => {
|
|
247
|
+
it('should get all entries with pagination', async () => {
|
|
248
|
+
// Create 100 entries for first page
|
|
249
|
+
const firstPageEntries = Array.from({ length: 100 }, (_, i) => ({
|
|
250
|
+
...mockEntry,
|
|
251
|
+
sys: { ...mockEntry.sys, id: `entry-${i}` }
|
|
252
|
+
}));
|
|
253
|
+
|
|
254
|
+
// Create 50 entries for second page (to reach total of 150)
|
|
255
|
+
const secondPageEntries = Array.from({ length: 50 }, (_, i) => ({
|
|
256
|
+
...mockEntry,
|
|
257
|
+
sys: { ...mockEntry.sys, id: `entry-${100 + i}` }
|
|
258
|
+
}));
|
|
259
|
+
|
|
260
|
+
// First page
|
|
261
|
+
mockPlainClient.entry.getMany.mockResolvedValueOnce({
|
|
262
|
+
items: firstPageEntries,
|
|
263
|
+
total: 150,
|
|
264
|
+
skip: 0,
|
|
265
|
+
limit: 100
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Second page
|
|
269
|
+
mockPlainClient.entry.getMany.mockResolvedValueOnce({
|
|
270
|
+
items: secondPageEntries,
|
|
271
|
+
total: 150,
|
|
272
|
+
skip: 100,
|
|
273
|
+
limit: 100
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const result = await client.getAllEntries();
|
|
277
|
+
|
|
278
|
+
expect(mockPlainClient.entry.getMany).toHaveBeenCalledTimes(2);
|
|
279
|
+
expect(result).toHaveLength(150);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should handle single page results', async () => {
|
|
283
|
+
mockPlainClient.entry.getMany.mockResolvedValue({
|
|
284
|
+
items: [mockEntry],
|
|
285
|
+
total: 1,
|
|
286
|
+
skip: 0,
|
|
287
|
+
limit: 100
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const result = await client.getAllEntries();
|
|
291
|
+
|
|
292
|
+
expect(mockPlainClient.entry.getMany).toHaveBeenCalledTimes(1);
|
|
293
|
+
expect(result).toEqual([mockEntry]);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe('createEntry', () => {
|
|
298
|
+
it('should create entry', async () => {
|
|
299
|
+
mockPlainClient.entry.create.mockResolvedValue(mockEntry);
|
|
300
|
+
|
|
301
|
+
const fields = { title: { 'en-US': 'New Entry' } };
|
|
302
|
+
const result = await client.createEntry('blogPost', fields);
|
|
303
|
+
|
|
304
|
+
expect(mockPlainClient.entry.create).toHaveBeenCalledWith(
|
|
305
|
+
{ contentTypeId: 'blogPost', entryId: undefined },
|
|
306
|
+
{ fields }
|
|
307
|
+
);
|
|
308
|
+
expect(result).toEqual(mockEntry);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should create entry with custom ID', async () => {
|
|
312
|
+
mockPlainClient.entry.create.mockResolvedValue(mockEntry);
|
|
313
|
+
|
|
314
|
+
const fields = { title: { 'en-US': 'New Entry' } };
|
|
315
|
+
await client.createEntry('blogPost', fields, {
|
|
316
|
+
entryId: 'custom-id'
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
expect(mockPlainClient.entry.create).toHaveBeenCalledWith(
|
|
320
|
+
{ contentTypeId: 'blogPost', entryId: 'custom-id' },
|
|
321
|
+
{ fields }
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe('updateEntry', () => {
|
|
327
|
+
it('should update entry', async () => {
|
|
328
|
+
const updatedEntry = { ...mockEntry, sys: { ...mockEntry.sys, version: 2 } };
|
|
329
|
+
mockPlainClient.entry.update.mockResolvedValue(updatedEntry);
|
|
330
|
+
|
|
331
|
+
const fields = { title: { 'en-US': 'Updated Entry' } };
|
|
332
|
+
const result = await client.updateEntry('entry-123', fields, 1);
|
|
333
|
+
|
|
334
|
+
expect(mockPlainClient.entry.update).toHaveBeenCalledWith(
|
|
335
|
+
{ entryId: 'entry-123' },
|
|
336
|
+
{ sys: { version: 1 }, fields }
|
|
337
|
+
);
|
|
338
|
+
expect(result).toEqual(updatedEntry);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe('publishEntry', () => {
|
|
343
|
+
it('should publish entry', async () => {
|
|
344
|
+
const publishedEntry = {
|
|
345
|
+
...mockEntry,
|
|
346
|
+
sys: { ...mockEntry.sys, publishedVersion: 1 }
|
|
347
|
+
};
|
|
348
|
+
mockPlainClient.entry.publish.mockResolvedValue(publishedEntry);
|
|
349
|
+
|
|
350
|
+
const result = await client.publishEntry('entry-123', 1);
|
|
351
|
+
|
|
352
|
+
expect(mockPlainClient.entry.publish).toHaveBeenCalledWith(
|
|
353
|
+
{ entryId: 'entry-123' },
|
|
354
|
+
{ sys: { version: 1 } }
|
|
355
|
+
);
|
|
356
|
+
expect(result).toEqual(publishedEntry);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
describe('unpublishEntry', () => {
|
|
361
|
+
it('should unpublish entry', async () => {
|
|
362
|
+
mockPlainClient.entry.unpublish.mockResolvedValue(mockEntry);
|
|
363
|
+
|
|
364
|
+
const result = await client.unpublishEntry('entry-123');
|
|
365
|
+
|
|
366
|
+
expect(mockPlainClient.entry.unpublish).toHaveBeenCalledWith({
|
|
367
|
+
entryId: 'entry-123'
|
|
368
|
+
});
|
|
369
|
+
expect(result).toEqual(mockEntry);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
describe('archiveEntry', () => {
|
|
374
|
+
it('should archive entry', async () => {
|
|
375
|
+
mockPlainClient.entry.archive.mockResolvedValue(mockEntry);
|
|
376
|
+
|
|
377
|
+
const result = await client.archiveEntry('entry-123', 1);
|
|
378
|
+
|
|
379
|
+
expect(mockPlainClient.entry.archive).toHaveBeenCalledWith(
|
|
380
|
+
{ entryId: 'entry-123', version: 1 }
|
|
381
|
+
);
|
|
382
|
+
expect(result).toEqual(mockEntry);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
describe('unarchiveEntry', () => {
|
|
387
|
+
it('should unarchive entry', async () => {
|
|
388
|
+
mockPlainClient.entry.unarchive.mockResolvedValue(mockEntry);
|
|
389
|
+
|
|
390
|
+
const result = await client.unarchiveEntry('entry-123');
|
|
391
|
+
|
|
392
|
+
expect(mockPlainClient.entry.unarchive).toHaveBeenCalledWith({
|
|
393
|
+
entryId: 'entry-123'
|
|
394
|
+
});
|
|
395
|
+
expect(result).toEqual(mockEntry);
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe('deleteEntry', () => {
|
|
400
|
+
it('should delete entry', async () => {
|
|
401
|
+
mockPlainClient.entry.delete.mockResolvedValue(undefined);
|
|
402
|
+
|
|
403
|
+
await client.deleteEntry('entry-123');
|
|
404
|
+
|
|
405
|
+
expect(mockPlainClient.entry.delete).toHaveBeenCalledWith({
|
|
406
|
+
entryId: 'entry-123'
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe('Asset Operations', () => {
|
|
413
|
+
describe('getAsset', () => {
|
|
414
|
+
it('should get asset by ID', async () => {
|
|
415
|
+
mockPlainClient.asset.get.mockResolvedValue(mockAsset);
|
|
416
|
+
|
|
417
|
+
const result = await client.getAsset('asset-123');
|
|
418
|
+
|
|
419
|
+
expect(mockPlainClient.asset.get).toHaveBeenCalledWith({
|
|
420
|
+
assetId: 'asset-123'
|
|
421
|
+
});
|
|
422
|
+
expect(result).toEqual(mockAsset);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe('getAssets', () => {
|
|
427
|
+
it('should get assets with query', async () => {
|
|
428
|
+
mockPlainClient.asset.getMany.mockResolvedValue({
|
|
429
|
+
items: [mockAsset],
|
|
430
|
+
total: 1,
|
|
431
|
+
skip: 0,
|
|
432
|
+
limit: 100
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const result = await client.getAssets({ locale: 'en-US' });
|
|
436
|
+
|
|
437
|
+
expect(mockPlainClient.asset.getMany).toHaveBeenCalledWith({
|
|
438
|
+
query: { locale: 'en-US' }
|
|
439
|
+
});
|
|
440
|
+
expect(result).toEqual([mockAsset]);
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
describe('getAllAssets', () => {
|
|
445
|
+
it('should get all assets with pagination', async () => {
|
|
446
|
+
// Create 100 assets for first page
|
|
447
|
+
const firstPageAssets = Array.from({ length: 100 }, (_, i) => ({
|
|
448
|
+
...mockAsset,
|
|
449
|
+
sys: { ...mockAsset.sys, id: `asset-${i}` }
|
|
450
|
+
}));
|
|
451
|
+
|
|
452
|
+
// Create 50 assets for second page (to reach total of 150)
|
|
453
|
+
const secondPageAssets = Array.from({ length: 50 }, (_, i) => ({
|
|
454
|
+
...mockAsset,
|
|
455
|
+
sys: { ...mockAsset.sys, id: `asset-${100 + i}` }
|
|
456
|
+
}));
|
|
457
|
+
|
|
458
|
+
mockPlainClient.asset.getMany.mockResolvedValueOnce({
|
|
459
|
+
items: firstPageAssets,
|
|
460
|
+
total: 150,
|
|
461
|
+
skip: 0,
|
|
462
|
+
limit: 100
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
mockPlainClient.asset.getMany.mockResolvedValueOnce({
|
|
466
|
+
items: secondPageAssets,
|
|
467
|
+
total: 150,
|
|
468
|
+
skip: 100,
|
|
469
|
+
limit: 100
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const result = await client.getAllAssets();
|
|
473
|
+
|
|
474
|
+
expect(mockPlainClient.asset.getMany).toHaveBeenCalledTimes(2);
|
|
475
|
+
expect(result).toHaveLength(150);
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
describe('createAsset', () => {
|
|
480
|
+
it('should create asset', async () => {
|
|
481
|
+
mockPlainClient.asset.create.mockResolvedValue(mockAsset);
|
|
482
|
+
|
|
483
|
+
const fields = mockAsset.fields!;
|
|
484
|
+
const result = await client.createAsset(fields);
|
|
485
|
+
|
|
486
|
+
expect(mockPlainClient.asset.create).toHaveBeenCalledWith(
|
|
487
|
+
{ assetId: undefined },
|
|
488
|
+
{ fields }
|
|
489
|
+
);
|
|
490
|
+
expect(result).toEqual(mockAsset);
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
describe('processAsset', () => {
|
|
495
|
+
it('should process asset', async () => {
|
|
496
|
+
mockPlainClient.asset.processForLocale.mockResolvedValue(mockAsset);
|
|
497
|
+
|
|
498
|
+
const result = await client.processAsset('asset-123', 1, 'en-US');
|
|
499
|
+
|
|
500
|
+
expect(mockPlainClient.asset.processForLocale).toHaveBeenCalledWith(
|
|
501
|
+
{ assetId: 'asset-123', version: 1 },
|
|
502
|
+
{ sys: { version: 1 } },
|
|
503
|
+
'en-US'
|
|
504
|
+
);
|
|
505
|
+
expect(result).toEqual(mockAsset);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('should use default locale', async () => {
|
|
509
|
+
mockPlainClient.asset.processForLocale.mockResolvedValue(mockAsset);
|
|
510
|
+
|
|
511
|
+
await client.processAsset('asset-123', 1);
|
|
512
|
+
|
|
513
|
+
expect(mockPlainClient.asset.processForLocale).toHaveBeenCalledWith(
|
|
514
|
+
{ assetId: 'asset-123', version: 1 },
|
|
515
|
+
{ sys: { version: 1 } },
|
|
516
|
+
'en-US'
|
|
517
|
+
);
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
describe('publishAsset', () => {
|
|
522
|
+
it('should publish asset', async () => {
|
|
523
|
+
const publishedAsset = {
|
|
524
|
+
...mockAsset,
|
|
525
|
+
sys: { ...mockAsset.sys, publishedVersion: 1 }
|
|
526
|
+
};
|
|
527
|
+
mockPlainClient.asset.publish.mockResolvedValue(publishedAsset);
|
|
528
|
+
|
|
529
|
+
const result = await client.publishAsset('asset-123', 1);
|
|
530
|
+
|
|
531
|
+
expect(mockPlainClient.asset.publish).toHaveBeenCalledWith(
|
|
532
|
+
{ assetId: 'asset-123' },
|
|
533
|
+
{ sys: { version: 1 } }
|
|
534
|
+
);
|
|
535
|
+
expect(result).toEqual(publishedAsset);
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
describe('unpublishAsset', () => {
|
|
540
|
+
it('should unpublish asset', async () => {
|
|
541
|
+
mockPlainClient.asset.unpublish.mockResolvedValue(mockAsset);
|
|
542
|
+
|
|
543
|
+
const result = await client.unpublishAsset('asset-123');
|
|
544
|
+
|
|
545
|
+
expect(mockPlainClient.asset.unpublish).toHaveBeenCalledWith({
|
|
546
|
+
assetId: 'asset-123'
|
|
547
|
+
});
|
|
548
|
+
expect(result).toEqual(mockAsset);
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
describe('deleteAsset', () => {
|
|
553
|
+
it('should delete asset', async () => {
|
|
554
|
+
mockPlainClient.asset.delete.mockResolvedValue(undefined);
|
|
555
|
+
|
|
556
|
+
await client.deleteAsset('asset-123');
|
|
557
|
+
|
|
558
|
+
expect(mockPlainClient.asset.delete).toHaveBeenCalledWith({
|
|
559
|
+
assetId: 'asset-123'
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
describe('Content Type Operations', () => {
|
|
566
|
+
describe('getContentType', () => {
|
|
567
|
+
it('should get content type by ID', async () => {
|
|
568
|
+
mockPlainClient.contentType.get.mockResolvedValue(mockContentType);
|
|
569
|
+
|
|
570
|
+
const result = await client.getContentType('blogPost');
|
|
571
|
+
|
|
572
|
+
expect(mockPlainClient.contentType.get).toHaveBeenCalledWith({
|
|
573
|
+
contentTypeId: 'blogPost'
|
|
574
|
+
});
|
|
575
|
+
expect(result).toEqual(mockContentType);
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
describe('getContentTypes', () => {
|
|
580
|
+
it('should get all content types', async () => {
|
|
581
|
+
mockPlainClient.contentType.getMany.mockResolvedValue({
|
|
582
|
+
items: [mockContentType],
|
|
583
|
+
total: 1,
|
|
584
|
+
skip: 0,
|
|
585
|
+
limit: 100
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
const result = await client.getContentTypes();
|
|
589
|
+
|
|
590
|
+
expect(mockPlainClient.contentType.getMany).toHaveBeenCalledWith({
|
|
591
|
+
query: undefined
|
|
592
|
+
});
|
|
593
|
+
expect(result).toEqual([mockContentType]);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('should get content types with query', async () => {
|
|
597
|
+
mockPlainClient.contentType.getMany.mockResolvedValue({
|
|
598
|
+
items: [mockContentType],
|
|
599
|
+
total: 1,
|
|
600
|
+
skip: 0,
|
|
601
|
+
limit: 100
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
await client.getContentTypes({ limit: 10 });
|
|
605
|
+
|
|
606
|
+
expect(mockPlainClient.contentType.getMany).toHaveBeenCalledWith({
|
|
607
|
+
query: { limit: 10 }
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
describe('Bulk Operations', () => {
|
|
614
|
+
describe('bulkCreateEntries', () => {
|
|
615
|
+
it('should create multiple entries successfully', async () => {
|
|
616
|
+
mockPlainClient.entry.create.mockResolvedValue(mockEntry);
|
|
617
|
+
|
|
618
|
+
const entries = [
|
|
619
|
+
{ contentTypeId: 'blogPost', fields: { title: { 'en-US': 'Entry 1' } } },
|
|
620
|
+
{ contentTypeId: 'blogPost', fields: { title: { 'en-US': 'Entry 2' } } }
|
|
621
|
+
];
|
|
622
|
+
|
|
623
|
+
const result = await client.bulkCreateEntries(entries);
|
|
624
|
+
|
|
625
|
+
expect(mockPlainClient.entry.create).toHaveBeenCalledTimes(2);
|
|
626
|
+
expect(result.successful).toHaveLength(2);
|
|
627
|
+
expect(result.failed).toHaveLength(0);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('should handle partial failures', async () => {
|
|
631
|
+
mockPlainClient.entry.create
|
|
632
|
+
.mockResolvedValueOnce(mockEntry)
|
|
633
|
+
.mockRejectedValueOnce(new Error('Failed to create'));
|
|
634
|
+
|
|
635
|
+
const entries = [
|
|
636
|
+
{ contentTypeId: 'blogPost', fields: { title: { 'en-US': 'Entry 1' } } },
|
|
637
|
+
{ contentTypeId: 'blogPost', fields: { title: { 'en-US': 'Entry 2' } } }
|
|
638
|
+
];
|
|
639
|
+
|
|
640
|
+
const result = await client.bulkCreateEntries(entries);
|
|
641
|
+
|
|
642
|
+
expect(result.successful).toHaveLength(1);
|
|
643
|
+
expect(result.failed).toHaveLength(1);
|
|
644
|
+
expect(result.failed[0].error).toBe('Failed to create');
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
describe('bulkDeleteEntries', () => {
|
|
649
|
+
it('should delete multiple entries successfully', async () => {
|
|
650
|
+
mockPlainClient.entry.delete.mockResolvedValue(undefined);
|
|
651
|
+
|
|
652
|
+
const entryIds = ['entry-1', 'entry-2', 'entry-3'];
|
|
653
|
+
const result = await client.bulkDeleteEntries(entryIds);
|
|
654
|
+
|
|
655
|
+
expect(mockPlainClient.entry.delete).toHaveBeenCalledTimes(3);
|
|
656
|
+
expect(result.successful).toEqual(entryIds);
|
|
657
|
+
expect(result.failed).toHaveLength(0);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it('should handle partial failures', async () => {
|
|
661
|
+
mockPlainClient.entry.delete
|
|
662
|
+
.mockResolvedValueOnce(undefined)
|
|
663
|
+
.mockRejectedValueOnce(new Error('Not found'));
|
|
664
|
+
|
|
665
|
+
const entryIds = ['entry-1', 'entry-2'];
|
|
666
|
+
const result = await client.bulkDeleteEntries(entryIds);
|
|
667
|
+
|
|
668
|
+
expect(result.successful).toEqual(['entry-1']);
|
|
669
|
+
expect(result.failed).toHaveLength(1);
|
|
670
|
+
expect(result.failed[0].item).toBe('entry-2');
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
describe('bulkPublishEntries', () => {
|
|
675
|
+
it('should publish multiple entries successfully', async () => {
|
|
676
|
+
mockPlainClient.entry.publish.mockResolvedValue(mockEntry);
|
|
677
|
+
|
|
678
|
+
const entries = [
|
|
679
|
+
{ entryId: 'entry-1', version: 1 },
|
|
680
|
+
{ entryId: 'entry-2', version: 1 }
|
|
681
|
+
];
|
|
682
|
+
|
|
683
|
+
const result = await client.bulkPublishEntries(entries);
|
|
684
|
+
|
|
685
|
+
expect(mockPlainClient.entry.publish).toHaveBeenCalledTimes(2);
|
|
686
|
+
expect(result.successful).toHaveLength(2);
|
|
687
|
+
expect(result.failed).toHaveLength(0);
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
it('should handle version conflicts', async () => {
|
|
691
|
+
mockPlainClient.entry.publish
|
|
692
|
+
.mockResolvedValueOnce(mockEntry)
|
|
693
|
+
.mockRejectedValueOnce(new Error('Version mismatch'));
|
|
694
|
+
|
|
695
|
+
const entries = [
|
|
696
|
+
{ entryId: 'entry-1', version: 1 },
|
|
697
|
+
{ entryId: 'entry-2', version: 1 }
|
|
698
|
+
];
|
|
699
|
+
|
|
700
|
+
const result = await client.bulkPublishEntries(entries);
|
|
701
|
+
|
|
702
|
+
expect(result.successful).toHaveLength(1);
|
|
703
|
+
expect(result.failed).toHaveLength(1);
|
|
704
|
+
expect(result.failed[0].error).toBe('Version mismatch');
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
describe('Configuration Getters', () => {
|
|
710
|
+
it('should return spaceId', () => {
|
|
711
|
+
expect(client.getSpaceId()).toBe('test-space');
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it('should return environmentId', () => {
|
|
715
|
+
expect(client.getEnvironmentId()).toBe('master');
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
describe('Error Handling', () => {
|
|
720
|
+
it('should handle string errors', async () => {
|
|
721
|
+
mockPlainClient.entry.get.mockRejectedValue('String error');
|
|
722
|
+
|
|
723
|
+
await expect(client.getEntry('entry-123')).rejects.toBe('String error');
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
it('should handle Error objects', async () => {
|
|
727
|
+
const error = new Error('Test error');
|
|
728
|
+
mockPlainClient.entry.get.mockRejectedValue(error);
|
|
729
|
+
|
|
730
|
+
await expect(client.getEntry('entry-123')).rejects.toThrow('Test error');
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it('should handle unknown error types', async () => {
|
|
734
|
+
mockPlainClient.entry.get.mockRejectedValue({ code: 'UNKNOWN' });
|
|
735
|
+
|
|
736
|
+
await expect(client.getEntry('entry-123')).rejects.toEqual({ code: 'UNKNOWN' });
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
it('should handle errors in getEntries', async () => {
|
|
740
|
+
mockPlainClient.entry.getMany.mockRejectedValue(new Error('API error'));
|
|
741
|
+
|
|
742
|
+
await expect(client.getEntries()).rejects.toThrow('API error');
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
it('should handle errors in createEntry', async () => {
|
|
746
|
+
mockPlainClient.entry.create.mockRejectedValue(new Error('Create failed'));
|
|
747
|
+
|
|
748
|
+
await expect(client.createEntry('blogPost', {})).rejects.toThrow('Create failed');
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
it('should handle errors in updateEntry', async () => {
|
|
752
|
+
mockPlainClient.entry.update.mockRejectedValue(new Error('Update failed'));
|
|
753
|
+
|
|
754
|
+
await expect(client.updateEntry('entry-123', {}, 1)).rejects.toThrow('Update failed');
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it('should handle errors in publishEntry', async () => {
|
|
758
|
+
mockPlainClient.entry.publish.mockRejectedValue(new Error('Publish failed'));
|
|
759
|
+
|
|
760
|
+
await expect(client.publishEntry('entry-123', 1)).rejects.toThrow('Publish failed');
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
it('should handle errors in unpublishEntry', async () => {
|
|
764
|
+
mockPlainClient.entry.unpublish.mockRejectedValue(new Error('Unpublish failed'));
|
|
765
|
+
|
|
766
|
+
await expect(client.unpublishEntry('entry-123')).rejects.toThrow('Unpublish failed');
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
it('should handle errors in archiveEntry', async () => {
|
|
770
|
+
mockPlainClient.entry.archive.mockRejectedValue(new Error('Archive failed'));
|
|
771
|
+
|
|
772
|
+
await expect(client.archiveEntry('entry-123', 1)).rejects.toThrow('Archive failed');
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
it('should handle errors in unarchiveEntry', async () => {
|
|
776
|
+
mockPlainClient.entry.unarchive.mockRejectedValue(new Error('Unarchive failed'));
|
|
777
|
+
|
|
778
|
+
await expect(client.unarchiveEntry('entry-123')).rejects.toThrow('Unarchive failed');
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
it('should handle errors in deleteEntry', async () => {
|
|
782
|
+
mockPlainClient.entry.delete.mockRejectedValue(new Error('Delete failed'));
|
|
783
|
+
|
|
784
|
+
await expect(client.deleteEntry('entry-123')).rejects.toThrow('Delete failed');
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
it('should handle errors in getAsset', async () => {
|
|
788
|
+
mockPlainClient.asset.get.mockRejectedValue(new Error('Asset not found'));
|
|
789
|
+
|
|
790
|
+
await expect(client.getAsset('asset-123')).rejects.toThrow('Asset not found');
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it('should handle errors in getAssets', async () => {
|
|
794
|
+
mockPlainClient.asset.getMany.mockRejectedValue(new Error('Assets error'));
|
|
795
|
+
|
|
796
|
+
await expect(client.getAssets()).rejects.toThrow('Assets error');
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
it('should handle errors in createAsset', async () => {
|
|
800
|
+
mockPlainClient.asset.create.mockRejectedValue(new Error('Asset creation failed'));
|
|
801
|
+
|
|
802
|
+
await expect(client.createAsset({})).rejects.toThrow('Asset creation failed');
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
it('should handle errors in processAsset', async () => {
|
|
806
|
+
mockPlainClient.asset.processForLocale.mockRejectedValue(new Error('Processing failed'));
|
|
807
|
+
|
|
808
|
+
await expect(client.processAsset('asset-123', 1)).rejects.toThrow('Processing failed');
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('should handle errors in publishAsset', async () => {
|
|
812
|
+
mockPlainClient.asset.publish.mockRejectedValue(new Error('Asset publish failed'));
|
|
813
|
+
|
|
814
|
+
await expect(client.publishAsset('asset-123', 1)).rejects.toThrow('Asset publish failed');
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it('should handle errors in unpublishAsset', async () => {
|
|
818
|
+
mockPlainClient.asset.unpublish.mockRejectedValue(new Error('Asset unpublish failed'));
|
|
819
|
+
|
|
820
|
+
await expect(client.unpublishAsset('asset-123')).rejects.toThrow('Asset unpublish failed');
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
it('should handle errors in deleteAsset', async () => {
|
|
824
|
+
mockPlainClient.asset.delete.mockRejectedValue(new Error('Asset delete failed'));
|
|
825
|
+
|
|
826
|
+
await expect(client.deleteAsset('asset-123')).rejects.toThrow('Asset delete failed');
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
it('should handle errors in getContentType', async () => {
|
|
830
|
+
mockPlainClient.contentType.get.mockRejectedValue(new Error('ContentType not found'));
|
|
831
|
+
|
|
832
|
+
await expect(client.getContentType('blogPost')).rejects.toThrow('ContentType not found');
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
it('should handle errors in getContentTypes', async () => {
|
|
836
|
+
mockPlainClient.contentType.getMany.mockRejectedValue(new Error('ContentTypes error'));
|
|
837
|
+
|
|
838
|
+
await expect(client.getContentTypes()).rejects.toThrow('ContentTypes error');
|
|
839
|
+
});
|
|
840
|
+
});
|
|
841
|
+
});
|