Skip to content

Commit 13cc65b

Browse files
authored
Merge pull request #2213 from contentstack/tests/DX-3670
DX - 3670 - Added Unit test cases for common-helper, backup-handler and file-helper
2 parents eaf6b2b + f11587d commit 13cc65b

15 files changed

Lines changed: 2497 additions & 1 deletion

File tree

.talismanrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,16 @@ fileignoreconfig:
159159
checksum: ba02c3d580e02fc4ecd5e6a0fc59e6c7d56d7de735339aa00e2c2241ffe22176
160160
- filename: packages/contentstack-import/test/unit/import/modules/webhooks.test.ts
161161
checksum: 9f6dc9fb12f0d30600dac28846c7a9972e1dafe7c7bf5385ea677100a1d8fbd1
162+
- filename: packages/contentstack-import/test/unit/utils/backup-handler.test.ts
163+
checksum: 696aea5f9a4ccd75fe22e4a839f9ad279077f59d738ed62864b91aed7b54f053
164+
- filename: packages/contentstack-import/test/unit/utils/mock-data/common-helper/import-configs.json
165+
checksum: 1f48841db580d53ec39db163c8ef45bff26545dd51cdeb9b201a66ff96c31693
166+
- filename: packages/contentstack-import/test/unit/utils/mock-data/file-helper/test-data.json
167+
checksum: db64a1f13a3079080ffd0aeea36a3a7576e56f27b57befc6e077aa45f147a3de
168+
- filename: packages/contentstack-import/test/unit/utils/file-helper.test.ts
169+
checksum: a5cd371d7f327c083027da4157b3c5b4df548f2c2c3ad6193aa133031994252e
170+
- filename: packages/contentstack-import/test/unit/utils/common-helper.test.ts
171+
checksum: 61b3cfe0c0571dcc366e372990e3c11ced2b49703ac88155110d33897e58ca5d
162172
- filename: packages/contentstack-import/test/unit/import/module-importer.test.ts
163173
checksum: aa265917b806286c8d4d1d3f422cf5d6736a0cf6a5f50f2e9c04ec0f81eee376
164174
- filename: packages/contentstack-export/test/unit/utils/interactive.test.ts

packages/contentstack-import/test/unit/import/modules/base-class.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1098,7 +1098,8 @@ describe('BaseClass', () => {
10981098
);
10991099
const end = Date.now();
11001100

1101-
expect(end - start).to.be.at.least(950); // Should wait ~950ms
1101+
// Allow some tolerance for timing (at least 940ms to account for execution time variance)
1102+
expect(end - start).to.be.at.least(940);
11021103
});
11031104

11041105
it('should handle very long execution times', async () => {
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
import { expect } from 'chai';
2+
import sinon from 'sinon';
3+
import * as fs from 'fs';
4+
import * as path from 'path';
5+
import * as os from 'os';
6+
import * as cliUtilities from '@contentstack/cli-utilities';
7+
import backupHandler from '../../../src/utils/backup-handler';
8+
import * as fileHelper from '../../../src/utils/file-helper';
9+
import { ImportConfig } from '../../../src/types';
10+
11+
describe('Backup Handler', () => {
12+
let mockImportConfig: ImportConfig;
13+
let logStub: any;
14+
let cliuxStub: any;
15+
let tempDir: string;
16+
let sourceDir: string;
17+
let backupDir: string;
18+
let originalCwd: string;
19+
let processCwdStub: sinon.SinonStub;
20+
21+
beforeEach(() => {
22+
// Store original working directory
23+
originalCwd = process.cwd();
24+
25+
// Create temp directory - os.tmpdir() works in both local and CI environments (e.g., /tmp on Linux)
26+
// This ensures backups are created in isolated temp space, not in the working directory
27+
// In CI, os.tmpdir() returns a safe temp directory that's cleaned up automatically
28+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'backup-handler-test-'));
29+
sourceDir = path.join(tempDir, 'source');
30+
backupDir = path.join(tempDir, 'backup');
31+
32+
// Stub process.cwd() to return tempDir so backups are created there, not in actual working directory
33+
// This is critical for CI - prevents creating files in the workspace during tests
34+
processCwdStub = sinon.stub(process, 'cwd').returns(tempDir);
35+
36+
// Create source directory with some files
37+
fs.mkdirSync(sourceDir);
38+
fs.writeFileSync(path.join(sourceDir, 'test.json'), JSON.stringify({ key: 'value' }));
39+
fs.writeFileSync(path.join(sourceDir, 'test.txt'), 'test content');
40+
41+
mockImportConfig = {
42+
apiKey: 'test-api-key',
43+
data: '/test/data',
44+
contentDir: sourceDir,
45+
context: {
46+
command: 'cm:stacks:import',
47+
module: 'all',
48+
},
49+
contentVersion: 1,
50+
masterLocale: { code: 'en-us' },
51+
backupDir: backupDir,
52+
region: 'us',
53+
modules: {} as any,
54+
host: 'https://api.contentstack.io',
55+
'exclude-global-modules': false,
56+
} as any as ImportConfig;
57+
58+
logStub = {
59+
debug: sinon.stub(),
60+
info: sinon.stub(),
61+
error: sinon.stub(),
62+
};
63+
sinon.stub(cliUtilities, 'log').value(logStub);
64+
65+
cliuxStub = {
66+
print: sinon.stub(),
67+
};
68+
sinon.stub(cliUtilities, 'cliux').value(cliuxStub);
69+
});
70+
71+
afterEach(() => {
72+
// Restore process.cwd stub first
73+
if (processCwdStub) {
74+
processCwdStub.restore();
75+
}
76+
77+
// Restore all stubs
78+
sinon.restore();
79+
80+
// Clean up temp directory (which includes any backup dirs created in it)
81+
// This is critical for CI - must clean up temp files
82+
try {
83+
if (tempDir && fs.existsSync(tempDir)) {
84+
fs.rmSync(tempDir, { recursive: true, force: true });
85+
}
86+
} catch (error) {
87+
// Ignore cleanup errors - temp dirs will be cleaned by OS
88+
console.warn(`Failed to clean temp dir ${tempDir}:`, error);
89+
}
90+
91+
// Clean up any backup directories that might have been created in original working directory
92+
// This ensures CI doesn't leave files behind
93+
// Note: In CI (GitHub Actions), os.tmpdir() returns /tmp and we stub process.cwd(),
94+
// so this should rarely be needed, but it's a safety net
95+
try {
96+
if (originalCwd && fs.existsSync(originalCwd) && originalCwd !== tempDir) {
97+
const files = fs.readdirSync(originalCwd);
98+
for (const file of files) {
99+
// Only clean up backup dirs that match our test pattern
100+
// This prevents accidentally deleting unrelated backup dirs
101+
if (file.startsWith('_backup_') && /^_backup_\d+$/.test(file)) {
102+
const backupPath = path.join(originalCwd, file);
103+
try {
104+
const stat = fs.statSync(backupPath);
105+
if (stat.isDirectory()) {
106+
// Use force and recursive to handle permissions in CI
107+
fs.rmSync(backupPath, { recursive: true, force: true, maxRetries: 3 });
108+
}
109+
} catch (err: any) {
110+
// Ignore cleanup errors - might be permission issues in CI or already cleaned
111+
// Don't fail tests on cleanup errors
112+
}
113+
}
114+
}
115+
}
116+
} catch (error: any) {
117+
// Ignore all cleanup errors - CI environments may have permission restrictions
118+
// The temp directory cleanup above is sufficient for normal operation
119+
}
120+
});
121+
122+
describe('backupHandler()', () => {
123+
it('should return existing backup directory when useBackedupDir is provided', async () => {
124+
const existingBackupPath = '/existing/backup/path';
125+
const config = {
126+
...mockImportConfig,
127+
useBackedupDir: existingBackupPath,
128+
};
129+
130+
const result = await backupHandler(config);
131+
132+
expect(result).to.equal(existingBackupPath);
133+
expect(logStub.debug.calledWith(`Using existing backup directory: ${existingBackupPath}`)).to.be.true;
134+
});
135+
136+
it('should use branchDir over contentDir when both are provided', async () => {
137+
const branchDir = path.join(tempDir, 'branch');
138+
fs.mkdirSync(branchDir);
139+
fs.writeFileSync(path.join(branchDir, 'branch-file.json'), '{}');
140+
141+
const config = {
142+
...mockImportConfig,
143+
branchDir: branchDir,
144+
contentDir: sourceDir,
145+
};
146+
147+
const result = await backupHandler(config);
148+
149+
expect(result).to.be.a('string');
150+
expect(fs.existsSync(result)).to.be.true;
151+
expect(logStub.debug.called).to.be.true;
152+
});
153+
154+
it('should use contentDir when branchDir is not provided', async () => {
155+
const config = {
156+
...mockImportConfig,
157+
contentDir: sourceDir,
158+
};
159+
160+
const result = await backupHandler(config);
161+
162+
expect(result).to.be.a('string');
163+
expect(fs.existsSync(result)).to.be.true;
164+
// Verify files were copied
165+
expect(fs.existsSync(path.join(result, 'test.json'))).to.be.true;
166+
});
167+
168+
it('should create backup in subdirectory when createBackupDir is a subdirectory', async () => {
169+
const subDir = path.join(sourceDir, 'subdirectory');
170+
const config = {
171+
...mockImportConfig,
172+
contentDir: sourceDir,
173+
createBackupDir: subDir,
174+
};
175+
176+
const result = await backupHandler(config);
177+
178+
expect(result).to.be.a('string');
179+
expect(result).to.not.equal(subDir); // Should create a different backup dir
180+
expect(logStub.debug.called).to.be.true;
181+
});
182+
183+
it('should show warning when backup directory is a subdirectory and createBackupDir is set', async () => {
184+
const subDir = path.join(sourceDir, 'subdirectory');
185+
const config = {
186+
...mockImportConfig,
187+
contentDir: sourceDir,
188+
createBackupDir: subDir,
189+
};
190+
191+
await backupHandler(config);
192+
193+
expect(cliuxStub.print.called).to.be.true;
194+
const printCall = cliuxStub.print.getCall(0);
195+
expect(printCall.args[0]).to.include('Warning!!!');
196+
expect(printCall.args[1]).to.deep.equal({ color: 'yellow' });
197+
});
198+
199+
it('should create default backup directory when createBackupDir is not provided', async () => {
200+
const config = {
201+
...mockImportConfig,
202+
contentDir: sourceDir,
203+
};
204+
205+
const result = await backupHandler(config);
206+
207+
expect(result).to.be.a('string');
208+
expect(result).to.include('_backup_');
209+
expect(fs.existsSync(result)).to.be.true;
210+
});
211+
212+
it('should use custom backup directory when createBackupDir is provided and not a subdirectory', async () => {
213+
const customBackupPath = path.join(tempDir, 'custom-backup');
214+
const config = {
215+
...mockImportConfig,
216+
contentDir: sourceDir,
217+
createBackupDir: customBackupPath,
218+
};
219+
220+
const result = await backupHandler(config);
221+
222+
expect(result).to.equal(customBackupPath);
223+
expect(fs.existsSync(customBackupPath)).to.be.true;
224+
expect(fs.existsSync(path.join(customBackupPath, 'test.json'))).to.be.true;
225+
});
226+
227+
it('should remove existing backup directory before creating new one', async () => {
228+
const customBackupPath = path.join(tempDir, 'custom-backup');
229+
fs.mkdirSync(customBackupPath);
230+
fs.writeFileSync(path.join(customBackupPath, 'old-file.txt'), 'old content');
231+
232+
const config = {
233+
...mockImportConfig,
234+
contentDir: sourceDir,
235+
createBackupDir: customBackupPath,
236+
};
237+
238+
const result = await backupHandler(config);
239+
240+
expect(result).to.equal(customBackupPath);
241+
// Old file should be gone, new files should be present
242+
expect(fs.existsSync(path.join(customBackupPath, 'old-file.txt'))).to.be.false;
243+
expect(fs.existsSync(path.join(customBackupPath, 'test.json'))).to.be.true;
244+
});
245+
246+
it('should successfully copy content to backup directory', async () => {
247+
const config = {
248+
...mockImportConfig,
249+
contentDir: sourceDir,
250+
};
251+
252+
const result = await backupHandler(config);
253+
254+
expect(result).to.be.a('string');
255+
expect(fs.existsSync(result)).to.be.true;
256+
expect(fs.existsSync(path.join(result, 'test.json'))).to.be.true;
257+
expect(fs.existsSync(path.join(result, 'test.txt'))).to.be.true;
258+
expect(logStub.info.calledWith('Copying content to the backup directory...', config.context)).to.be.true;
259+
});
260+
261+
it('should handle isSubDirectory when relative path is empty (same paths)', async () => {
262+
const config = {
263+
...mockImportConfig,
264+
contentDir: sourceDir,
265+
createBackupDir: sourceDir,
266+
};
267+
268+
const result = await backupHandler(config);
269+
270+
expect(result).to.be.a('string');
271+
expect(result).to.not.equal(sourceDir); // Should create backup outside
272+
expect(logStub.debug.called).to.be.true;
273+
});
274+
275+
it('should handle isSubDirectory when relative path starts with .. (not subdirectory)', async () => {
276+
const parentDir = path.join(tempDir, 'parent');
277+
const childDir = path.join(tempDir, 'child');
278+
fs.mkdirSync(parentDir);
279+
fs.mkdirSync(childDir);
280+
281+
const config = {
282+
...mockImportConfig,
283+
contentDir: parentDir,
284+
createBackupDir: childDir,
285+
};
286+
287+
const result = await backupHandler(config);
288+
289+
expect(result).to.equal(childDir);
290+
expect(fs.existsSync(result)).to.be.true;
291+
});
292+
});
293+
});

0 commit comments

Comments
 (0)