diff --git a/.travis.yml b/.travis.yml index 6adef68c..7e1bbc4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,4 +11,4 @@ before_install: - sudo apt-get update - sudo apt-get install -y libappindicator1 fonts-liberation - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - - sudo dpkg -i google-chrome*.deb + - sudo dpkg -i google-chrome*.deb \ No newline at end of file diff --git a/api.js b/api.js new file mode 100644 index 00000000..24f5c933 --- /dev/null +++ b/api.js @@ -0,0 +1,2 @@ +const api = require('./dist/bdsm.api.js'); +module.exports = api; diff --git a/integration-tests/index.html b/integration-tests/index.html new file mode 100644 index 00000000..f8effcac --- /dev/null +++ b/integration-tests/index.html @@ -0,0 +1,219 @@ + + + + + Title + + + + +
+

Requests Basics

+ + + + + +
+ +
+

Url wildcards

+ + +
+ +
+

Request param wildcards

+ + +
+ +
+

Url Wildcards + Request param wildcards

+ + +
+ +
+

Request Delays

+ + +
+ +
+

Web worker request

+ + +
+ +

Result

+
+ + + + + \ No newline at end of file diff --git a/integration-tests/worker.js b/integration-tests/worker.js new file mode 100644 index 00000000..cb98133f --- /dev/null +++ b/integration-tests/worker.js @@ -0,0 +1,30 @@ +importScripts('../dist/bdsm.worker.js'); +importScripts('https://cdnjs.cloudflare.com/ajax/libs/superagent/2.3.0/superagent.js'); + +self.addEventListener('message', (message) => { + const { method, url, requestMethod } = message.data; + + console.log({ method, url, requestMethod }); + + // XHR Sample with superagent + if (requestMethod === 'xhr') { + superagent + .get(url) + .end((err, res) => { + if (err) { + self.postMessage('request failed'); + return; + } + + self.postMessage(res.text); + }); + } + + // fetch API sample + if (requestMethod === 'fetch') { + fetch(url) + .then((response) => response.text()) + .then((response) => self.postMessage(response)) + .catch((err) => self.postMessage('request failed')); + } +}); \ No newline at end of file diff --git a/lib/api/communicator.js b/lib/api/communicator.js new file mode 100644 index 00000000..386903be --- /dev/null +++ b/lib/api/communicator.js @@ -0,0 +1,66 @@ +import Emitter from 'api/emitter'; +import Scenarios from 'api/scenarios'; +import Requests from 'api/requests'; +import { MockedRequest } from 'api/models/mocked-request'; +import { getHeadersObject } from 'api/utils/headers'; + +const isWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; + +let mockedRequests = []; +let fetchedRequests = false; + +if (isWebWorker) { + self.addEventListener('message', (event) => { + if (event.data.type === 'BDSM_SET_DATA') { + event.stopImmediatePropagation(); + mockedRequests = event.data.payload.map((request) => new MockedRequest(request)); + fetchedRequests = true; + } + }); +} + +export function getCurrentMockedRequests() { + return new Promise((resolve, reject) => { + if (!isWebWorker) { + resolve(Scenarios.getCurrentMockedRequests()); + } + + setTimeout(() => resolve(mockedRequests), 0); + }); +} + +export function captureRequest(request, response) { + if (isWebWorker) { + const jsonRequest = { + method: request.method, + url: request.url, + headers: getHeadersObject(request.headers), + startTime: request.startTime + }; + const jsonResponse = { + headers: getHeadersObject(response.headers), + status: response.status, + statusText: response.statusText, + url: response.url, + data: response.data + }; + + self.postMessage({ type: 'BDSM_CAPTURE_REQUEST', payload: { + mockId: request.mock ? request.mock.id : null, + request: jsonRequest, + response: jsonResponse + }}); + return; + } + + Requests.capture(request, response); +} + +export function emit(event) { + if (isWebWorker) { + self.postMessage({ type: 'BDSM_EMIT_EVENT', payload: event }); + return; + } + + Emitter.emit(event); +} diff --git a/lib/api/index.js b/lib/api/index.js index 7b51b5e0..93dc3e8a 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -4,6 +4,7 @@ import { PersistentStorage } from 'api/storage'; import Scenarios from 'api/scenarios'; import Requests from 'api/requests'; import EVENTS from 'api/constants/events'; +import { bootstrapWorker } from 'api/utils/worker'; class PublicAPI { @@ -17,6 +18,10 @@ class PublicAPI { Scenarios.init(); } + bootstrapWorker(worker) { + bootstrapWorker(worker, API); + } + get capturedRequests() { return Requests.capturedRequests; } @@ -50,7 +55,7 @@ class PublicAPI { Emitter.emit(EVENTS.IMPORT); } - export() { + export(prettify) { const scenarios = Scenarios.scenarios.map((scenario) => scenario.export()); const exportObject = { @@ -58,10 +63,10 @@ class PublicAPI { "scenarios": scenarios }; - return JSON.stringify(exportObject); + return JSON.stringify(exportObject, null, prettify ? 2 : null); } - exportMock(scenarioId, mockId) { + exportMock(scenarioId, mockId, prettify) { const scenario = Scenarios.getById(scenarioId).exportMock(mockId); const exportObject = { @@ -69,10 +74,21 @@ class PublicAPI { "scenarios": [scenario] }; - return JSON.stringify(exportObject); + return JSON.stringify(exportObject, null, prettify ? 2 : null); + } + + exportMocks(scenarioId, mockIds, prettify) { + const scenario = Scenarios.getById(scenarioId).exportMocks(mockIds); + + const exportObject = { + "version": PersistentStorage.getVersion(), + "scenarios": [scenario] + }; + + return JSON.stringify(exportObject, null, prettify ? 2 : null); } - exportScenario(scenarioId) { + exportScenario(scenarioId, prettify) { const scenario = Scenarios.getById(scenarioId).export(); const exportObject = { @@ -80,7 +96,18 @@ class PublicAPI { "scenarios": [scenario] }; - return JSON.stringify(exportObject); + return JSON.stringify(exportObject, null, prettify ? 2 : null); + } + + exportScenarios(scenarioIds, prettify) { + const scenarios = scenarioIds.map((scenarioId) => Scenarios.getById(scenarioId).export()); + + const exportObject = { + "version": PersistentStorage.getVersion(), + "scenarios": scenarios + }; + + return JSON.stringify(exportObject, null, prettify ? 2 : null); } addScenario(name) { diff --git a/lib/api/interceptor.js b/lib/api/interceptor.js index a7fcc5fc..84350937 100644 --- a/lib/api/interceptor.js +++ b/lib/api/interceptor.js @@ -1,7 +1,6 @@ import xhook from 'xhook'; -import Emitter from 'api/emitter'; -import Scenarios from 'api/scenarios'; -import Requests from 'api/requests'; +import * as Communicator from 'api/communicator'; +import { getHeadersObject } from 'api/utils/headers'; import STATUS_CODES from 'api/constants/status-codes'; import EVENTS from 'api/constants/events'; @@ -14,24 +13,26 @@ class XHRInterceptor { // Use this to determine how much time the request took request.startTime = Date.now(); - const mockedRequests = Scenarios.getCurrentMockedRequests(); - if (!mockedRequests.length) { - return responder(); - } + Communicator.getCurrentMockedRequests() + .then((mockedRequests) => { + if (!mockedRequests.length) { + return responder(); + } - this.getRequestDetails(request).then(({ url, method, body }) => { - for (let mockedRequest of mockedRequests) { - if (mockedRequest.matches({ url, params: body, method })) { - request.mock = mockedRequest; - let responseRule = mockedRequest.getResponse(); + this.getRequestDetails(request).then(({ url, method, body }) => { + for (let mockedRequest of mockedRequests) { + if (mockedRequest.matches({ url, params: body, method })) { + request.mock = mockedRequest; + let responseRule = mockedRequest.getResponse(); - this.logRequest(url, method, body, responseRule, request); - return this.buildResponse(responder, responseRule, request); - } - } + this.logRequest(url, method, body, responseRule, request); + return this.buildResponse(responder, responseRule, request); + } + } - responder(); - }); + responder(); + }); + }); }); xhook.after((request, response) => { @@ -88,13 +89,13 @@ class XHRInterceptor { captureRequest(request, response = {}) { if (!(request instanceof Request)) { - Requests.capture(request, response); - Emitter.emit(EVENTS.REQUEST_CAPTURED); + Communicator.captureRequest(request, response); + Communicator.emit(EVENTS.REQUEST_CAPTURED); return; } - const responseHeaders = this.getHeadersObject(response.headers); - const requestHeaders = this.getHeadersObject(request.headers); + const responseHeaders = getHeadersObject(response.headers); + const requestHeaders = getHeadersObject(request.headers); const { method, url, startTime, mock } = request; const { status } = response; @@ -123,17 +124,9 @@ class XHRInterceptor { data: body }; - Requests.capture(capturedRequest, capturedResponse); - Emitter.emit(EVENTS.REQUEST_CAPTURED); + Communicator.captureRequest(capturedRequest, capturedResponse); + Communicator.emit(EVENTS.REQUEST_CAPTURED); }); - - } - - getHeadersObject(headers = []) { - return Array.from(headers).reduce((result, item) => { - result[item[0]] = item[1]; - return result - }, {}); } enable() { @@ -147,6 +140,7 @@ class XHRInterceptor { buildResponse(responder, responseRule, request) { //let responseBody = evalResponse(responseRule, request); const response = { + url: request.url, status: Number(responseRule.status) || 200, statusText: STATUS_CODES[responseRule.status || 200].toUpperCase(), body: responseRule.body, diff --git a/lib/api/models/mocked-request.js b/lib/api/models/mocked-request.js index 2eb8d3b8..a1ef991b 100644 --- a/lib/api/models/mocked-request.js +++ b/lib/api/models/mocked-request.js @@ -50,8 +50,8 @@ export class MockedRequest { return true; } - const emptyMockParams = this.params === undefined || this.params === null; - const emptyRequestParams = params === undefined || params === null; + const emptyMockParams = this.params === undefined || this.params === null || this.params === ''; + const emptyRequestParams = params === undefined || params === null || this.params === ''; if (emptyMockParams && emptyRequestParams) { return true; @@ -63,19 +63,17 @@ export class MockedRequest { export() { const mock = Object.assign({}, this); - if (mock.headers['content-type'].indexOf('application/json') > -1) { - try { - mock.params = JSON.parse(mock.params); - } catch (ex) { - // failed to parse params, reverting to string - } + try { + mock.params = JSON.parse(mock.params); + } catch (ex) { + // failed to parse params, reverting to string + } - try { - mock.response.body = JSON.parse(mock.response.body); - } catch (ex) { - // failed to parse body, reverting to string - } + try { + mock.response.body = JSON.parse(mock.response.body); + } catch (ex) { + // failed to parse body, reverting to string } return mock; diff --git a/lib/api/models/scenario.js b/lib/api/models/scenario.js index 5411d98a..88aa92d8 100644 --- a/lib/api/models/scenario.js +++ b/lib/api/models/scenario.js @@ -55,6 +55,9 @@ export class Scenario { const mockedRequests = this.mockedRequests.map((mockedRequest) => mockedRequest.export()); return Object.assign({}, this, { mockedRequests }); } catch(ex) { + if (__ENV === 'development') { + console.log(ex); + } return null; } } @@ -64,6 +67,23 @@ export class Scenario { const mock = this.findMockedRequestById(mockId).export(); return Object.assign({}, this, { mockedRequests: [mock] }); } catch (ex) { + if (__ENV === 'development') { + console.log(ex); + } + return null; + } + } + + exportMocks(mockIds) { + try { + const mocks = mockIds + .map((mockId) => this.findMockedRequestById(mockId).export()); + + return Object.assign({}, this, { mockedRequests: mocks }); + } catch (ex) { + if (__ENV === 'development') { + console.log(ex); + } return null; } } diff --git a/lib/api/utils/headers.js b/lib/api/utils/headers.js new file mode 100644 index 00000000..efbcfe83 --- /dev/null +++ b/lib/api/utils/headers.js @@ -0,0 +1,6 @@ +export function getHeadersObject(headers = []) { + return Array.from(headers).reduce((result, item) => { + result[item[0]] = item[1]; + return result + }, {}); +} diff --git a/lib/api/utils/worker.js b/lib/api/utils/worker.js new file mode 100644 index 00000000..c4ee699e --- /dev/null +++ b/lib/api/utils/worker.js @@ -0,0 +1,33 @@ +import Emitter from 'api/emitter'; +import Requests from 'api/requests'; +import EVENTS from 'api/constants/events'; +import { get } from 'lodash'; + +export function bootstrapWorker(worker, API) { + worker.addEventListener('message', (event) => { + const messageType = get(event, 'data.type'); + + if (messageType === 'BDSM_CAPTURE_REQUEST') { + event.stopImmediatePropagation(); + + const request = event.data.payload.request; + const response = event.data.payload.response; + const mockId = event.data.payload.mockId; + + if (mockId) { + request.mock = API.mockedRequests.filter((mock) => mock.id === mockId)[0]; + } + + Requests.capture(request, response); + Emitter.emit(EVENTS.REQUEST_CAPTURED); + } + + if (messageType === 'BDSM_EMIT_EVENT') { + event.stopImmediatePropagation(); + Emitter.emit(event.data.payload); + } + }); + + worker.postMessage({ type: 'BDSM_SET_DATA', payload: API.mockedRequests }); + API.on(EVENTS.STORAGE_PERSIST, () => worker.postMessage({ type: 'BDSM_SET_DATA', payload: API.mockedRequests })); +} diff --git a/lib/api/worker.js b/lib/api/worker.js new file mode 100644 index 00000000..8866c854 --- /dev/null +++ b/lib/api/worker.js @@ -0,0 +1,4 @@ +import 'api/interceptor'; + + + diff --git a/lib/index.js b/lib/index.js index cc7c479b..8471de24 100644 --- a/lib/index.js +++ b/lib/index.js @@ -6,4 +6,12 @@ bootstrapUI(API); // export API export const api = API; -export default API; \ No newline at end of file +export default API; + +// Run web worker request in development +if (__ENV === 'development') { + const RequestWorker = require('worker!./worker_example'); + const requestWorker = new RequestWorker(); + + API.bootstrapWorker(requestWorker); +} diff --git a/lib/ui/components/export-button.js b/lib/ui/components/export-button.js new file mode 100644 index 00000000..dda878df --- /dev/null +++ b/lib/ui/components/export-button.js @@ -0,0 +1,34 @@ +import React, { Component } from 'react'; +import FlatButton from 'material-ui/lib/flat-button'; +import { FileDownload } from 'ui/components/file-download'; +import { API } from 'api'; + +class ExportButton extends Component { + handleClick() { + if (this.props.mode === 'scenarios' && this.props.scenarioId) { + const scenariosIds = this.props.scenarioId.split(','); + this.props.performExport(API.exportScenarios(scenariosIds, this.props.prettify)); + } else if (this.props.mode === 'mocks' && this.props.scenarioId && this.props.mockId) { + const mockIds = this.props.mockId.split(','); + this.props.performExport(API.exportMocks(this.props.scenarioId, mockIds, this.props.prettify)); + } else { + this.props.performExport(API.export(this.props.prettify)); + } + + if (this.props.onClick) { + this.props.onClick(); + } + } + + render() { + return ( + + ); + } +} + +export default FileDownload(ExportButton); \ No newline at end of file diff --git a/lib/ui/components/export.js b/lib/ui/components/export.js index 9917f422..23291051 100644 --- a/lib/ui/components/export.js +++ b/lib/ui/components/export.js @@ -1,14 +1,28 @@ import React, { Component } from 'react'; -import { API } from 'api'; import { FileDownload } from 'ui/components/file-download'; +import ExportModal from 'ui/components/modals/export'; import CloudDownloadIcon from 'material-ui/lib/svg-icons/file/cloud-download'; export class Export extends Component { + constructor(props) { + super(props); + + this.state = { open: false }; + } + + onClose() { + this.setState({ open: false }); + } + + handleClick() { + this.setState({ open: true }); + } + render() { return ( -
this.props.performExport(API.export()) }> +
@@ -21,6 +35,10 @@ export class Export extends Component {
+ +
); } diff --git a/lib/ui/components/modals/export.js b/lib/ui/components/modals/export.js new file mode 100644 index 00000000..f2c74c20 --- /dev/null +++ b/lib/ui/components/modals/export.js @@ -0,0 +1,196 @@ +import React, { Component } from 'react'; +import Dialog from 'material-ui/lib/dialog'; +import FlatButton from 'material-ui/lib/flat-button'; +import TextField from 'material-ui/lib/text-field'; +import RadioButtonGroup from 'material-ui/lib/radio-button-group'; +import RadioButton from 'material-ui/lib/radio-button'; +import Checkbox from 'material-ui/lib/checkbox'; +import ExportButton from 'ui/components/export-button'; +import Select from 'react-select'; +import reactSelect from 'react-select/dist/react-select.css'; +import applicationCss from 'ui/assets/stylesheets/application.scss'; +import { connect } from 'react-redux'; +import { find, get } from 'lodash'; + +let styles = { + __html: ` + +` +}; + +const mockOption = ({ method, url, params }) => ( +
+

{method} ({url})

+

{params}

+
+); + +const mockValue = ({ method, url, params }) => ( +
+

{method} ({url}) {params}

+
+); + +export class ExportModal extends Component { + constructor(props) { + super(props); + + this.state = { + exportType: 'all', + filename: 'bdsm', + prettify: false, + selectedScenario: 'default-scenario', + selectedMock: null + } + } + + onFilenameChange(event) { + this.setState({ filename: event.target.value }); + } + + onPrettifyChange(event) { + this.setState({ prettify: event.target.checked }); + } + + onExportTypeChange(event) { + const newState = { + exportType: event.target.value, + selectedScenario: 'default-scenario' + }; + + this.setState(newState); + } + + onScenarioChange(value) { + this.setState({ selectedScenario: value }); + } + + onMockChange(mockId) { + this.setState({ selectedMock: mockId }); + } + + handleSubmit() { + if (this.props.onConfirm) { + this.props.onConfirm(this.state.scenarioName); + } + + this.props.onClose(); + } + + handleScenarioChange(event) { + this.setState({ scenarioName: event.target.value }); + } + + renderScenarioSelect() { + if (this.state.exportType === 'all') { + return; + } + + const options = this.props.scenarios.map((scenario) => ({ + value: scenario.id, + label: scenario.name + })); + + return ( +
+ + +
+ ) + } + + render() { + const actions = [ + , + + ]; + + return ( +
+ +
+ +
+ + + + + +
+ + { this.renderScenarioSelect() } + { this.renderMockSelect() } + + + +
+
+ ); + } + +} + +const mapStateToProps = (state) => ({ + scenarios: state.scenarios +}); + +export default connect(mapStateToProps, null)(ExportModal); \ No newline at end of file diff --git a/lib/ui/index.js b/lib/ui/index.js index 426ae163..df5f50b7 100644 --- a/lib/ui/index.js +++ b/lib/ui/index.js @@ -67,11 +67,10 @@ function toggleUI() { function APIProxy(API) { window.addEventListener('message', (event) => { - // console.log('parent window message:', event.data); if (event.data.type === 'get') { event.source.postMessage({ type: `response_${event.data.resource}`, - data: API[event.data.resource] + data: JSON.stringify(API[event.data.resource]) }, event.origin); } diff --git a/lib/ui/utils/api.js b/lib/ui/utils/api.js index 5f7cfd50..a2521af2 100644 --- a/lib/ui/utils/api.js +++ b/lib/ui/utils/api.js @@ -9,7 +9,7 @@ export class APIBridge { const cb = (event) => { if (event.data.type === `response_${resource}`) { - resolve(event.data.data); + resolve(JSON.parse(event.data.data)); window.removeEventListener('message', cb); } diff --git a/lib/worker_example.js b/lib/worker_example.js new file mode 100644 index 00000000..97fca8fb --- /dev/null +++ b/lib/worker_example.js @@ -0,0 +1,9 @@ +import 'api/worker'; + +fetch('http://jsonplaceholder.typicode.com/posts/4') + .then((response) => { + return response.json() + }) + .then((response) => console.log(response)) + .catch((error) => console.log(error)); + diff --git a/nightwatch-ci.js b/nightwatch-ci.js new file mode 100644 index 00000000..252b65f6 --- /dev/null +++ b/nightwatch-ci.js @@ -0,0 +1,41 @@ +module.exports = { + "src_folders": ["test/integration"], + "output_folder": "reports", + "custom_commands_path": "", + "custom_assertions_path": "", + "page_objects_path": "", + "globals_path": "", + + "selenium": { + "start_process": false, + "log_path": "", + "host": "127.0.0.1", + "port": 4445 + }, + + "test_settings": { + "default": { + "launch_url": 'http://ondemand.saucelabs.com:80', + "selenium_port": 80, + "selenium_host": 'ondemand.saucelabs.com', + "silent": true, + "username": process.env.SAUCE_USERNAME, + "access_key": process.env.SAUCE_ACCESS_KEY, + "screenshots": { + "enabled": false, + "path": '', + }, + "globals": { + "waitForConditionTimeout": 10000, + }, + }, + + chrome: { + desiredCapabilities: { + browserName: 'chrome', + platform: 'OS X 10.11', + version: '54', + }, + }, + } +}; diff --git a/nightwatch.json b/nightwatch.json new file mode 100644 index 00000000..5c6d6537 --- /dev/null +++ b/nightwatch.json @@ -0,0 +1,46 @@ +{ + "src_folders" : ["test/integration"], + "output_folder" : "reports", + "custom_commands_path" : "", + "custom_assertions_path" : "", + "page_objects_path" : "", + "globals_path" : "", + + "selenium" : { + "start_process" : true, + "server_path" : "/Users/maayan/selenium/selenium-server-standalone-2.53.1.jar", + "log_path" : "", + "host" : "127.0.0.1", + "port" : 4444, + "cli_args" : { + "webdriver.chrome.driver" : "/Users/maayan/selenium/chromedriver", + "webdriver.ie.driver" : "" + } + }, + + "test_settings" : { + "default" : { + "launch_url" : "http://localhost", + "selenium_port" : 4444, + "selenium_host" : "localhost", + "silent": true, + "screenshots" : { + "enabled" : false, + "path" : "" + }, + "desiredCapabilities": { + "browserName": "chrome", + "javascriptEnabled": true, + "acceptSslCerts": true + } + }, + + "chrome" : { + "desiredCapabilities": { + "browserName": "firefox", + "javascriptEnabled": true, + "acceptSslCerts": true + } + } + } +} diff --git a/package.json b/package.json index 1df71f78..e7f2964f 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,24 @@ { "name": "bdsm", - "version": "1.0.2", + "version": "1.0.3", "description": "Bad ass client side mocking solution", "main": "dist/bdsm.js", "scripts": { "prestart": "npm install", "start": "webpack-dev-server --progress", "build": "NODE_ENV=production webpack -p --progress", + "build:dev": "NODE_ENV=development webpack", "test": "NODE_ENV=test node_modules/.bin/karma start --single-run", - "tdd": "NODE_ENV=test node_modules/.bin/karma start" + "tdd": "NODE_ENV=test node_modules/.bin/karma start", + "start:integration-server": "http-server .", + "test:integration": "npm run build ; npm run start:integration-server & nightwatch ; pkill node" }, "files": [ - "dist" + "dist/bdsm.js", + "dist/bdsm.api.js", + "dist/bdsm.worker.js", + "worker.js", + "api.js" ], "repository": { "type": "git", @@ -36,17 +43,41 @@ "homepage": "", "dependencies": {}, "devDependencies": { + "autoprefixer-loader": "3.1.0", + "babel-core": "6.10.4", + "babel-loader": "6.2.4", + "babel-plugin-transform-object-rest-spread": "6.8.0", + "babel-preset-es2015": "6.9.0", + "babel-preset-react": "6.11.1", "classnames": "2.2.3", + "clean-webpack-plugin": "0.1.6", "codemirror": "5.13.2", + "css-loader": "0.23.1", "eventemitter3": "1.1.1", "faker": "3.0.1", "fbjs": "0.8.0-alpha.3", + "file-loader": "0.8.5", "flexboxgrid": "6.3.0", + "http-server": "0.9.0", "http-status-codes": "1.0.6", + "iframe-loader": "git+https://github.com/morsdyce/iframe-loader.git", + "jasmine-core": "2.4.1", "js-beautify": "1.6.4", + "json-loader": "0.5.4", + "karma": "0.13.19", + "karma-chrome-launcher": "0.2.2", + "karma-clear-screen-reporter": "1.0.0", + "karma-jasmine": "0.3.6", + "karma-mocha-reporter": "1.1.5", + "karma-osx-reporter": "0.2.1", + "karma-sourcemap-loader": "0.3.6", + "karma-webpack": "1.7.0", "lodash": "4.6.1", "material-ui": "0.14.4", + "nightwatch": "0.9.8", + "node-sass": "3.4.2", "normalize.css": "3.0.3", + "raw-loader": "0.5.1", "react": "0.14.7", "react-addons-create-fragment": "0.14.7", "react-addons-pure-render-mixin": "0.14.7", @@ -61,42 +92,21 @@ "react-redux": "4.4.0", "react-select": "0.9.1", "react-tap-event-plugin": "0.2.2", + "react-toggle": "2.0.1", "redux": "3.3.1", "roboto-fontface": "0.4.5", + "sass-loader": "3.1.2", + "scss-loader": "0.0.1", "semver": "5.1.0", "standard-headers": "0.1.1", + "style-loader": "0.13.0", "superagent": "1.6.0", "urijs": "1.17.0", - "uuid": "2.0.1", - "xhook": "git+https://github.com/morsdyce/xhook.git#1b3241544d8ae55a92ac585dacef43fec150de06", - "autoprefixer-loader": "3.1.0", - "babel-core": "6.10.4", - "babel-loader": "6.2.4", - "babel-plugin-transform-object-rest-spread": "6.8.0", - "babel-preset-es2015": "6.9.0", - "babel-preset-react": "6.11.1", - "clean-webpack-plugin": "0.1.6", - "css-loader": "0.23.1", - "file-loader": "0.8.5", - "iframe-loader": "git+https://github.com/morsdyce/iframe-loader.git", - "jasmine-core": "2.4.1", - "json-loader": "0.5.4", - "karma": "0.13.19", - "karma-chrome-launcher": "0.2.2", - "karma-clear-screen-reporter": "1.0.0", - "karma-jasmine": "0.3.6", - "karma-mocha-reporter": "1.1.5", - "karma-osx-reporter": "0.2.1", - "karma-sourcemap-loader": "0.3.6", - "karma-webpack": "1.7.0", - "node-sass": "3.4.2", - "raw-loader": "0.5.1", - "react-toggle": "2.0.1", - "sass-loader": "3.1.2", - "scss-loader": "0.0.1", - "style-loader": "0.13.0", "url-loader": "0.5.7", + "uuid": "2.0.1", "webpack": "1.12.10", - "webpack-dev-server": "1.14.0" + "webpack-dev-server": "1.14.0", + "worker-loader": "0.7.1", + "xhook": "git+https://github.com/morsdyce/xhook.git#bd74241bdee9c4b857bc3d991b7ce2e4c4387639" } } diff --git a/test/integration/interceptor.js b/test/integration/interceptor.js new file mode 100644 index 00000000..3eb8f580 --- /dev/null +++ b/test/integration/interceptor.js @@ -0,0 +1,327 @@ +module.exports = { + 'Get Fetch Failed' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#get-fetch', 1000) + .click('#get-fetch') + .pause(500) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'Get Fetch mocked' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2","scenarios":[{"id":"3165323e-f5cd-44f4-afda-d8b9045989a9","name":"Integration tests","active":true,"mockedRequests":[{"id":"1e4cef8d-5724-4e9b-812c-4ed260107194","active":true,"method":"GET","url":"http://bdsm-example.com/get","headers":{"pragma":"no-cache","content-type":"text/plain; charset=utf-8","cache-control":"no-cache","expires":-1},"params":"","response":{"delay":20,"status":200,"body":"GET-FETCH"}}]}]}'); + }) + .waitForElementVisible('#get-fetch', 1000) + .click('#get-fetch') + .pause(500) + .assert.containsText('#result', 'GET-FETCH') + .end(); + }, + + 'Get XHR failed' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#get-xhr', 1000) + .click('#get-xhr') + .pause(500) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'Get XHR mocked' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2","scenarios":[{"id":"3165323e-f5cd-44f4-afda-d8b9045989a9","name":"Integration tests","active":true,"mockedRequests":[{"id":"1e4cef8d-5724-4e9b-812c-4ed260107194","active":true,"method":"GET","url":"http://bdsm-example.com/get","headers":{"pragma":"no-cache","content-type":"text/plain; charset=utf-8","cache-control":"no-cache","expires":-1},"params":"","response":{"delay":20,"status":200,"body":"GET-FETCH"}}]}]}'); + }) + .waitForElementVisible('#get-xhr', 1000) + .click('#get-xhr') + .pause(500) + .assert.containsText('#result', 'GET-FETCH') + .end(); + }, + + 'Post Fetch Failed' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#post-fetch', 1000) + .click('#post-fetch') + .pause(500) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'Post Fetch mocked' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2", "scenarios":[{"id":"3165323e-f5cd-44f4-afda-d8b9045989a9", "name":"Integrationtests", "active":true, "mockedRequests":[{"id":"4b202785-2ac4-4a06-881c-316e0036e46c", "active":true, "method":"POST", "url":"http://bdsm-example.com/post", "headers":{"pragma":"no-cache", "content-type":"text/plain;charset=utf-8", "cache-control":"no-cache", "expires":-1}, "params":{"user":"user", "password":"password"}, "response":{"delay":26, "status":200, "body":"POST-PARAMS"}}]}]}'); + }) + .waitForElementVisible('#post-fetch', 1000) + .click('#post-fetch') + .pause(500) + .assert.containsText('#result', 'POST-PARAMS') + .end(); + }, + + 'Post XHR failed' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#post-xhr', 1000) + .click('#post-xhr') + .pause(500) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'Post XHR mocked' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2","scenarios":[{"id":"3165323e-f5cd-44f4-afda-d8b9045989a9","name":"Integration tests","active":true,"mockedRequests":[{"id":"320af574-1a3e-4cd7-9cfc-d170a6b22217","active":true,"method":"POST","url":"http://bdsm-example.com/post","headers":{"pragma":"no-cache","content-type":"text/plain; charset=utf-8","cache-control":"no-cache","expires":-1},"params":"user=user&password=password","response":{"delay":0,"status":200,"body":"POST-PARAMS"}}]}]}'); + }) + .waitForElementVisible('#post-xhr', 1000) + .click('#post-xhr') + .pause(500) + .assert.containsText('#result', 'POST-PARAMS') + .end(); + }, + + 'GET FETCH Wildcard failed' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#get-fetch-wildcard', 1000) + .click('#get-fetch-wildcard') + .pause(500) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'GET Fetch Wildcard mocked' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2","scenarios":[{"id":"3165323e-f5cd-44f4-afda-d8b9045989a9","name":"Integration tests","active":true,"mockedRequests":[{"id":"5f3e7faa-64ea-40c8-9002-4d596c396c43","active":true,"method":"GET","url":"http://bdsm-example.com/get-wildcard/*","headers":{"pragma":"no-cache","content-type":"text/plain; charset=utf-8","cache-control":"no-cache","expires":-1},"params":"","response":{"delay":19,"status":200,"body":"GET-WILDCARD"}}]}]}'); + }) + .waitForElementVisible('#get-fetch-wildcard', 1000) + .click('#get-fetch-wildcard') + .pause(500) + .assert.containsText('#result', 'GET-WILDCARD') + .end(); + }, + + 'GET XHR Wildcard failed' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#get-xhr-wildcard', 1000) + .click('#get-xhr-wildcard') + .pause(500) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'GET XHR Wildcard mocked' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2","scenarios":[{"id":"3165323e-f5cd-44f4-afda-d8b9045989a9","name":"Integration tests","active":true,"mockedRequests":[{"id":"5f3e7faa-64ea-40c8-9002-4d596c396c43","active":true,"method":"GET","url":"http://bdsm-example.com/get-wildcard/*","headers":{"pragma":"no-cache","content-type":"text/plain; charset=utf-8","cache-control":"no-cache","expires":-1},"params":"","response":{"delay":19,"status":200,"body":"GET-WILDCARD"}}]}]}'); + }) + .waitForElementVisible('#get-xhr-wildcard', 1000) + .click('#get-xhr-wildcard') + .pause(500) + .assert.containsText('#result', 'GET-WILDCARD') + .end(); + }, + + 'GET FETCH Request Params Wildcard failed' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#post-fetch-params-wildcard', 1000) + .click('#post-fetch-params-wildcard') + .pause(500) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'GET Fetch Request Params Wildcard mocked' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2", "scenarios":[{"id":"3165323e-f5cd-44f4-afda-d8b9045989a9", "name":"Integrationtests", "active":true, "mockedRequests":[{"id":"f1597822-c95d-479b-85a2-e60acdc1785f", "active":true, "method":"POST", "url":"http://bdsm-example.com/post-wildcard", "headers":{"pragma":"no-cache", "content-type":"text/plain;charset=utf-8", "cache-control":"no-cache", "expires":-1}, "params":{"user":"*", "password":"*"}, "response":{"delay":25, "status":200, "body":"POST-WILDCARD"}}]}]}'); + }) + .waitForElementVisible('#post-fetch-params-wildcard', 1000) + .click('#post-fetch-params-wildcard') + .pause(50) + .assert.containsText('#result', 'POST-WILDCARD') + .end(); + }, + + 'GET XHR Request Params Wildcard failed' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#post-fetch-params-wildcard', 1000) + .click('#post-fetch-params-wildcard') + .pause(500) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'GET XHR Request Params Wildcard mocked' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2","scenarios":[{"id":"3165323e-f5cd-44f4-afda-d8b9045989a9","name":"Integration tests","active":true,"mockedRequests":[{"id":"1ad3d21d-c7e8-42c2-a90d-a548c53b4029","active":true,"method":"POST","url":"http://bdsm-example.com/post-wildcard","headers":{"pragma":"no-cache","content-type":"text/plain; charset=utf-8","cache-control":"no-cache","expires":-1},"params":"user = * & password = *","response":{"delay":0,"status":200,"body":"POST-WILDCARD"}}]}]}'); + }) + .waitForElementVisible('#post-xhr-params-wildcard', 1000) + .click('#post-xhr-params-wildcard') + .pause(500) + .assert.containsText('#result', 'POST-WILDCARD') + .end(); + }, + + 'GET FETCH Url Wildcards + Request param wildcards failed' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#post-fetch-both-wildcard', 1000) + .click('#post-fetch-both-wildcard') + .pause(500) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'GET Fetch Url Wildcards + Request param wildcards mocked' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version": "1.0.2", "scenarios": [{"id": "3165323e-f5cd-44f4-afda-d8b9045989a9", "name": "Integration tests", "active": true, "mockedRequests": [{"id": "9946425a-5da5-4dfa-adc2-4e166fd5e62c", "active": true, "method": "POST", "url": "http://bdsm-example.com/post-wildcard-both/*", "headers": {"pragma": "no-cache", "content-type": "text/plain; charset=utf-8", "cache-control": "no-cache", "expires": -1}, "params": {"user": "user*", "password": "password*"}, "response": {"delay": 26, "status": 200, "body": "POST-WILDCARD-BOTH"}}]}]}'); + }) + .waitForElementVisible('#post-fetch-both-wildcard', 1000) + .click('#post-fetch-both-wildcard') + .pause(50) + .assert.containsText('#result', 'POST-WILDCARD') + .end(); + }, + + 'GET XHR Url Wildcards + Request param wildcards failed' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#post-xhr-both-wildcard', 1000) + .click('#post-xhr-both-wildcard') + .pause(500) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'GET XHR Url Wildcards + Request param wildcards mocked' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2","scenarios":[{"id":"3165323e-f5cd-44f4-afda-d8b9045989a9","name":"Integration tests","active":true,"mockedRequests":[{"id":"1850df9b-4260-43fd-b978-1a6b0cfec928","active":true,"method":"POST","url":"http://bdsm-example.com/post-wildcard-both/*","headers":{"pragma":"no-cache","content-type":"text/plain; charset=utf-8","cache-control":"no-cache","expires":-1},"params":"user = user* & password = password*","response":{"delay":0,"status":200,"body":"POST-WILDCARD-BOTH"}}]}]}'); + }) + .waitForElementVisible('#post-xhr-both-wildcard', 1000) + .click('#post-xhr-both-wildcard') + .pause(50) + .assert.containsText('#result', 'POST-WILDCARD') + .end(); + }, + + 'GET FETCH Delay' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#get-fetch-delay', 1000) + .click('#get-fetch-delay') + .pause(500) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'GET Fetch Url Wildcards + Request param wildcards mocked' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2","scenarios":[{"id":"default-scenario","name":"Default Scenario","active":true,"mockedRequests":[{"id":"ed974e36-cdb5-4987-9390-7acc7d342a24","active":true,"method":"GET","url":"http://bdsm-example.com/get-delay","headers":{"pragma":"no-cache","content-type":"text/plain; charset=utf-8","cache-control":"no-cache","expires":-1},"params":"","response":{"delay":"2000","status":200,"body":"GET-DELAY"}}]}]}'); + }) + .waitForElementVisible('#get-fetch-delay', 1000) + .click('#get-fetch-delay') + .pause(50) + .assert.containsText('#result', '') + .pause(4000) + .assert.containsText('#result', 'GET-DELAY') + .end(); + }, + + 'GET XHR Delay' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#get-xhr-delay', 1000) + .click('#get-xhr-delay') + .pause(500) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'GET XHR Url Wildcards + Request param wildcards mocked' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2","scenarios":[{"id":"default-scenario","name":"Default Scenario","active":true,"mockedRequests":[{"id":"ed974e36-cdb5-4987-9390-7acc7d342a24","active":true,"method":"GET","url":"http://bdsm-example.com/get-delay","headers":{"pragma":"no-cache","content-type":"text/plain; charset=utf-8","cache-control":"no-cache","expires":-1},"params":"","response":{"delay":"2000","status":200,"body":"GET-DELAY"}}]}]}'); + }) + .waitForElementVisible('#get-xhr-delay', 1000) + .click('#get-xhr-delay') + .pause(50) + .assert.containsText('#result', '') + .pause(4000) + .assert.containsText('#result', 'GET-DELAY') + .end(); + }, + + 'GET FETCH Worker failed' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#webworker-get-fetch', 1000) + .click('#webworker-get-fetch') + .pause(1000) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'GET Fetch Worker' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2","scenarios":[{"id":"default-scenario","name":"Default Scenario","active":true,"mockedRequests":[{"id":"e218a790-5c9b-47f2-87da-184b949a9ed4","active":true,"method":"GET","url":"http://bdsm-example.com/worker","headers":{"pragma":"no-cache","content-type":"text/plain; charset=utf-8","cache-control":"no-cache","expires":-1},"response":{"delay":25,"status":200,"body":"FETCH-WORKER"}}]}]}'); + }) + .waitForElementVisible('#webworker-get-fetch', 1000) + .click('#webworker-get-fetch') + .pause(1000) + .assert.containsText('#result', '"FETCH-WORKER"') + .end(); + }, + + 'GET XHR Worker failed' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .waitForElementVisible('#webworker-get-xhr', 1000) + .click('#webworker-get-xhr') + .pause(1000) + .assert.containsText('#result', 'request failed') + .end(); + }, + + 'GET XHR Worker' : function (browser) { + browser + .url('http://127.0.0.1:8080/integration-tests') + .execute(function(data) { + window.bdsm.api.import('{"version":"1.0.2","scenarios":[{"id":"default-scenario","name":"Default Scenario","active":true,"mockedRequests":[{"id":"e218a790-5c9b-47f2-87da-184b949a9ed4","active":true,"method":"GET","url":"http://bdsm-example.com/worker","headers":{"pragma":"no-cache","content-type":"text/plain; charset=utf-8","cache-control":"no-cache","expires":-1},"response":{"delay":25,"status":200,"body":"FETCH-WORKER"}}]}]}'); + }) + .waitForElementVisible('#webworker-get-xhr', 1000) + .click('#webworker-get-xhr') + .pause(1000) + .assert.containsText('#result', '"FETCH-WORKER"') + .end(); + }, +}; diff --git a/webpack.config.js b/webpack.config.js index 2f3427d7..b6ff9596 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,6 +19,8 @@ let config = { entry: { 'bdsm.api': ['api/index.js'], + 'bdsm.worker': ['api/worker.js'], + 'bdsm': 'index.js' }, diff --git a/worker.js b/worker.js new file mode 100644 index 00000000..563560eb --- /dev/null +++ b/worker.js @@ -0,0 +1,2 @@ +const worker = require('./dist/bdsm.worker.js'); +module.exports = worker;