diff --git a/packages/cli/generators/service/index.js b/packages/cli/generators/service/index.js index 62810ec7e835..c0996b26e920 100644 --- a/packages/cli/generators/service/index.js +++ b/packages/cli/generators/service/index.js @@ -63,10 +63,11 @@ module.exports = class ServiceGenerator extends ArtifactGenerator { if (!fs.existsSync(this.artifactInfo.datasourcesDir)) { return this.exit( new Error( - `${ERROR_NO_DATA_SOURCES_FOUND} ${this.artifactInfo.datasourcesDir}. - ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/DataSource-generator.html for information on how datasources are discovered', - )}`, + `${ERROR_NO_DATA_SOURCES_FOUND} ${ + this.artifactInfo.datasourcesDir + }.${chalk.yellow( + 'Please visit https://loopback.io/doc/en/lb4/DataSource-generator.html for information on how datasources are discovered', + )}`, ), ); } @@ -142,10 +143,11 @@ module.exports = class ServiceGenerator extends ArtifactGenerator { if (availableDatasources.length === 0) { return this.exit( new Error( - `${ERROR_NO_DATA_SOURCES_FOUND} ${this.artifactInfo.datasourcesDir}. - ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/DataSource-generator.html for information on how datasources are discovered', - )}`, + `${ERROR_NO_DATA_SOURCES_FOUND} ${ + this.artifactInfo.datasourcesDir + }.${chalk.yellow( + 'Please visit https://loopback.io/doc/en/lb4/DataSource-generator.html for information on how datasources are discovered', + )}`, ), ); } @@ -183,7 +185,9 @@ module.exports = class ServiceGenerator extends ArtifactGenerator { /** * this method will be in charge of inferring and create * the remote interfaces from either SOAP wsdl or REST openApi json - * TODO: @marioestradarosa + * + * TODO: add functionality to inspect service specs to generate + * strongly-typed service proxies and corresponding model definitions. */ async inferInterfaces() { let connectorType = utils.getDataSourceConnectorName( diff --git a/packages/cli/generators/service/templates/service-template.ts.ejs b/packages/cli/generators/service/templates/service-template.ts.ejs index 61bd5193b714..21b6c895da6c 100644 --- a/packages/cli/generators/service/templates/service-template.ts.ejs +++ b/packages/cli/generators/service/templates/service-template.ts.ejs @@ -2,13 +2,10 @@ import {getService, juggler} from '@loopback/service-proxy'; import {inject, Provider} from '@loopback/core'; import {<%= dataSourceClassName %>} from '../datasources'; -/** - * this is where you define the Node.js methods that will be - * mapped to the SOAP operations as stated in the datasource - * json file. - */ export interface <%= className %>Service { - + // this is where you define the Node.js methods that will be + // mapped to the SOAP operations as stated in the datasource + // json file. } export class <%= className %>ServiceProvider implements Provider<<%= className %>Service> { diff --git a/packages/cli/lib/utils.js b/packages/cli/lib/utils.js index 745a1d207062..628b20b6cf55 100644 --- a/packages/cli/lib/utils.js +++ b/packages/cli/lib/utils.js @@ -419,7 +419,7 @@ exports.getDataSourceConnectorName = function(datasourcesDir, dataSourceClass) { jsonFileContent = JSON.parse(fs.readFileSync(datasourceJSONFile, 'utf8')); } catch (err) { debug(`Error reading file ${datasourceJSONFile}: ${err.message}`); - throw new Error(err); + throw err; } if (jsonFileContent.connector) { @@ -458,7 +458,7 @@ exports.isConnectorOfType = function( jsonFileContent = JSON.parse(fs.readFileSync(datasourceJSONFile, 'utf8')); } catch (err) { debug(`Error reading file ${datasourceJSONFile}: ${err.message}`); - throw new Error(err); + throw err; } for (let connector of Object.values(connectors)) { diff --git a/packages/cli/test/integration/generators/repository.integration.js b/packages/cli/test/integration/generators/repository.integration.js index f8d1bf5c6a6e..dc8c1e8b6bca 100644 --- a/packages/cli/test/integration/generators/repository.integration.js +++ b/packages/cli/test/integration/generators/repository.integration.js @@ -36,9 +36,12 @@ describe('lb4 repository', () => { await testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForRepository(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withPrompts(multiItemPrompt); @@ -93,9 +96,12 @@ describe('lb4 repository', () => { await testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForRepository(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withPrompts(multiItemPrompt); @@ -121,9 +127,12 @@ describe('lb4 repository', () => { it('generates a custom name repository', async () => { await testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForRepository(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withArguments('myrepo --datasource dbmem --model MultiWord'); const expectedFile = path.join( @@ -145,9 +154,12 @@ describe('lb4 repository', () => { it('generates a crud repository from a config file', async () => { await testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForRepository(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withArguments('--config myconfig.json'); const expectedFile = path.join( @@ -180,9 +192,12 @@ describe('lb4 repository', () => { await testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForRepository(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withPrompts(multiItemPrompt); @@ -214,9 +229,12 @@ describe('lb4 repository', () => { return expect( testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForRepository(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withPrompts(basicPrompt) .withArguments(' --model InvalidModel'), @@ -230,9 +248,12 @@ describe('lb4 repository', () => { return expect( testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForRepository(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withPrompts(basicPrompt), ).to.be.rejectedWith(/You did not select a valid model/); @@ -240,13 +261,15 @@ describe('lb4 repository', () => { it('does not run with empty datasource list', async () => { return expect( - testUtils.executeGenerator(generator).inDir( - SANDBOX_PATH, - async () => - await prepareGeneratorForRepository(SANDBOX_PATH, { - noFixtures: true, - }), - ), + testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: false}, + SANDBOX_FILES, + ), + ), ).to.be.rejectedWith(/No datasources found/); }); }); @@ -258,9 +281,12 @@ describe('lb4 repository', () => { }; await testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForRepository(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withPrompts(basicPrompt) .withArguments(' --model Defaultmodel'); @@ -285,9 +311,12 @@ describe('lb4 repository', () => { it('generates a crud repository from decorator defined model', async () => { await testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForRepository(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withArguments('--datasource dbmem --model decoratordefined'); const expectedFile = path.join( @@ -316,9 +345,12 @@ describe('lb4 repository', () => { it('generates a kv repository from default model', async () => { await testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForRepository(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withArguments('--datasource dbkv --model Defaultmodel'); const expectedFile = path.join( @@ -344,9 +376,12 @@ describe('lb4 repository', () => { }; await testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForRepository(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withPrompts(basicPrompt) .withArguments('--model decoratordefined'); @@ -534,53 +569,3 @@ const SANDBOX_FILES = [ `, }, ]; - -async function prepareGeneratorForRepository(rootDir, options) { - options = options || {}; - const content = {}; - if (!options.excludeKeyword) { - content.keywords = ['loopback']; - } - - if (!options.excludePackageJSON) { - fs.writeFileSync( - path.join(rootDir, 'package.json'), - JSON.stringify(content), - ); - } - - if (!options.excludeYoRcJSON) { - fs.writeFileSync(path.join(rootDir, '.yo-rc.json'), JSON.stringify({})); - } - - fs.mkdirSync(path.join(rootDir, 'src')); - - if (!options.excludeControllersDir) { - fs.mkdirSync(path.join(rootDir, 'src', 'controllers')); - } - - if (!options.excludeModelsDir) { - fs.mkdirSync(path.join(rootDir, 'src', 'models')); - } - - if (!options.excludeRepositoriesDir) { - fs.mkdirSync(path.join(rootDir, 'src', 'repositories')); - } - - if (!options.excludeDataSourcesDir) { - fs.mkdirSync(path.join(rootDir, 'src', 'datasources')); - } - - if (!options.noFixtures) { - copyFixtures(); - } -} - -function copyFixtures() { - for (let theFile of SANDBOX_FILES) { - const fullPath = path.join(SANDBOX_PATH, theFile.path, theFile.file); - if (!fs.existsSync(fullPath)) { - fs.writeFileSync(fullPath, theFile.content); - } - } -} diff --git a/packages/cli/test/integration/generators/service.integration.js b/packages/cli/test/integration/generators/service.integration.js index 54767b5546a3..a111dab7eeb7 100644 --- a/packages/cli/test/integration/generators/service.integration.js +++ b/packages/cli/test/integration/generators/service.integration.js @@ -8,7 +8,6 @@ const path = require('path'); const assert = require('yeoman-assert'); const testlab = require('@loopback/testlab'); -const fs = require('fs'); const expect = testlab.expect; const TestSandbox = testlab.TestSandbox; @@ -29,13 +28,11 @@ describe('lb4 service', () => { describe('invalid generation of services', () => { it('does not run with empty datasource list', async () => { return expect( - testUtils.executeGenerator(generator).inDir( - SANDBOX_PATH, - async () => - await prepareGeneratorForServices(SANDBOX_PATH, { - noFixtures: true, - }), - ), + testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, {}, SANDBOX_FILES), + ), ).to.be.rejectedWith(/No datasources found/); }); }); @@ -44,9 +41,12 @@ describe('lb4 service', () => { it('generates a basic soap service from command line arguments', async () => { await testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForServices(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withArguments('myService --datasource myds'); const expectedFile = path.join( @@ -69,6 +69,78 @@ describe('lb4 service', () => { assert.fileContent(INDEX_FILE, /export \* from '.\/my-service.service';/); }); + it('generates a soap service from a config file', async () => { + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), + ) + .withArguments('--config mysoapconfig.json'); + const expectedFile = path.join( + SANDBOX_PATH, + SERVICE_APP_PATH, + 'multi-word-service.service.ts', + ); + assert.file(expectedFile); + assert.fileContent( + expectedFile, + /export class MultiWordServiceServiceProvider implements Provider\ {/, + ); + assert.fileContent( + expectedFile, + /protected datasource: MydsDataSource = new MydsDataSource\(\),/, + ); + assert.fileContent( + expectedFile, + /value\(\): Promise\ {/, + ); + assert.file(INDEX_FILE); + assert.fileContent( + INDEX_FILE, + /export \* from '.\/multi-word-service.service';/, + ); + }); + + it('generates a basic soap service from the prompts', async () => { + const multiItemPrompt = { + name: 'myService', + dataSourceClass: 'MydsDatasource', + }; + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), + ) + .withPrompts(multiItemPrompt); + + const expectedFile = path.join( + SANDBOX_PATH, + SERVICE_APP_PATH, + 'my-service.service.ts', + ); + assert.file(expectedFile); + assert.fileContent( + expectedFile, + /export class MyServiceServiceProvider implements Provider {/, + ); + assert.fileContent(expectedFile, /export interface MyServiceService {/); + assert.fileContent(expectedFile, /\@inject\('datasources.myds'\)/); + assert.fileContent( + expectedFile, + /value\(\): Promise\ {/, + ); + assert.file(INDEX_FILE); + assert.fileContent(INDEX_FILE, /export \* from '.\/my-service.service';/); + }); + it('generates a basic rest service from the prompts', async () => { const multiItemPrompt = { name: 'myservice', @@ -77,9 +149,12 @@ describe('lb4 service', () => { await testUtils .executeGenerator(generator) - .inDir( - SANDBOX_PATH, - async () => await prepareGeneratorForServices(SANDBOX_PATH), + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), ) .withPrompts(multiItemPrompt); @@ -102,39 +177,66 @@ describe('lb4 service', () => { assert.file(INDEX_FILE); assert.fileContent(INDEX_FILE, /export \* from '.\/myservice.service';/); }); - }); - - it('generates a soap service from a config file', async () => { - await testUtils - .executeGenerator(generator) - .inDir( + it('generates a basic rest service from a config file', async () => { + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), + ) + .withArguments('--config myrestconfig.json'); + const expectedFile = path.join( + SANDBOX_PATH, + SERVICE_APP_PATH, + 'myservice.service.ts', + ); + assert.file(expectedFile); + assert.fileContent( + expectedFile, + /export class MyserviceServiceProvider implements Provider {/, + ); + assert.fileContent(expectedFile, /export interface MyserviceService {/); + assert.fileContent(expectedFile, /\@inject\('datasources.restdb'\)/); + assert.fileContent( + expectedFile, + /value\(\): Promise\ {/, + ); + assert.file(INDEX_FILE); + assert.fileContent(INDEX_FILE, /export \* from '.\/myservice.service';/); + }); + it('generates a basic rest service from command line arguments', async () => { + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject( + SANDBOX_PATH, + {includeSandboxFilesFixtures: true}, + SANDBOX_FILES, + ), + ) + .withArguments('myservice --datasource restdb'); + const expectedFile = path.join( SANDBOX_PATH, - async () => await prepareGeneratorForServices(SANDBOX_PATH), - ) - .withArguments('--config myconfig.json'); - const expectedFile = path.join( - SANDBOX_PATH, - SERVICE_APP_PATH, - 'multi-word-service.service.ts', - ); - assert.file(expectedFile); - assert.fileContent( - expectedFile, - /export class MultiWordServiceServiceProvider implements Provider\ {/, - ); - assert.fileContent( - expectedFile, - /protected datasource: MydsDataSource = new MydsDataSource\(\),/, - ); - assert.fileContent( - expectedFile, - /value\(\): Promise\ {/, - ); - assert.file(INDEX_FILE); - assert.fileContent( - INDEX_FILE, - /export \* from '.\/multi-word-service.service';/, - ); + SERVICE_APP_PATH, + 'myservice.service.ts', + ); + assert.file(expectedFile); + assert.fileContent( + expectedFile, + /export class MyserviceServiceProvider implements Provider {/, + ); + assert.fileContent(expectedFile, /export interface MyserviceService {/); + assert.fileContent(expectedFile, /\@inject\('datasources.restdb'\)/); + assert.fileContent( + expectedFile, + /value\(\): Promise\ {/, + ); + assert.file(INDEX_FILE); + assert.fileContent(INDEX_FILE, /export \* from '.\/myservice.service';/); + }); }); }); @@ -148,12 +250,20 @@ const DUMMY_CONTENT = '--DUMMY VALUE--'; const SANDBOX_FILES = [ { path: CONFIG_PATH, - file: 'myconfig.json', + file: 'mysoapconfig.json', content: `{ "name": "MultiWordService", "datasource": "myds" }`, }, + { + path: CONFIG_PATH, + file: 'myrestconfig.json', + content: `{ + "name": "myservice", + "datasource": "restdb" + }`, + }, { path: DATASOURCE_APP_PATH, file: 'myds.datasource.json', @@ -194,57 +304,3 @@ const SANDBOX_FILES = [ content: DUMMY_CONTENT, }, ]; - -async function prepareGeneratorForServices(rootDir, options) { - options = options || {}; - const content = {}; - if (!options.excludeKeyword) { - content.keywords = ['loopback']; - } - - if (!options.excludePackageJSON) { - fs.writeFileSync( - path.join(rootDir, 'package.json'), - JSON.stringify(content), - ); - } - - if (!options.excludeYoRcJSON) { - fs.writeFileSync(path.join(rootDir, '.yo-rc.json'), JSON.stringify({})); - } - - fs.mkdirSync(path.join(rootDir, 'src')); - - if (!options.excludeControllersDir) { - fs.mkdirSync(path.join(rootDir, 'src', 'controllers')); - } - - if (!options.excludeModelsDir) { - fs.mkdirSync(path.join(rootDir, 'src', 'models')); - } - - if (!options.excludeRepositoriesDir) { - fs.mkdirSync(path.join(rootDir, 'src', 'repositories')); - } - - if (!options.excludeDataSourcesDir) { - fs.mkdirSync(path.join(rootDir, 'src', 'datasources')); - } - - if (!options.excludeDataSourcesDir) { - fs.mkdirSync(path.join(rootDir, 'src', 'services')); - } - - if (!options.noFixtures) { - copyFixtures(); - } -} - -function copyFixtures() { - for (let theFile of SANDBOX_FILES) { - const fullPath = path.join(SANDBOX_PATH, theFile.path, theFile.file); - if (!fs.existsSync(fullPath)) { - fs.writeFileSync(fullPath, theFile.content); - } - } -} diff --git a/packages/cli/test/test-utils.js b/packages/cli/test/test-utils.js index ac68b179353e..07199292a694 100644 --- a/packages/cli/test/test-utils.js +++ b/packages/cli/test/test-utils.js @@ -63,10 +63,13 @@ exports.executeGenerator = function(GeneratorOrNamespace, settings) { * @property {boolean} excludeDataSourcesDir Excludes the datasources directory * @property {boolean} includeDummyModel Creates a dummy model file in /src/models/product-review.model.ts * @property {boolean} includeDummyRepository Creates a dummy repository file in /src/repositories/bar.repository.ts + * @property {boolean} includeSandboxFilesFixtures creates files specified in SANDBOX_FILES array + * @param {Object[]} sandBoxFiles specify files, directories and their content to be included as fixtures */ -exports.givenLBProject = function(rootDir, options) { +exports.givenLBProject = function(rootDir, options, sandBoxFiles) { options = options || {}; - const context = {}; + sandBoxFiles = sandBoxFiles || []; + const content = {}; if (!options.excludeKeyword) { content.keywords = ['loopback']; @@ -110,4 +113,13 @@ exports.givenLBProject = function(rootDir, options) { const repoPath = path.join(rootDir, '/src/repositories/bar.repository.ts'); fs.writeFileSync(repoPath, '--DUMMY VALUE--'); } + + if (options.includeSandboxFilesFixtures && sandBoxFiles.length > 0) { + for (let theFile of sandBoxFiles) { + const fullPath = path.join(rootDir, theFile.path, theFile.file); + if (!fs.existsSync(fullPath)) { + fs.writeFileSync(fullPath, theFile.content); + } + } + } };