Skip to content

Commit c2b979a

Browse files
author
naman-contentstack
committed
chore: add test cases for utils
1 parent 02c34bb commit c2b979a

File tree

9 files changed

+2318
-373
lines changed

9 files changed

+2318
-373
lines changed

.github/workflows/unit-test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ jobs:
2727
- name: Run tests for Contentstack Import Plugin
2828
working-directory: ./packages/contentstack-import
2929
run: npm run test:unit
30+
31+
- name: Run tests for Contentstack Export Plugin
32+
working-directory: ./packages/contentstack-export
33+
run: npm run test:unit
3034

3135
- name: Run tests for Audit plugin
3236
working-directory: ./packages/contentstack-audit

.talismanrc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
fileignoreconfig:
22
- filename: package-lock.json
3-
checksum: 6ff9c8334d085a39cbda0377f9b36f1af3f3735f62d9372c0e51efaa4f4a960e
3+
checksum: 020710f2cd2ac9715ed34fe6f5412b6bed6a5db96fa5722defc0374b06388a63
44
- filename: pnpm-lock.yaml
5-
checksum: d02a60a70a50b191dcb746ce9644b01202957e6b5fb56cdaa564d7105623bb9d
5+
checksum: 9b3d466b8de5bcb3a1319ebfe90c6003a1c7e7450fb7f529be27b554c16d28e9
66
- filename: packages/contentstack-import-setup/test/unit/backup-handler.test.ts
77
checksum: 0582d62b88834554cf12951c8690a73ef3ddbb78b82d2804d994cf4148e1ef93
88
- filename: packages/contentstack-import-setup/test/config.json
@@ -151,4 +151,10 @@ fileignoreconfig:
151151
checksum: 457912f0f1ad3cadabbdf19cff6c325164e76063f12b968a00af37ec15a875e9
152152
- filename: packages/contentstack-export/test/unit/export/modules/global-fields.test.ts
153153
checksum: 64d204d0ff6232d161275b1df5b2ea5612b53c72d9ba2c22bd13564229353c4d
154+
- filename: packages/contentstack-export/test/unit/utils/common-helper.test.ts
155+
checksum: 276e850e4caddc89372f09f4eee5832cc4ab5b513da2a662a821f5feb8561349
156+
- filename: packages/contentstack-export/test/unit/utils/file-helper.test.ts
157+
checksum: a16f5833515ececd93c582b35d19b8a5df4880f22126fba18f110692c679025b
158+
- filename: packages/contentstack-export/test/unit/utils/export-config-handler.test.ts
159+
checksum: ba02c3d580e02fc4ecd5e6a0fc59e6c7d56d7de735339aa00e2c2241ffe22176
154160
version: "1.0"

package-lock.json

Lines changed: 240 additions & 124 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/contentstack-export/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@types/mkdirp": "^1.0.2",
3232
"@types/mocha": "^10.0.6",
3333
"@types/progress-stream": "^2.0.5",
34+
"@types/proxyquire": "^1.3.30",
3435
"@types/sinon": "^17.0.2",
3536
"chai": "^4.4.1",
3637
"dotenv": "^16.5.0",
@@ -40,6 +41,7 @@
4041
"mocha": "10.8.2",
4142
"nyc": "^15.1.0",
4243
"oclif": "^4.17.46",
44+
"proxyquire": "^2.1.3",
4345
"sinon": "^17.0.1",
4446
"source-map-support": "^0.5.21",
4547
"ts-node": "^10.9.2",
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
import { expect } from 'chai';
2+
import sinon from 'sinon';
3+
import { validateConfig, formatError, executeTask, writeExportMetaFile } from '../../../src/utils/common-helper';
4+
import { ExternalConfig, ExportConfig } from '../../../src/types';
5+
6+
describe('Common Helper Utils', () => {
7+
afterEach(() => {
8+
sinon.restore();
9+
});
10+
11+
describe('validateConfig', () => {
12+
it('should throw error when host and cdn are missing', () => {
13+
const config: ExternalConfig = {} as any;
14+
15+
expect(() => validateConfig(config)).to.throw('Host/CDN end point is missing from config');
16+
});
17+
18+
it('should validate correctly with all credentials provided', () => {
19+
const config: ExternalConfig = {
20+
host: 'https://api.contentstack.io',
21+
cdn: 'https://cdn.contentstack.io',
22+
23+
password: 'password',
24+
management_token: 'token',
25+
access_token: 'token'
26+
} as any;
27+
28+
expect(() => validateConfig(config)).to.not.throw();
29+
});
30+
31+
it('should validate email and password with access_token', () => {
32+
const config: ExternalConfig = {
33+
host: 'https://api.contentstack.io',
34+
cdn: 'https://cdn.contentstack.io',
35+
36+
password: 'password',
37+
access_token: 'token',
38+
source_stack: 'stack-key'
39+
} as any;
40+
41+
// Should not throw with access token
42+
expect(() => validateConfig(config)).to.not.throw();
43+
});
44+
45+
it('should throw error when authentication credentials are missing', () => {
46+
const config: ExternalConfig = {
47+
host: 'https://api.contentstack.io',
48+
cdn: 'https://cdn.contentstack.io',
49+
email: '',
50+
password: '',
51+
source_stack: 'stack-key'
52+
} as any;
53+
54+
// This will throw when no valid credentials provided
55+
try {
56+
validateConfig(config);
57+
// If it doesn't throw, check if email/password path throws
58+
const config2 = { ...config, email: 'test', password: '' };
59+
validateConfig(config2);
60+
expect.fail('Should have thrown an error');
61+
} catch (error: any) {
62+
expect(error.message).to.exist;
63+
}
64+
});
65+
66+
it('should validate preserveStackVersion requires email and password', () => {
67+
const config: ExternalConfig = {
68+
host: 'https://api.contentstack.io',
69+
cdn: 'https://cdn.contentstack.io',
70+
preserveStackVersion: true
71+
} as any;
72+
73+
expect(() => validateConfig(config)).to.throw('Kindly provide Email and password for stack details');
74+
});
75+
76+
it('should validate with management token', () => {
77+
const config: ExternalConfig = {
78+
host: 'https://api.contentstack.io',
79+
cdn: 'https://cdn.contentstack.io',
80+
management_token: 'token',
81+
source_stack: 'stack-key'
82+
} as any;
83+
84+
expect(() => validateConfig(config)).to.not.throw();
85+
});
86+
});
87+
88+
describe('formatError', () => {
89+
it('should format string error correctly', () => {
90+
const errorStr = 'Simple error message';
91+
const result = formatError(errorStr);
92+
expect(result).to.equal(errorStr);
93+
});
94+
95+
it('should parse and format JSON error string', () => {
96+
const errorJson = JSON.stringify({ errorMessage: 'Test error' });
97+
const result = formatError(errorJson);
98+
expect(result).to.equal('Test error');
99+
});
100+
101+
it('should format error message from Error object', () => {
102+
const error = { message: 'Error occurred' };
103+
const result = formatError(error);
104+
expect(result).to.equal('Error occurred');
105+
});
106+
107+
it('should include error details when available', () => {
108+
const error = {
109+
errorMessage: 'Main error',
110+
errors: {
111+
authorization: 'Invalid token',
112+
api_key: 'Invalid key'
113+
}
114+
};
115+
const result = formatError(error);
116+
expect(result).to.include('Main error');
117+
expect(result).to.include('Management Token Invalid token');
118+
expect(result).to.include('Stack API key Invalid key');
119+
});
120+
121+
it('should map entity names correctly', () => {
122+
const error = {
123+
errors: {
124+
authorization: 'fail',
125+
api_key: 'fail',
126+
uid: 'fail',
127+
access_token: 'fail'
128+
}
129+
};
130+
const result = formatError(error);
131+
expect(result).to.include('Management Token');
132+
expect(result).to.include('Stack API key');
133+
expect(result).to.include('Content Type');
134+
expect(result).to.include('Delivery Token');
135+
});
136+
137+
it('should handle null or undefined error gracefully', () => {
138+
// formatError doesn't handle null gracefully, so we expect it to throw
139+
expect(() => formatError(null as any)).to.throw();
140+
});
141+
});
142+
143+
describe('executeTask', () => {
144+
it('should execute tasks with concurrency limit', async () => {
145+
const tasks = [1, 2, 3, 4, 5];
146+
const handler = async (task: unknown) => (task as number) * 2;
147+
148+
const results = await executeTask(tasks, handler, { concurrency: 2 });
149+
150+
expect(results).to.deep.equal([2, 4, 6, 8, 10]);
151+
});
152+
153+
it('should handle empty tasks array', async () => {
154+
const tasks: any[] = [];
155+
const handler = async (): Promise<void> => { return; };
156+
157+
const results = await executeTask(tasks, handler, { concurrency: 1 });
158+
159+
expect(results).to.be.an('array');
160+
expect(results.length).to.equal(0);
161+
});
162+
163+
it('should throw error when handler is not a function', () => {
164+
const tasks = [1, 2, 3];
165+
const handler = 'not a function' as any;
166+
167+
expect(() => executeTask(tasks, handler, { concurrency: 1 })).to.throw('Invalid handler');
168+
});
169+
170+
it('should execute tasks sequentially when concurrency is 1', async () => {
171+
const order: number[] = [];
172+
const tasks = [1, 2, 3];
173+
const handler = async (task: unknown) => {
174+
order.push(task as number);
175+
return task;
176+
};
177+
178+
await executeTask(tasks, handler, { concurrency: 1 });
179+
180+
expect(order).to.deep.equal([1, 2, 3]);
181+
});
182+
183+
it('should handle task errors gracefully', async () => {
184+
const tasks = [1, 2, 3];
185+
const handler = async (task: unknown) => {
186+
if ((task as number) === 2) throw new Error('Task failed');
187+
return task;
188+
};
189+
190+
try {
191+
await executeTask(tasks, handler, { concurrency: 1 });
192+
expect.fail('Should have thrown an error');
193+
} catch (error) {
194+
expect(error).to.exist;
195+
}
196+
});
197+
});
198+
199+
describe('writeExportMetaFile', () => {
200+
it('should write export meta file with correct data', () => {
201+
const exportConfig: ExportConfig = {
202+
contentVersion: 1,
203+
exportDir: '/test/export'
204+
} as ExportConfig;
205+
206+
// Stub FsUtility constructor to avoid fs operations
207+
const FsUtility = require('@contentstack/cli-utilities').FsUtility;
208+
const originalWriteFile = FsUtility.prototype.writeFile;
209+
const writeFileStub = sinon.stub().resolves();
210+
FsUtility.prototype.writeFile = writeFileStub;
211+
212+
writeExportMetaFile(exportConfig);
213+
214+
// Verify that writeFile was called with correct data
215+
expect(writeFileStub.called).to.be.true;
216+
const filePath = writeFileStub.firstCall.args[0];
217+
const metaData = writeFileStub.firstCall.args[1];
218+
219+
expect(filePath).to.include('export-info.json');
220+
expect(metaData.contentVersion).to.equal(1);
221+
expect(metaData.logsPath).to.exist;
222+
223+
// Restore original
224+
FsUtility.prototype.writeFile = originalWriteFile;
225+
});
226+
227+
it('should accept custom meta file path', () => {
228+
const exportConfig: ExportConfig = {
229+
contentVersion: 2,
230+
exportDir: '/test/export'
231+
} as ExportConfig;
232+
233+
// Stub FsUtility constructor to avoid fs operations
234+
const FsUtility = require('@contentstack/cli-utilities').FsUtility;
235+
const originalWriteFile = FsUtility.prototype.writeFile;
236+
const writeFileStub = sinon.stub().resolves();
237+
FsUtility.prototype.writeFile = writeFileStub;
238+
239+
writeExportMetaFile(exportConfig, '/custom/path');
240+
241+
// Verify that writeFile was called with custom path and correct data
242+
expect(writeFileStub.called).to.be.true;
243+
const filePath = writeFileStub.firstCall.args[0];
244+
const metaData = writeFileStub.firstCall.args[1];
245+
246+
expect(filePath).to.include('/custom/path');
247+
expect(filePath).to.include('export-info.json');
248+
expect(metaData.contentVersion).to.equal(2);
249+
250+
// Restore original
251+
FsUtility.prototype.writeFile = originalWriteFile;
252+
});
253+
});
254+
});
255+

0 commit comments

Comments
 (0)