diff --git a/CHANGELOG.md b/CHANGELOG.md index f50343ac2..c496a7548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [To be deprecated] -- Remove "/features" api path. +- Remove deprecated "/mocks" api path. ## [unreleased] ### Added @@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed ### Removed +## [1.2.0] - 2019-12-24 +### Added +- Add "adminApiPath" option, which allows to change the new API path ("/admin"). +- Add "adminApiDeprecatedPaths" option, which allows to disable the deprecated API path ("/mocks"). +- Add new api resources under "/admin" path. + ## [1.1.0] - 2019-12-07 ### Changed - Upgrade "@mocks-server" core dependency. Use new "path" option. diff --git a/README.md b/README.md index d91c946c8..0fef05d0b 100644 --- a/README.md +++ b/README.md @@ -15,18 +15,30 @@ This is __very useful when running acceptance tests, as you can change the behav This plugin is included in the [main distribution of the Mocks Server project][main-distribution-url], so you can refer to the [official documentation website][website-url]. +## Options + +* `adminApiPath` - Base path for the administration api. Default is "/admin". You should change it only if there is any conflict with the api you are mocking. +* `adminApiDeprecatedPaths` - Boolean option, disables deprecated "/mocks" api path, which is still enabled by default. + +Read more about [how to define options for the mocks-server plugins here](https://www.mocks-server.org/docs/configuration-options). + ## API Resources Available api resources are: -* `GET` `/mocks/behaviors/current` Returns current behavior. -* `PUT` `/mocks/behaviors/current` Set current behavior. - * Request body example: `{ "name": "behavior-name" }` -* `GET` `/mocks/settings` Returns current server settings. - * Response body example: `{ "delay": 0 }` -* `PUT` `/mocks/settings` Changes current server settings. +* `GET` `/admin/about` Returns plugin information. + * Response body example: `{ "version": "1.2.0" }` +* `GET` `/admin/behaviors` Returns behaviors collection. +* `GET` `/admin/behaviors/:name` Returns an specific behavior. +* `GET` `/admin/fixtures` Returns fixtures collection. +* `GET` `/admin/fixtures/:id` Returns an specific fixture. +* `GET` `/admin/settings` Returns current server settings. + * Response body example: `{ "delay": 0, behavior: "foo-behavior", path: "mocks" }` +* `PATCH` `/admin/settings` Changes current server settings. * Request body example: `{ "delay": 3000 }` +> Deprecated api resources under "/mocks" api path are still available. + ## Contributing Contributors are welcome. diff --git a/index.js b/index.js index 875fbc2b3..beea31ec3 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,6 @@ Unless required by applicable law or agreed to in writing, software distributed "use strict"; -const PluginAdminApi = require("./src/Api"); +const PluginAdminApi = require("./src/Plugin"); module.exports = PluginAdminApi; diff --git a/jest.config.js b/jest.config.js index 077d9f7b4..55a220e85 100644 --- a/jest.config.js +++ b/jest.config.js @@ -26,7 +26,6 @@ module.exports = { // The glob patterns Jest uses to detect test files testMatch: ["**/test/unit/**/?(*.)+(spec|test).js?(x)"], - //testMatch: ["**/test/unit/core/Plugins.spec.js"], // The test environment that will be used for testing testEnvironment: "node" diff --git a/package-lock.json b/package-lock.json index d8a87adec..67d87b82a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@mocks-server/plugin-admin-api", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -211,6 +211,19 @@ } } }, + "@hapi/boom": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-8.0.1.tgz", + "integrity": "sha512-SnBM2GzEYEA6AGFKXBqNLWXR3uNBui0bkmklYXX1gYtevVhDTy2uakwkSauxvIWMtlANGRhzChYg95If3FWCwA==", + "requires": { + "@hapi/hoek": "8.x.x" + } + }, + "@hapi/hoek": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.0.tgz", + "integrity": "sha512-7XYT10CZfPsH7j9F1Jmg1+d0ezOux2oM2GfArAzLwWe4mE2Dr3hVjsAL6+TFY49RRJlCdJDMw3nJsLFroTc8Kw==" + }, "@jest/console": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", @@ -464,20 +477,26 @@ "@types/yargs": "^13.0.0" } }, + "@mocks-server/admin-api-paths": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@mocks-server/admin-api-paths/-/admin-api-paths-1.0.1.tgz", + "integrity": "sha512-rV3py6WXauRTIj+t9GvbT6jf8V9yAC2F0vtB4TTDeFmuO42oJPNSbXAY9G5gVcpx1FhUPJQ3ZhngOHDAooheVQ==" + }, "@mocks-server/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mocks-server/core/-/core-1.1.0.tgz", - "integrity": "sha512-uucdenxR1nIlmP8s+Rjh71pwkDxLSXb6Haadq1SZjFVRk3JDuxVj0jbxRXJdzqoiGW/WOSuV7hpysZcrHkqJKw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mocks-server/core/-/core-1.2.0.tgz", + "integrity": "sha512-ngXLcxIwxQce1JRnDEEAKFJIe64rpOF0PHKgDFn/QXm0Aa3o2AK++DajSScXa2wOssoH0xMw8nGNGGUMUBnCDw==", "dev": true, "requires": { + "@hapi/boom": "8.0.1", "body-parser": "1.19.0", - "boom": "7.3.0", "commander": "4.0.1", "cors": "2.8.5", "express": "4.17.1", "express-request-id": "1.4.1", "fs-extra": "^8.1.0", "lodash": "4.17.15", + "md5": "^2.2.1", "node-watch": "0.6.3", "require-all": "3.0.0", "route-parser": "0.0.5", @@ -1138,15 +1157,6 @@ } } }, - "boom": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-7.3.0.tgz", - "integrity": "sha512-Swpoyi2t5+GhOEGw8rEsKvTxFLIDiiKoUc2gsoV6Lyr43LHBIzch3k2MvYUs8RTROrIkVJ3Al0TkaOGjnb+B6A==", - "dev": true, - "requires": { - "hoek": "6.x.x" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1305,6 +1315,12 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", + "dev": true + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -1680,6 +1696,12 @@ } } }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", + "dev": true + }, "cssom": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", @@ -3503,12 +3525,6 @@ } } }, - "hoek": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", - "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", - "dev": true - }, "hosted-git-info": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", @@ -5495,6 +5511,17 @@ "object-visit": "^1.0.0" } }, + "md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "dev": true, + "requires": { + "charenc": "~0.0.1", + "crypt": "~0.0.1", + "is-buffer": "~1.1.1" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", diff --git a/package.json b/package.json index 10f9851b1..f382d3fc1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mocks-server/plugin-admin-api", - "version": "1.1.0", + "version": "1.2.0", "description": "Plugin for Mocks Server. Provides a REST API for administrating settings, fixtures and behaviors", "keywords": [ "mocks-server-plugin", @@ -35,10 +35,12 @@ "@mocks-server/core": "^1.1.0" }, "dependencies": { + "@mocks-server/admin-api-paths": "1.0.1", + "@hapi/boom": "8.0.1", "express": "4.17.1" }, "devDependencies": { - "@mocks-server/core": "^1.1.0", + "@mocks-server/core": "^1.2.0", "coveralls": "^3.0.7", "eslint": "6.6.0", "eslint-config-prettier": "6.5.0", diff --git a/sonar-project.properties b/sonar-project.properties index 40c3037c4..3084309dd 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,6 @@ sonar.organization=mocks-server sonar.projectKey=mocks-server-plugin-admin-api -sonar.projectVersion=1.1.0 +sonar.projectVersion=1.2.0 sonar.javascript.file.suffixes=.js sonar.sourceEncoding=UTF-8 diff --git a/src/About.js b/src/About.js new file mode 100644 index 000000000..fdc339c63 --- /dev/null +++ b/src/About.js @@ -0,0 +1,41 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +"use strict"; + +const express = require("express"); + +const { PLUGIN_NAME } = require("./constants"); +const { version } = require("../package.json"); + +class AboutApi { + constructor(core) { + this._core = core; + this._tracer = core.tracer; + this._fixtures = this._core.fixtures; + this._router = express.Router(); + this._router.get("/", this.getAbout.bind(this)); + } + + getAbout(req, res) { + this._tracer.verbose(`${PLUGIN_NAME}: Sending about | ${req.id}`); + res.status(200); + res.send({ + version + }); + } + + get router() { + return this._router; + } +} + +module.exports = AboutApi; diff --git a/src/Behaviors.js b/src/Behaviors.js index 90fe30cb4..081a32abe 100644 --- a/src/Behaviors.js +++ b/src/Behaviors.js @@ -12,34 +12,48 @@ Unless required by applicable law or agreed to in writing, software distributed "use strict"; const express = require("express"); +const Boom = require("@hapi/boom"); + +const { PLUGIN_NAME } = require("./constants"); class BehaviorsApi { constructor(core) { - this._tracer = core.tracer; this._core = core; + this._tracer = core.tracer; + this._behaviors = this._core.behaviors; this._router = express.Router(); this._router.get("/", this.getCollection.bind(this)); - this._router.get("/current", this.getCurrent.bind(this)); - this._router.put("/current", this.putCurrent.bind(this)); + this._router.get("/:name", this.getModel.bind(this)); } - getCurrent(req, res) { - this._tracer.verbose(`Sending current behavior | ${req.id}`); - res.status(200); - res.send(this._core.behaviors.currentFromCollection); + _parseModel(behavior) { + return { + name: behavior.name, + fixtures: behavior.fixtures.map(fixture => fixture.id), + extendedFrom: behavior.extendedFrom + }; } - putCurrent(req, res) { - const newCurrent = req.body.name; - this._tracer.verbose(`Changing current behavior to "${newCurrent}" | ${req.id}`); - this._core.settings.set("behavior", newCurrent); - this.getCurrent(req, res); + _parseCollection() { + return this._behaviors.collection.map(this._parseModel); } getCollection(req, res) { - this._tracer.verbose(`Sending behaviors collection | ${req.id}`); + this._tracer.verbose(`${PLUGIN_NAME}: Sending behaviors | ${req.id}`); res.status(200); - res.send(this._core.behaviors.collection); + res.send(this._parseCollection()); + } + + getModel(req, res, next) { + const name = req.params.name; + this._tracer.verbose(`${PLUGIN_NAME}: Sending behavior ${name} | ${req.id}`); + const foundBehavior = this._behaviors.collection.find(behavior => behavior.name === name); + if (foundBehavior) { + res.status(200); + res.send(this._parseModel(foundBehavior)); + } else { + next(Boom.notFound(`Behavior with name "${name}" was not found`)); + } } get router() { diff --git a/src/Fixtures.js b/src/Fixtures.js new file mode 100644 index 000000000..676839c35 --- /dev/null +++ b/src/Fixtures.js @@ -0,0 +1,66 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +"use strict"; + +const express = require("express"); +const Boom = require("@hapi/boom"); + +const { PLUGIN_NAME } = require("./constants"); + +class FixturesApi { + constructor(core) { + this._core = core; + this._tracer = core.tracer; + this._fixtures = this._core.fixtures; + this._router = express.Router(); + this._router.get("/", this.getCollection.bind(this)); + this._router.get("/:id", this.getModel.bind(this)); + } + + _parseModel(fixture) { + return { + id: fixture.id, + requestMatchId: fixture.requestMatchId, + handler: fixture.constructor.displayName, + request: fixture.request, + response: fixture.response + }; + } + + _parseCollection() { + return this._fixtures.collection.map(this._parseModel); + } + + getCollection(req, res) { + this._tracer.verbose(`${PLUGIN_NAME}: Sending fixtures | ${req.id}`); + res.status(200); + res.send(this._parseCollection()); + } + + getModel(req, res, next) { + const id = req.params.id; + this._tracer.verbose(`${PLUGIN_NAME}: Sending fixture ${id} | ${req.id}`); + const foundFixture = this._fixtures.collection.find(fixture => fixture.id === id); + if (foundFixture) { + res.status(200); + res.send(this._parseModel(foundFixture)); + } else { + next(Boom.notFound(`Fixture with id "${id}" was not found`)); + } + } + + get router() { + return this._router; + } +} + +module.exports = FixturesApi; diff --git a/src/Plugin.js b/src/Plugin.js new file mode 100644 index 000000000..ac18e3e7f --- /dev/null +++ b/src/Plugin.js @@ -0,0 +1,113 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const express = require("express"); +const { + DEFAULT_BASE_PATH, + SETTINGS, + BEHAVIORS, + ABOUT, + FIXTURES +} = require("@mocks-server/admin-api-paths"); + +const DeprecatedApi = require("./deprecated/Api"); + +const Settings = require("./Settings"); +const Behaviors = require("./Behaviors"); +const Fixtures = require("./Fixtures"); +const About = require("./About"); + +const { + ADMIN_API_PATH_OPTION, + ADMIN_API_DEPRECATED_PATHS_OPTION, + PLUGIN_NAME, + DEPRECATED_API_PATH +} = require("./constants"); + +class Plugin { + constructor(core) { + this._core = core; + this._tracer = core.tracer; + this._settings = this._core.settings; + this._deprecatedApi = new DeprecatedApi(core); + this._settingsApi = new Settings(this._core); + this._behaviorsApi = new Behaviors(this._core); + this._aboutApi = new About(this._core); + this._fixturesApi = new Fixtures(this._core); + core.addSetting({ + name: ADMIN_API_PATH_OPTION, + type: "string", + description: `Api path for ${PLUGIN_NAME}`, + default: DEFAULT_BASE_PATH + }); + + core.addSetting({ + name: ADMIN_API_DEPRECATED_PATHS_OPTION, + type: "boolean", + description: `Disable deprecated paths of ${PLUGIN_NAME}`, + default: true + }); + + this._onChangeSettings = this._onChangeSettings.bind(this); + } + + async init() { + await this._deprecatedApi.init(); + this._core.onChangeSettings(this._onChangeSettings); + this._initRouter(); + this._addDeprecatedRouter(); + this._addRouter(); + } + + _initRouter() { + this._router = express.Router(); + this._router.use(SETTINGS, this._settingsApi.router); + this._router.use(BEHAVIORS, this._behaviorsApi.router); + this._router.use(ABOUT, this._aboutApi.router); + this._router.use(FIXTURES, this._fixturesApi.router); + } + + _addDeprecatedRouter() { + if ( + this._settings.get(ADMIN_API_DEPRECATED_PATHS_OPTION) === false && + this._addedDeprecatedRouter + ) { + this._core.removeRouter(DEPRECATED_API_PATH, this._deprecatedApi.router); + this._addedDeprecatedRouter = false; + } + if ( + this._settings.get(ADMIN_API_DEPRECATED_PATHS_OPTION) === true && + !this._addedDeprecatedRouter + ) { + this._core.addRouter(DEPRECATED_API_PATH, this._deprecatedApi.router); + this._addedDeprecatedRouter = true; + } + } + + _addRouter() { + if (this._previousRoutersPath) { + this._core.removeRouter(this._previousRoutersPath, this._router); + } + this._previousRoutersPath = this._settings.get(ADMIN_API_PATH_OPTION); + this._core.addRouter(this._previousRoutersPath, this._router); + } + + _onChangeSettings(newSettings) { + if (newSettings.hasOwnProperty(ADMIN_API_DEPRECATED_PATHS_OPTION)) { + this._addDeprecatedRouter(); + } + if (newSettings.hasOwnProperty(ADMIN_API_PATH_OPTION)) { + this._addRouter(); + } + } +} + +module.exports = Plugin; diff --git a/src/Settings.js b/src/Settings.js index f4ddbfa78..4e7f3353b 100644 --- a/src/Settings.js +++ b/src/Settings.js @@ -12,29 +12,51 @@ Unless required by applicable law or agreed to in writing, software distributed "use strict"; const express = require("express"); +const Boom = require("@hapi/boom"); + +const { PLUGIN_NAME } = require("./constants"); class SettingsApi { - constructor(settings, tracer) { - this._tracer = tracer; - this._settings = settings; + constructor(core) { + this._core = core; + this._tracer = core.tracer; + this._settings = this._core.settings; this._router = express.Router(); - this._router.put("/", this.put.bind(this)); + this._router.patch("/", this.patch.bind(this)); this._router.get("/", this.get.bind(this)); } - put(req, res) { - const newDelay = req.body.delay; - this._tracer.verbose(`Changing delay to "${newDelay}" | ${req.id}`); - this._settings.set("delay", newDelay); - this.get(req, res); + _validateNewSettings(newSettings) { + const errors = []; + Object.keys(newSettings).forEach(newSettingKey => { + if (!this._settings.getValidOptionName(newSettingKey)) { + errors.push(`Invalid option name "${newSettingKey}"`); + } + }); + return errors; + } + + patch(req, res, next) { + const newSettings = req.body; + const errors = this._validateNewSettings(newSettings); + if (errors.length) { + next(Boom.badRequest(errors.join(". "))); + } else { + Object.keys(newSettings).forEach(newSettingKey => { + this._tracer.verbose( + `${PLUGIN_NAME}: Changing setting "${newSettingKey}" to "${newSettings[newSettingKey]}" | ${req.id}` + ); + this._settings.set(newSettingKey, newSettings[newSettingKey]); + }); + res.status(204); + res.send(); + } } get(req, res) { - this._tracer.verbose(`Sending delay to | ${req.id}`); + this._tracer.verbose(`${PLUGIN_NAME}: Sending settings | ${req.id}`); res.status(200); - res.send({ - delay: this._settings.get("delay") - }); + res.send(this._settings.all); } get router() { diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 000000000..dabc07575 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,16 @@ +/* +Copyright 2019 Javier Brea + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +module.exports = { + ADMIN_API_PATH_OPTION: "adminApiPath", + ADMIN_API_DEPRECATED_PATHS_OPTION: "adminApiDeprecatedPaths", + PLUGIN_NAME: "plugin-admin-api", + DEPRECATED_API_PATH: "/mocks" +}; diff --git a/src/Api.js b/src/deprecated/Api.js similarity index 70% rename from src/Api.js rename to src/deprecated/Api.js index e19fa7c45..fc46d6b18 100644 --- a/src/Api.js +++ b/src/deprecated/Api.js @@ -9,14 +9,15 @@ http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -"use strict"; - const express = require("express"); +const { DEFAULT_BASE_PATH } = require("@mocks-server/admin-api-paths"); const Behaviors = require("./Behaviors"); const Settings = require("./Settings"); -const FEATURES_PATH = "/features"; +const { DEPRECATED_API_PATH, PLUGIN_NAME } = require("../constants"); + +// TODO, deprecate mocks router class Api { constructor(core) { @@ -27,20 +28,21 @@ class Api { init() { const behaviorsRouter = new Behaviors(this._core).router; this._router = express.Router(); - // TODO, deprecate features router - this._router.use(FEATURES_PATH, (req, res, next) => { - this._tracer.warn( - `Deprecation warning: "${FEATURES_PATH}" api path will be deprecated. Use "/behaviors" instead` + this._router.use((req, res, next) => { + this._core.tracer.deprecationWarn( + `"${DEPRECATED_API_PATH}" ${PLUGIN_NAME} path`, + DEFAULT_BASE_PATH ); next(); }); - // TODO, deprecate features router - this._router.use(FEATURES_PATH, behaviorsRouter); + + this._router.use("/features", behaviorsRouter); this._router.use("/behaviors", behaviorsRouter); this._router.use("/settings", new Settings(this._core.settings, this._tracer).router); + } - this._core.addCustomRouter("/mocks", this._router); - return Promise.resolve(); + get router() { + return this._router; } } diff --git a/src/deprecated/Behaviors.js b/src/deprecated/Behaviors.js new file mode 100644 index 000000000..a6c50e106 --- /dev/null +++ b/src/deprecated/Behaviors.js @@ -0,0 +1,54 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +"use strict"; + +const express = require("express"); + +const { PLUGIN_NAME } = require("../constants"); + +class BehaviorsApi { + constructor(core) { + this._tracer = core.tracer; + this._core = core; + this._router = express.Router(); + this._router.get("/", this.getCollection.bind(this)); + this._router.get("/current", this.getCurrent.bind(this)); + this._router.put("/current", this.putCurrent.bind(this)); + } + + getCurrent(req, res) { + this._tracer.verbose(`${PLUGIN_NAME}: Sending current behavior | ${req.id}`); + res.status(200); + res.send(this._core.behaviors.currentFromCollection); + } + + putCurrent(req, res) { + const newCurrent = req.body.name; + this._tracer.verbose( + `${PLUGIN_NAME}: Changing current behavior to "${newCurrent}" | ${req.id}` + ); + this._core.settings.set("behavior", newCurrent); + this.getCurrent(req, res); + } + + getCollection(req, res) { + this._tracer.verbose(`${PLUGIN_NAME}: Sending behaviors collection | ${req.id}`); + res.status(200); + res.send(this._core.behaviors.collection); + } + + get router() { + return this._router; + } +} + +module.exports = BehaviorsApi; diff --git a/src/deprecated/Settings.js b/src/deprecated/Settings.js new file mode 100644 index 000000000..ac5ffca68 --- /dev/null +++ b/src/deprecated/Settings.js @@ -0,0 +1,47 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +"use strict"; + +const express = require("express"); + +const { PLUGIN_NAME } = require("../constants"); + +class SettingsApi { + constructor(settings, tracer) { + this._tracer = tracer; + this._settings = settings; + this._router = express.Router(); + this._router.put("/", this.put.bind(this)); + this._router.get("/", this.get.bind(this)); + } + + put(req, res) { + const newDelay = req.body.delay; + this._tracer.verbose(`${PLUGIN_NAME}: Changing delay to "${newDelay}" | ${req.id}`); + this._settings.set("delay", newDelay); + this.get(req, res); + } + + get(req, res) { + this._tracer.verbose(`${PLUGIN_NAME}: Sending delay to | ${req.id}`); + res.status(200); + res.send({ + delay: this._settings.get("delay") + }); + } + + get router() { + return this._router; + } +} + +module.exports = SettingsApi; diff --git a/test/acceptance/about-api.spec.js b/test/acceptance/about-api.spec.js new file mode 100644 index 000000000..64b2e21fd --- /dev/null +++ b/test/acceptance/about-api.spec.js @@ -0,0 +1,32 @@ +/* +Copyright 2019 Javier Brea + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const { startServer, stopServer, request } = require("./utils"); +const { version } = require("../../package.json"); + +describe("about api", () => { + let server; + beforeAll(async () => { + server = await startServer("web-tutorial"); + }); + + afterAll(() => { + stopServer(server); + }); + + describe("get /", () => { + it("should return current version", async () => { + const response = await request("/admin/about"); + expect(response).toEqual({ + version + }); + }); + }); +}); diff --git a/test/acceptance/behaviors-api.spec.js b/test/acceptance/behaviors-api.spec.js new file mode 100644 index 000000000..3668fb3cb --- /dev/null +++ b/test/acceptance/behaviors-api.spec.js @@ -0,0 +1,90 @@ +/* +Copyright 2019 Javier Brea + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const { startServer, stopServer, request } = require("./utils"); + +describe("behaviors api", () => { + let server; + beforeAll(async () => { + server = await startServer("web-tutorial"); + }); + + afterAll(() => { + stopServer(server); + }); + + describe("get /", () => { + it("should return current behaviors", async () => { + const response = await request("/admin/behaviors"); + expect(response).toEqual([ + { + extendedFrom: null, + fixtures: ["12e5f429b92f67d4ec2bf90940ec1135", "0dbc954f9d9c9f3f7996c60e63384c9e"], + name: "standard" + }, + { + extendedFrom: "standard", + fixtures: [ + "bd5292849ee3fda9fa8383837bb908e7", + "12e5f429b92f67d4ec2bf90940ec1135", + "0dbc954f9d9c9f3f7996c60e63384c9e" + ], + name: "user2" + }, + { + extendedFrom: "standard", + fixtures: [ + "e82af88532da929b0592925899eb056e", + "12e5f429b92f67d4ec2bf90940ec1135", + "0dbc954f9d9c9f3f7996c60e63384c9e" + ], + name: "dynamic" + } + ]); + }); + }); + + describe("get /standard", () => { + it("should return standard behavior", async () => { + const response = await request("/admin/behaviors/standard"); + expect(response).toEqual({ + extendedFrom: null, + fixtures: ["12e5f429b92f67d4ec2bf90940ec1135", "0dbc954f9d9c9f3f7996c60e63384c9e"], + name: "standard" + }); + }); + }); + + describe("get /dynamic", () => { + it("should return standard behavior", async () => { + const response = await request("/admin/behaviors/dynamic"); + expect(response).toEqual({ + extendedFrom: "standard", + fixtures: [ + "e82af88532da929b0592925899eb056e", + "12e5f429b92f67d4ec2bf90940ec1135", + "0dbc954f9d9c9f3f7996c60e63384c9e" + ], + name: "dynamic" + }); + }); + }); + + describe("get unexistant behavior", () => { + it("should return a not found error", async () => { + const response = await request("/admin/behaviors/foo", { + resolveWithFullResponse: true, + simple: false + }); + expect(response.statusCode).toEqual(404); + expect(response.body.message).toEqual('Behavior with name "foo" was not found'); + }); + }); +}); diff --git a/test/acceptance/change-behavior.spec.js b/test/acceptance/deprecated-change-behavior.spec.js similarity index 90% rename from test/acceptance/change-behavior.spec.js rename to test/acceptance/deprecated-change-behavior.spec.js index e3c1324d1..559b01429 100644 --- a/test/acceptance/change-behavior.spec.js +++ b/test/acceptance/deprecated-change-behavior.spec.js @@ -8,9 +8,15 @@ http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -const { startServer, stopServer, request, changeBehavior, getBehaviors } = require("./utils"); - -describe("API for changing current behavior", () => { +const { + startServer, + stopServer, + request, + deprecatedChangeBehavior, + deprecatedGetBehaviors +} = require("./utils"); + +describe("deprecated API for changing current behavior", () => { let server; beforeAll(async () => { @@ -23,7 +29,7 @@ describe("API for changing current behavior", () => { describe("When started", () => { it("should have 3 behaviors available", async () => { - const behaviors = await getBehaviors(); + const behaviors = await deprecatedGetBehaviors(); expect(behaviors.length).toEqual(3); }); @@ -48,7 +54,7 @@ describe("API for changing current behavior", () => { describe('When changing current behavior to "user2"', () => { beforeAll(async () => { - await changeBehavior("user2"); + await deprecatedChangeBehavior("user2"); }); it("should serve users collection mock under the /api/users path", async () => { @@ -72,7 +78,7 @@ describe("API for changing current behavior", () => { describe('When changing current behavior to "dynamic"', () => { beforeAll(async () => { - await changeBehavior("dynamic"); + await deprecatedChangeBehavior("dynamic"); }); it("should serve users collection mock under the /api/users path", async () => { diff --git a/test/acceptance/core-events.spec.js b/test/acceptance/deprecated-core-events.spec.js similarity index 94% rename from test/acceptance/core-events.spec.js rename to test/acceptance/deprecated-core-events.spec.js index b900ccf5e..3141f498e 100644 --- a/test/acceptance/core-events.spec.js +++ b/test/acceptance/deprecated-core-events.spec.js @@ -12,8 +12,8 @@ const path = require("path"); const fsExtra = require("fs-extra"); const { request, - changeBehavior, - getBehaviors, + deprecatedChangeBehavior, + deprecatedGetBehaviors, fixturesFolder, wait, CliRunner @@ -37,7 +37,7 @@ describe("Plugin listening to core events", () => { describe("When server is started", () => { it("should have 3 behaviors available", async () => { - const behaviors = await getBehaviors(); + const behaviors = await deprecatedGetBehaviors(); expect(behaviors.length).toEqual(3); }); @@ -68,7 +68,7 @@ describe("Plugin listening to core events", () => { describe("without changing current behavior", () => { it("should have 4 behaviors available", async () => { - const behaviors = await getBehaviors(); + const behaviors = await deprecatedGetBehaviors(); expect(behaviors.length).toEqual(4); }); @@ -93,7 +93,7 @@ describe("Plugin listening to core events", () => { describe('When changing current behavior to "user2"', () => { beforeAll(async () => { - await changeBehavior("user2"); + await deprecatedChangeBehavior("user2"); }); it("should serve users collection mock under the /api/users path", async () => { @@ -117,7 +117,7 @@ describe("Plugin listening to core events", () => { describe('When changing current behavior to "dynamic"', () => { beforeAll(async () => { - await changeBehavior("dynamic"); + await deprecatedChangeBehavior("dynamic"); }); it("should serve users collection mock under the /api/users path", async () => { @@ -141,7 +141,7 @@ describe("Plugin listening to core events", () => { describe('When changing current behavior to "newOne"', () => { beforeAll(async () => { - await changeBehavior("newOne"); + await deprecatedChangeBehavior("newOne"); }); it("should serve users collection mock under the /api/users path", async () => { diff --git a/test/acceptance/fixtures-api.spec.js b/test/acceptance/fixtures-api.spec.js new file mode 100644 index 000000000..e3b4238e7 --- /dev/null +++ b/test/acceptance/fixtures-api.spec.js @@ -0,0 +1,110 @@ +/* +Copyright 2019 Javier Brea + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const { startServer, stopServer, request } = require("./utils"); + +describe("fixtures api", () => { + let server; + beforeAll(async () => { + server = await startServer("web-tutorial"); + }); + + afterAll(() => { + stopServer(server); + }); + + describe("get /", () => { + it("should return current fixtures", async () => { + const response = await request("/admin/fixtures"); + expect(response).toEqual([ + { + id: "e82af88532da929b0592925899eb056e", + requestMatchId: "9989c8c9766561cd432c625deabca48b", + handler: "mocks-server-fixture", + request: { url: "/api/users/:id", method: "GET" }, + response: { + type: "dynamic", + function: + '(req, res) => {\n const userId = req.params.id;\n const user = INITIAL_USERS.find(userData => userData.id === Number(userId));\n\n if (user) {\n res.status(200);\n res.send(user);\n } else {\n res.status(404);\n res.send({\n message: "User not found"\n });\n }\n }' + } + }, + { + id: "bd5292849ee3fda9fa8383837bb908e7", + requestMatchId: "9989c8c9766561cd432c625deabca48b", + handler: "mocks-server-fixture", + request: { url: "/api/users/:id", method: "GET" }, + response: { type: "static", status: 200, body: { id: 2, name: "Jane Doe" } } + }, + { + id: "12e5f429b92f67d4ec2bf90940ec1135", + requestMatchId: "9989c8c9766561cd432c625deabca48b", + handler: "mocks-server-fixture", + request: { url: "/api/users/:id", method: "GET" }, + response: { type: "static", status: 200, body: { id: 1, name: "John Doe" } } + }, + { + id: "0dbc954f9d9c9f3f7996c60e63384c9e", + requestMatchId: "8b4d07b38f9320be1702c093fd1daa76", + handler: "mocks-server-fixture", + request: { url: "/api/users", method: "GET" }, + response: { + type: "static", + status: 200, + body: [ + { id: 1, name: "John Doe" }, + { id: 2, name: "Jane Doe" } + ] + } + } + ]); + }); + }); + + describe("get /e82af88532da929b0592925899eb056e", () => { + it("should return fixture with id e82af88532da929b0592925899eb056e", async () => { + const response = await request("/admin/fixtures/e82af88532da929b0592925899eb056e"); + expect(response).toEqual({ + id: "e82af88532da929b0592925899eb056e", + requestMatchId: "9989c8c9766561cd432c625deabca48b", + handler: "mocks-server-fixture", + request: { url: "/api/users/:id", method: "GET" }, + response: { + type: "dynamic", + function: + '(req, res) => {\n const userId = req.params.id;\n const user = INITIAL_USERS.find(userData => userData.id === Number(userId));\n\n if (user) {\n res.status(200);\n res.send(user);\n } else {\n res.status(404);\n res.send({\n message: "User not found"\n });\n }\n }' + } + }); + }); + }); + + describe("get /bd5292849ee3fda9fa8383837bb908e7", () => { + it("should return fixture with id bd5292849ee3fda9fa8383837bb908e7", async () => { + const response = await request("/admin/fixtures/bd5292849ee3fda9fa8383837bb908e7"); + expect(response).toEqual({ + id: "bd5292849ee3fda9fa8383837bb908e7", + requestMatchId: "9989c8c9766561cd432c625deabca48b", + handler: "mocks-server-fixture", + request: { url: "/api/users/:id", method: "GET" }, + response: { type: "static", status: 200, body: { id: 2, name: "Jane Doe" } } + }); + }); + }); + + describe("get unexistant fixture", () => { + it("should return a not found error", async () => { + const response = await request("/admin/fixtures/foo", { + resolveWithFullResponse: true, + simple: false + }); + expect(response.statusCode).toEqual(404); + expect(response.body.message).toEqual('Fixture with id "foo" was not found'); + }); + }); +}); diff --git a/test/acceptance/fixtures/start.js b/test/acceptance/fixtures/start.js new file mode 100755 index 000000000..893e60868 --- /dev/null +++ b/test/acceptance/fixtures/start.js @@ -0,0 +1,39 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +"use strict"; + +const { Core } = require("@mocks-server/core"); +const AdminApi = require("../../../index"); + +const handleError = error => { + console.error(`Error: ${error.message}`); + process.exitCode = 1; +}; + +const start = () => { + try { + const mocksServer = new Core({ + plugins: [AdminApi] + }); + return mocksServer + .init() + .then(() => { + console.log(mocksServer.settings.all); + return mocksServer.start(); + }) + .catch(handleError); + } catch (error) { + return handleError(error); + } +}; + +start(); diff --git a/test/acceptance/plugin-options.spec.js b/test/acceptance/plugin-options.spec.js new file mode 100644 index 000000000..7901a0841 --- /dev/null +++ b/test/acceptance/plugin-options.spec.js @@ -0,0 +1,120 @@ +/* +Copyright 2019 Javier Brea + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ +const path = require("path"); +const { startServer, stopServer, request, CliRunner, wait, fixturesFolder } = require("./utils"); + +describe("plugin options", () => { + let server; + let cli; + + describe("adminApiDeprecatedPaths option", () => { + beforeAll(async () => { + server = await startServer("web-tutorial", { + adminApiDeprecatedPaths: false + }); + }); + + afterAll(() => { + stopServer(server); + }); + + it("should disable deprecated behaviors api path", async () => { + const behaviorsResponse = await request("/mocks/behaviors", { + resolveWithFullResponse: true, + simple: false + }); + expect(behaviorsResponse.statusCode).toEqual(404); + }); + + it("should disable deprecated features api path", async () => { + const behaviorsResponse = await request("/mocks/features", { + resolveWithFullResponse: true, + simple: false + }); + expect(behaviorsResponse.statusCode).toEqual(404); + }); + + it("should disable deprecated settings api path", async () => { + const behaviorsResponse = await request("/mocks/settings", { + resolveWithFullResponse: true, + simple: false + }); + expect(behaviorsResponse.statusCode).toEqual(404); + }); + }); + + describe("adminApiDeprecatedPaths option used from CLI", () => { + beforeAll(async () => { + cli = new CliRunner( + ["node", "start.js", "--path=web-tutorial", "--no-adminApiDeprecatedPaths"], + { + cwd: path.resolve(__dirname, "fixtures") + } + ); + await wait(1000); + }); + + afterAll(async () => { + await cli.kill(); + }); + + it("should disable deprecated behaviors api path", async () => { + console.log(cli.logs); + const behaviorsResponse = await request("/mocks/behaviors", { + resolveWithFullResponse: true, + simple: false + }); + expect(behaviorsResponse.statusCode).toEqual(404); + }); + + it("should disable deprecated features api path", async () => { + const behaviorsResponse = await request("/mocks/features", { + resolveWithFullResponse: true, + simple: false + }); + expect(behaviorsResponse.statusCode).toEqual(404); + }); + + it("should disable deprecated settings api path", async () => { + const behaviorsResponse = await request("/mocks/settings", { + resolveWithFullResponse: true, + simple: false + }); + expect(behaviorsResponse.statusCode).toEqual(404); + }); + }); + + describe("adminApiPath option", () => { + beforeAll(async () => { + server = await startServer("web-tutorial", { + adminApiPath: "/foo" + }); + }); + + afterAll(() => { + stopServer(server); + }); + + it("should change the administration api path", async () => { + const adminResponse = await request("/foo/settings"); + expect(adminResponse).toEqual({ + behavior: "standard", + path: fixturesFolder("web-tutorial"), + delay: 0, + host: "0.0.0.0", + port: 3100, + watch: false, + log: "silly", + adminApiPath: "/foo", + adminApiDeprecatedPaths: true + }); + }); + }); +}); diff --git a/test/acceptance/settings-api.spec.js b/test/acceptance/settings-api.spec.js new file mode 100644 index 000000000..c22fba843 --- /dev/null +++ b/test/acceptance/settings-api.spec.js @@ -0,0 +1,291 @@ +/* +Copyright 2019 Javier Brea + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const { startServer, stopServer, request, fixturesFolder, TimeCounter, wait } = require("./utils"); + +describe("settings api", () => { + let server; + beforeAll(async () => { + server = await startServer("web-tutorial"); + }); + + afterAll(() => { + stopServer(server); + }); + + describe("get", () => { + it("should return current settings", async () => { + const settingsResponse = await request("/admin/settings"); + expect(settingsResponse).toEqual({ + behavior: "standard", + path: fixturesFolder("web-tutorial"), + delay: 0, + host: "0.0.0.0", + port: 3100, + watch: false, + log: "silly", + adminApiPath: "/admin", + adminApiDeprecatedPaths: true + }); + }); + }); + + describe("patch", () => { + describe("when changing an unexistant option", () => { + it("should response with a bad request containing errors", async () => { + expect.assertions(2); + const settingsResponse = await request("/admin/settings", { + method: "PATCH", + body: { + foo: "foo-value", + anotherFoo: 45, + third: { + foo: "foo" + } + }, + resolveWithFullResponse: true, + simple: false + }); + expect(settingsResponse.statusCode).toEqual(400); + expect(settingsResponse.body.message).toEqual( + 'Invalid option name "foo". Invalid option name "anotherFoo". Invalid option name "third"' + ); + }); + + it("should not apply any change if request contains any error", async () => { + expect.assertions(3); + const settingsUpdateResponse = await request("/admin/settings", { + method: "PATCH", + body: { + foo: "foo-value", + delay: 1000 + }, + resolveWithFullResponse: true, + simple: false + }); + const settingsResponse = await request("/admin/settings"); + expect(settingsUpdateResponse.statusCode).toEqual(400); + expect(settingsUpdateResponse.body.message).toEqual('Invalid option name "foo"'); + expect(settingsResponse.delay).toEqual(0); + }); + }); + + describe("when changing delay option", () => { + it("should respond with no delay", async () => { + const timeCounter = new TimeCounter(); + await request("/api/users"); + timeCounter.stop(); + expect(timeCounter.total).toBeLessThan(200); + }); + + it("should set delay option and have effect on server response time", async () => { + const timeCounter = new TimeCounter(); + await request("/admin/settings", { + method: "PATCH", + body: { + delay: 2000 + } + }); + await request("/api/users"); + timeCounter.stop(); + expect(timeCounter.total).toBeGreaterThan(2000); + }); + + it("should set delay option to 0", async () => { + const timeCounter = new TimeCounter(); + await request("/admin/settings", { + method: "PATCH", + body: { + delay: 0 + } + }); + await request("/api/users"); + timeCounter.stop(); + expect(timeCounter.total).toBeLessThan(200); + }); + }); + + describe("when changing behavior option", () => { + describe("without changing it", () => { + it("should serve user 1 under the /api/users/1 path", async () => { + const users = await request("/api/users/1"); + expect(users).toEqual({ id: 1, name: "John Doe" }); + }); + + it("should serve user 1 under the /api/users/2 path", async () => { + const users = await request("/api/users/2"); + expect(users).toEqual({ id: 1, name: "John Doe" }); + }); + }); + + describe('changing it to "user2"', () => { + beforeAll(async () => { + await request("/admin/settings", { + method: "PATCH", + body: { + behavior: "user2" + } + }); + }); + + it("should return new behavior when getting settings", async () => { + const settingsResponse = await request("/admin/settings"); + expect(settingsResponse.behavior).toEqual("user2"); + }); + + it("should serve user 2 under the /api/users/1 path", async () => { + const users = await request("/api/users/1"); + expect(users).toEqual({ id: 2, name: "Jane Doe" }); + }); + + it("should serve user 2 under the /api/users/2 path", async () => { + const users = await request("/api/users/2"); + expect(users).toEqual({ id: 2, name: "Jane Doe" }); + }); + }); + }); + + describe("when changing path option", () => { + beforeAll(async () => { + await request("/admin/settings", { + method: "PATCH", + body: { + path: fixturesFolder("files-modification") + } + }); + await wait(1000); + }); + + afterAll(async () => { + await request("/admin/settings", { + method: "PATCH", + body: { + path: fixturesFolder("web-tutorial") + } + }); + await wait(1000); + }); + + it("should return new path option when getting settings", async () => { + const settingsResponse = await request("/admin/settings"); + expect(settingsResponse.path).toEqual(fixturesFolder("files-modification")); + }); + + it("should serve users collection mock under the /api/users path", async () => { + const users = await request("/api/users"); + expect(users).toEqual([ + { id: 1, name: "John Doe modified" }, + { id: 2, name: "Jane Doe modified" } + ]); + }); + }); + + describe("when changing port option", () => { + beforeAll(async () => { + await request("/admin/settings", { + method: "PATCH", + body: { + port: 3101 + } + }); + await wait(1000); + }); + + afterAll(async () => { + await request("/admin/settings", { + port: 3101, + method: "PATCH", + body: { + port: 3100 + } + }); + await wait(1000); + }); + + it("should return new port option when getting settings, using new port", async () => { + const settingsResponse = await request("/admin/settings", { + port: 3101 + }); + expect(settingsResponse.port).toEqual(3101); + }); + + it("should serve user 2 under the /api/users/1 path using new port", async () => { + const users = await request("/api/users/1", { + port: 3101 + }); + expect(users).toEqual({ id: 2, name: "Jane Doe" }); + }); + }); + + describe("when changing adminApiPath option", () => { + beforeAll(async () => { + await request("/admin/settings", { + method: "PATCH", + body: { + adminApiPath: "/administration" + } + }); + await wait(1000); + }); + + afterAll(async () => { + await request("/administration/settings", { + method: "PATCH", + body: { + adminApiPath: "/admin" + } + }); + await wait(1000); + }); + + it("should return new port adminApiPath when getting settings, using new admin api path", async () => { + const settingsResponse = await request("/administration/settings"); + expect(settingsResponse.adminApiPath).toEqual("/administration"); + }); + + it("should return not found adminApiPath when getting settings in old admin api path", async () => { + const settingsResponse = await request("/admin/settings", { + resolveWithFullResponse: true, + simple: false + }); + expect(settingsResponse.statusCode).toEqual(404); + }); + }); + + describe("without changing adminApiDeprecatedPaths option", () => { + it("should return current delay option in deprecated api path", async () => { + const settingsResponse = await request("/mocks/settings"); + expect(settingsResponse).toEqual({ + delay: 0 + }); + }); + }); + + describe("when changing adminApiDeprecatedPaths option", () => { + beforeAll(async () => { + await request("/admin/settings", { + method: "PATCH", + body: { + adminApiDeprecatedPaths: false + } + }); + await wait(1000); + }); + + it("should return not found when getting settings in deprecated admin api path", async () => { + const settingsResponse = await request("/mocks/settings", { + resolveWithFullResponse: true, + simple: false + }); + expect(settingsResponse.statusCode).toEqual(404); + }); + }); + }); +}); diff --git a/test/acceptance/settings-logs-api.spec.js b/test/acceptance/settings-logs-api.spec.js new file mode 100644 index 000000000..9998d710d --- /dev/null +++ b/test/acceptance/settings-logs-api.spec.js @@ -0,0 +1,60 @@ +/* +Copyright 2019 Javier Brea + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ +const path = require("path"); +const fsExtra = require("fs-extra"); + +const { CliRunner, request, fixturesFolder, wait } = require("./utils"); + +describe("log option modified through api", () => { + let cli; + beforeAll(async () => { + cli = new CliRunner(["node", "start.js", "--path=web-tutorial", "--log=info"], { + cwd: path.resolve(__dirname, "fixtures") + }); + await wait(1000); + }); + + afterAll(async () => { + await cli.kill(); + }); + + describe("when started", () => { + it("should return log level when getting settings", async () => { + const settingsResponse = await request("/admin/settings"); + expect(settingsResponse.log).toEqual("info"); + }); + + it("should not have logged any log with verbose level", async () => { + expect(cli.logs).toEqual(expect.not.stringContaining("Mocks verbose")); + }); + }); + + describe("when log level is changed to verbose", () => { + beforeAll(async () => { + await request("/admin/settings", { + method: "PATCH", + body: { + log: "verbose" + } + }); + fsExtra.copySync(fixturesFolder("files-modification"), fixturesFolder("files-watch")); + await wait(1000); + }); + + it("should return new log level when getting settings", async () => { + const settingsResponse = await request("/admin/settings"); + expect(settingsResponse.log).toEqual("verbose"); + }); + + it("should have logged logs with verbose level", async () => { + expect(cli.logs).toEqual(expect.stringContaining("Mocks verbose")); + }); + }); +}); diff --git a/test/acceptance/settings-watch-api.spec.js b/test/acceptance/settings-watch-api.spec.js new file mode 100644 index 000000000..ff2a30da3 --- /dev/null +++ b/test/acceptance/settings-watch-api.spec.js @@ -0,0 +1,98 @@ +/* +Copyright 2019 Javier Brea + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ +const path = require("path"); +const fsExtra = require("fs-extra"); + +const { CliRunner, request, fixturesFolder, wait } = require("./utils"); + +describe("watch option modified through api", () => { + let cli; + beforeAll(async () => { + fsExtra.removeSync(fixturesFolder("files-watch")); + fsExtra.copySync(fixturesFolder("web-tutorial"), fixturesFolder("files-watch")); + cli = new CliRunner(["node", "start.js", "--path=files-watch", "--watch=false"], { + cwd: path.resolve(__dirname, "fixtures") + }); + await wait(1000); + }); + + afterAll(async () => { + await cli.kill(); + }); + + describe("when started", () => { + it("should return watch disabled when getting settings", async () => { + const settingsResponse = await request("/admin/settings"); + expect(settingsResponse.watch).toEqual(false); + }); + + it("should serve users collection mock under the /api/users path", async () => { + const users = await request("/api/users"); + expect(users).toEqual([ + { id: 1, name: "John Doe" }, + { id: 2, name: "Jane Doe" } + ]); + }); + }); + + describe("when watch is enabled and files are modified", () => { + beforeAll(async () => { + await request("/admin/settings", { + method: "PATCH", + body: { + watch: true + } + }); + await wait(); + fsExtra.copySync(fixturesFolder("files-modification"), fixturesFolder("files-watch")); + await wait(6000); + }, 10000); + + it("should return watch enabled when getting settings", async () => { + const settingsResponse = await request("/admin/settings"); + expect(settingsResponse.watch).toEqual(true); + }); + + it("should serve new users collection mock under the /api/users path", async () => { + const users = await request("/api/users"); + expect(users).toEqual([ + { id: 1, name: "John Doe modified" }, + { id: 2, name: "Jane Doe modified" } + ]); + }); + }); + + describe("when watch is disabled and files are modified", () => { + beforeAll(async () => { + await request("/admin/settings", { + method: "PATCH", + body: { + watch: false + } + }); + await wait(); + fsExtra.copySync(fixturesFolder("web-tutorial"), fixturesFolder("files-watch")); + await wait(6000); + }, 10000); + + it("should return watch disabled when getting settings", async () => { + const settingsResponse = await request("/admin/settings"); + expect(settingsResponse.watch).toEqual(false); + }); + + it("should serve new users collection mock under the /api/users path", async () => { + const users = await request("/api/users"); + expect(users).toEqual([ + { id: 1, name: "John Doe modified" }, + { id: 2, name: "Jane Doe modified" } + ]); + }); + }); +}); diff --git a/test/acceptance/utils.js b/test/acceptance/utils.js index 786e00bd3..cb17a0d85 100644 --- a/test/acceptance/utils.js +++ b/test/acceptance/utils.js @@ -61,13 +61,13 @@ const request = (uri, options = {}) => { }; return requestPromise({ - uri: `http://localhost:${SERVER_PORT}${uri}`, + uri: `http://localhost:${options.port || SERVER_PORT}${uri}`, json: true, ...requestOptions }); }; -const changeBehavior = behavior => { +const deprecatedChangeBehavior = behavior => { return request("/mocks/behaviors/current", { method: "PUT", body: { @@ -76,11 +76,11 @@ const changeBehavior = behavior => { }); }; -const getBehaviors = () => { +const deprecatedGetBehaviors = () => { return request("/mocks/behaviors"); }; -const changeDelay = delay => { +const deprecatedChangeDelay = delay => { return request("/mocks/settings", { method: "PUT", body: { @@ -120,9 +120,9 @@ module.exports = { startServer, stopServer, request, - changeBehavior, - getBehaviors, - changeDelay, + deprecatedChangeBehavior, + deprecatedGetBehaviors, + deprecatedChangeDelay, TimeCounter, wait, fixturesFolder, diff --git a/test/unit/Core.mocks.js b/test/unit/Core.mocks.js index 8e88ddb3e..04b03df36 100644 --- a/test/unit/Core.mocks.js +++ b/test/unit/Core.mocks.js @@ -24,8 +24,10 @@ class CoreMock { stop: this._sandbox.stub().resolves(), restart: this._sandbox.stub().resolves(), settings: { + getValidOptionName: this._sandbox.stub(), get: this._sandbox.stub(), - set: this._sandbox.stub() + set: this._sandbox.stub(), + all: {} }, tracer: { silly: this._sandbox.stub(), @@ -33,12 +35,16 @@ class CoreMock { verbose: this._sandbox.stub(), info: this._sandbox.stub(), warn: this._sandbox.stub(), + deprecationWarn: this._sandbox.stub(), error: this._sandbox.stub() }, onChangeSettings: this._sandbox.stub(), onLoadMocks: this._sandbox.stub(), addCustomRouter: this._sandbox.stub(), addCustomSetting: this._sandbox.stub(), + addSetting: this._sandbox.stub(), + addRouter: this._sandbox.stub(), + removeRouter: this._sandbox.stub(), behaviors: { currentFromCollection: "foo-current", collection: "foo-behaviors-collection" diff --git a/test/unit/Libs.mocks.js b/test/unit/Libs.mocks.js index 71852215e..dc440f1e3 100644 --- a/test/unit/Libs.mocks.js +++ b/test/unit/Libs.mocks.js @@ -11,53 +11,21 @@ Unless required by applicable law or agreed to in writing, software distributed const sinon = require("sinon"); -const http = require("http"); - -jest.mock("express"); - const express = require("express"); -class CallBackRunner { - constructor() { - this.runner = this.runner.bind(this); - } - - runner(eventName, cb) { - if (this._returns !== undefined) { - cb(this._returns); - } - } - - returns(code) { - this._returns = code; - } -} - class Mock { constructor() { this._sandbox = sinon.createSandbox(); - const httpCreateServerOnError = new CallBackRunner(); - const httpCreateServerOnListen = new CallBackRunner(); - this._stubs = { express: { use: this._sandbox.stub(), - options: this._sandbox.stub() - }, - http: { - createServer: { - on: this._sandbox.stub().callsFake(httpCreateServerOnError.runner), - onError: httpCreateServerOnError, - listen: this._sandbox.stub().callsFake(httpCreateServerOnListen.runner), - onListen: httpCreateServerOnListen, - close: this._sandbox.stub().callsFake(cb => cb()) - } + patch: this._sandbox.stub(), + get: this._sandbox.stub() } }; - express.mockImplementation(() => this._stubs.express); - this._sandbox.stub(http, "createServer").returns(this._stubs.http.createServer); + this._sandbox.stub(express, "Router").returns(this._stubs.express); } get stubs() { diff --git a/test/unit/index.spec.js b/test/unit/index.spec.js index dc8467965..094b1e343 100644 --- a/test/unit/index.spec.js +++ b/test/unit/index.spec.js @@ -10,10 +10,10 @@ Unless required by applicable law or agreed to in writing, software distributed */ const index = require("../../index"); -const Api = require("../../src/Api"); +const Plugin = require("../../src/Plugin"); describe("index", () => { - it("should export the Api constructor", () => { - expect(index).toEqual(Api); + it("should export the Plugin constructor", () => { + expect(index).toEqual(Plugin); }); }); diff --git a/test/unit/src/About.mocks.js b/test/unit/src/About.mocks.js new file mode 100644 index 000000000..0a8ce2fb9 --- /dev/null +++ b/test/unit/src/About.mocks.js @@ -0,0 +1,41 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const sinon = require("sinon"); + +jest.mock("../../../src/About"); + +const About = require("../../../src/About"); + +const Mock = class Mock { + constructor() { + this._sandbox = sinon.createSandbox(); + + this._stubs = { + init: this._sandbox.stub() + }; + + About.mockImplementation(() => this._stubs); + } + + get stubs() { + return { + Constructor: About, + instance: this._stubs + }; + } + + restore() { + this._sandbox.restore(); + } +}; + +module.exports = Mock; diff --git a/test/unit/src/About.spec.js b/test/unit/src/About.spec.js new file mode 100644 index 000000000..f237c47c2 --- /dev/null +++ b/test/unit/src/About.spec.js @@ -0,0 +1,72 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const express = require("express"); +const sinon = require("sinon"); + +const LibMocks = require("../Libs.mocks"); +const CoreMocks = require("../Core.mocks"); + +const About = require("../../../src/About"); +const { version } = require("../../../package.json"); + +describe("About", () => { + let sandbox; + let libMocks; + let coreMock; + let coreInstance; + let resMock; + let about; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + resMock = { + status: sandbox.stub(), + send: sandbox.stub() + }; + libMocks = new LibMocks(); + coreMock = new CoreMocks(); + coreInstance = coreMock.stubs.instance; + about = new About(coreInstance); + expect.assertions(1); + }); + + afterEach(() => { + sandbox.restore(); + libMocks.restore(); + coreMock.restore(); + }); + + describe("when created", () => { + it("should create an express Router", async () => { + expect(express.Router.calledOnce).toEqual(true); + }); + + it("should have added get router at /", async () => { + expect(libMocks.stubs.express.get.getCall(0).args[0]).toEqual("/"); + }); + }); + + describe("getAbout router", () => { + it("should return current package version", () => { + about.getAbout({}, resMock); + expect(resMock.send.getCall(0).args[0]).toEqual({ + version + }); + }); + }); + + describe("router getter", () => { + it("should return express created router", async () => { + expect(about.router).toEqual(libMocks.stubs.express); + }); + }); +}); diff --git a/test/unit/src/Behaviors.mocks.js b/test/unit/src/Behaviors.mocks.js index 9cfeda200..7e31ea4d6 100644 --- a/test/unit/src/Behaviors.mocks.js +++ b/test/unit/src/Behaviors.mocks.js @@ -20,10 +20,7 @@ const Mock = class Mock { this._sandbox = sinon.createSandbox(); this._stubs = { - getCurrent: this._sandbox.stub(), - putCurrent: this._sandbox.stub(), - getCollection: this._sandbox.stub(), - router: "foo-behaviors-router" + init: this._sandbox.stub() }; Behaviors.mockImplementation(() => this._stubs); diff --git a/test/unit/src/Behaviors.spec.js b/test/unit/src/Behaviors.spec.js index 5a71ec9eb..18afdffb2 100644 --- a/test/unit/src/Behaviors.spec.js +++ b/test/unit/src/Behaviors.spec.js @@ -11,106 +11,153 @@ Unless required by applicable law or agreed to in writing, software distributed const express = require("express"); const sinon = require("sinon"); +const Boom = require("@hapi/boom"); -const CoreMocks = require("../Core.mocks.js"); +const LibMocks = require("../Libs.mocks"); +const CoreMocks = require("../Core.mocks"); const Behaviors = require("../../../src/Behaviors"); -describe("Behaviors Api", () => { +describe("Behavior", () => { let sandbox; - let routerStubs; - let resMock; - let statusSpy; - let sendSpy; + let libMocks; let coreMock; - let coreMocks; - let tracerMock; + let coreInstance; + let resMock; let behaviors; beforeEach(() => { sandbox = sinon.createSandbox(); - routerStubs = { - get: sandbox.stub(), - put: sandbox.stub() - }; - coreMock = new CoreMocks(); - coreMocks = coreMock.stubs.instance; - tracerMock = coreMocks.tracer; - sandbox.stub(express, "Router").returns(routerStubs); - statusSpy = sandbox.spy(); - sendSpy = sandbox.spy(); resMock = { - status: statusSpy, - send: sendSpy + status: sandbox.stub(), + send: sandbox.stub() }; - behaviors = new Behaviors(coreMocks, tracerMock); + libMocks = new LibMocks(); + coreMock = new CoreMocks(); + coreInstance = coreMock.stubs.instance; + behaviors = new Behaviors(coreInstance); expect.assertions(1); }); afterEach(() => { sandbox.restore(); + libMocks.restore(); coreMock.restore(); }); - describe("when instanciated", () => { - it("should create an express Router", () => { + describe("when created", () => { + it("should create an express Router", async () => { expect(express.Router.calledOnce).toEqual(true); }); - }); - describe("getCurrent route", () => { - it("should set response status as 200", () => { - behaviors.getCurrent({}, resMock); - expect(statusSpy.getCall(0).args[0]).toEqual(200); + it("should have added get router at /", async () => { + expect(libMocks.stubs.express.get.getCall(0).args[0]).toEqual("/"); }); - it("should send current feature from collection", () => { - behaviors.getCurrent({}, resMock); - expect(sendSpy.getCall(0).args[0]).toEqual(coreMocks.behaviors.currentFromCollection); + it("should register a get router for behavior names", async () => { + expect(libMocks.stubs.express.get.getCall(1).args[0]).toEqual("/:name"); }); }); - describe("putCurrent route", () => { - it("should set current feature", () => { - behaviors.putCurrent( - { - body: { - name: "foo-name" + describe("getCollection router", () => { + it("should return the behaviors collection parsed", () => { + coreInstance.behaviors = { + collection: [ + { + name: "foo", + fixtures: [{ id: "foo-fixture-id-1" }, { id: "foo-fixture-id-2" }], + extendedFrom: "foo-base-behavior" + }, + { + name: "foo2", + fixtures: [{ id: "foo-fixture-id-3" }], + extendedFrom: null } + ] + }; + behaviors = new Behaviors(coreInstance); + behaviors.getCollection({}, resMock); + expect(resMock.send.getCall(0).args[0]).toEqual([ + { + name: "foo", + fixtures: ["foo-fixture-id-1", "foo-fixture-id-2"], + extendedFrom: "foo-base-behavior" }, - resMock - ); - expect(coreMocks.settings.set.getCall(0).args).toEqual(["behavior", "foo-name"]); + { + name: "foo2", + fixtures: ["foo-fixture-id-3"], + extendedFrom: null + } + ]); }); + }); - it("should send current feature from collection", () => { - behaviors.putCurrent( + describe("getModel router", () => { + it("should return the requested behavior model parsed", () => { + coreInstance.behaviors = { + collection: [ + { + name: "foo", + fixtures: [{ id: "foo-fixture-id-1" }, { id: "foo-fixture-id-2" }], + extendedFrom: "foo-base-behavior" + }, + { + name: "foo2", + fixtures: [{ id: "foo-fixture-id-3" }], + extendedFrom: null + } + ] + }; + behaviors = new Behaviors(coreInstance); + behaviors.getModel( { - body: { - name: "foo-name" + params: { + name: "foo" } }, resMock ); - expect(sendSpy.getCall(0).args[0]).toEqual(coreMocks.behaviors.currentFromCollection); - }); - }); - - describe("getCollection route", () => { - it("should set response status as 200", () => { - behaviors.getCollection({}, resMock); - expect(statusSpy.getCall(0).args[0]).toEqual(200); + expect(resMock.send.getCall(0).args[0]).toEqual({ + name: "foo", + fixtures: ["foo-fixture-id-1", "foo-fixture-id-2"], + extendedFrom: "foo-base-behavior" + }); }); - it("should send current behaviors collection", () => { - behaviors.getCollection({}, resMock); - expect(sendSpy.getCall(0).args[0]).toEqual(coreMocks.behaviors.collection); + it("should return a not found error if behavior is not found", () => { + const nextStub = sandbox.stub(); + sandbox.stub(Boom, "notFound").returns("foo-error"); + coreInstance.behaviors = { + collection: [ + { + name: "foo", + fixtures: [{ id: "foo-fixture-id-1" }, { id: "foo-fixture-id-2" }], + extendedFrom: "foo-base-behavior" + }, + { + name: "foo2", + fixtures: [{ id: "foo-fixture-id-3" }], + extendedFrom: null + } + ] + }; + behaviors = new Behaviors(coreInstance); + behaviors.getModel( + { + params: { + name: "foo3" + } + }, + resMock, + nextStub + ); + expect(nextStub.getCall(0).args[0]).toEqual("foo-error"); }); }); describe("router getter", () => { - it("should return the express router", () => { - expect(behaviors.router).toEqual(routerStubs); + it("should return express created router", async () => { + expect(behaviors.router).toEqual(libMocks.stubs.express); }); }); }); diff --git a/test/unit/src/Fixtures.mocks.js b/test/unit/src/Fixtures.mocks.js new file mode 100644 index 000000000..4d16e9f88 --- /dev/null +++ b/test/unit/src/Fixtures.mocks.js @@ -0,0 +1,41 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const sinon = require("sinon"); + +jest.mock("../../../src/Fixtures"); + +const Fixtures = require("../../../src/Fixtures"); + +const Mock = class Mock { + constructor() { + this._sandbox = sinon.createSandbox(); + + this._stubs = { + init: this._sandbox.stub() + }; + + Fixtures.mockImplementation(() => this._stubs); + } + + get stubs() { + return { + Constructor: Fixtures, + instance: this._stubs + }; + } + + restore() { + this._sandbox.restore(); + } +}; + +module.exports = Mock; diff --git a/test/unit/src/Fixtures.spec.js b/test/unit/src/Fixtures.spec.js new file mode 100644 index 000000000..a6d3117f1 --- /dev/null +++ b/test/unit/src/Fixtures.spec.js @@ -0,0 +1,169 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const express = require("express"); +const sinon = require("sinon"); +const Boom = require("@hapi/boom"); + +const LibMocks = require("../Libs.mocks"); +const CoreMocks = require("../Core.mocks"); + +const Fixtures = require("../../../src/Fixtures"); + +describe("Fixtures", () => { + let sandbox; + let libMocks; + let coreMock; + let coreInstance; + let resMock; + let fixtures; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + resMock = { + status: sandbox.stub(), + send: sandbox.stub() + }; + libMocks = new LibMocks(); + coreMock = new CoreMocks(); + coreInstance = coreMock.stubs.instance; + fixtures = new Fixtures(coreInstance); + expect.assertions(1); + }); + + afterEach(() => { + sandbox.restore(); + libMocks.restore(); + coreMock.restore(); + }); + + describe("when created", () => { + it("should create an express Router", async () => { + expect(express.Router.calledOnce).toEqual(true); + }); + + it("should have added get router at /", async () => { + expect(libMocks.stubs.express.get.getCall(0).args[0]).toEqual("/"); + }); + + it("should register a get router for fixtures ids", async () => { + expect(libMocks.stubs.express.get.getCall(1).args[0]).toEqual("/:id"); + }); + }); + + describe("getCollection router", () => { + it("should return the fixtures collection parsed", () => { + coreInstance.fixtures = { + collection: [ + { + id: "foo-id", + requestMatchId: "request-match-id", + request: "foo-request", + response: "foo-response" + }, + { + id: "foo-id-2", + requestMatchId: "request-match-id-2", + request: "foo-request-2", + response: "foo-response-2" + } + ] + }; + fixtures = new Fixtures(coreInstance); + fixtures.getCollection({}, resMock); + expect(resMock.send.getCall(0).args[0]).toEqual([ + { + id: "foo-id", + requestMatchId: "request-match-id", + handler: undefined, + request: "foo-request", + response: "foo-response" + }, + { + id: "foo-id-2", + requestMatchId: "request-match-id-2", + handler: undefined, + request: "foo-request-2", + response: "foo-response-2" + } + ]); + }); + }); + + describe("getModel router", () => { + it("should return the requested fixture model parsed", () => { + coreInstance.fixtures = { + collection: [ + { + id: "foo-id", + requestMatchId: "request-match-id", + request: "foo-request", + response: "foo-response" + }, + { + id: "foo-id-2", + requestMatchId: "request-match-id-2", + request: "foo-request-2", + response: "foo-response-2" + } + ] + }; + fixtures = new Fixtures(coreInstance); + fixtures.getModel( + { + params: { + id: "foo-id" + } + }, + resMock + ); + expect(resMock.send.getCall(0).args[0]).toEqual({ + id: "foo-id", + requestMatchId: "request-match-id", + handler: undefined, + request: "foo-request", + response: "foo-response" + }); + }); + + it("should return a not found error if fixture is not found", () => { + const nextStub = sandbox.stub(); + sandbox.stub(Boom, "notFound").returns("foo-error"); + coreInstance.fixtures = { + collection: [ + { + id: "foo-id", + requestMatchId: "request-match-id", + request: "foo-request", + response: "foo-response" + } + ] + }; + fixtures = new Fixtures(coreInstance); + fixtures.getModel( + { + params: { + id: "foo3" + } + }, + resMock, + nextStub + ); + expect(nextStub.getCall(0).args[0]).toEqual("foo-error"); + }); + }); + + describe("router getter", () => { + it("should return express created router", async () => { + expect(fixtures.router).toEqual(libMocks.stubs.express); + }); + }); +}); diff --git a/test/unit/src/Plugin.spec.js b/test/unit/src/Plugin.spec.js new file mode 100644 index 000000000..3d058434c --- /dev/null +++ b/test/unit/src/Plugin.spec.js @@ -0,0 +1,147 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const express = require("express"); +const sinon = require("sinon"); + +const LibMocks = require("../Libs.mocks"); +const CoreMocks = require("../Core.mocks"); +const DeprecatedApiMocks = require("./deprecated/Api.mocks"); +const SettingsMocks = require("./Settings.mocks"); +const BehaviorsMocks = require("./Behaviors.mocks"); +const FixturesMock = require("./Fixtures.mocks"); +const AboutMock = require("./About.mocks"); + +const Plugin = require("../../../src/Plugin"); + +describe("Plugin", () => { + let sandbox; + let libMocks; + let settingsMocks; + let behaviorsMocks; + let fixturesMock; + let deprecatedApiMock; + let deprecatedApiInstance; + let coreMock; + let coreInstance; + let aboutMock; + let plugin; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + libMocks = new LibMocks(); + settingsMocks = new SettingsMocks(); + behaviorsMocks = new BehaviorsMocks(); + aboutMock = new AboutMock(); + fixturesMock = new FixturesMock(); + deprecatedApiMock = new DeprecatedApiMocks(); + deprecatedApiInstance = deprecatedApiMock.stubs.instance; + coreMock = new CoreMocks(); + coreInstance = coreMock.stubs.instance; + plugin = new Plugin(coreInstance); + expect.assertions(1); + }); + + afterEach(() => { + sandbox.restore(); + libMocks.restore(); + settingsMocks.restore(); + behaviorsMocks.restore(); + fixturesMock.restore(); + coreMock.restore(); + aboutMock.restore(); + deprecatedApiMock.restore(); + }); + + describe("when initializated", () => { + it("should create an express Router", async () => { + await plugin.init(); + expect(express.Router.calledOnce).toEqual(true); + }); + + it("should have added adminApiPath setting", async () => { + await plugin.init(); + expect(coreInstance.addSetting.getCall(0).args[0].name).toEqual("adminApiPath"); + }); + + it("should have added adminApiDeprecatedPaths setting", async () => { + await plugin.init(); + expect(coreInstance.addSetting.getCall(1).args[0].name).toEqual("adminApiDeprecatedPaths"); + }); + + it("should init deprecated api", async () => { + await plugin.init(); + expect(deprecatedApiInstance.init.callCount).toEqual(1); + }); + + it("should add deprecated router if adminApiDeprecatedPaths setting returns true", async () => { + coreInstance.settings.get.withArgs("adminApiDeprecatedPaths").returns(true); + await plugin.init(); + expect(coreInstance.addRouter.callCount).toEqual(2); + }); + + it("should not add deprecated router if adminApiDeprecatedPaths setting returns false", async () => { + coreInstance.settings.get.withArgs("adminApiDeprecatedPaths").returns(false); + await plugin.init(); + expect(coreInstance.addRouter.callCount).toEqual(1); + }); + + it("should register Express router into the core under the path returned by settings", async () => { + coreInstance.settings.get.withArgs("adminApiPath").returns("/foo"); + await plugin.init(); + expect(coreInstance.addRouter.getCall(0).args).toEqual(["/foo", plugin._router]); + }); + }); + + describe("when settings change", () => { + it("should not register deprecated router again if it is enabled and was already registered", async () => { + coreInstance.settings.get.withArgs("adminApiDeprecatedPaths").returns(true); + await plugin.init(); + coreInstance.onChangeSettings.getCall(0).args[0]({ + adminApiDeprecatedPaths: true + }); + expect(coreInstance.addRouter.callCount).toEqual(2); + }); + + it("should remove deprecated router if it is disabled and was already enabled", async () => { + coreInstance.settings.get.withArgs("adminApiDeprecatedPaths").returns(true); + await plugin.init(); + coreInstance.settings.get.withArgs("adminApiDeprecatedPaths").returns(false); + coreInstance.onChangeSettings.getCall(0).args[0]({ + adminApiDeprecatedPaths: false + }); + expect(coreInstance.removeRouter.callCount).toEqual(1); + }); + + it("should not remove deprecated router if it was not added", async () => { + coreInstance.settings.get.withArgs("adminApiDeprecatedPaths").returns(false); + await plugin.init(); + coreInstance.settings.get.withArgs("adminApiDeprecatedPaths").returns(false); + coreInstance.onChangeSettings.getCall(0).args[0]({ + adminApiDeprecatedPaths: false + }); + expect(coreInstance.removeRouter.callCount).toEqual(0); + }); + + it("should remove and add router again if adminApiPath option changes", async () => { + expect.assertions(3); + coreInstance.settings.get.withArgs("adminApiPath").returns("/foo"); + await plugin.init(); + coreInstance.settings.get.withArgs("adminApiPath").returns("/foo2"); + coreInstance.onChangeSettings.getCall(0).args[0]({ + adminApiPath: "/foo2" + }); + expect(coreInstance.removeRouter.callCount).toEqual(1); + expect(coreInstance.addRouter.callCount).toEqual(2); + expect(coreInstance.addRouter.getCall(1).args).toEqual(["/foo2", plugin._router]); + }); + }); +}); diff --git a/test/unit/src/Settings.mocks.js b/test/unit/src/Settings.mocks.js index 6dc81bc23..a905e0291 100644 --- a/test/unit/src/Settings.mocks.js +++ b/test/unit/src/Settings.mocks.js @@ -20,9 +20,7 @@ const Mock = class Mock { this._sandbox = sinon.createSandbox(); this._stubs = { - get: this._sandbox.stub(), - put: this._sandbox.stub(), - router: "foo-settings-router" + init: this._sandbox.stub() }; Settings.mockImplementation(() => this._stubs); diff --git a/test/unit/src/Settings.spec.js b/test/unit/src/Settings.spec.js index 8c47ba927..958bc1de9 100644 --- a/test/unit/src/Settings.spec.js +++ b/test/unit/src/Settings.spec.js @@ -11,104 +11,126 @@ Unless required by applicable law or agreed to in writing, software distributed const express = require("express"); const sinon = require("sinon"); +const Boom = require("@hapi/boom"); -const CoreMocks = require("../Core.mocks.js"); +const LibMocks = require("../Libs.mocks"); +const CoreMocks = require("../Core.mocks"); const Settings = require("../../../src/Settings"); -describe("Settings Api", () => { +describe("Fixtures", () => { let sandbox; - let routerStubs; + let libMocks; + let coreMock; + let coreInstance; let resMock; - let statusSpy; - let sendSpy; - let coreMocks; - let settingsMock; - let tracerMock; + let nextStub; + let settings; beforeEach(() => { sandbox = sinon.createSandbox(); - routerStubs = { - get: sandbox.stub(), - put: sandbox.stub() - }; - coreMocks = new CoreMocks(); - settingsMock = coreMocks.stubs.instance.settings; - tracerMock = coreMocks.stubs.instance.tracer; - sandbox.stub(express, "Router").returns(routerStubs); - statusSpy = sandbox.spy(); - sendSpy = sandbox.spy(); + nextStub = sandbox.stub(); resMock = { - status: statusSpy, - send: sendSpy + status: sandbox.stub(), + send: sandbox.stub() }; + libMocks = new LibMocks(); + coreMock = new CoreMocks(); + coreInstance = coreMock.stubs.instance; + settings = new Settings(coreInstance); expect.assertions(1); }); afterEach(() => { sandbox.restore(); - coreMocks.restore(); + libMocks.restore(); + coreMock.restore(); }); - describe("when instanciated", () => { - it("should create an express Router", () => { - new Settings(settingsMock, tracerMock); + describe("when created", () => { + it("should create an express Router", async () => { expect(express.Router.calledOnce).toEqual(true); }); - }); - describe("get route", () => { - it("should set response status as 200", () => { - const settings = new Settings(settingsMock, tracerMock); - settings.get({}, resMock); - expect(statusSpy.getCall(0).args[0]).toEqual(200); + it("should have added a patch router at /", async () => { + expect(libMocks.stubs.express.get.getCall(0).args[0]).toEqual("/"); }); - it("should send current settings", () => { - settingsMock.get.withArgs("delay").returns(3000); - const settings = new Settings(settingsMock, tracerMock); - settings.get({}, resMock); - expect(sendSpy.getCall(0).args[0]).toEqual({ - delay: 3000 - }); + it("should have added a get router at /", async () => { + expect(libMocks.stubs.express.get.getCall(0).args[0]).toEqual("/"); }); }); - describe("put route", () => { - it("should set current delay", () => { - const settings = new Settings(settingsMock, tracerMock); - settings.put( + describe("patch router", () => { + it("should set new settings", () => { + expect.assertions(4); + coreInstance.settings.getValidOptionName.withArgs("log").returns("log"); + coreInstance.settings.getValidOptionName.withArgs("delay").returns("delay"); + settings = new Settings(coreInstance); + settings.patch( + { + body: { + log: "foo", + delay: 4000 + } + }, + resMock, + nextStub + ); + expect(coreInstance.settings.set.getCall(0).args[0]).toEqual("log"); + expect(coreInstance.settings.set.getCall(0).args[1]).toEqual("foo"); + expect(coreInstance.settings.set.getCall(1).args[0]).toEqual("delay"); + expect(coreInstance.settings.set.getCall(1).args[1]).toEqual(4000); + }); + + it("should send a badRequest error if there is any invalid option", () => { + sandbox.stub(Boom, "badRequest").returns("foo-error"); + coreInstance.settings.getValidOptionName.withArgs("log").returns(null); + coreInstance.settings.getValidOptionName.withArgs("delay").returns("delay"); + settings = new Settings(coreInstance); + settings.patch( { body: { - delay: 5000 + log: "foo", + delay: 4000 } }, - resMock + resMock, + nextStub ); - expect(settingsMock.set.getCall(0).args).toEqual(["delay", 5000]); + expect(nextStub.getCall(0).args[0]).toEqual("foo-error"); }); - it("should send current settings", () => { - const settings = new Settings(settingsMock, tracerMock); - settingsMock.get.withArgs("delay").returns(2000); - settings.put( + it("should not set any option if one is invalid", () => { + sandbox.stub(Boom, "badRequest").returns("foo-error"); + coreInstance.settings.getValidOptionName.withArgs("log").returns("log"); + coreInstance.settings.getValidOptionName.withArgs("delay").returns(null); + settings = new Settings(coreInstance); + settings.patch( { body: { - delay: 2000 + log: "foo", + delay: 4000 } }, - resMock + resMock, + nextStub ); - expect(sendSpy.getCall(0).args[0]).toEqual({ - delay: 2000 - }); + expect(coreInstance.settings.set.callCount).toEqual(0); + }); + }); + + describe("get router", () => { + it("should return all settings", () => { + settings = new Settings(coreInstance); + settings.get({}, resMock, nextStub); + expect(resMock.send.getCall(0).args[0]).toEqual(coreInstance.settings.all); }); }); describe("router getter", () => { - it("should return the express router", () => { - const settings = new Settings(settingsMock, tracerMock); - expect(settings.router).toEqual(routerStubs); + it("should return express created router", async () => { + expect(settings.router).toEqual(libMocks.stubs.express); }); }); }); diff --git a/test/unit/src/deprecated/Api.mocks.js b/test/unit/src/deprecated/Api.mocks.js new file mode 100644 index 000000000..062df82d4 --- /dev/null +++ b/test/unit/src/deprecated/Api.mocks.js @@ -0,0 +1,41 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const sinon = require("sinon"); + +jest.mock("../../../../src/deprecated/Api"); + +const Api = require("../../../../src/deprecated/Api"); + +const Mock = class Mock { + constructor() { + this._sandbox = sinon.createSandbox(); + + this._stubs = { + init: this._sandbox.stub() + }; + + Api.mockImplementation(() => this._stubs); + } + + get stubs() { + return { + Constructor: Api, + instance: this._stubs + }; + } + + restore() { + this._sandbox.restore(); + } +}; + +module.exports = Mock; diff --git a/test/unit/src/Api.spec.js b/test/unit/src/deprecated/Api.spec.js similarity index 70% rename from test/unit/src/Api.spec.js rename to test/unit/src/deprecated/Api.spec.js index 6651f7fca..de24c5cec 100644 --- a/test/unit/src/Api.spec.js +++ b/test/unit/src/deprecated/Api.spec.js @@ -12,29 +12,27 @@ Unless required by applicable law or agreed to in writing, software distributed const express = require("express"); const sinon = require("sinon"); -const CoreMocks = require("../Core.mocks.js"); +const LibsMocks = require("../../Libs.mocks"); +const CoreMocks = require("../../Core.mocks.js"); const BehaviorsMocks = require("./Behaviors.mocks.js"); const SettingsMocks = require("./Settings.mocks.js"); -const Api = require("../../../src/Api"); +const Api = require("../../../../src/deprecated/Api"); describe("Api", () => { let sandbox; + let libMocks; let behaviorsMocks; let settingsMocks; let coreMock; let coreMocks; - let routerUseStub; let api; beforeEach(() => { sandbox = sinon.createSandbox(); - routerUseStub = sandbox.stub(); + libMocks = new LibsMocks(); coreMock = new CoreMocks(); coreMocks = coreMock.stubs.instance; - sandbox.stub(express, "Router").returns({ - use: routerUseStub - }); behaviorsMocks = new BehaviorsMocks(); settingsMocks = new SettingsMocks(); api = new Api(coreMocks); @@ -43,6 +41,7 @@ describe("Api", () => { afterEach(() => { sandbox.restore(); + libMocks.restore(); behaviorsMocks.restore(); settingsMocks.restore(); coreMock.restore(); @@ -54,50 +53,51 @@ describe("Api", () => { expect(express.Router.calledOnce).toEqual(true); }); - it("should register Express router into the core under the /mocks path", async () => { - await api.init(); - expect(coreMocks.addCustomRouter.getCall(0).args).toEqual(["/mocks", api._router]); - }); - it("should create Behaviors passing the core", async () => { await api.init(); expect(behaviorsMocks.stubs.Constructor).toHaveBeenCalledWith(coreMocks); }); - it('should trace a warning each time any path under "/features" is requested', async () => { - expect.assertions(3); + it('should trace a warning each time any path under "/mocks" is requested', async () => { + expect.assertions(2); await api.init(); const nextStub = sandbox.stub(); - routerUseStub.getCall(0).args[1](null, null, nextStub); - expect(routerUseStub.getCall(0).args[0]).toEqual("/features"); + libMocks.stubs.express.use.getCall(0).args[0](null, null, nextStub); expect(nextStub.callCount).toEqual(1); - expect(coreMocks.tracer.warn.getCall(0).args[0]).toEqual( - expect.stringContaining("Deprecation warning: ") + expect(coreMocks.tracer.deprecationWarn.getCall(0).args[0]).toEqual( + expect.stringContaining('"/mocks" plugin-admin-api path') ); }); it('should add an express path under "/features"', async () => { await api.init(); - expect(routerUseStub.getCall(1).args[0]).toEqual("/features"); + expect(libMocks.stubs.express.use.getCall(1).args[0]).toEqual("/features"); }); it('should use the created behaviors under the "/features" router path', async () => { await api.init(); const fooBehaviorsRouter = "foo-behaviors-router"; behaviorsMocks.stubs.instance.router = fooBehaviorsRouter; - expect(routerUseStub.getCall(1).args[1]).toEqual(fooBehaviorsRouter); + expect(libMocks.stubs.express.use.getCall(1).args[1]).toEqual(fooBehaviorsRouter); }); it('should add an express path under "/behaviors"', async () => { await api.init(); - expect(routerUseStub.getCall(2).args[0]).toEqual("/behaviors"); + expect(libMocks.stubs.express.use.getCall(2).args[0]).toEqual("/behaviors"); }); it('should use the created behaviors under the "/behaviors" router path', async () => { await api.init(); const fooBehaviorsRouter = "foo-behaviors-router"; behaviorsMocks.stubs.instance.router = fooBehaviorsRouter; - expect(routerUseStub.getCall(2).args[1]).toEqual(fooBehaviorsRouter); + expect(libMocks.stubs.express.use.getCall(2).args[1]).toEqual(fooBehaviorsRouter); + }); + }); + + describe("router getter", () => { + it("should return express created router", async () => { + await api.init(); + expect(api.router).toEqual(libMocks.stubs.express); }); }); }); diff --git a/test/unit/src/deprecated/Behaviors.mocks.js b/test/unit/src/deprecated/Behaviors.mocks.js new file mode 100644 index 000000000..dfface5af --- /dev/null +++ b/test/unit/src/deprecated/Behaviors.mocks.js @@ -0,0 +1,44 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const sinon = require("sinon"); + +jest.mock("../../../../src/deprecated/Behaviors"); + +const Behaviors = require("../../../../src/deprecated/Behaviors"); + +const Mock = class Mock { + constructor() { + this._sandbox = sinon.createSandbox(); + + this._stubs = { + getCurrent: this._sandbox.stub(), + putCurrent: this._sandbox.stub(), + getCollection: this._sandbox.stub(), + router: "foo-behaviors-router" + }; + + Behaviors.mockImplementation(() => this._stubs); + } + + get stubs() { + return { + Constructor: Behaviors, + instance: this._stubs + }; + } + + restore() { + this._sandbox.restore(); + } +}; + +module.exports = Mock; diff --git a/test/unit/src/deprecated/Behaviors.spec.js b/test/unit/src/deprecated/Behaviors.spec.js new file mode 100644 index 000000000..b15513a8a --- /dev/null +++ b/test/unit/src/deprecated/Behaviors.spec.js @@ -0,0 +1,116 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const express = require("express"); +const sinon = require("sinon"); + +const CoreMocks = require("../../Core.mocks.js"); + +const Behaviors = require("../../../../src/deprecated/Behaviors"); + +describe("Behaviors Api", () => { + let sandbox; + let routerStubs; + let resMock; + let statusSpy; + let sendSpy; + let coreMock; + let coreMocks; + let tracerMock; + let behaviors; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + routerStubs = { + get: sandbox.stub(), + put: sandbox.stub() + }; + coreMock = new CoreMocks(); + coreMocks = coreMock.stubs.instance; + tracerMock = coreMocks.tracer; + sandbox.stub(express, "Router").returns(routerStubs); + statusSpy = sandbox.spy(); + sendSpy = sandbox.spy(); + resMock = { + status: statusSpy, + send: sendSpy + }; + behaviors = new Behaviors(coreMocks, tracerMock); + expect.assertions(1); + }); + + afterEach(() => { + sandbox.restore(); + coreMock.restore(); + }); + + describe("when instanciated", () => { + it("should create an express Router", () => { + expect(express.Router.calledOnce).toEqual(true); + }); + }); + + describe("getCurrent route", () => { + it("should set response status as 200", () => { + behaviors.getCurrent({}, resMock); + expect(statusSpy.getCall(0).args[0]).toEqual(200); + }); + + it("should send current feature from collection", () => { + behaviors.getCurrent({}, resMock); + expect(sendSpy.getCall(0).args[0]).toEqual(coreMocks.behaviors.currentFromCollection); + }); + }); + + describe("putCurrent route", () => { + it("should set current feature", () => { + behaviors.putCurrent( + { + body: { + name: "foo-name" + } + }, + resMock + ); + expect(coreMocks.settings.set.getCall(0).args).toEqual(["behavior", "foo-name"]); + }); + + it("should send current feature from collection", () => { + behaviors.putCurrent( + { + body: { + name: "foo-name" + } + }, + resMock + ); + expect(sendSpy.getCall(0).args[0]).toEqual(coreMocks.behaviors.currentFromCollection); + }); + }); + + describe("getCollection route", () => { + it("should set response status as 200", () => { + behaviors.getCollection({}, resMock); + expect(statusSpy.getCall(0).args[0]).toEqual(200); + }); + + it("should send current behaviors collection", () => { + behaviors.getCollection({}, resMock); + expect(sendSpy.getCall(0).args[0]).toEqual(coreMocks.behaviors.collection); + }); + }); + + describe("router getter", () => { + it("should return the express router", () => { + expect(behaviors.router).toEqual(routerStubs); + }); + }); +}); diff --git a/test/unit/src/deprecated/Settings.mocks.js b/test/unit/src/deprecated/Settings.mocks.js new file mode 100644 index 000000000..2fa835cba --- /dev/null +++ b/test/unit/src/deprecated/Settings.mocks.js @@ -0,0 +1,43 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const sinon = require("sinon"); + +jest.mock("../../../../src/deprecated/Settings"); + +const Settings = require("../../../../src/deprecated/Settings"); + +const Mock = class Mock { + constructor() { + this._sandbox = sinon.createSandbox(); + + this._stubs = { + get: this._sandbox.stub(), + put: this._sandbox.stub(), + router: "foo-settings-router" + }; + + Settings.mockImplementation(() => this._stubs); + } + + get stubs() { + return { + Constructor: Settings, + instance: this._stubs + }; + } + + restore() { + this._sandbox.restore(); + } +}; + +module.exports = Mock; diff --git a/test/unit/src/deprecated/Settings.spec.js b/test/unit/src/deprecated/Settings.spec.js new file mode 100644 index 000000000..d1db1cea6 --- /dev/null +++ b/test/unit/src/deprecated/Settings.spec.js @@ -0,0 +1,114 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const express = require("express"); +const sinon = require("sinon"); + +const CoreMocks = require("../../Core.mocks.js"); + +const Settings = require("../../../../src/deprecated/Settings"); + +describe("Settings Api", () => { + let sandbox; + let routerStubs; + let resMock; + let statusSpy; + let sendSpy; + let coreMocks; + let settingsMock; + let tracerMock; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + routerStubs = { + get: sandbox.stub(), + put: sandbox.stub() + }; + coreMocks = new CoreMocks(); + settingsMock = coreMocks.stubs.instance.settings; + tracerMock = coreMocks.stubs.instance.tracer; + sandbox.stub(express, "Router").returns(routerStubs); + statusSpy = sandbox.spy(); + sendSpy = sandbox.spy(); + resMock = { + status: statusSpy, + send: sendSpy + }; + expect.assertions(1); + }); + + afterEach(() => { + sandbox.restore(); + coreMocks.restore(); + }); + + describe("when instanciated", () => { + it("should create an express Router", () => { + new Settings(settingsMock, tracerMock); + expect(express.Router.calledOnce).toEqual(true); + }); + }); + + describe("get route", () => { + it("should set response status as 200", () => { + const settings = new Settings(settingsMock, tracerMock); + settings.get({}, resMock); + expect(statusSpy.getCall(0).args[0]).toEqual(200); + }); + + it("should send current settings", () => { + settingsMock.get.withArgs("delay").returns(3000); + const settings = new Settings(settingsMock, tracerMock); + settings.get({}, resMock); + expect(sendSpy.getCall(0).args[0]).toEqual({ + delay: 3000 + }); + }); + }); + + describe("put route", () => { + it("should set current delay", () => { + const settings = new Settings(settingsMock, tracerMock); + settings.put( + { + body: { + delay: 5000 + } + }, + resMock + ); + expect(settingsMock.set.getCall(0).args).toEqual(["delay", 5000]); + }); + + it("should send current settings", () => { + const settings = new Settings(settingsMock, tracerMock); + settingsMock.get.withArgs("delay").returns(2000); + settings.put( + { + body: { + delay: 2000 + } + }, + resMock + ); + expect(sendSpy.getCall(0).args[0]).toEqual({ + delay: 2000 + }); + }); + }); + + describe("router getter", () => { + it("should return the express router", () => { + const settings = new Settings(settingsMock, tracerMock); + expect(settings.router).toEqual(routerStubs); + }); + }); +});