diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 24959ff290..db04e7626c 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -87,7 +87,6 @@ "@types/node-forge": "^1.3.0", "@types/stoppable": "^1.1.0", "http-errors": "^2.0.0", - "mock-fs": "^5.2.0", "supertest": "^6.1.3" }, "configSchema": "config.d.ts", diff --git a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts index 448f1a8ff7..92f34bb94f 100644 --- a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts +++ b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts @@ -14,28 +14,31 @@ * limitations under the License. */ -import mockFs from 'mock-fs'; -import { resolve as resolvePath, dirname } from 'path'; -import { startTestBackend, mockServices } from '@backstage/backend-test-utils'; +import { + startTestBackend, + mockServices, + createMockDirectory, +} from '@backstage/backend-test-utils'; import { featureDiscoveryServiceFactory } from './featureDiscoveryServiceFactory'; -const rootDir = dirname(process.argv[1]); +const mockDir = createMockDirectory(); +process.argv[1] = mockDir.path; + +const pluginApiPath = require.resolve('@backstage/backend-plugin-api'); describe('featureDiscoveryServiceFactory', () => { beforeEach(() => { - mockFs({ - [rootDir]: { - 'package.json': JSON.stringify({ - name: 'example-app', - dependencies: { - 'detected-plugin': '0.0.0', - 'detected-module': '0.0.0', - 'detected-plugin-with-alpha': '0.0.0', - 'detected-library': '0.0.0', - }, - }), - }, - [resolvePath(rootDir, 'node_modules/detected-plugin')]: { + mockDir.setContent({ + 'package.json': JSON.stringify({ + name: 'example-app', + dependencies: { + 'detected-plugin': '0.0.0', + 'detected-module': '0.0.0', + 'detected-plugin-with-alpha': '0.0.0', + 'detected-library': '0.0.0', + }, + }), + 'node_modules/detected-plugin': { 'package.json': JSON.stringify({ name: 'detected-plugin', main: 'index.js', @@ -44,7 +47,7 @@ describe('featureDiscoveryServiceFactory', () => { }, }), 'index.js': ` - const { createBackendPlugin, coreServices } = require('@backstage/backend-plugin-api'); + const { createBackendPlugin, coreServices } = require('${pluginApiPath}'); exports.default = createBackendPlugin({ pluginId: 'detected', register(env) { @@ -58,7 +61,7 @@ describe('featureDiscoveryServiceFactory', () => { }); `, }, - [resolvePath(rootDir, 'node_modules/detected-module')]: { + 'node_modules/detected-module': { 'package.json': JSON.stringify({ name: 'detected-module', main: 'index.js', @@ -67,7 +70,7 @@ describe('featureDiscoveryServiceFactory', () => { }, }), 'index.js': ` - const { createBackendModule, coreServices } = require('@backstage/backend-plugin-api'); + const { createBackendModule, coreServices } = require('${pluginApiPath}'); exports.default = createBackendModule({ pluginId: 'detected', moduleId: 'derp', @@ -82,7 +85,7 @@ describe('featureDiscoveryServiceFactory', () => { }); `, }, - [resolvePath(rootDir, 'node_modules/detected-plugin-with-alpha')]: { + 'node_modules/detected-plugin-with-alpha': { 'package.json': JSON.stringify({ name: 'detected-plugin-with-alpha', main: 'index.js', @@ -101,7 +104,7 @@ describe('featureDiscoveryServiceFactory', () => { }), 'index.js': `exports.default = undefined;`, 'alpha.js': ` - const { createBackendPlugin, coreServices } = require('@backstage/backend-plugin-api'); + const { createBackendPlugin, coreServices } = require('${pluginApiPath}'); exports.default = createBackendPlugin({ pluginId: 'detected-alpha', register(env) { @@ -115,7 +118,7 @@ describe('featureDiscoveryServiceFactory', () => { }); `, }, - [resolvePath(rootDir, 'node_modules/detected-library')]: { + 'node_modules/detected-library': { 'package.json': JSON.stringify({ name: 'detected-library', main: 'index.js', @@ -124,7 +127,7 @@ describe('featureDiscoveryServiceFactory', () => { }, }), 'index.js': ` - const { createServiceFactory, createServiceRef, coreServices } = require('@backstage/backend-plugin-api'); + const { createServiceFactory, createServiceRef, coreServices } = require('${pluginApiPath}'); exports.default = createServiceFactory({ service: createServiceRef({ id: 'test', scope: 'root' }), deps: { logger: coreServices.rootLogger }, @@ -138,10 +141,6 @@ describe('featureDiscoveryServiceFactory', () => { }); }); - afterEach(() => { - mockFs.restore(); - }); - it('should detect plugin and module packages when "all" is specified', async () => { const mock = mockServices.rootLogger.mock({ child: () => mock }); diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index fb17d00378..2478bc7036 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -57,7 +57,6 @@ "@types/json-schema-merge-allof": "^0.6.0", "@types/mock-fs": "^4.10.0", "@types/yup": "^0.29.13", - "mock-fs": "^5.2.0", "msw": "^1.0.0", "zen-observable": "^0.10.0" }, diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index 9554e8767e..a8299962b7 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -16,15 +16,62 @@ import { AppConfig } from '@backstage/config'; import { loadConfig } from './loader'; -import mockFs from 'mock-fs'; import fs from 'fs-extra'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { resolve as resolvePath, sep } from 'path'; - -const root = resolvePath('/'); +import { createMockDirectory } from '@backstage/backend-test-utils'; describe('loadConfig', () => { + const mockDir = createMockDirectory({ + content: { + 'app-config.yaml': ` + app: + title: Example App + sessionKey: + $file: secrets/session-key.txt + escaped: \$\${Escaped} + `, + 'app-config2.yaml': ` + app: + title: Example App 2 + sessionKey: + $file: secrets/session-key.txt + escaped: \$\${Escaped} + `, + 'app-config.development.yaml': ` + app: + sessionKey: development-key + backend: + $include: ./included.yaml + other: + $include: secrets/included.yaml + `, + 'secrets/session-key.txt': 'abc123', + 'secrets/included.yaml': ` + secret: + $file: session-key.txt + `, + 'included.yaml': ` + foo: + bar: token \${MY_SECRET} + `, + 'app-config.substitute.yaml': ` + app: + someConfig: + $include: \${SUBSTITUTE_ME}.yaml + noSubstitute: + $file: \$\${ESCAPE_ME}.txt + `, + 'substituted.yaml': ` + secret: + $file: secrets/\${SUBSTITUTE_ME}.txt + `, + 'secrets/substituted.txt': '123abc', + '${ESCAPE_ME}.txt': 'notSubstituted', + 'empty.yaml': '# just a comment', + }, + }); + const server = setupServer(); const initialLoaderHandler = rest.get( `https://some.domain.io/app-config.yaml`, @@ -61,58 +108,9 @@ describe('loadConfig', () => { beforeEach(() => { process.env.MY_SECRET = 'is-secret'; process.env.SUBSTITUTE_ME = 'substituted'; - - mockFs({ - '/root/app-config.yaml': ` - app: - title: Example App - sessionKey: - $file: secrets/session-key.txt - escaped: \$\${Escaped} - `, - '/root/app-config2.yaml': ` - app: - title: Example App 2 - sessionKey: - $file: secrets/session-key.txt - escaped: \$\${Escaped} - `, - '/root/app-config.development.yaml': ` - app: - sessionKey: development-key - backend: - $include: ./included.yaml - other: - $include: secrets/included.yaml - `, - '/root/secrets/session-key.txt': 'abc123', - '/root/secrets/included.yaml': ` - secret: - $file: session-key.txt - `, - '/root/included.yaml': ` - foo: - bar: token \${MY_SECRET} - `, - '/root/app-config.substitute.yaml': ` - app: - someConfig: - $include: \${SUBSTITUTE_ME}.yaml - noSubstitute: - $file: \$\${ESCAPE_ME}.txt - `, - '/root/substituted.yaml': ` - secret: - $file: secrets/\${SUBSTITUTE_ME}.txt - `, - '/root/secrets/substituted.txt': '123abc', - '/root/${ESCAPE_ME}.txt': 'notSubstituted', - '/root/empty.yaml': '# just a comment', - }); }); afterEach(() => { - mockFs.restore(); server.resetHandlers(); }); @@ -121,7 +119,7 @@ describe('loadConfig', () => { it('load config from default path', async () => { await expect( loadConfig({ - configRoot: '/root', + configRoot: mockDir.path, configTargets: [], }), ).resolves.toEqual({ @@ -135,7 +133,7 @@ describe('loadConfig', () => { escaped: '${Escaped}', }, }, - path: `${root}root${sep}app-config.yaml`, + path: mockDir.resolve('app-config.yaml'), }, ], }); @@ -148,7 +146,7 @@ describe('loadConfig', () => { await expect( loadConfig({ - configRoot: '/root', + configRoot: mockDir.path, configTargets: [{ url: configUrl }], remote: { reloadIntervalSeconds: 30, @@ -173,10 +171,10 @@ describe('loadConfig', () => { it('loads config with secrets from two different files', async () => { await expect( loadConfig({ - configRoot: '/root', + configRoot: mockDir.path, configTargets: [ - { path: '/root/app-config.yaml' }, - { path: '/root/app-config2.yaml' }, + { path: mockDir.resolve('app-config.yaml') }, + { path: mockDir.resolve('app-config2.yaml') }, ], }), ).resolves.toEqual({ @@ -190,7 +188,7 @@ describe('loadConfig', () => { escaped: '${Escaped}', }, }, - path: '/root/app-config.yaml', + path: mockDir.resolve('app-config.yaml'), }, { context: 'app-config2.yaml', @@ -201,7 +199,7 @@ describe('loadConfig', () => { escaped: '${Escaped}', }, }, - path: '/root/app-config2.yaml', + path: mockDir.resolve('app-config2.yaml'), }, ], }); @@ -210,8 +208,8 @@ describe('loadConfig', () => { it('loads config with secrets from single file', async () => { await expect( loadConfig({ - configRoot: '/root', - configTargets: [{ path: '/root/app-config.yaml' }], + configRoot: mockDir.path, + configTargets: [{ path: mockDir.resolve('app-config.yaml') }], }), ).resolves.toEqual({ appConfigs: [ @@ -224,7 +222,7 @@ describe('loadConfig', () => { escaped: '${Escaped}', }, }, - path: '/root/app-config.yaml', + path: mockDir.resolve('app-config.yaml'), }, ], }); @@ -233,10 +231,10 @@ describe('loadConfig', () => { it('loads development config with secrets', async () => { await expect( loadConfig({ - configRoot: '/root', + configRoot: mockDir.path, configTargets: [ - { path: '/root/app-config.yaml' }, - { path: '/root/app-config.development.yaml' }, + { path: mockDir.resolve('app-config.yaml') }, + { path: mockDir.resolve('app-config.development.yaml') }, ], }), ).resolves.toEqual({ @@ -250,7 +248,7 @@ describe('loadConfig', () => { escaped: '${Escaped}', }, }, - path: '/root/app-config.yaml', + path: mockDir.resolve('app-config.yaml'), }, { context: 'app-config.development.yaml', @@ -267,7 +265,7 @@ describe('loadConfig', () => { secret: 'abc123', }, }, - path: '/root/app-config.development.yaml', + path: mockDir.resolve('app-config.development.yaml'), }, ], }); @@ -276,8 +274,10 @@ describe('loadConfig', () => { it('loads deep substituted config', async () => { await expect( loadConfig({ - configRoot: '/root', - configTargets: [{ path: '/root/app-config.substitute.yaml' }], + configRoot: mockDir.path, + configTargets: [ + { path: mockDir.resolve('app-config.substitute.yaml') }, + ], }), ).resolves.toEqual({ appConfigs: [ @@ -291,7 +291,7 @@ describe('loadConfig', () => { noSubstitute: 'notSubstituted', }, }, - path: '/root/app-config.substitute.yaml', + path: mockDir.resolve('app-config.substitute.yaml'), }, ], }); @@ -303,7 +303,7 @@ describe('loadConfig', () => { await expect( loadConfig({ - configRoot: '/root', + configRoot: mockDir.path, configTargets: [], watch: { onChange: onChange.resolve, @@ -321,12 +321,12 @@ describe('loadConfig', () => { escaped: '${Escaped}', }, }, - path: `${root}root${sep}app-config.yaml`, + path: mockDir.resolve('app-config.yaml'), }, ], }); - await fs.writeJson('/root/app-config.yaml', { + await fs.writeJson(mockDir.resolve('app-config.yaml'), { app: { title: 'New Title', }, @@ -339,7 +339,7 @@ describe('loadConfig', () => { title: 'New Title', }, }, - path: `${root}root${sep}app-config.yaml`, + path: mockDir.resolve('app-config.yaml'), }, ]); @@ -352,8 +352,10 @@ describe('loadConfig', () => { await expect( loadConfig({ - configRoot: '/root', - configTargets: [{ path: '/root/app-config.development.yaml' }], + configRoot: mockDir.path, + configTargets: [ + { path: mockDir.resolve('app-config.development.yaml') }, + ], watch: { onChange: onChange.resolve, stopSignal: stopSignal.promise, @@ -376,14 +378,14 @@ describe('loadConfig', () => { secret: 'abc123', }, }, - path: '/root/app-config.development.yaml', + path: mockDir.resolve('app-config.development.yaml'), }, ], }); // session-key is indirectly included in app-config.development.yaml // via included.yaml - await fs.writeFile('/root/secrets/session-key.txt', 'abc234'); + await fs.writeFile(mockDir.resolve('secrets/session-key.txt'), 'abc234'); await expect(onChange.promise).resolves.toEqual([ { @@ -401,7 +403,7 @@ describe('loadConfig', () => { secret: 'abc234', }, }, - path: '/root/app-config.development.yaml', + path: mockDir.resolve('app-config.development.yaml'), }, ]); @@ -417,7 +419,7 @@ describe('loadConfig', () => { const configUrl = 'https://some.domain.io/app-config.yaml'; await expect( loadConfig({ - configRoot: '/root', + configRoot: mockDir.path, configTargets: [{ url: configUrl }], watch: { onChange: onChange.resolve, @@ -464,7 +466,7 @@ describe('loadConfig', () => { const stopSignal = defer(); await loadConfig({ - configRoot: '/root', + configRoot: mockDir.path, configTargets: [], watch: { onChange: () => { @@ -476,7 +478,7 @@ describe('loadConfig', () => { stopSignal.resolve(); - await fs.writeJson('/root/app-config.yaml', { + await fs.writeJson(mockDir.resolve('app-config.yaml'), { app: { title: 'New Title', }, @@ -487,8 +489,8 @@ describe('loadConfig', () => { it('handles empty files gracefully', async () => { await expect( loadConfig({ - configRoot: '/root', - configTargets: [{ path: '/root/empty.yaml' }], + configRoot: mockDir.path, + configTargets: [{ path: mockDir.resolve('empty.yaml') }], }), ).resolves.toEqual({ appConfigs: [], diff --git a/packages/config-loader/src/schema/collect.test.ts b/packages/config-loader/src/schema/collect.test.ts index 2754f788dd..f513253619 100644 --- a/packages/config-loader/src/schema/collect.test.ts +++ b/packages/config-loader/src/schema/collect.test.ts @@ -14,10 +14,16 @@ * limitations under the License. */ -import mockFs from 'mock-fs'; +import { createMockDirectory } from '@backstage/backend-test-utils'; import { collectConfigSchemas } from './collect'; import path from 'path'; +// cwd must be restored +const origDir = process.cwd(); +afterAll(() => { + process.chdir(origDir); +}); + const mockSchema = { type: 'object', properties: { @@ -28,25 +34,15 @@ const mockSchema = { }, }; -// Gotta make sure this is in the compiler cache before we start mocking the filesystem -require('typescript-json-schema'); - -// We need to load in actual TS libraries when using mock-fs. -// This lookup is to allow the `typescript` dependency to exist either -// at top level or inside node_modules of typescript-json-schema -const typescriptModuleDir = path.dirname( - require.resolve('typescript/package.json', { - paths: [require.resolve('typescript-json-schema')], - }), -); - describe('collectConfigSchemas', () => { + const mockDir = createMockDirectory(); + afterEach(() => { - mockFs.restore(); + mockDir.clear(); }); it('should not find any schemas without packages', async () => { - mockFs({ + mockDir.setContent({ 'lerna.json': JSON.stringify({ packages: ['packages/*'], }), @@ -56,7 +52,7 @@ describe('collectConfigSchemas', () => { }); it('should find schema in a local package', async () => { - mockFs({ + mockDir.setContent({ node_modules: { a: { 'package.json': JSON.stringify({ @@ -66,6 +62,7 @@ describe('collectConfigSchemas', () => { }, }, }); + process.chdir(mockDir.path); await expect(collectConfigSchemas(['a'], [])).resolves.toEqual([ { @@ -76,7 +73,7 @@ describe('collectConfigSchemas', () => { }); it('should find schema at explicit package path', async () => { - mockFs({ + mockDir.setContent({ root: { 'package.json': JSON.stringify({ name: 'root', @@ -84,6 +81,7 @@ describe('collectConfigSchemas', () => { }), }, }); + process.chdir(mockDir.path); await expect( collectConfigSchemas([], [path.join('root', 'package.json')]), @@ -96,7 +94,7 @@ describe('collectConfigSchemas', () => { }); it('should find schema in transitive dependencies and explicit path', async () => { - mockFs({ + mockDir.setContent({ root: { 'package.json': JSON.stringify({ name: 'root', @@ -152,6 +150,7 @@ describe('collectConfigSchemas', () => { }, }, }); + process.chdir(mockDir.path); await expect( collectConfigSchemas(['a'], [path.join('root', 'package.json')]), @@ -178,7 +177,7 @@ describe('collectConfigSchemas', () => { }); it('should schema of different types', async () => { - mockFs({ + mockDir.setContent({ node_modules: { a: { 'package.json': JSON.stringify({ @@ -198,15 +197,16 @@ describe('collectConfigSchemas', () => { name: 'c', configSchema: 'schema.d.ts', }), - 'schema.d.ts': `export interface Config { + 'schema.d.ts': ` + export interface Config { /** @visibility secret */ tsKey: string - }`, + } + `, }, }, - // TypeScript compilation needs to load some real files inside the typescript dir - [typescriptModuleDir]: (mockFs as any).load(typescriptModuleDir), }); + process.chdir(mockDir.path); await expect(collectConfigSchemas(['a', 'b', 'c'], [])).resolves.toEqual([ { @@ -235,7 +235,7 @@ describe('collectConfigSchemas', () => { }); it('should load schema from different package versions', async () => { - mockFs({ + mockDir.setContent({ node_modules: { a: { 'package.json': JSON.stringify({ @@ -275,6 +275,7 @@ describe('collectConfigSchemas', () => { }, }, }); + process.chdir(mockDir.path); await expect(collectConfigSchemas(['a'], [])).resolves.toEqual([ { @@ -303,7 +304,7 @@ describe('collectConfigSchemas', () => { }); it('should not allow unknown schema file types', async () => { - mockFs({ + mockDir.setContent({ node_modules: { a: { 'package.json': JSON.stringify({ @@ -314,6 +315,7 @@ describe('collectConfigSchemas', () => { }, }, }); + process.chdir(mockDir.path); await expect(collectConfigSchemas(['a'], [])).rejects.toThrow( 'Config schema files must be .json or .d.ts, got schema.yaml', @@ -321,7 +323,7 @@ describe('collectConfigSchemas', () => { }); it('should reject typescript config declaration without a Config type', async () => { - mockFs({ + mockDir.setContent({ node_modules: { a: { 'package.json': JSON.stringify({ @@ -331,9 +333,8 @@ describe('collectConfigSchemas', () => { 'schema.d.ts': `export interface NotConfig {}`, }, }, - // TypeScript compilation needs to load some real files inside the typescript dir - [typescriptModuleDir]: (mockFs as any).load(typescriptModuleDir), }); + process.chdir(mockDir.path); await expect(collectConfigSchemas(['a'], [])).rejects.toThrow( `Invalid schema in ${path.join( diff --git a/packages/config-loader/src/schema/load.test.ts b/packages/config-loader/src/schema/load.test.ts index d84bb3871b..525565f3d5 100644 --- a/packages/config-loader/src/schema/load.test.ts +++ b/packages/config-loader/src/schema/load.test.ts @@ -14,16 +14,24 @@ * limitations under the License. */ -import mockFs from 'mock-fs'; +import { createMockDirectory } from '@backstage/backend-test-utils'; import { loadConfigSchema } from './load'; +// cwd must be restored +const origDir = process.cwd(); +afterAll(() => { + process.chdir(origDir); +}); + describe('loadConfigSchema', () => { + const mockDir = createMockDirectory(); + afterEach(() => { - mockFs.restore(); + mockDir.clear(); }); it('should load schema from packages or data', async () => { - mockFs({ + mockDir.setContent({ node_modules: { a: { 'package.json': JSON.stringify({ @@ -53,6 +61,7 @@ describe('loadConfigSchema', () => { }, }, }); + process.chdir(mockDir.path); const schema = await loadConfigSchema({ dependencies: ['a'], @@ -119,7 +128,7 @@ describe('loadConfigSchema', () => { describe('should consider schema', () => { it('when filtering simple config', async () => { - mockFs({ + mockDir.setContent({ 'package.json': JSON.stringify({ name: 'a', configSchema: { @@ -131,6 +140,7 @@ describe('loadConfigSchema', () => { }, }), }); + process.chdir(mockDir.path); const schema = await loadConfigSchema({ packagePaths: ['package.json'], @@ -156,7 +166,7 @@ describe('loadConfigSchema', () => { }); it('when filtering nested config', async () => { - mockFs({ + mockDir.setContent({ 'package.json': JSON.stringify({ name: 'a', configSchema: { @@ -185,6 +195,7 @@ describe('loadConfigSchema', () => { }, }), }); + process.chdir(mockDir.path); const schema = await loadConfigSchema({ packagePaths: ['package.json'], @@ -244,7 +255,7 @@ describe('loadConfigSchema', () => { }); it('when filtering config with required values', async () => { - mockFs({ + mockDir.setContent({ 'package.json': JSON.stringify({ name: 'a', configSchema: { @@ -261,6 +272,7 @@ describe('loadConfigSchema', () => { }, }), }); + process.chdir(mockDir.path); const schema = await loadConfigSchema({ packagePaths: ['package.json'], diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 6ae88a8e4c..56764b6be9 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -42,6 +42,7 @@ "recursive-readdir": "^2.2.2" }, "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", diff --git a/packages/create-app/src/createApp.test.ts b/packages/create-app/src/createApp.test.ts index 7cb9733567..844d2aee1b 100644 --- a/packages/create-app/src/createApp.test.ts +++ b/packages/create-app/src/createApp.test.ts @@ -15,13 +15,13 @@ */ import inquirer from 'inquirer'; -import mockFs from 'mock-fs'; import path from 'path'; import { Command } from 'commander'; import * as tasks from './lib/tasks'; import createApp from './createApp'; import { findPaths } from '@backstage/cli-common'; import { tmpdir } from 'os'; +import { createMockDirectory } from '@backstage/backend-test-utils'; jest.mock('./lib/tasks'); @@ -40,16 +40,7 @@ const moveAppMock = jest.spyOn(tasks, 'moveAppTask'); const buildAppMock = jest.spyOn(tasks, 'buildAppTask'); describe('command entrypoint', () => { - beforeEach(() => { - mockFs({ - [`${__dirname}/package.json`]: '', // required by `findPaths(__dirname)` - 'templates/': mockFs.load(path.resolve(__dirname, '../templates/')), - }); - }); - - afterEach(() => { - mockFs.restore(); - }); + const mockDir = createMockDirectory({ mockOsTmpDir: true }); beforeEach(() => { promptMock.mockResolvedValueOnce({ @@ -62,6 +53,7 @@ describe('command entrypoint', () => { }); afterEach(() => { + mockDir.clear(); jest.resetAllMocks(); }); @@ -75,7 +67,6 @@ describe('command entrypoint', () => { findPaths(__dirname).resolveTarget( 'packages', 'create-app', - 'src', 'templates', 'default-app', ), @@ -97,7 +88,6 @@ describe('command entrypoint', () => { findPaths(__dirname).resolveTarget( 'packages', 'create-app', - 'src', 'templates', 'default-app', ), diff --git a/packages/create-app/src/lib/tasks.test.ts b/packages/create-app/src/lib/tasks.test.ts index 68afdc9222..e122374004 100644 --- a/packages/create-app/src/lib/tasks.test.ts +++ b/packages/create-app/src/lib/tasks.test.ts @@ -15,9 +15,8 @@ */ import fs from 'fs-extra'; -import mockFs from 'mock-fs'; import child_process from 'child_process'; -import path, { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'path'; import os from 'os'; import { Task, @@ -29,6 +28,7 @@ import { tryInitGitRepository, readGitConfig, } from './tasks'; +import { createMockDirectory } from '@backstage/backend-test-utils'; jest.spyOn(Task, 'log').mockReturnValue(undefined); jest.spyOn(Task, 'error').mockReturnValue(undefined); @@ -101,21 +101,33 @@ describe('tasks', () => { ) => void >; + const mockDir = createMockDirectory(); + + const realChdir = process.chdir; + // If anyone calls chdir then make it resolve within the tmpdir + const mockChdir = jest.spyOn(process, 'chdir'); + beforeEach(() => { - mockFs({ - 'projects/my-module.ts': '', - 'projects/dir/my-file.txt': '', - 'tmp/mockApp/.gitignore': '', - 'tmp/mockApp/package.json': '', - 'tmp/mockApp/packages/app/package.json': '', - // load templates into mock filesystem - 'templates/': mockFs.load(path.resolve(__dirname, '../../templates/')), + mockDir.setContent({ + projects: { + 'my-module.ts': '', + 'dir/my-file.txt': '', + }, + 'tmp/mockApp': { + '.gitignore': '', + 'package.json': '', + 'packages/app/package.json': '', + }, }); + realChdir(mockDir.path); + mockChdir.mockImplementation((dir: string) => + realChdir(mockDir.resolve(dir)), + ); }); afterEach(() => { mockExec.mockRestore(); - mockFs.restore(); + mockChdir.mockReset(); }); describe('checkAppExistsTask', () => { @@ -164,8 +176,6 @@ describe('tasks', () => { describe('buildAppTask', () => { it('should change to `appDir` and run `yarn install` and `yarn tsc`', async () => { - const mockChdir = jest.spyOn(process, 'chdir'); - // requires callback implementation to support `promisify` wrapper // https://stackoverflow.com/a/60579617/10044859 mockExec.mockImplementation((_command, callback) => { @@ -199,8 +209,6 @@ describe('tasks', () => { }); it('should error out on incorrect yarn version', async () => { - const mockChdir = jest.spyOn(process, 'chdir'); - // requires callback implementation to support `promisify` wrapper // https://stackoverflow.com/a/60579617/10044859 mockExec.mockImplementation((_command, callback) => { @@ -265,7 +273,7 @@ describe('tasks', () => { describe('templatingTask', () => { it('should generate a project populating context parameters', async () => { - const templateDir = 'templates/default-app'; + const templateDir = resolvePath(__dirname, '../../templates/default-app'); const destinationDir = 'templatedApp'; const context = { name: 'SuperCoolBackstageInstance', diff --git a/yarn.lock b/yarn.lock index 24252922b9..3bd1e9d44e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3415,7 +3415,6 @@ __metadata: logform: ^2.3.2 minimatch: ^5.0.0 minimist: ^1.2.5 - mock-fs: ^5.2.0 morgan: ^1.10.0 node-forge: ^1.3.1 selfsigned: ^2.0.0 @@ -3913,7 +3912,6 @@ __metadata: json-schema-traverse: ^1.0.0 lodash: ^4.17.21 minimist: ^1.2.5 - mock-fs: ^5.2.0 msw: ^1.0.0 node-fetch: ^2.6.7 typescript-json-schema: ^0.55.0 @@ -4207,6 +4205,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/create-app@workspace:packages/create-app" dependencies: + "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/cli-common": "workspace:^" "@types/command-exists": ^1.2.0