diff --git a/packages/cli/package.json b/packages/cli/package.json index dd9e82c325..2300612d3f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -114,6 +114,7 @@ "@types/http-proxy": "^1.17.4", "@types/inquirer": "^7.3.1", "@types/mini-css-extract-plugin": "^0.9.1", + "@types/mock-fs": "^4.13.0", "@types/node": "^13.7.2", "@types/ora": "^3.2.0", "@types/react-dev-utils": "^9.0.4", diff --git a/packages/cli/src/commands/create-plugin/createPlugin.test.ts b/packages/cli/src/commands/create-plugin/createPlugin.test.ts index 72b95b072c..356b315380 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.test.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.test.ts @@ -16,14 +16,20 @@ import fs from 'fs-extra'; import path from 'path'; +import mockFs from 'mock-fs'; import os from 'os'; import del from 'del'; import { createTemporaryPluginFolder, movePlugin } from './createPlugin'; +const id = 'testPluginMock'; + describe('createPlugin', () => { + afterAll(() => { + mockFs.restore(); + }); + describe('createPluginFolder', () => { it('should create a temporary plugin directory in the correct place', async () => { - const id = 'testPlugin'; const tempDir = path.join(os.tmpdir(), id); try { await createTemporaryPluginFolder(tempDir); @@ -35,35 +41,27 @@ describe('createPlugin', () => { }); it('should not create a temporary plugin directory if it already exists', async () => { - const id = 'testPlugin'; - const tempDir = path.join(os.tmpdir(), id); - try { - await createTemporaryPluginFolder(tempDir); - await expect(fs.pathExists(tempDir)).resolves.toBe(true); - await expect(createTemporaryPluginFolder(tempDir)).rejects.toThrow( - /Failed to create temporary plugin directory/, - ); - } finally { - await del(tempDir, { force: true }); - } + mockFs({ + [id]: {}, + }); + + await expect(createTemporaryPluginFolder(id)).rejects.toThrow( + /Failed to create temporary plugin directory/, + ); }); }); describe('movePlugin', () => { it('should move the temporary plugin directory to its final place', async () => { - const id = 'testPlugin'; - const tempDir = path.join(os.tmpdir(), id); - const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), 'test-')); - const pluginDir = path.join(rootDir, 'plugins', id); - try { - await createTemporaryPluginFolder(tempDir); - await movePlugin(tempDir, pluginDir, id); - await expect(fs.pathExists(pluginDir)).resolves.toBe(true); - expect(pluginDir).toMatch(path.join('', 'plugins', id)); - } finally { - await del(tempDir, { force: true }); - await del(rootDir, { force: true }); - } + mockFs({ + [id]: {}, + }); + const tempDir = id; + const pluginDir = `/test-temp/plugins/${id}`; + + await movePlugin(tempDir, pluginDir, id); + await expect(fs.pathExists(pluginDir)).resolves.toBe(true); + expect(pluginDir).toMatch(path.join('', 'plugins', id)); }); }); }); diff --git a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts index 6433cb6116..076f54402b 100644 --- a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts +++ b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts @@ -16,13 +16,9 @@ import fse from 'fs-extra'; import path from 'path'; -import os from 'os'; +import mockFs from 'mock-fs'; import { paths } from '../../lib/paths'; -import { - addExportStatement, - capitalize, - createTemporaryPluginFolder, -} from '../create-plugin/createPlugin'; +import { addExportStatement, capitalize } from '../create-plugin/createPlugin'; import { addCodeownersEntry } from '../../lib/codeowners'; import { removeReferencesFromAppPackage, @@ -35,7 +31,7 @@ import { const BACKSTAGE = `@backstage`; const testPluginName = 'yarn-test-package'; const testPluginPackage = `${BACKSTAGE}/plugin-${testPluginName}`; -const tempDir = path.join(os.tmpdir(), 'remove-plugin-test'); +const tempDir = '/remove-plugin-test'; const removeEmptyLines = (file: string): string => file.split(/\r?\n/).filter(Boolean).join('\n'); @@ -47,13 +43,24 @@ const createTestPackageFile = async ( // Copy contents of package file for test const packageFileContent = JSON.parse(fse.readFileSync(packageFile, 'utf8')); - packageFileContent.dependencies[testPluginPackage] = '0.1.0'; - fse.createFileSync(testFilePath); - fse.writeFileSync( - testFilePath, - `${JSON.stringify(packageFileContent, null, 2)}\n`, - 'utf8', - ); + const testFileContent = { + ...packageFileContent, + dependencies: { + ...packageFileContent.dependencies, + [testPluginPackage]: '0.1.0', + }, + }; + + mockFs({ + '/packages': { + app: { + 'package.json': `${JSON.stringify(packageFileContent, null, 2)}\n`, + }, + }, + [tempDir]: { + [testFilePath]: `${JSON.stringify(testFileContent, null, 2)}\n`, + }, + }); return; }; @@ -62,93 +69,126 @@ const createTestPluginFile = async ( pluginsFilePath: string, ) => { // Copy contents of package file for test - fse.copyFileSync(pluginsFilePath, testFilePath); + const pluginsFileContent = fse.readFileSync(pluginsFilePath); + + mockFs({ + [tempDir]: { + [testFilePath]: `${pluginsFileContent}\n`, + [pluginsFilePath]: `${pluginsFileContent}\n`, + }, + '/packages': { + app: { + src: { + 'plugin.ts': `${pluginsFileContent}\n`, + }, + }, + }, + }); + const pluginNameCapitalized = testPluginName .split('-') .map(name => capitalize(name)) .join(''); - const exportStatement = `export { plugin as ${pluginNameCapitalized}} from @backstage/plugin-${testPluginName}`; - await addExportStatement(testFilePath, exportStatement); + const exportStatement = `export { default as ${pluginNameCapitalized}} from @backstage/plugin-${testPluginName}`; + await addExportStatement(path.join(tempDir, testFilePath), exportStatement); }; const mkTestPluginDir = (testDirPath: string) => { - fse.mkdirSync(testDirPath); - for (let i = 0; i < 50; i++) - fse.createFileSync(path.join(testDirPath, `testFile${i}.ts`)); + const dirPath = `/${testDirPath}`; + + const pluginFiles: { [index: number]: string } = {}; + for (let i = 0; i < 50; i++) { + pluginFiles[i] = ''; + } + + mockFs({ + [dirPath]: pluginFiles, + }); }; describe('removePlugin', () => { - beforeAll(() => { + beforeEach(() => { // Create temporary directory for all tests - createTemporaryPluginFolder(tempDir); + const appPath = paths.resolveTargetRoot('packages', 'app'); + mockFs({ + [tempDir]: { + 'package.json': mockFs.load(path.join(appPath, 'package.json')), + src: { + 'plugin.ts': mockFs.load(path.join(appPath, 'src', 'plugins.ts')), + }, + }, + }); }); afterAll(() => { - // Remove temporary directory - fse.removeSync(tempDir); + mockFs.restore(); }); describe('Remove Plugin Dependencies', () => { - const appPath = paths.resolveTargetRoot('packages', 'app'); const githubDir = paths.resolveTargetRoot('.github'); it('removes plugin references from /packages/app/package.json', async () => { // Set up test - const packageFilePath = path.join(appPath, 'package.json'); - const testFilePath = path.join(tempDir, 'test.json'); + const packageFilePath = path.join(tempDir, 'package.json'); + const testFilePath = 'test.json'; createTestPackageFile(testFilePath, packageFilePath); - try { - await removeReferencesFromAppPackage(testFilePath, testPluginName); - const testFileContent = removeEmptyLines( - fse.readFileSync(testFilePath, 'utf8'), - ); - const packageFileContent = removeEmptyLines( - fse.readFileSync(packageFilePath, 'utf8'), - ); - expect(testFileContent).toBe(packageFileContent); - } finally { - fse.removeSync(testFilePath); - } - }); + await removeReferencesFromAppPackage( + path.join(tempDir, testFilePath), + testPluginName, + ); + const testFileContent = removeEmptyLines( + fse.readFileSync(path.join(tempDir, testFilePath), 'utf8'), + ); - it('removes plugin exports from /packages/app/src/package.json', async () => { - const testFilePath = path.join(tempDir, 'test.ts'); - const pluginsFilePaths = path.join(appPath, 'src', 'plugins.ts'); - await createTestPluginFile(testFilePath, pluginsFilePaths); - try { - await removeReferencesFromPluginsFile(testFilePath, testPluginName); - const testFileContent = removeEmptyLines( - fse.readFileSync(testFilePath, 'utf8'), - ); - const pluginsFileContent = removeEmptyLines( - fse.readFileSync(pluginsFilePaths, 'utf8'), - ); - expect(testFileContent).toBe(pluginsFileContent); - } finally { - fse.removeSync(testFilePath); - } + const packageFileContent = removeEmptyLines( + fse.readFileSync('/packages/app/package.json', 'utf8'), + ); + expect(testFileContent).toBe(packageFileContent); + }); + it('removes plugin exports from /packages/app/src/packacge.json', async () => { + const testFilePath = 'test.ts'; + const pluginsFilePaths = path.join(tempDir, 'src/plugin.ts'); + createTestPluginFile(testFilePath, pluginsFilePaths); + await removeReferencesFromPluginsFile( + path.join(tempDir, testFilePath), + testPluginName, + ); + const testFileContent = removeEmptyLines( + fse.readFileSync(path.join(tempDir, testFilePath), 'utf8'), + ); + const pluginsFileContent = removeEmptyLines( + fse.readFileSync('/packages/app/src/plugin.ts', 'utf8'), + ); + expect(testFileContent).toBe(pluginsFileContent); }); it('removes codeOwners references', async () => { - const testFilePath = path.join(tempDir, 'test'); + const testFileName = 'test'; + const testFilePath = path.join(tempDir, testFileName); const codeownersPath = path.join(githubDir, 'CODEOWNERS'); - try { - fse.copySync(codeownersPath, testFilePath); - const testFileContent = removeEmptyLines( - fse.readFileSync(testFilePath, 'utf8'), - ); - const codeOwnersFileContent = removeEmptyLines( - fse.readFileSync(codeownersPath, 'utf8'), - ); - await addCodeownersEntry(testFilePath!, `/plugins/${testPluginName}`, [ - '@thisIsAtestTeam', - 'test@gmail.com', - ]); - await removePluginFromCodeOwners(testFilePath, testPluginName); - expect(testFileContent).toBe(codeOwnersFileContent); - } finally { - if (fse.existsSync(testFilePath)) fse.removeSync(testFilePath); - } + const mockedCodeownersPath = '/.github/CODEOWNERS'; + + mockFs({ + [tempDir]: { + [testFileName]: '', + }, + '/.github': { + CODEOWNERS: mockFs.load(codeownersPath), + }, + }); + fse.copySync(mockedCodeownersPath, testFilePath); + const testFileContent = removeEmptyLines( + fse.readFileSync(testFilePath, 'utf8'), + ); + const codeOwnersFileContent = removeEmptyLines( + fse.readFileSync(mockedCodeownersPath, 'utf8'), + ); + await addCodeownersEntry(testFilePath!, `/plugins/${testPluginName}`, [ + '@thisIsAtestTeam', + 'test@gmail.com', + ]); + await removePluginFromCodeOwners(testFilePath, testPluginName); + expect(testFileContent).toBe(codeOwnersFileContent); }); }); @@ -161,34 +201,35 @@ describe('removePlugin', () => { describe('Removes Plugin Directory', () => { it('removes plugin directory from /plugins', async () => { - try { - mkTestPluginDir(testDirPath); - expect(fse.existsSync(testDirPath)).toBeTruthy(); - await removePluginDirectory(testDirPath); - expect(fse.existsSync(testDirPath)).toBeFalsy(); - } finally { - if (fse.existsSync(testDirPath)) fse.removeSync(testDirPath); - } + mkTestPluginDir(testDirPath); + expect(fse.existsSync(testDirPath)).toBeTruthy(); + await removePluginDirectory(testDirPath); + expect(fse.existsSync(testDirPath)).toBeFalsy(); }); }); describe('Removes System Link', () => { it('removes system link from @backstage', async () => { - const scopedDir = paths.resolveTargetRoot('node_modules', '@backstage'); - const testSymLinkPath = path.join( - scopedDir, - `plugin-${testPluginName}`, - ); - try { - mkTestPluginDir(testDirPath); - fse.ensureSymlinkSync(testSymLinkPath, testDirPath); + const symLink = `plugin-${testPluginName}`; + const testSymLinkPath = `/node_modules/@backstage/${symLink}`; + const mockedTestDirPath = path.join('/plugins', testPluginName); - await removeSymLink(testSymLinkPath); - expect(fse.existsSync(testSymLinkPath)).toBeFalsy(); - } finally { - if (fse.existsSync(testDirPath)) fse.removeSync(testDirPath); - if (fse.existsSync(testSymLinkPath)) fse.removeSync(testSymLinkPath); - } + mockFs({ + '/plugins': { + [testPluginName]: {}, + }, + '/node_modules': { + '@backstage': { + [symLink]: mockFs.symlink({ + path: mockedTestDirPath, + }), + }, + }, + }); + + expect(fse.existsSync(testSymLinkPath)).toBeTruthy(); + await removeSymLink(testSymLinkPath); + expect(fse.existsSync(testSymLinkPath)).toBeFalsy(); }); }); }); diff --git a/packages/cli/src/lib/tasks.test.ts b/packages/cli/src/lib/tasks.test.ts index 871d5a4d37..1fb07d06fc 100644 --- a/packages/cli/src/lib/tasks.test.ts +++ b/packages/cli/src/lib/tasks.test.ts @@ -15,39 +15,41 @@ */ import fs from 'fs-extra'; +import mockFs from 'mock-fs'; import { resolve as resolvePath } from 'path'; -import os from 'os'; -import del from 'del'; import { templatingTask } from './tasks'; describe('templatingTask', () => { + afterEach(() => { + mockFs.restore(); + }); + it('should template a directory with mix of regular files and templates', async () => { - // Set up a testing template directory - const tmplDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'test-')); - await fs.ensureDir(resolvePath(tmplDir, 'sub')); - await fs.writeFile(resolvePath(tmplDir, 'test.txt'), 'testing'); - await fs.writeFile( - resolvePath(tmplDir, 'sub/version.txt.hbs'), - 'version: {{version}}', - ); + // Testing template directory + const tmplDir = 'test-tmpl'; - // Set up a temporary dest dir to write the template to - const destDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'test-')); + // Temporary dest dir to write the template to + const destDir = 'test-dest'; - try { - await templatingTask(tmplDir, destDir, { - version: '0.0.0', - }); + mockFs({ + [tmplDir]: { + sub: { + 'version.txt.hbs': 'version: {{version}}', + }, + 'test.txt': 'testing', + }, + [destDir]: {}, + }); - await expect( - fs.readFile(resolvePath(destDir, 'test.txt'), 'utf8'), - ).resolves.toBe('testing'); - await expect( - fs.readFile(resolvePath(destDir, 'sub/version.txt'), 'utf8'), - ).resolves.toBe('version: 0.0.0'); - } finally { - await del(tmplDir, { force: true }); - await del(destDir, { force: true }); - } + await templatingTask(tmplDir, destDir, { + version: '0.0.0', + }); + + await expect( + fs.readFile(resolvePath(destDir, 'test.txt'), 'utf8'), + ).resolves.toBe('testing'); + await expect( + fs.readFile(resolvePath(destDir, 'sub/version.txt'), 'utf8'), + ).resolves.toBe('version: 0.0.0'); }); }); diff --git a/yarn.lock b/yarn.lock index 7e15bfdf28..4d1ec03ed1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5365,6 +5365,13 @@ dependencies: "@types/node" "*" +"@types/mock-fs@^4.13.0": + version "4.13.0" + resolved "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.0.tgz#b8b01cd2db588668b2532ecd21b1babd3fffb2c0" + integrity sha512-FUqxhURwqFtFBCuUj3uQMp7rPSQs//b3O9XecAVxhqS9y4/W8SIJEZFq2mmpnFVZBXwR/2OyPLE97CpyYiB8Mw== + dependencies: + "@types/node" "*" + "@types/morgan@^1.9.0": version "1.9.1" resolved "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.1.tgz#6457872df95647c1dbc6b3741e8146b71ece74bf"