From 9dea4d48f6d191756e90130159f19c69dcf8c703 Mon Sep 17 00:00:00 2001 From: Pierrick Tassery Date: Thu, 4 Jun 2026 21:31:33 +0200 Subject: [PATCH] Handle empty file paths in cucumber pull --- pull.js | 29 ++++++++++++++++++++++++++++- tests/pull_test.js | 19 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/pull.js b/pull.js index 68bce1d..e04dc2e 100644 --- a/pull.js +++ b/pull.js @@ -33,10 +33,12 @@ class Pull { const filesCreated = []; const fileTree = {}; + const usedFileNames = new Set(Object.keys(data.files).filter(Boolean)); // Process files from server - server now sends .feature files directly for (const [fileName, content] of Object.entries(data.files)) { - const targetPath = path.join(this.targetDir, fileName); + const resolvedFileName = fileName || this.fallbackFileName(content, usedFileNames); + const targetPath = path.join(this.targetDir, resolvedFileName); const targetDir = path.dirname(targetPath); // Create directory structure @@ -76,6 +78,31 @@ class Pull { } + /** + * Generate a fallback filename when the server returns an empty path. + * @param {string} content - Feature file content + * @param {Set} usedFileNames - File names already reserved for this pull + * @returns {string} Safe fallback file name + */ + fallbackFileName(content, usedFileNames) { + const match = (content || '').match(/^\s*Feature:\s*(.+)$/m); + const baseName = (match ? match[1] : 'feature') + .toLowerCase() + .replace(/[^a-z0-9]+/g, '_') + .replace(/^_+|_+$/g, '') + .slice(0, 120) || 'feature'; + let fileName = `${baseName}.feature`; + let counter = 2; + + while (usedFileNames.has(fileName)) { + fileName = `${baseName}_${counter}.feature`; + counter += 1; + } + + usedFileNames.add(fileName); + return fileName; + } + /** * Add file to tree structure for display * @param {Object} tree - File tree object diff --git a/tests/pull_test.js b/tests/pull_test.js index 56e3c54..d13e74c 100644 --- a/tests/pull_test.js +++ b/tests/pull_test.js @@ -93,6 +93,25 @@ Feature: User Management expect(content).to.include('Feature: Deep Test'); }); + it('should generate a fallback file name for empty server file paths', async () => { + mockReporter.getFilesFromServer = () => + Promise.resolve({ + files: { + '': `Feature: Missing Source Path + Scenario: Pulled from Testomat + Given a feature has no source path` + } + }); + + await pull.execute(); + + const fallbackFile = path.join(testDir, 'missing_source_path.feature'); + expect(fs.existsSync(fallbackFile)).to.be.true; + + const content = fs.readFileSync(fallbackFile, 'utf8'); + expect(content).to.include('Feature: Missing Source Path'); + }); + it('should handle empty server response', async () => { mockReporter.getFilesFromServer = () => Promise.resolve({ files: {} });