From 57187a9a12d6d4699859f36e3c133327f0e6d49e Mon Sep 17 00:00:00 2001 From: Nora Date: Sun, 26 Apr 2020 01:12:59 -0400 Subject: [PATCH 01/11] feat(example-lb3-application): add lb3 tests to mocha --- SPIKE.md | 19 +++++++++++++++++++ examples/lb3-application/package.json | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 SPIKE.md diff --git a/SPIKE.md b/SPIKE.md new file mode 100644 index 000000000000..092f850ca20a --- /dev/null +++ b/SPIKE.md @@ -0,0 +1,19 @@ +Since LoopBack 4 offers a way to mount LoopBack 3 applications on a LoopBack 4 +project with the use of +[`@loopback/booter-lb3app`](https://github.com/strongloop/loopback-next/tree/master/packages/booter-lb3app), +there should also be a way for users to run their tests as part of LoopBack 4's +`npm test` command. + +We want the LoopBack 3 tests to use the LoopBack 4 server rather than the +LoopBack 3 application. This spike aims to test running both acceptance and +integration LoopBack 3 tests. + +## All Tests + +In order to run LoopBack 3's tests from their current folder, add LB3 tests' +path to `test` entry in package.json: + +- `"test": "lb-mocha \"dist/**tests**/\*_/_.js\" \"lb3app/test/\*.js\""` + +In this case, the test folder lies is `/lb3app/test` from the root of the +LoopBack 4 project. diff --git a/examples/lb3-application/package.json b/examples/lb3-application/package.json index 5fa158025b99..0390da21c8b9 100644 --- a/examples/lb3-application/package.json +++ b/examples/lb3-application/package.json @@ -19,7 +19,7 @@ "eslint": "lb-eslint --report-unused-disable-directives .", "eslint:fix": "npm run eslint -- --fix", "pretest": "npm run build", - "test": "lb-mocha \"dist/__tests__/**/*.js\"", + "test": "lb-mocha \"dist/__tests__/**/*.js\" \"lb3app/test/*.js\"", "test:dev": "lb-mocha --allow-console-logs dist/__tests__/**/*.js && npm run posttest", "verify": "npm pack && tar xf loopback-lb3-application*.tgz && tree package && npm run clean", "migrate": "node ./dist/migrate", From d6329b85d1ca2691ad4c0e457f2af2e2249e6af9 Mon Sep 17 00:00:00 2001 From: Nora Date: Sun, 26 Apr 2020 01:16:14 -0400 Subject: [PATCH 02/11] test(example-lb3-application): acceptance tests --- SPIKE.md | 65 +++++++++ .../lb3-application/lb3app/test/acceptance.js | 134 ++++++++++++++++++ examples/lb3-application/package-lock.json | 54 +++++++ examples/lb3-application/package.json | 1 + examples/lb3-application/src/server.ts | 2 +- 5 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 examples/lb3-application/lb3app/test/acceptance.js diff --git a/SPIKE.md b/SPIKE.md index 092f850ca20a..7555c5625978 100644 --- a/SPIKE.md +++ b/SPIKE.md @@ -17,3 +17,68 @@ path to `test` entry in package.json: In this case, the test folder lies is `/lb3app/test` from the root of the LoopBack 4 project. + +This will run LoopBack 4 tests first then LoopBack 3 tests. + +## Acceptance Tests + +First, move any LoopBack 3 test dependencies to package.json's devDependencies +and run: + +```sh +npm install +``` + +In your test file: + +- Update to use the LB4 Express server when doing requests: + + ```ts + // can use lb4's testlab's supertest as the dependency is already installed + const request = require('@loopback/testlab').supertest; + const assert = require('assert'); + const should = require('should'); + const ExpressServer = require('../../dist/server').ExpressServer; + + let app; + + function json(verb, url) { + // use the LB4 express server + return request(app.server) + [verb](url) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect('Content-Type', /json/); + } + ``` + +- Boot and start the LB4 app in your before hook, and stop the app in the after + hook: + + ```ts + describe('LoopBack 3 style tests', function () { + before(async function () { + app = new ExpressServer(); + await app.boot(); + await app.start(); + }); + + after(async () => { + await app.stop(); + }); + + // your tests here + }); + ``` + +- Example of this use can be seen in + `examples/lb3-application/lb3app/test/acceptance.js` which has the same tests + as `src/__tests__/acceptance/lb3app.acceptance.ts`, but in LB3 style. + +Now when you run `npm test` your LoopBack 3 tests should be run along with any +LoopBack 4 tests you have. + +Optional: Another option is to migrate your tests to use LoopBack 4 style of +testing, similar to `src/__tests__/acceptance/lb3app.acceptance.ts`. +Documentation for LoopBack testing can be found in +https://loopback.io/doc/en/lb4/Testing-your-application.html. diff --git a/examples/lb3-application/lb3app/test/acceptance.js b/examples/lb3-application/lb3app/test/acceptance.js new file mode 100644 index 000000000000..181837b8dbed --- /dev/null +++ b/examples/lb3-application/lb3app/test/acceptance.js @@ -0,0 +1,134 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-lb3-application +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +'use strict'; +const lb3App = require('../server/server'); +const request = require('@loopback/testlab').supertest; +const assert = require('assert'); +const should = require('should'); +const ExpressServer = require('../../dist/server').ExpressServer; + +let app; + +function json(verb, url) { + // use the original app's server + return request(app.server) + [verb](url) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect('Content-Type', /json/); +} + +describe('LoopBack 3 style acceptance tests', function () { + before(async function () { + app = new ExpressServer(); + await app.boot(); + await app.start(); + }); + + after(async () => { + await app.stop(); + }); + + context('basic REST calls for LoopBack 3 application', () => { + it('creates and finds a CoffeeShop', function (done) { + json('post', '/api/CoffeeShops') + .send({ + name: 'Coffee Shop', + city: 'Toronto', + }) + .expect(200) + .end(function (err, res) { + assert(typeof res.body === 'object'); + assert(res.body.name); + assert(res.body.city); + assert.equal(res.body.name, 'Coffee Shop'); + assert.equal(res.body.city, 'Toronto'); + done(); + }); + }); + + it("gets the CoffeeShop's status", function (done) { + json('get', '/api/CoffeeShops/status').expect(200, function (err, res) { + res.body.status.should.be.equalOneOf( + 'We are open for business.', + 'Sorry, we are closed. Open daily from 6am to 8pm.', + ); + done(); + }); + }); + + it('gets external route in application', function (done) { + json('get', '/ping').expect(200, function (err, res) { + assert(res.body, 'pong'); + done(); + }); + }); + }); + + context('authentication', () => { + let User; + + before(() => { + User = lb3App.models.User; + }); + + it('creates a User and logs them in and out', function (done) { + User.create({email: 'new@email.com', password: 'L00pBack!'}, function ( + err, + user, + ) { + assert(user.email, 'new@email.com'); + User.login( + { + email: 'new@email.com', + password: 'L00pBack!', + }, + function (err, token) { + token.should.have.properties('ttl', 'userId', 'created', 'id'); + assert(token.userId, user.id); + User.logout(token.id); + User.deleteById(user.id); + }, + ); + done(); + }); + }); + + it('rejects anonymous requests to protected endpoints', function (done) { + json('get', '/api/CoffeeShops/greet').expect(401, function (err, res) { + assert(res.body.error.code, 'AUTHORIZATION_REQUIRED'); + }); + done(); + }); + + // keep getting unauthorized despite correct token, skipping for now + it.skip('makes an authenticated request', function (done) { + User.create({email: 'new@email.com', password: 'L00pBack!'}, function ( + err, + user, + ) { + user.email.should.be.equal('new@email.com'); + User.login( + { + email: 'new@email.com', + password: 'L00pBack!', + }, + function (err, token) { + json( + 'get', + `/api/CoffeeShops/greet?access_token=${token.id}`, + ).expect(200, function (err, res) { + res.body.should.be.equal('Hello from this Coffee Shop'); + }); + User.logout(token.id); + User.deleteById(user.id); + }, + ); + done(); + }); + }); + }); +}); diff --git a/examples/lb3-application/package-lock.json b/examples/lb3-application/package-lock.json index 265f7c003427..0dd8cad4f73b 100644 --- a/examples/lb3-application/package-lock.json +++ b/examples/lb3-application/package-lock.json @@ -2640,6 +2640,60 @@ "nanoid": "^2.1.0" } }, + "should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dev": true, + "requires": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "requires": { + "should-type": "^1.4.0" + } + }, + "should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "dev": true + }, + "should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "dev": true + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", diff --git a/examples/lb3-application/package.json b/examples/lb3-application/package.json index 0390da21c8b9..9441b9286113 100644 --- a/examples/lb3-application/package.json +++ b/examples/lb3-application/package.json @@ -67,6 +67,7 @@ "eslint-plugin-eslint-plugin": "^2.2.1", "eslint-plugin-mocha": "^6.3.0", "lodash": "^4.17.15", + "should": "^13.2.3", "typescript": "~3.8.3" }, "keywords": [ diff --git a/examples/lb3-application/src/server.ts b/examples/lb3-application/src/server.ts index 5f9886cb4266..8f4b0f1f7f8a 100644 --- a/examples/lb3-application/src/server.ts +++ b/examples/lb3-application/src/server.ts @@ -19,7 +19,7 @@ const helmet = require('helmet'); export class ExpressServer { private app: express.Application; public readonly lbApp: CoffeeShopApplication; - private server?: http.Server; + public server?: http.Server; public url: String; constructor(options: ApplicationConfig = {}) { From 458e0c26d57f04a1ae322978a3e80d1104ee585f Mon Sep 17 00:00:00 2001 From: Nora Date: Sun, 26 Apr 2020 01:17:59 -0400 Subject: [PATCH 03/11] feat(booter-lb3app): bind lb3 models to context --- .../acceptance/booter-lb3app.acceptance.ts | 16 ++++++++++++++++ packages/booter-lb3app/src/lb3app.booter.ts | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/packages/booter-lb3app/src/__tests__/acceptance/booter-lb3app.acceptance.ts b/packages/booter-lb3app/src/__tests__/acceptance/booter-lb3app.acceptance.ts index 557b42d43af4..e6bc2d0e24ec 100644 --- a/packages/booter-lb3app/src/__tests__/acceptance/booter-lb3app.acceptance.ts +++ b/packages/booter-lb3app/src/__tests__/acceptance/booter-lb3app.acceptance.ts @@ -242,4 +242,20 @@ describe('booter-lb3app', () => { expect(ds).to.eql(expected); }); }); + + context('binding LoopBack 3 models', () => { + before(async () => { + ({app, client} = await setupApplication({ + lb3app: {path: '../fixtures/app-with-model'}, + })); + }); + + it('binds model to the context', async () => { + const expected = require('../../../fixtures/app-with-model').models.Color; + const modelBindings = app.findByTag('model'); + const key = modelBindings[0].key; + const model = await app.get(key); + expect(model).to.eql(expected); + }); + }); }); diff --git a/packages/booter-lb3app/src/lb3app.booter.ts b/packages/booter-lb3app/src/lb3app.booter.ts index 88050609e5dc..9c2311201012 100644 --- a/packages/booter-lb3app/src/lb3app.booter.ts +++ b/packages/booter-lb3app/src/lb3app.booter.ts @@ -73,6 +73,17 @@ export class Lb3AppBooter implements Booter { }); } + const models = lb3App.models; + if (models) { + const visited: unknown[] = []; + Object.keys(models).forEach(key => { + const model = models[key]; + if (visited.includes(model)) return; + visited.push(model); + this.app.bind(`models.lb3-${key}`).to(model).tag('model'); + }); + } + // TODO(bajtos) Listen for the following events to update the OpenAPI spec: // - modelRemoted // - modelDeleted @@ -149,4 +160,5 @@ export interface Lb3AppBooterOptions { interface Lb3Application extends ExpressApplication { handler(name: 'rest'): ExpressRequestHandler; dataSources?: {[name: string]: unknown}; + models?: {[name: string]: unknown}; } From 31d552cb0f00c5d84e32ad97e8ae7487abca9756 Mon Sep 17 00:00:00 2001 From: Nora Date: Sun, 26 Apr 2020 01:35:22 -0400 Subject: [PATCH 04/11] test(example-lb3-application): integration tests --- SPIKE.md | 37 ++++++++++++ .../lb3app/test/integration.js | 58 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 examples/lb3-application/lb3app/test/integration.js diff --git a/SPIKE.md b/SPIKE.md index 7555c5625978..9210acc612ea 100644 --- a/SPIKE.md +++ b/SPIKE.md @@ -82,3 +82,40 @@ Optional: Another option is to migrate your tests to use LoopBack 4 style of testing, similar to `src/__tests__/acceptance/lb3app.acceptance.ts`. Documentation for LoopBack testing can be found in https://loopback.io/doc/en/lb4/Testing-your-application.html. + +## Integration Tests + +For the integration tests, LoopBack 3 models were bound to the LoopBack 4 +application in order to allow JavaScript API to call application logic such as +`Model.create()`. This can be seen in +`packages/booter-lb3app/src/lb3app.booter.ts`. + +In order to retrieve the model from the application's context, +`getValueOrPromise()` can be used as follows: + +```ts +describe('LoopBack 3 style integration tests', function () { + let app; + let CoffeeShop; + + before(async function () { + app = new ExpressServer(); + await app.boot(); + await app.start(); + }); + + before(() => { + // follow the syntax: models.lb3-{ModelName} + CoffeeShop = app.lbApp.getValueOrPromise('models.lb3-CoffeeShop'); + }); + + after(async () => { + await app.stop(); + }); + + // your tests here +}); +``` + +Example integration tests can be found in +`examples/lb3-application/lb3app/test/acceptance.js`. diff --git a/examples/lb3-application/lb3app/test/integration.js b/examples/lb3-application/lb3app/test/integration.js new file mode 100644 index 000000000000..584c73107618 --- /dev/null +++ b/examples/lb3-application/lb3app/test/integration.js @@ -0,0 +1,58 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-lb3-application +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +'use strict'; + +const assert = require('assert'); +const should = require('should'); +const ExpressServer = require('../../dist/server').ExpressServer; + +describe('LoopBack 3 style integration tests', function () { + let app; + let CoffeeShop; + + before(async function () { + app = new ExpressServer(); + await app.boot(); + await app.start(); + }); + + before(() => { + CoffeeShop = app.lbApp.getValueOrPromise('models.lb3-CoffeeShop'); + }); + + after(async () => { + await app.stop(); + }); + + it('CoffeeShop.find', function (done) { + CoffeeShop.find({where: {name: 'Bel Cafe'}}, function (err, shop) { + shop[0].__data.name.should.be.equal('Bel Cafe'); + shop[0].__data.city.should.be.equal('Vancouver'); + }); + done(); + }); + + it('CoffeeShop.count', function (done) { + CoffeeShop.count({}, function (err, count) { + assert(count, 3); + }); + done(); + }); + + it('CoffeeShop.create', function (done) { + CoffeeShop.create( + { + name: 'Nook Shop', + city: 'Toronto', + }, + function (err, shop) { + shop.__data.name.should.be.equal('Nook Shop'); + shop.__data.city.should.be.equal('Toronto'); + }, + ); + done(); + }); +}); From 176651d1af57bb8f06c50e318551c7cb8a6570ed Mon Sep 17 00:00:00 2001 From: Nora Date: Sun, 26 Apr 2020 02:36:12 -0400 Subject: [PATCH 05/11] style(example-lb3-application): fix linter errors --- examples/lb3-application/lb3app/test/acceptance.js | 7 ++++--- examples/lb3-application/lb3app/test/integration.js | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/lb3-application/lb3app/test/acceptance.js b/examples/lb3-application/lb3app/test/acceptance.js index 181837b8dbed..568381a01211 100644 --- a/examples/lb3-application/lb3app/test/acceptance.js +++ b/examples/lb3-application/lb3app/test/acceptance.js @@ -7,6 +7,7 @@ const lb3App = require('../server/server'); const request = require('@loopback/testlab').supertest; const assert = require('assert'); +// eslint-disable-next-line @typescript-eslint/no-unused-vars const should = require('should'); const ExpressServer = require('../../dist/server').ExpressServer; @@ -86,7 +87,7 @@ describe('LoopBack 3 style acceptance tests', function () { email: 'new@email.com', password: 'L00pBack!', }, - function (err, token) { + function (err2, token) { token.should.have.properties('ttl', 'userId', 'created', 'id'); assert(token.userId, user.id); User.logout(token.id); @@ -116,11 +117,11 @@ describe('LoopBack 3 style acceptance tests', function () { email: 'new@email.com', password: 'L00pBack!', }, - function (err, token) { + function (err2, token) { json( 'get', `/api/CoffeeShops/greet?access_token=${token.id}`, - ).expect(200, function (err, res) { + ).expect(200, function (err3, res) { res.body.should.be.equal('Hello from this Coffee Shop'); }); User.logout(token.id); diff --git a/examples/lb3-application/lb3app/test/integration.js b/examples/lb3-application/lb3app/test/integration.js index 584c73107618..c680b4edbe1e 100644 --- a/examples/lb3-application/lb3app/test/integration.js +++ b/examples/lb3-application/lb3app/test/integration.js @@ -6,6 +6,7 @@ 'use strict'; const assert = require('assert'); +// eslint-disable-next-line @typescript-eslint/no-unused-vars const should = require('should'); const ExpressServer = require('../../dist/server').ExpressServer; From 230d4c41b5530c57fbdaf26a61e3bb02ca2dbcaa Mon Sep 17 00:00:00 2001 From: Nora Date: Sun, 26 Apr 2020 03:29:33 -0400 Subject: [PATCH 06/11] fix: add links to spike --- SPIKE.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/SPIKE.md b/SPIKE.md index 9210acc612ea..c9eb9c6bd729 100644 --- a/SPIKE.md +++ b/SPIKE.md @@ -1,3 +1,5 @@ +# Run LB3 tests from LB4 when LB3 is mounted on the LB4 app + Since LoopBack 4 offers a way to mount LoopBack 3 applications on a LoopBack 4 project with the use of [`@loopback/booter-lb3app`](https://github.com/strongloop/loopback-next/tree/master/packages/booter-lb3app), @@ -15,8 +17,9 @@ path to `test` entry in package.json: - `"test": "lb-mocha \"dist/**tests**/\*_/_.js\" \"lb3app/test/\*.js\""` -In this case, the test folder lies is `/lb3app/test` from the root of the -LoopBack 4 project. +In this case, the test folder is +[`/lb3app/test`](https://github.com/strongloop/loopback-next/tree/spike/lb3test/examples/lb3-application/lb3app/test) +from the root of the LoopBack 4 project. This will run LoopBack 4 tests first then LoopBack 3 tests. @@ -72,8 +75,10 @@ In your test file: ``` - Example of this use can be seen in - `examples/lb3-application/lb3app/test/acceptance.js` which has the same tests - as `src/__tests__/acceptance/lb3app.acceptance.ts`, but in LB3 style. + [`examples/lb3-application/lb3app/test/acceptance.js`](https://github.com/strongloop/loopback-next/blob/spike/lb3test/examples/lb3-application/lb3app/test/acceptance.js) + which has the same tests as + [`src/__tests__/acceptance/lb3app.acceptance.ts`](https://github.com/strongloop/loopback-next/blob/spike/lb3test/examples/lb3-application/src/__tests__/acceptance/lb3app.acceptance.ts), + but in LB3 style. Now when you run `npm test` your LoopBack 3 tests should be run along with any LoopBack 4 tests you have. @@ -88,7 +93,7 @@ https://loopback.io/doc/en/lb4/Testing-your-application.html. For the integration tests, LoopBack 3 models were bound to the LoopBack 4 application in order to allow JavaScript API to call application logic such as `Model.create()`. This can be seen in -`packages/booter-lb3app/src/lb3app.booter.ts`. +[`packages/booter-lb3app/src/lb3app.booter.ts`](https://github.com/strongloop/loopback-next/blob/spike/lb3test/packages/booter-lb3app/src/lb3app.booter.ts#L76-L85). In order to retrieve the model from the application's context, `getValueOrPromise()` can be used as follows: @@ -118,4 +123,4 @@ describe('LoopBack 3 style integration tests', function () { ``` Example integration tests can be found in -`examples/lb3-application/lb3app/test/acceptance.js`. +[`examples/lb3-application/lb3app/test/integration.js`](https://github.com/strongloop/loopback-next/blob/spike/lb3test/examples/lb3-application/lb3app/test/integration.js). From c6541a882b70abe196128529d885de593d996a51 Mon Sep 17 00:00:00 2001 From: Nora Date: Sun, 26 Apr 2020 03:29:47 -0400 Subject: [PATCH 07/11] fixup! fix assertions --- examples/lb3-application/lb3app/test/acceptance.js | 9 +++++---- examples/lb3-application/lb3app/test/integration.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/lb3-application/lb3app/test/acceptance.js b/examples/lb3-application/lb3app/test/acceptance.js index 568381a01211..226ee4e1a177 100644 --- a/examples/lb3-application/lb3app/test/acceptance.js +++ b/examples/lb3-application/lb3app/test/acceptance.js @@ -63,7 +63,7 @@ describe('LoopBack 3 style acceptance tests', function () { it('gets external route in application', function (done) { json('get', '/ping').expect(200, function (err, res) { - assert(res.body, 'pong'); + assert.equal(res.text, 'pong'); done(); }); }); @@ -81,7 +81,7 @@ describe('LoopBack 3 style acceptance tests', function () { err, user, ) { - assert(user.email, 'new@email.com'); + assert.equal(user.email, 'new@email.com'); User.login( { email: 'new@email.com', @@ -89,7 +89,7 @@ describe('LoopBack 3 style acceptance tests', function () { }, function (err2, token) { token.should.have.properties('ttl', 'userId', 'created', 'id'); - assert(token.userId, user.id); + assert.equal(token.userId, user.id); User.logout(token.id); User.deleteById(user.id); }, @@ -100,7 +100,7 @@ describe('LoopBack 3 style acceptance tests', function () { it('rejects anonymous requests to protected endpoints', function (done) { json('get', '/api/CoffeeShops/greet').expect(401, function (err, res) { - assert(res.body.error.code, 'AUTHORIZATION_REQUIRED'); + assert.equal(res.body.error.code, 'AUTHORIZATION_REQUIRED'); }); done(); }); @@ -118,6 +118,7 @@ describe('LoopBack 3 style acceptance tests', function () { password: 'L00pBack!', }, function (err2, token) { + assert.equal(token.userId, user.id); json( 'get', `/api/CoffeeShops/greet?access_token=${token.id}`, diff --git a/examples/lb3-application/lb3app/test/integration.js b/examples/lb3-application/lb3app/test/integration.js index c680b4edbe1e..3ea594d98cfd 100644 --- a/examples/lb3-application/lb3app/test/integration.js +++ b/examples/lb3-application/lb3app/test/integration.js @@ -38,7 +38,7 @@ describe('LoopBack 3 style integration tests', function () { it('CoffeeShop.count', function (done) { CoffeeShop.count({}, function (err, count) { - assert(count, 3); + assert.equal(count, 5); }); done(); }); From 01e6d3b4e0c0e577acad754b61669d4e6e5425b1 Mon Sep 17 00:00:00 2001 From: Nora Date: Mon, 27 Apr 2020 13:43:51 -0400 Subject: [PATCH 08/11] fixup! fix failing test --- .../lb3app/common/models/coffee-shop.js | 2 +- .../lb3-application/lb3app/test/acceptance.js | 28 ++++++++----------- .../lb3app/test/integration.js | 3 +- .../__tests__/acceptance/lb3app.acceptance.ts | 2 +- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/examples/lb3-application/lb3app/common/models/coffee-shop.js b/examples/lb3-application/lb3app/common/models/coffee-shop.js index 4917a2f582ef..c88f44497b2c 100755 --- a/examples/lb3-application/lb3app/common/models/coffee-shop.js +++ b/examples/lb3-application/lb3app/common/models/coffee-shop.js @@ -38,6 +38,6 @@ module.exports = function (CoffeeShop) { }; CoffeeShop.remoteMethod('greet', { http: {path: '/greet', verb: 'get'}, - returns: {type: 'string'}, + returns: {arg: 'greeting', type: 'string'}, }); }; diff --git a/examples/lb3-application/lb3app/test/acceptance.js b/examples/lb3-application/lb3app/test/acceptance.js index 226ee4e1a177..9ff851188038 100644 --- a/examples/lb3-application/lb3app/test/acceptance.js +++ b/examples/lb3-application/lb3app/test/acceptance.js @@ -7,9 +7,8 @@ const lb3App = require('../server/server'); const request = require('@loopback/testlab').supertest; const assert = require('assert'); -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const should = require('should'); const ExpressServer = require('../../dist/server').ExpressServer; +require('should'); let app; @@ -105,31 +104,28 @@ describe('LoopBack 3 style acceptance tests', function () { done(); }); - // keep getting unauthorized despite correct token, skipping for now - it.skip('makes an authenticated request', function (done) { + it('makes an authenticated request', function (done) { User.create({email: 'new@email.com', password: 'L00pBack!'}, function ( err, user, ) { user.email.should.be.equal('new@email.com'); - User.login( - { + json('post', '/api/users/login') + .send({ email: 'new@email.com', password: 'L00pBack!', - }, - function (err2, token) { - assert.equal(token.userId, user.id); + }) + .expect(200, function (err2, token) { + assert(typeof token.body === 'object'); + const accessToken = token.body.id; json( 'get', - `/api/CoffeeShops/greet?access_token=${token.id}`, + `/api/CoffeeShops/greet?access_token=${accessToken}`, ).expect(200, function (err3, res) { - res.body.should.be.equal('Hello from this Coffee Shop'); + res.body.greeting.should.be.equal('Hello from this Coffee Shop'); + done(); }); - User.logout(token.id); - User.deleteById(user.id); - }, - ); - done(); + }); }); }); }); diff --git a/examples/lb3-application/lb3app/test/integration.js b/examples/lb3-application/lb3app/test/integration.js index 3ea594d98cfd..7a8ac7717306 100644 --- a/examples/lb3-application/lb3app/test/integration.js +++ b/examples/lb3-application/lb3app/test/integration.js @@ -6,9 +6,8 @@ 'use strict'; const assert = require('assert'); -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const should = require('should'); const ExpressServer = require('../../dist/server').ExpressServer; +require('should'); describe('LoopBack 3 style integration tests', function () { let app; diff --git a/examples/lb3-application/src/__tests__/acceptance/lb3app.acceptance.ts b/examples/lb3-application/src/__tests__/acceptance/lb3app.acceptance.ts index 2c1db8b8e1de..aa2313d1ecc2 100644 --- a/examples/lb3-application/src/__tests__/acceptance/lb3app.acceptance.ts +++ b/examples/lb3-application/src/__tests__/acceptance/lb3app.acceptance.ts @@ -90,7 +90,7 @@ describe('CoffeeShopApplication', () => { .get(`/api/CoffeeShops/greet?access_token=${token.id}`) .expect(200); - expect(response.body.undefined).to.eql('Hello from this Coffee Shop'); + expect(response.body.greeting).to.eql('Hello from this Coffee Shop'); }); it('rejects anonymous requests to protected endpoints', async () => { From 0776ac2e67a7d84f7ac0687cc221a96ec62c3775 Mon Sep 17 00:00:00 2001 From: Nora Date: Tue, 28 Apr 2020 11:55:31 -0400 Subject: [PATCH 09/11] fixup! apply feedback --- SPIKE.md | 21 ++++++++++----- .../lb3-application/lb3app/test/acceptance.js | 26 +++++++++---------- .../lb3app/test/integration.js | 2 +- packages/booter-lb3app/src/lb3app.booter.ts | 5 ++-- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/SPIKE.md b/SPIKE.md index c9eb9c6bd729..09556c1ac7d6 100644 --- a/SPIKE.md +++ b/SPIKE.md @@ -3,8 +3,8 @@ Since LoopBack 4 offers a way to mount LoopBack 3 applications on a LoopBack 4 project with the use of [`@loopback/booter-lb3app`](https://github.com/strongloop/loopback-next/tree/master/packages/booter-lb3app), -there should also be a way for users to run their tests as part of LoopBack 4's -`npm test` command. +there should also be a way for users to run their LoopBack 3 tests as part of +LoopBack 4's `npm test` command. We want the LoopBack 3 tests to use the LoopBack 4 server rather than the LoopBack 3 application. This spike aims to test running both acceptance and @@ -95,8 +95,8 @@ application in order to allow JavaScript API to call application logic such as `Model.create()`. This can be seen in [`packages/booter-lb3app/src/lb3app.booter.ts`](https://github.com/strongloop/loopback-next/blob/spike/lb3test/packages/booter-lb3app/src/lb3app.booter.ts#L76-L85). -In order to retrieve the model from the application's context, -`getValueOrPromise()` can be used as follows: +In order to retrieve the model from the application's context, `getSync()` can +be used as follows: ```ts describe('LoopBack 3 style integration tests', function () { @@ -110,8 +110,8 @@ describe('LoopBack 3 style integration tests', function () { }); before(() => { - // follow the syntax: models.lb3-{ModelName} - CoffeeShop = app.lbApp.getValueOrPromise('models.lb3-CoffeeShop'); + // follow the syntax: lb3-models.{ModelName} + CoffeeShop = app.lbApp.getSync('lb3-models.CoffeeShop'); }); after(async () => { @@ -122,5 +122,14 @@ describe('LoopBack 3 style integration tests', function () { }); ``` +Alternatively, `get()` can also be used in to retrieve the model asynchronously: + +```ts +before(async () => { + // follow the syntax: lb3-models.{ModelName} + CoffeeShop = await app.lbApp.get('lb3-models.CoffeeShop'); +}); +``` + Example integration tests can be found in [`examples/lb3-application/lb3app/test/integration.js`](https://github.com/strongloop/loopback-next/blob/spike/lb3test/examples/lb3-application/lb3app/test/integration.js). diff --git a/examples/lb3-application/lb3app/test/acceptance.js b/examples/lb3-application/lb3app/test/acceptance.js index 9ff851188038..b9a81435447d 100644 --- a/examples/lb3-application/lb3app/test/acceptance.js +++ b/examples/lb3-application/lb3app/test/acceptance.js @@ -110,22 +110,20 @@ describe('LoopBack 3 style acceptance tests', function () { user, ) { user.email.should.be.equal('new@email.com'); - json('post', '/api/users/login') - .send({ - email: 'new@email.com', - password: 'L00pBack!', - }) - .expect(200, function (err2, token) { - assert(typeof token.body === 'object'); - const accessToken = token.body.id; - json( - 'get', - `/api/CoffeeShops/greet?access_token=${accessToken}`, - ).expect(200, function (err3, res) { + User.login({email: 'new@email.com', password: 'L00pBack!'}, function ( + err2, + token, + ) { + assert.equal(typeof token, 'object'); + assert.equal(token.userId, user.id); + json('get', `/api/CoffeeShops/greet?access_token=${token.id}`).expect( + 200, + function (err3, res) { res.body.greeting.should.be.equal('Hello from this Coffee Shop'); done(); - }); - }); + }, + ); + }); }); }); }); diff --git a/examples/lb3-application/lb3app/test/integration.js b/examples/lb3-application/lb3app/test/integration.js index 7a8ac7717306..4160eeca799e 100644 --- a/examples/lb3-application/lb3app/test/integration.js +++ b/examples/lb3-application/lb3app/test/integration.js @@ -20,7 +20,7 @@ describe('LoopBack 3 style integration tests', function () { }); before(() => { - CoffeeShop = app.lbApp.getValueOrPromise('models.lb3-CoffeeShop'); + CoffeeShop = app.lbApp.getSync('lb3-models.CoffeeShop'); }); after(async () => { diff --git a/packages/booter-lb3app/src/lb3app.booter.ts b/packages/booter-lb3app/src/lb3app.booter.ts index 9c2311201012..60b5ea5d8bf1 100644 --- a/packages/booter-lb3app/src/lb3app.booter.ts +++ b/packages/booter-lb3app/src/lb3app.booter.ts @@ -80,7 +80,7 @@ export class Lb3AppBooter implements Booter { const model = models[key]; if (visited.includes(model)) return; visited.push(model); - this.app.bind(`models.lb3-${key}`).to(model).tag('model'); + this.app.bind(`lb3-models.${key}`).to(model).tag('lb3-model'); }); } @@ -160,5 +160,6 @@ export interface Lb3AppBooterOptions { interface Lb3Application extends ExpressApplication { handler(name: 'rest'): ExpressRequestHandler; dataSources?: {[name: string]: unknown}; - models?: {[name: string]: unknown}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + models?: {[name: string]: any}; } From 81fc3f2c5bca39160f80e4c2c309500271555fe5 Mon Sep 17 00:00:00 2001 From: Nora Date: Wed, 29 Apr 2020 13:02:22 -0400 Subject: [PATCH 10/11] fixup! apply janny feedback --- SPIKE.md | 9 +- .../lb3-application/lb3app/test/acceptance.js | 76 ++------------- .../lb3app/test/authentication.js | 94 +++++++++++++++++++ 3 files changed, 109 insertions(+), 70 deletions(-) create mode 100644 examples/lb3-application/lb3app/test/authentication.js diff --git a/SPIKE.md b/SPIKE.md index 09556c1ac7d6..5026d7c8bbeb 100644 --- a/SPIKE.md +++ b/SPIKE.md @@ -38,16 +38,16 @@ In your test file: ```ts // can use lb4's testlab's supertest as the dependency is already installed - const request = require('@loopback/testlab').supertest; + const supertest = require('@loopback/testlab').supertest; const assert = require('assert'); const should = require('should'); const ExpressServer = require('../../dist/server').ExpressServer; let app; - function json(verb, url) { + function request(verb, url) { // use the LB4 express server - return request(app.server) + return supertest(app.server) [verb](url) .set('Content-Type', 'application/json') .set('Accept', 'application/json') @@ -133,3 +133,6 @@ before(async () => { Example integration tests can be found in [`examples/lb3-application/lb3app/test/integration.js`](https://github.com/strongloop/loopback-next/blob/spike/lb3test/examples/lb3-application/lb3app/test/integration.js). + +Example authentication tests can be found in +[`examples/lb3-application/lb3app/test/authentication.js`](https://github.com/strongloop/loopback-next/blob/spike/lb3test/examples/lb3-application/lb3app/test/authentication.js). diff --git a/examples/lb3-application/lb3app/test/acceptance.js b/examples/lb3-application/lb3app/test/acceptance.js index b9a81435447d..4055b1e29a80 100644 --- a/examples/lb3-application/lb3app/test/acceptance.js +++ b/examples/lb3-application/lb3app/test/acceptance.js @@ -4,17 +4,16 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; -const lb3App = require('../server/server'); -const request = require('@loopback/testlab').supertest; +const supertest = require('@loopback/testlab').supertest; const assert = require('assert'); const ExpressServer = require('../../dist/server').ExpressServer; require('should'); let app; -function json(verb, url) { +function request(verb, url) { // use the original app's server - return request(app.server) + return supertest(app.server) [verb](url) .set('Content-Type', 'application/json') .set('Accept', 'application/json') @@ -34,7 +33,7 @@ describe('LoopBack 3 style acceptance tests', function () { context('basic REST calls for LoopBack 3 application', () => { it('creates and finds a CoffeeShop', function (done) { - json('post', '/api/CoffeeShops') + request('post', '/api/CoffeeShops') .send({ name: 'Coffee Shop', city: 'Toronto', @@ -51,7 +50,10 @@ describe('LoopBack 3 style acceptance tests', function () { }); it("gets the CoffeeShop's status", function (done) { - json('get', '/api/CoffeeShops/status').expect(200, function (err, res) { + request('get', '/api/CoffeeShops/status').expect(200, function ( + err, + res, + ) { res.body.status.should.be.equalOneOf( 'We are open for business.', 'Sorry, we are closed. Open daily from 6am to 8pm.', @@ -61,70 +63,10 @@ describe('LoopBack 3 style acceptance tests', function () { }); it('gets external route in application', function (done) { - json('get', '/ping').expect(200, function (err, res) { + request('get', '/ping').expect(200, function (err, res) { assert.equal(res.text, 'pong'); done(); }); }); }); - - context('authentication', () => { - let User; - - before(() => { - User = lb3App.models.User; - }); - - it('creates a User and logs them in and out', function (done) { - User.create({email: 'new@email.com', password: 'L00pBack!'}, function ( - err, - user, - ) { - assert.equal(user.email, 'new@email.com'); - User.login( - { - email: 'new@email.com', - password: 'L00pBack!', - }, - function (err2, token) { - token.should.have.properties('ttl', 'userId', 'created', 'id'); - assert.equal(token.userId, user.id); - User.logout(token.id); - User.deleteById(user.id); - }, - ); - done(); - }); - }); - - it('rejects anonymous requests to protected endpoints', function (done) { - json('get', '/api/CoffeeShops/greet').expect(401, function (err, res) { - assert.equal(res.body.error.code, 'AUTHORIZATION_REQUIRED'); - }); - done(); - }); - - it('makes an authenticated request', function (done) { - User.create({email: 'new@email.com', password: 'L00pBack!'}, function ( - err, - user, - ) { - user.email.should.be.equal('new@email.com'); - User.login({email: 'new@email.com', password: 'L00pBack!'}, function ( - err2, - token, - ) { - assert.equal(typeof token, 'object'); - assert.equal(token.userId, user.id); - json('get', `/api/CoffeeShops/greet?access_token=${token.id}`).expect( - 200, - function (err3, res) { - res.body.greeting.should.be.equal('Hello from this Coffee Shop'); - done(); - }, - ); - }); - }); - }); - }); }); diff --git a/examples/lb3-application/lb3app/test/authentication.js b/examples/lb3-application/lb3app/test/authentication.js new file mode 100644 index 000000000000..c38a0da7ba73 --- /dev/null +++ b/examples/lb3-application/lb3app/test/authentication.js @@ -0,0 +1,94 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-lb3-application +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +'use strict'; +const lb3App = require('../server/server'); +const supertest = require('@loopback/testlab').supertest; +const assert = require('assert'); +const ExpressServer = require('../../dist/server').ExpressServer; +require('should'); + +let app; + +function request(verb, url) { + // use the original app's server + return supertest(app.server) + [verb](url) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + .expect('Content-Type', /json/); +} + +describe('LoopBack 3 authentication', function () { + before(async function () { + app = new ExpressServer(); + await app.boot(); + await app.start(); + }); + + after(async () => { + await app.stop(); + }); + + context('authentication', () => { + let User; + + before(() => { + User = lb3App.models.User; + }); + + it('creates a User and logs them in and out', function (done) { + User.create({email: 'new@email.com', password: 'L00pBack!'}, function ( + err, + user, + ) { + assert.equal(user.email, 'new@email.com'); + User.login( + { + email: 'new@email.com', + password: 'L00pBack!', + }, + function (err2, token) { + token.should.have.properties('ttl', 'userId', 'created', 'id'); + assert.equal(token.userId, user.id); + User.logout(token.id); + User.deleteById(user.id); + }, + ); + done(); + }); + }); + + it('rejects anonymous requests to protected endpoints', function (done) { + request('get', '/api/CoffeeShops/greet').expect(401, function (err, res) { + assert.equal(res.body.error.code, 'AUTHORIZATION_REQUIRED'); + }); + done(); + }); + + it('makes an authenticated request', function (done) { + User.create({email: 'new@email.com', password: 'L00pBack!'}, function ( + err, + user, + ) { + user.email.should.be.equal('new@email.com'); + User.login({email: 'new@email.com', password: 'L00pBack!'}, function ( + err2, + token, + ) { + assert.equal(typeof token, 'object'); + assert.equal(token.userId, user.id); + request( + 'get', + `/api/CoffeeShops/greet?access_token=${token.id}`, + ).expect(200, function (err3, res) { + res.body.greeting.should.be.equal('Hello from this Coffee Shop'); + done(); + }); + }); + }); + }); + }); +}); From a823ca89e3b2caf19452e46afd8a11f669ecbca7 Mon Sep 17 00:00:00 2001 From: Nora Date: Thu, 30 Apr 2020 15:14:45 -0400 Subject: [PATCH 11/11] fixup! more feedback --- SPIKE.md | 7 ++- .../lb3-application/lb3app/test/acceptance.js | 4 +- .../lb3app/test/authentication.js | 53 ++++++++++--------- .../acceptance/booter-lb3app.acceptance.ts | 2 +- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/SPIKE.md b/SPIKE.md index 5026d7c8bbeb..4e442e25db75 100644 --- a/SPIKE.md +++ b/SPIKE.md @@ -38,10 +38,10 @@ In your test file: ```ts // can use lb4's testlab's supertest as the dependency is already installed - const supertest = require('@loopback/testlab').supertest; + const {supertest} = require('@loopback/testlab'); const assert = require('assert'); const should = require('should'); - const ExpressServer = require('../../dist/server').ExpressServer; + const {ExpressServer} = require('../../dist/server'); let app; @@ -131,6 +131,9 @@ before(async () => { }); ``` +Additionally, LB3 datasources are also bound to the LB4 application's context +and can be retrieved with a key in the syntax `lb3-datasources.{ds name}`. + Example integration tests can be found in [`examples/lb3-application/lb3app/test/integration.js`](https://github.com/strongloop/loopback-next/blob/spike/lb3test/examples/lb3-application/lb3app/test/integration.js). diff --git a/examples/lb3-application/lb3app/test/acceptance.js b/examples/lb3-application/lb3app/test/acceptance.js index 4055b1e29a80..e9ad4e5ac874 100644 --- a/examples/lb3-application/lb3app/test/acceptance.js +++ b/examples/lb3-application/lb3app/test/acceptance.js @@ -4,9 +4,9 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; -const supertest = require('@loopback/testlab').supertest; +const {supertest} = require('@loopback/testlab'); const assert = require('assert'); -const ExpressServer = require('../../dist/server').ExpressServer; +const {ExpressServer} = require('../../dist/server'); require('should'); let app; diff --git a/examples/lb3-application/lb3app/test/authentication.js b/examples/lb3-application/lb3app/test/authentication.js index c38a0da7ba73..71f8423d8d22 100644 --- a/examples/lb3-application/lb3app/test/authentication.js +++ b/examples/lb3-application/lb3app/test/authentication.js @@ -5,9 +5,9 @@ 'use strict'; const lb3App = require('../server/server'); -const supertest = require('@loopback/testlab').supertest; +const {supertest} = require('@loopback/testlab'); const assert = require('assert'); -const ExpressServer = require('../../dist/server').ExpressServer; +const {ExpressServer} = require('../../dist/server'); require('should'); let app; @@ -40,25 +40,30 @@ describe('LoopBack 3 authentication', function () { }); it('creates a User and logs them in and out', function (done) { - User.create({email: 'new@email.com', password: 'L00pBack!'}, function ( - err, - user, - ) { - assert.equal(user.email, 'new@email.com'); - User.login( - { - email: 'new@email.com', - password: 'L00pBack!', - }, - function (err2, token) { - token.should.have.properties('ttl', 'userId', 'created', 'id'); - assert.equal(token.userId, user.id); - User.logout(token.id); - User.deleteById(user.id); - }, - ); - done(); - }); + request('post', '/api/users') + .send({email: 'new@email.com', password: 'L00pBack!'}) + .expect(200, function (err, user) { + assert.equal(user.body.email, 'new@email.com'); + request('post', '/api/users/login') + .send({ + email: 'new@email.com', + password: 'L00pBack!', + }) + .expect(200, function (err2, token) { + token.body.should.have.properties( + 'ttl', + 'userId', + 'created', + 'id', + ); + assert.equal(token.body.userId, user.body.id); + request( + 'post', + `/api/users/logout?access_token=${token.body.id}`, + ).expect(204); + done(); + }); + }); }); it('rejects anonymous requests to protected endpoints', function (done) { @@ -69,12 +74,12 @@ describe('LoopBack 3 authentication', function () { }); it('makes an authenticated request', function (done) { - User.create({email: 'new@email.com', password: 'L00pBack!'}, function ( + User.create({email: 'new@gmail.com', password: 'L00pBack!'}, function ( err, user, ) { - user.email.should.be.equal('new@email.com'); - User.login({email: 'new@email.com', password: 'L00pBack!'}, function ( + user.email.should.be.equal('new@gmail.com'); + User.login({email: 'new@gmail.com', password: 'L00pBack!'}, function ( err2, token, ) { diff --git a/packages/booter-lb3app/src/__tests__/acceptance/booter-lb3app.acceptance.ts b/packages/booter-lb3app/src/__tests__/acceptance/booter-lb3app.acceptance.ts index e6bc2d0e24ec..402418af143d 100644 --- a/packages/booter-lb3app/src/__tests__/acceptance/booter-lb3app.acceptance.ts +++ b/packages/booter-lb3app/src/__tests__/acceptance/booter-lb3app.acceptance.ts @@ -252,7 +252,7 @@ describe('booter-lb3app', () => { it('binds model to the context', async () => { const expected = require('../../../fixtures/app-with-model').models.Color; - const modelBindings = app.findByTag('model'); + const modelBindings = app.findByTag('lb3-model'); const key = modelBindings[0].key; const model = await app.get(key); expect(model).to.eql(expected);