diff --git a/tools/devworkspace-generator/package.json b/tools/devworkspace-generator/package.json index cf763c3fd..ea382d263 100644 --- a/tools/devworkspace-generator/package.json +++ b/tools/devworkspace-generator/package.json @@ -18,7 +18,7 @@ "prepare": "yarn run clean && yarn run build", "clean": "rimraf lib", "build": "yarn run format && yarn run compile && yarn run lint && yarn run test", - "compile": "tsc --project .", + "compile": "tsc --declaration --project .", "format": "if-env SKIP_FORMAT=true && echo 'skip format check' || prettier --check '{src,tests}/**/*.ts' package.json", "format:fix": "prettier --write '{src,tests}/**/*.ts' package.json", "lint": "if-env SKIP_LINT=true && echo 'skip lint check' || eslint --cache=true --no-error-on-unmatched-pattern=true '{src,tests}/(!model|**)/*.ts'", diff --git a/tools/devworkspace-generator/src/generate.ts b/tools/devworkspace-generator/src/generate.ts index 8d6c01d69..b8960ac59 100644 --- a/tools/devworkspace-generator/src/generate.ts +++ b/tools/devworkspace-generator/src/generate.ts @@ -28,17 +28,19 @@ export class Generate { @inject(DevContainerComponentFinder) private devContainerComponentFinder: DevContainerComponentFinder; - async generate(devfileContent: string, editorContent: string, outputFile: string): Promise { + async generate(devfileContent: string, editorContent: string, outputFile?: string): Promise { const context = await this.generateContent(devfileContent, editorContent); // write the result - // write templates and then DevWorkspace in a single file - const allContentArray = context.devWorkspaceTemplates.map(template => jsYaml.dump(template)); - allContentArray.push(jsYaml.dump(context.devWorkspace)); + if (outputFile) { + // write templates and then DevWorkspace in a single file + const allContentArray = context.devWorkspaceTemplates.map(template => jsYaml.dump(template)); + allContentArray.push(jsYaml.dump(context.devWorkspace)); - const generatedContent = allContentArray.join('---\n'); + const generatedContent = allContentArray.join('---\n'); - await fs.writeFile(outputFile, generatedContent, 'utf-8'); + await fs.writeFile(outputFile, generatedContent, 'utf-8'); + } console.log(`DevWorkspace ${context.devWorkspaceTemplates[0].metadata.name} was generated.`); return context; diff --git a/tools/devworkspace-generator/src/main.ts b/tools/devworkspace-generator/src/main.ts index f259b9023..a1f769fd2 100644 --- a/tools/devworkspace-generator/src/main.ts +++ b/tools/devworkspace-generator/src/main.ts @@ -20,56 +20,29 @@ import { V1alpha2DevWorkspaceSpecTemplate } from '@devfile/api'; import { DevfileContext } from './api/devfile-context'; export class Main { - protected async doStart(): Promise { - let devfilePath: string | undefined; - let devfileUrl: string | undefined; - let outputFile: string | undefined; - let editorPath: string | undefined; - let pluginRegistryUrl: string | undefined; - let editorEntry: string | undefined; - const projects: { name: string; location: string }[] = []; - - const args = process.argv.slice(2); - args.forEach(arg => { - if (arg.startsWith('--devfile-path:')) { - devfilePath = arg.substring('--devfile-path:'.length); - } - if (arg.startsWith('--devfile-url:')) { - devfileUrl = arg.substring('--devfile-url:'.length); - } - if (arg.startsWith('--plugin-registry-url:')) { - pluginRegistryUrl = arg.substring('--plugin-registry-url:'.length); - } - if (arg.startsWith('--editor-entry:')) { - editorEntry = arg.substring('--editor-entry:'.length); - } - if (arg.startsWith('--editor-path:')) { - editorPath = arg.substring('--editor-path:'.length); - } - if (arg.startsWith('--output-file:')) { - outputFile = arg.substring('--output-file:'.length); - } - if (arg.startsWith('--project.')) { - const name = arg.substring('--project.'.length, arg.indexOf('=')); - let location = arg.substring(arg.indexOf('=') + 1); - location = location.replace('{{_INTERNAL_URL_}}', '{{ INTERNAL_URL }}'); + // Generates a devfile context object based on params + public async generateDevfileContext(params: { + devfilePath?: string; + devfileUrl?: string; + devfileContent?: string; + outputFile?: string; + editorPath?: string; + pluginRegistryUrl?: string; + editorEntry?: string; + projects: { name: string; location: string }[]; + }): Promise { + let { devfilePath, devfileUrl, outputFile, editorPath, pluginRegistryUrl, editorEntry, projects } = params; - projects.push({ name, location }); - } - }); if (!editorPath && !editorEntry) { - throw new Error('missing --editor-path: or --editor-entry: parameter'); + throw new Error('missing editorPath or editorEntry'); + } + if (!devfilePath && !devfileUrl && !params.devfileContent) { + throw new Error('missing devfilePath or devfileUrl or devfileContent'); } if (editorEntry && !pluginRegistryUrl) { pluginRegistryUrl = 'https://eclipse-che.github.io/che-plugin-registry/main/v3'; console.log(`No plug-in registry url. Setting to ${pluginRegistryUrl}`); } - if (!devfilePath && !devfileUrl) { - throw new Error('missing --devfile-path: or --devfile-url: parameter'); - } - if (!outputFile) { - throw new Error('missing --output-file: parameter'); - } const axiosInstance = axios.default; const inversifyBinbding = new InversifyBinding(); @@ -107,8 +80,10 @@ export class Main { } // get back the content devfileContent = jsYaml.dump(devfileParsed); - } else { + } else if (devfilePath) { devfileContent = await fs.readFile(devfilePath); + } else { + devfileContent = params.devfileContent; } // enhance projects @@ -154,8 +129,62 @@ export class Main { } async start(): Promise { + let devfilePath: string | undefined; + let devfileUrl: string | undefined; + let outputFile: string | undefined; + let editorPath: string | undefined; + let pluginRegistryUrl: string | undefined; + let editorEntry: string | undefined; + const projects: { name: string; location: string }[] = []; + + const args = process.argv.slice(2); + args.forEach(arg => { + if (arg.startsWith('--devfile-path:')) { + devfilePath = arg.substring('--devfile-path:'.length); + } + if (arg.startsWith('--devfile-url:')) { + devfileUrl = arg.substring('--devfile-url:'.length); + } + if (arg.startsWith('--plugin-registry-url:')) { + pluginRegistryUrl = arg.substring('--plugin-registry-url:'.length); + } + if (arg.startsWith('--editor-entry:')) { + editorEntry = arg.substring('--editor-entry:'.length); + } + if (arg.startsWith('--editor-path:')) { + editorPath = arg.substring('--editor-path:'.length); + } + if (arg.startsWith('--output-file:')) { + outputFile = arg.substring('--output-file:'.length); + } + if (arg.startsWith('--project.')) { + const name = arg.substring('--project.'.length, arg.indexOf('=')); + let location = arg.substring(arg.indexOf('=') + 1); + location = location.replace('{{_INTERNAL_URL_}}', '{{ INTERNAL_URL }}'); + + projects.push({ name, location }); + } + }); + try { - await this.doStart(); + if (!editorPath && !editorEntry) { + throw new Error('missing --editor-path: or --editor-entry: parameter'); + } + if (!devfilePath && !devfileUrl) { + throw new Error('missing --devfile-path: or --devfile-url: parameter'); + } + if (!outputFile) { + throw new Error('missing --output-file: parameter'); + } + await this.generateDevfileContext({ + devfilePath, + devfileUrl, + editorPath, + outputFile, + pluginRegistryUrl, + editorEntry, + projects, + }); return true; } catch (error) { console.error('stack=' + error.stack); diff --git a/tools/devworkspace-generator/tests/generate.spec.ts b/tools/devworkspace-generator/tests/generate.spec.ts index b274564bf..922977746 100644 --- a/tools/devworkspace-generator/tests/generate.spec.ts +++ b/tools/devworkspace-generator/tests/generate.spec.ts @@ -34,8 +34,9 @@ describe('Test Generate', () => { devContainerFinder = container.get(DevContainerComponentFinder); }); - test('basics', async () => { - const devfileContent = ` + describe('Without writing an output file', () => { + test('basics', async () => { + const devfileContent = ` schemaVersion: 2.2.0 metadata: name: my-dummy-project @@ -45,60 +46,128 @@ components: container: image: quay.io/foo/bar `; - const fakeoutputDir = '/fake-output'; - const editorContent = ` + const editorContent = ` schemaVersion: 2.2.0 metadata: name: che-code `; - const fsWriteFileSpy = jest.spyOn(fs, 'writeFile'); - fsWriteFileSpy.mockReturnValue({}); - - let context = await generate.generate(devfileContent, editorContent, fakeoutputDir); - // expect to write the file - expect(fsWriteFileSpy).toBeCalled(); - expect(JSON.stringify(context.devfile)).toStrictEqual( - '{"schemaVersion":"2.2.0","metadata":{"name":"my-dummy-project"},"components":[{"name":"dev-container","mountSources":true,"container":{"image":"quay.io/foo/bar"},"attributes":{"controller.devfile.io/merge-contribution":true}}]}' - ); - const expectedDevWorkspace = { - apiVersion: 'workspace.devfile.io/v1alpha2', - kind: 'DevWorkspace', - metadata: { name: 'my-dummy-project' }, - spec: { - started: true, - template: { - components: [ - { - name: 'dev-container', - mountSources: true, - container: { - image: 'quay.io/foo/bar', - }, - attributes: { - 'controller.devfile.io/merge-contribution': true, + const fsWriteFileSpy = jest.spyOn(fs, 'writeFile'); + fsWriteFileSpy.mockReturnValue({}); + + let context = await generate.generate(devfileContent, editorContent); + // expect not to write the file + expect(fsWriteFileSpy).not.toBeCalled(); + expect(JSON.stringify(context.devfile)).toStrictEqual( + '{"schemaVersion":"2.2.0","metadata":{"name":"my-dummy-project"},"components":[{"name":"dev-container","mountSources":true,"container":{"image":"quay.io/foo/bar"},"attributes":{"controller.devfile.io/merge-contribution":true}}]}' + ); + const expectedDevWorkspace = { + apiVersion: 'workspace.devfile.io/v1alpha2', + kind: 'DevWorkspace', + metadata: { name: 'my-dummy-project' }, + spec: { + started: true, + template: { + components: [ + { + name: 'dev-container', + mountSources: true, + container: { + image: 'quay.io/foo/bar', + }, + attributes: { + 'controller.devfile.io/merge-contribution': true, + }, }, - }, - ], + ], + }, + contributions: [{ name: 'editor', kubernetes: { name: 'che-code-my-dummy-project' } }], }, - contributions: [{ name: 'editor', kubernetes: { name: 'che-code-my-dummy-project' } }], - }, - }; - expect(JSON.stringify(context.devWorkspace)).toStrictEqual(JSON.stringify(expectedDevWorkspace)); - const expectedDevWorkspaceTemplates = [ - { - apiVersion: 'workspace.devfile.io/v1alpha2', - kind: 'DevWorkspaceTemplate', - metadata: { name: 'che-code-my-dummy-project' }, - spec: {}, - }, - ]; - expect(JSON.stringify(context.devWorkspaceTemplates)).toStrictEqual(JSON.stringify(expectedDevWorkspaceTemplates)); - expect(context.suffix).toStrictEqual('my-dummy-project'); + }; + expect(JSON.stringify(context.devWorkspace)).toStrictEqual(JSON.stringify(expectedDevWorkspace)); + const expectedDevWorkspaceTemplates = [ + { + apiVersion: 'workspace.devfile.io/v1alpha2', + kind: 'DevWorkspaceTemplate', + metadata: { name: 'che-code-my-dummy-project' }, + spec: {}, + }, + ]; + expect(JSON.stringify(context.devWorkspaceTemplates)).toStrictEqual( + JSON.stringify(expectedDevWorkspaceTemplates) + ); + expect(context.suffix).toStrictEqual('my-dummy-project'); + }); }); - test('add attribute', async () => { - const devfileContent = ` + describe('With writing an output file', () => { + test('basics', async () => { + const devfileContent = ` +schemaVersion: 2.2.0 +metadata: + name: my-dummy-project +components: + - name: dev-container + mountSources: true + container: + image: quay.io/foo/bar +`; + const fakeoutputDir = '/fake-output'; + const editorContent = ` +schemaVersion: 2.2.0 +metadata: + name: che-code +`; + + const fsWriteFileSpy = jest.spyOn(fs, 'writeFile'); + fsWriteFileSpy.mockReturnValue({}); + + let context = await generate.generate(devfileContent, editorContent, fakeoutputDir); + // expect to write the file + expect(fsWriteFileSpy).toBeCalled(); + expect(JSON.stringify(context.devfile)).toStrictEqual( + '{"schemaVersion":"2.2.0","metadata":{"name":"my-dummy-project"},"components":[{"name":"dev-container","mountSources":true,"container":{"image":"quay.io/foo/bar"},"attributes":{"controller.devfile.io/merge-contribution":true}}]}' + ); + const expectedDevWorkspace = { + apiVersion: 'workspace.devfile.io/v1alpha2', + kind: 'DevWorkspace', + metadata: { name: 'my-dummy-project' }, + spec: { + started: true, + template: { + components: [ + { + name: 'dev-container', + mountSources: true, + container: { + image: 'quay.io/foo/bar', + }, + attributes: { + 'controller.devfile.io/merge-contribution': true, + }, + }, + ], + }, + contributions: [{ name: 'editor', kubernetes: { name: 'che-code-my-dummy-project' } }], + }, + }; + expect(JSON.stringify(context.devWorkspace)).toStrictEqual(JSON.stringify(expectedDevWorkspace)); + const expectedDevWorkspaceTemplates = [ + { + apiVersion: 'workspace.devfile.io/v1alpha2', + kind: 'DevWorkspaceTemplate', + metadata: { name: 'che-code-my-dummy-project' }, + spec: {}, + }, + ]; + expect(JSON.stringify(context.devWorkspaceTemplates)).toStrictEqual( + JSON.stringify(expectedDevWorkspaceTemplates) + ); + expect(context.suffix).toStrictEqual('my-dummy-project'); + }); + + test('add attribute', async () => { + const devfileContent = ` schemaVersion: 2.2.0 metadata: name: my-dummy-project @@ -110,61 +179,63 @@ components: container: image: quay.io/foo/bar `; - const fakeoutputDir = '/fake-output'; - const editorContent = ` + const fakeoutputDir = '/fake-output'; + const editorContent = ` schemaVersion: 2.2.0 metadata: name: che-code `; - const fsWriteFileSpy = jest.spyOn(fs, 'writeFile'); - fsWriteFileSpy.mockReturnValue({}); - - let context = await generate.generate(devfileContent, editorContent, fakeoutputDir); - // expect to write the file - expect(fsWriteFileSpy).toBeCalled(); - expect(JSON.stringify(context.devfile)).toStrictEqual( - '{"schemaVersion":"2.2.0","metadata":{"name":"my-dummy-project"},"components":[{"name":"dev-container","attributes":{"old":"attribute","controller.devfile.io/merge-contribution":true},"mountSources":true,"container":{"image":"quay.io/foo/bar"}}]}' - ); - const expectedDevWorkspace = { - apiVersion: 'workspace.devfile.io/v1alpha2', - kind: 'DevWorkspace', - metadata: { name: 'my-dummy-project' }, - spec: { - started: true, - template: { - components: [ - { - name: 'dev-container', - attributes: { - old: 'attribute', - 'controller.devfile.io/merge-contribution': true, - }, - mountSources: true, - container: { - image: 'quay.io/foo/bar', + const fsWriteFileSpy = jest.spyOn(fs, 'writeFile'); + fsWriteFileSpy.mockReturnValue({}); + + let context = await generate.generate(devfileContent, editorContent, fakeoutputDir); + // expect to write the file + expect(fsWriteFileSpy).toBeCalled(); + expect(JSON.stringify(context.devfile)).toStrictEqual( + '{"schemaVersion":"2.2.0","metadata":{"name":"my-dummy-project"},"components":[{"name":"dev-container","attributes":{"old":"attribute","controller.devfile.io/merge-contribution":true},"mountSources":true,"container":{"image":"quay.io/foo/bar"}}]}' + ); + const expectedDevWorkspace = { + apiVersion: 'workspace.devfile.io/v1alpha2', + kind: 'DevWorkspace', + metadata: { name: 'my-dummy-project' }, + spec: { + started: true, + template: { + components: [ + { + name: 'dev-container', + attributes: { + old: 'attribute', + 'controller.devfile.io/merge-contribution': true, + }, + mountSources: true, + container: { + image: 'quay.io/foo/bar', + }, }, - }, - ], + ], + }, + contributions: [{ name: 'editor', kubernetes: { name: 'che-code-my-dummy-project' } }], }, - contributions: [{ name: 'editor', kubernetes: { name: 'che-code-my-dummy-project' } }], - }, - }; - expect(JSON.stringify(context.devWorkspace)).toStrictEqual(JSON.stringify(expectedDevWorkspace)); - const expectedDevWorkspaceTemplates = [ - { - apiVersion: 'workspace.devfile.io/v1alpha2', - kind: 'DevWorkspaceTemplate', - metadata: { name: 'che-code-my-dummy-project' }, - spec: {}, - }, - ]; - expect(JSON.stringify(context.devWorkspaceTemplates)).toStrictEqual(JSON.stringify(expectedDevWorkspaceTemplates)); - expect(context.suffix).toStrictEqual('my-dummy-project'); - }); + }; + expect(JSON.stringify(context.devWorkspace)).toStrictEqual(JSON.stringify(expectedDevWorkspace)); + const expectedDevWorkspaceTemplates = [ + { + apiVersion: 'workspace.devfile.io/v1alpha2', + kind: 'DevWorkspaceTemplate', + metadata: { name: 'che-code-my-dummy-project' }, + spec: {}, + }, + ]; + expect(JSON.stringify(context.devWorkspaceTemplates)).toStrictEqual( + JSON.stringify(expectedDevWorkspaceTemplates) + ); + expect(context.suffix).toStrictEqual('my-dummy-project'); + }); - test('basics no name', async () => { - const devfileContent = ` + test('basics no name', async () => { + const devfileContent = ` schemaVersion: 2.2.0 metadata: foo: bar @@ -174,19 +245,20 @@ components: container: image: quay.io/foo/bar `; - const fakeoutputDir = '/fake-output'; - const editorContent = ` + const fakeoutputDir = '/fake-output'; + const editorContent = ` schemaVersion: 2.1.0 metadata: name: che-code `; - const fsWriteFileSpy = jest.spyOn(fs, 'writeFile'); - fsWriteFileSpy.mockReturnValue({}); + const fsWriteFileSpy = jest.spyOn(fs, 'writeFile'); + fsWriteFileSpy.mockReturnValue({}); - let context = await generate.generate(devfileContent, editorContent, fakeoutputDir); - // expect to write the file - expect(fsWriteFileSpy).toBeCalled(); - expect(context.suffix).toStrictEqual(''); + let context = await generate.generate(devfileContent, editorContent, fakeoutputDir); + // expect to write the file + expect(fsWriteFileSpy).toBeCalled(); + expect(context.suffix).toStrictEqual(''); + }); }); }); diff --git a/tools/devworkspace-generator/tests/main.spec.ts b/tools/devworkspace-generator/tests/main.spec.ts index 5c9447d8c..38a915352 100644 --- a/tools/devworkspace-generator/tests/main.spec.ts +++ b/tools/devworkspace-generator/tests/main.spec.ts @@ -82,165 +82,255 @@ describe('Test Main with stubs', () => { } } - beforeEach(() => { - initArgs(FAKE_DEVFILE_PATH, undefined, FAKE_EDITOR_PATH, undefined, FAKE_OUTPUT_FILE, FAKE_PLUGIN_REGISTRY_URL); - // mock devfile and editor - readFileSpy.mockResolvedValueOnce(''); - readFileSpy.mockResolvedValueOnce(''); - - spyInitBindings = jest.spyOn(InversifyBinding.prototype, 'initBindings'); - spyInitBindings.mockImplementation(() => Promise.resolve(container)); - toSelfMethod.mockReturnValue(selfMock), containerBindMethod.mockReturnValue(bindMock); - containerGetMethod.mockReturnValueOnce(generateMock); - }); - - afterEach(() => { - process.argv = originalArgs; - jest.restoreAllMocks(); - jest.resetAllMocks(); - }); - beforeEach(() => { console.error = mockedConsoleError; console.log = mockedConsoleLog; }); + afterEach(() => { console.error = originalConsoleError; console.log = originalConsoleLog; }); - test('success', async () => { - process.argv.push('--project.foo=bar'); - const main = new Main(); - const returnCode = await main.start(); - expect(mockedConsoleError).toBeCalledTimes(0); + describe('start', () => { + beforeEach(() => { + initArgs(FAKE_DEVFILE_PATH, undefined, FAKE_EDITOR_PATH, undefined, FAKE_OUTPUT_FILE, FAKE_PLUGIN_REGISTRY_URL); + // mock devfile and editor + readFileSpy.mockResolvedValueOnce(''); + readFileSpy.mockResolvedValueOnce(''); + + spyInitBindings = jest.spyOn(InversifyBinding.prototype, 'initBindings'); + spyInitBindings.mockImplementation(() => Promise.resolve(container)); + toSelfMethod.mockReturnValue(selfMock), containerBindMethod.mockReturnValue(bindMock); + containerGetMethod.mockReturnValueOnce(generateMock); + }); - expect(returnCode).toBeTruthy(); - expect(generateMethod).toBeCalledWith('', '', FAKE_OUTPUT_FILE); - }); + afterEach(() => { + process.argv = originalArgs; + jest.restoreAllMocks(); + jest.resetAllMocks(); + }); - test('success with custom devfile Url', async () => { - const main = new Main(); - initArgs(undefined, FAKE_DEVFILE_URL, undefined, FAKE_EDITOR_ENTRY, FAKE_OUTPUT_FILE, FAKE_PLUGIN_REGISTRY_URL); - containerGetMethod.mockReset(); - const githubResolverResolveMethod = jest.fn(); - const githubResolverMock = { - resolve: githubResolverResolveMethod as any, - }; - - const getContentUrlMethod = jest.fn(); - const getCloneUrlMethod = jest.fn(); - const getBranchNameMethod = jest.fn(); - const getRepoNameMethod = jest.fn(); - - const githubUrlMock = { - getContentUrl: githubResolverResolveMethod as any, - getCloneUrl: getCloneUrlMethod as any, - getBranchName: getBranchNameMethod as any, - getRepoName: getRepoNameMethod as any, - }; - getContentUrlMethod.mockReturnValue('http://foo.bar'); - getCloneUrlMethod.mockReturnValue('http://foo.bar'); - getBranchNameMethod.mockReturnValue('my-branch'); - getRepoNameMethod.mockReturnValue('my-repo'); - githubResolverResolveMethod.mockReturnValue(githubUrlMock); - containerGetMethod.mockReturnValueOnce(githubResolverMock); - - const urlFetcherFetchTextMethod = jest.fn(); - const urlFetcherMock = { - fetchText: urlFetcherFetchTextMethod as any, - }; - urlFetcherFetchTextMethod.mockReturnValueOnce('schemaVersion: 2.1.0'); - containerGetMethod.mockReturnValueOnce(urlFetcherMock); - - const loadDevfilePluginMethod = jest.fn(); - const pluginRegistryResolverMock = { - loadDevfilePlugin: loadDevfilePluginMethod as any, - }; - loadDevfilePluginMethod.mockReturnValue(''); - containerGetMethod.mockReturnValueOnce(pluginRegistryResolverMock); - - // last one is generate mock - containerGetMethod.mockReturnValueOnce(generateMock); - const returnCode = await main.start(); - expect(mockedConsoleError).toBeCalledTimes(0); - expect(loadDevfilePluginMethod).toBeCalled(); - expect(urlFetcherFetchTextMethod).toBeCalled(); - - expect(returnCode).toBeTruthy(); - - const result = { - schemaVersion: '2.1.0', - projects: [ - { - name: 'my-repo', - git: { - remotes: { - origin: 'http://foo.bar', - }, - checkoutFrom: { - revision: 'my-branch', + test('success', async () => { + process.argv.push('--project.foo=bar'); + const main = new Main(); + const returnCode = await main.start(); + expect(mockedConsoleError).toBeCalledTimes(0); + + expect(returnCode).toBeTruthy(); + expect(generateMethod).toBeCalledWith('', '', FAKE_OUTPUT_FILE); + }); + + test('success with custom devfile Url', async () => { + const main = new Main(); + initArgs(undefined, FAKE_DEVFILE_URL, undefined, FAKE_EDITOR_ENTRY, FAKE_OUTPUT_FILE, FAKE_PLUGIN_REGISTRY_URL); + containerGetMethod.mockReset(); + const githubResolverResolveMethod = jest.fn(); + const githubResolverMock = { + resolve: githubResolverResolveMethod as any, + }; + + const getContentUrlMethod = jest.fn(); + const getCloneUrlMethod = jest.fn(); + const getBranchNameMethod = jest.fn(); + const getRepoNameMethod = jest.fn(); + + const githubUrlMock = { + getContentUrl: githubResolverResolveMethod as any, + getCloneUrl: getCloneUrlMethod as any, + getBranchName: getBranchNameMethod as any, + getRepoName: getRepoNameMethod as any, + }; + getContentUrlMethod.mockReturnValue('http://foo.bar'); + getCloneUrlMethod.mockReturnValue('http://foo.bar'); + getBranchNameMethod.mockReturnValue('my-branch'); + getRepoNameMethod.mockReturnValue('my-repo'); + githubResolverResolveMethod.mockReturnValue(githubUrlMock); + containerGetMethod.mockReturnValueOnce(githubResolverMock); + + const urlFetcherFetchTextMethod = jest.fn(); + const urlFetcherMock = { + fetchText: urlFetcherFetchTextMethod as any, + }; + urlFetcherFetchTextMethod.mockReturnValueOnce('schemaVersion: 2.1.0'); + containerGetMethod.mockReturnValueOnce(urlFetcherMock); + + const loadDevfilePluginMethod = jest.fn(); + const pluginRegistryResolverMock = { + loadDevfilePlugin: loadDevfilePluginMethod as any, + }; + loadDevfilePluginMethod.mockReturnValue(''); + containerGetMethod.mockReturnValueOnce(pluginRegistryResolverMock); + + // last one is generate mock + containerGetMethod.mockReturnValueOnce(generateMock); + const returnCode = await main.start(); + expect(mockedConsoleError).toBeCalledTimes(0); + expect(loadDevfilePluginMethod).toBeCalled(); + expect(urlFetcherFetchTextMethod).toBeCalled(); + + expect(returnCode).toBeTruthy(); + + const result = { + schemaVersion: '2.1.0', + projects: [ + { + name: 'my-repo', + git: { + remotes: { + origin: 'http://foo.bar', + }, + checkoutFrom: { + revision: 'my-branch', + }, }, }, - }, - ], - }; - expect(generateMethod).toBeCalledWith(jsYaml.dump(result), "''\n", FAKE_OUTPUT_FILE); - }); + ], + }; + expect(generateMethod).toBeCalledWith(jsYaml.dump(result), "''\n", FAKE_OUTPUT_FILE); + }); - test('editorEntry with default plugin registry URL', async () => { - const main = new Main(); - initArgs(FAKE_DEVFILE_PATH, undefined, undefined, FAKE_EDITOR_ENTRY, FAKE_OUTPUT_FILE, undefined); - await main.start(); - expect(mockedConsoleLog).toBeCalled(); - expect(mockedConsoleLog.mock.calls[0][0].toString()).toContain('No plug-in registry url. Setting to'); - - // check plugin url is provided to initBindings - expect(spyInitBindings.mock.calls[0][0].pluginRegistryUrl).toBe( - 'https://eclipse-che.github.io/che-plugin-registry/main/v3' - ); - }); + test('editorEntry with default plugin registry URL', async () => { + const main = new Main(); + initArgs(FAKE_DEVFILE_PATH, undefined, undefined, FAKE_EDITOR_ENTRY, FAKE_OUTPUT_FILE, undefined); + await main.start(); + expect(mockedConsoleLog).toBeCalled(); + expect(mockedConsoleLog.mock.calls[0][0].toString()).toContain('No plug-in registry url. Setting to'); + + // check plugin url is provided to initBindings + expect(spyInitBindings.mock.calls[0][0].pluginRegistryUrl).toBe( + 'https://eclipse-che.github.io/che-plugin-registry/main/v3' + ); + }); - test('missing devfile', async () => { - const main = new Main(); - initArgs(undefined, undefined, FAKE_EDITOR_PATH, undefined, FAKE_OUTPUT_FILE, FAKE_PLUGIN_REGISTRY_URL); - const returnCode = await main.start(); - expect(mockedConsoleError).toBeCalled(); - expect(mockedConsoleError.mock.calls[1][1].toString()).toContain('missing --devfile-path:'); - expect(returnCode).toBeFalsy(); - expect(generateMethod).toBeCalledTimes(0); - }); + test('missing devfile', async () => { + const main = new Main(); + initArgs(undefined, undefined, FAKE_EDITOR_PATH, undefined, FAKE_OUTPUT_FILE, FAKE_PLUGIN_REGISTRY_URL); + const returnCode = await main.start(); + expect(mockedConsoleError).toBeCalled(); + expect(mockedConsoleError.mock.calls[1][1].toString()).toContain('missing --devfile-path:'); + expect(returnCode).toBeFalsy(); + expect(generateMethod).toBeCalledTimes(0); + }); - test('missing editor', async () => { - const main = new Main(); - initArgs(FAKE_DEVFILE_PATH, undefined, undefined, undefined, FAKE_OUTPUT_FILE, FAKE_PLUGIN_REGISTRY_URL); + test('missing editor', async () => { + const main = new Main(); + initArgs(FAKE_DEVFILE_PATH, undefined, undefined, undefined, FAKE_OUTPUT_FILE, FAKE_PLUGIN_REGISTRY_URL); - const returnCode = await main.start(); - expect(mockedConsoleError).toBeCalled(); - expect(mockedConsoleError.mock.calls[1][1].toString()).toContain('missing --editor-path:'); - expect(returnCode).toBeFalsy(); - expect(generateMethod).toBeCalledTimes(0); - }); + const returnCode = await main.start(); + expect(mockedConsoleError).toBeCalled(); + expect(mockedConsoleError.mock.calls[1][1].toString()).toContain('missing --editor-path:'); + expect(returnCode).toBeFalsy(); + expect(generateMethod).toBeCalledTimes(0); + }); - test('missing outputfile', async () => { - const main = new Main(); - initArgs(FAKE_DEVFILE_PATH, undefined, FAKE_EDITOR_PATH, undefined, undefined, FAKE_PLUGIN_REGISTRY_URL); - const returnCode = await main.start(); - expect(mockedConsoleError).toBeCalled(); - expect(mockedConsoleError.mock.calls[1][1].toString()).toContain('missing --output-file: parameter'); - expect(returnCode).toBeFalsy(); - expect(generateMethod).toBeCalledTimes(0); + test('missing outputfile', async () => { + const main = new Main(); + initArgs(FAKE_DEVFILE_PATH, undefined, FAKE_EDITOR_PATH, undefined, undefined, FAKE_PLUGIN_REGISTRY_URL); + const returnCode = await main.start(); + expect(mockedConsoleError).toBeCalled(); + expect(mockedConsoleError.mock.calls[1][1].toString()).toContain('missing --output-file: parameter'); + expect(returnCode).toBeFalsy(); + expect(generateMethod).toBeCalledTimes(0); + }); + + test('error', async () => { + jest.spyOn(InversifyBinding.prototype, 'initBindings').mockImplementation(() => { + throw new Error('Dummy error'); + }); + const main = new Main(); + const returnCode = await main.start(); + expect(mockedConsoleError).toBeCalled(); + expect(returnCode).toBeFalsy(); + expect(generateMethod).toBeCalledTimes(0); + }); }); - test('error', async () => { - jest.spyOn(InversifyBinding.prototype, 'initBindings').mockImplementation(() => { - throw new Error('Dummy error'); + describe('generateDevfileContext', () => { + beforeEach(() => { + spyInitBindings = jest.spyOn(InversifyBinding.prototype, 'initBindings'); + spyInitBindings.mockImplementation(() => Promise.resolve(container)); + toSelfMethod.mockReturnValue(selfMock), containerBindMethod.mockReturnValue(bindMock); + }); + + afterEach(() => { + process.argv = originalArgs; + jest.restoreAllMocks(); + jest.resetAllMocks(); + }); + + test('missing editor', async () => { + const main = new Main(); + let message: string | undefined; + try { + await main.generateDevfileContext({ + devfilePath: FAKE_DEVFILE_PATH, + projects: [], + }); + throw new Error('Dummy error'); + } catch (e) { + message = e.message; + } + expect(message).toEqual('missing editorPath or editorEntry'); + }); + + test('missing devfile', async () => { + const main = new Main(); + let message: string | undefined; + try { + await main.generateDevfileContext({ + editorEntry: FAKE_EDITOR_ENTRY, + projects: [], + }); + throw new Error('Dummy error'); + } catch (e) { + message = e.message; + } + expect(message).toEqual('missing devfilePath or devfileUrl or devfileContent'); + }); + + test('success with custom devfile content', async () => { + const main = new Main(); + containerGetMethod.mockReset(); + const loadDevfilePluginMethod = jest.fn(); + const pluginRegistryResolverMock = { + loadDevfilePlugin: loadDevfilePluginMethod as any, + }; + loadDevfilePluginMethod.mockReturnValue(''); + containerGetMethod.mockReturnValueOnce(pluginRegistryResolverMock); + + // last one is generate mock + containerGetMethod.mockReturnValueOnce(generateMock); + + const devfileContent = jsYaml.dump({ + schemaVersion: '2.1.0', + projects: [ + { + name: 'my-repo', + git: { + remotes: { + origin: 'http://foo.bar', + }, + checkoutFrom: { + revision: 'my-branch', + }, + }, + }, + ], + }); + + await main.generateDevfileContext({ + devfileContent, + outputFile: FAKE_OUTPUT_FILE, + pluginRegistryUrl: FAKE_PLUGIN_REGISTRY_URL, + editorEntry: FAKE_EDITOR_ENTRY, + projects: [], + }); + + expect(mockedConsoleError).toBeCalledTimes(0); + expect(loadDevfilePluginMethod).toBeCalled(); + expect(generateMethod).toBeCalledWith(devfileContent, "''\n", FAKE_OUTPUT_FILE); }); - const main = new Main(); - const returnCode = await main.start(); - expect(mockedConsoleError).toBeCalled(); - expect(returnCode).toBeFalsy(); - expect(generateMethod).toBeCalledTimes(0); }); describe('replaceIfExistingProjects', () => {