├── docs ├── favicon.png ├── images │ ├── logo.png │ ├── logo.blue.png │ ├── text.logo.png │ ├── logo.small.png │ ├── logo.blue.small.png │ ├── text.logo.small.png │ ├── logo.svg │ ├── logo.blue.svg │ └── text.logo.svg ├── docs │ ├── creating-mocks.md │ ├── mock-validation.md │ ├── getting-started.md │ └── mock-queries.md ├── index.md └── api │ ├── utils.md │ ├── data-types.md │ ├── queryinterface.md │ ├── instance.md │ ├── errors.md │ ├── sequelize.md │ └── model.md ├── src ├── index.js ├── utils.js ├── queryinterface.js ├── instance.js ├── errors.js ├── sequelize.js └── data-types.js ├── circle.yml ├── .editorconfig ├── .gitignore ├── scripts ├── version.js └── doc-gen.js ├── LICENSE ├── mkdocs.yml ├── package.json ├── README.md ├── CODE_OF_CONDUCT.md ├── test ├── utils.spec.js ├── sequelize.spec.js ├── instance.spec.js └── errors.spec.js └── changelog.md /docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlinkUX/sequelize-mock/HEAD/docs/favicon.png -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlinkUX/sequelize-mock/HEAD/docs/images/logo.png -------------------------------------------------------------------------------- /docs/images/logo.blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlinkUX/sequelize-mock/HEAD/docs/images/logo.blue.png -------------------------------------------------------------------------------- /docs/images/text.logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlinkUX/sequelize-mock/HEAD/docs/images/text.logo.png -------------------------------------------------------------------------------- /docs/images/logo.small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlinkUX/sequelize-mock/HEAD/docs/images/logo.small.png -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Sequelize = require('./sequelize'); 4 | 5 | module.exports = Sequelize; 6 | -------------------------------------------------------------------------------- /docs/images/logo.blue.small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlinkUX/sequelize-mock/HEAD/docs/images/logo.blue.small.png -------------------------------------------------------------------------------- /docs/images/text.logo.small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlinkUX/sequelize-mock/HEAD/docs/images/text.logo.small.png -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | general: 2 | artifacts: 3 | - "coverage" 4 | machine: 5 | node: 6 | version: 5.9.1 7 | timezone: 8 | America/Los_Angeles 9 | test: 10 | override: 11 | - npm test 12 | post: 13 | - npm run coverage -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = false 8 | insert_final_newline = true 9 | 10 | [{package.json,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /scripts/version.js: -------------------------------------------------------------------------------- 1 | /** Versioning 2 | * 3 | * This script contains logic for what needs to take place when 4 | * publish a new version of this code to NPM. 5 | * 6 | **/ 7 | 'use strict'; 8 | 9 | var fs = require('fs'), 10 | path = require('path'), 11 | moment = require('moment'), 12 | pkg = require('../package.json'); 13 | 14 | // Update the change log to the new version 15 | console.log('START UPDATING CHANGELOG'); 16 | var changelogFile = path.resolve(__dirname, '../changelog.md'), 17 | changelog = fs.readFileSync(changelogFile, { encoding: 'utf8' }), 18 | now = moment(); 19 | changelog = changelog.replace('vNext', 'v' + pkg.version + ' - ' + now.format('MMM Do YYYY')); 20 | fs.writeFileSync(changelogFile, changelog, { encoding: 'utf8' }); 21 | console.log('FINISHED UPDATING CHANGELOG'); 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Blink UX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Sequelize Mock 2 | site_author: Blink UX 3 | site_description: Sequelize Mock is a Mocking Library for code relying on Sequelize ORM Models 4 | site_favicon: favicon.png 5 | site_url: http://sequelize-mock.readthedocs.io/ 6 | repo_url: https://github.com/BlinkUX/sequelize-mock 7 | theme: readthedocs 8 | extra_css: 9 | - //cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/styles/vs.min.css 10 | extra_javascript: 11 | - //cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/highlight.min.js 12 | pages: 13 | - Home: 'index.md' 14 | - Documentation: 15 | - 'Getting Started': 'docs/getting-started.md' 16 | - 'Creating Mock Objects': 'docs/creating-mocks.md' 17 | - 'Mock Queries': 'docs/mock-queries.md' 18 | - 'Mock Object Validation': 'docs/mock-validation.md' 19 | - API: 20 | ### API PAGES START 21 | - 'Sequelize': 'api/sequelize.md' 22 | - 'Model': 'api/model.md' 23 | - 'Instance': 'api/instance.md' 24 | - 'DataTypes': 'api/data-types.md' 25 | - 'QueryInterface': 'api/queryinterface.md' 26 | - 'Errors': 'api/errors.md' 27 | - 'Utils': 'api/utils.md' 28 | ### API PAGES END 29 | #- Misc: 30 | # - 'Changelog': 'changelog.md' 31 | -------------------------------------------------------------------------------- /docs/docs/creating-mocks.md: -------------------------------------------------------------------------------- 1 | # Defining Mocks 2 | 3 | To create a mock object for use in tests, you will need to use the `define` method. This will set up basic mocking functionality similar to what Sequelize would provide. In addition to whatever default values you define, Sequelize Mock will also create an auto-incrementing `id` value as well as a `createdAt` and `updatedAt` value which defaults to the time of creation. This automatic values can be turned off the same way they can be disabled in standard Sequelize. 4 | 5 | Defining a mock object is primarily about setting up a set of default values that will be used for new models. It is also best to define a name for the model. For the most part, you should name your mock object the same thing you have named your model. This will allow any associations to be properly set up with calls to associations. 6 | 7 | ```javascript 8 | var User = dbMock.define('user', { 9 | username: 'myTestUsername', 10 | email: 'test@example.com', 11 | }); 12 | 13 | var Team = dbMock.define('team', { 14 | name: 'Test Team', 15 | }); 16 | ``` 17 | 18 | ## Instance Methods 19 | 20 | Similar to Sequelize, you can set up methods to be available on every returned mock instance. 21 | 22 | ```javascript 23 | var Project = dbMock.define('project', { 24 | name: 'Test Project', 25 | }, { 26 | instanceMethods: { 27 | getProjectInfo: function () { 28 | return this.get('id') + ' - ' + this.get('name'); 29 | } 30 | }, 31 | }); 32 | ``` 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sequelize-mock", 3 | "version": "0.10.2", 4 | "description": "A simple mock interface specifically for testing code relying on Sequelize models", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "doc-gen": "node scripts/doc-gen.js", 8 | "test": "nyc --reporter=text --reporter=html mocha", 9 | "test-report": "nyc report --reporter=html", 10 | "coverage": "nyc report --reporter=text-lcov | coveralls", 11 | "preversion": "npm test", 12 | "version": "node scripts/version.js && git add changelog.md" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/BlinkUX/sequelize-mock.git" 17 | }, 18 | "keywords": [ 19 | "sequelize", 20 | "mocking", 21 | "test", 22 | "testing" 23 | ], 24 | "author": "Blink UX", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/BlinkUX/sequelize-mock/issues" 28 | }, 29 | "homepage": "https://github.com/BlinkUX/sequelize-mock#readme", 30 | "dependencies": { 31 | "bluebird": "^3.4.6", 32 | "inflection": "^1.10.0", 33 | "lodash": "^4.16.4" 34 | }, 35 | "devDependencies": { 36 | "coveralls": "^2.11.14", 37 | "dox": "^0.9.0", 38 | "glob": "^7.1.1", 39 | "istanbul": "^0.4.5", 40 | "mocha": "^3.1.2", 41 | "moment": "^2.17.1", 42 | "nyc": "^8.3.1", 43 | "proxyquire": "^1.7.10", 44 | "should": "^11.1.1" 45 | }, 46 | "nyc": { 47 | "exclude": [ 48 | "src/index.js" 49 | ], 50 | "include": [ 51 | "src/**/*.js" 52 | ], 53 | "all": true 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docs/docs/mock-validation.md: -------------------------------------------------------------------------------- 1 | # Validation on Mock Objects 2 | 3 | We *do not* include the complex validation provided by Sequelize for validating your object input. However, we do support testing against validation errors. There is a simple Mock Object specific interface for setting up any validation errors. 4 | 5 | Queued validation errors will be returned any time `save()`, or `update()` are called on Instances, as well as from `create()` on Models. Any validation errors will be returned in a `ValidationError` object, and then the error list will be cleared for the next `validate()` call. 6 | 7 | ## Usage 8 | 9 | To queue validation errors, call `$addValidationError(column[, message[, type]])` on an Instance and any queries to that instance that trigger validation will cause a validation error to be returned or thrown. 10 | 11 | ```javascript 12 | var user = UserMock.build(); 13 | 14 | user.$addValidationError('username', 'Username is too short'); 15 | 16 | user.update({ 17 | 'username': 'abc', 18 | }).catch(function (error) { 19 | // Validation error caught here 20 | }); 21 | ``` 22 | 23 | ## Error object 24 | 25 | The error object is a copy of the `ValidationError` object that would be [returned by Sequelize](http://docs.sequelizejs.com/en/latest/api/errors/#new-validationerrormessage-errors). 26 | 27 | This will return 1 object with a sub array of `errors` that will include each of the errors above with the following format. 28 | 29 | ```javascript 30 | { 31 | "message": "", // Message passed in to each validation error 32 | "type": "", // Type passed in to each validation error 33 | "path": "", // Column name that has the validation error 34 | "value": "" // Value of the column in that object 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # ![Sequelize Mock](images/text.logo.png) 2 | 3 | Sequelize Mock is a mocking library for [Sequelize](http://sequelizejs.com). Mock objects created with this library are meant for use in testing code that relies on Sequelize Models. Code does not rely on any database connections and can therefore be easily used in unit and integration tests without requiring the setup of a test database system. 4 | 5 | [Installation](docs/getting-started.md) 6 | 7 | ## Example Usage 8 | 9 | ```javascript 10 | // Import the mock library 11 | var SequelizeMock = require('sequelize-mock'); 12 | 13 | // Setup the mock database connection 14 | var DBConnectionMock = new SequelizeMock(); 15 | 16 | // Define our Model 17 | var UserMock = DBConnectionMock.define('users', { 18 | 'email': 'email@example.com', 19 | 'username': 'blink', 20 | 'picture': 'user-picture.jpg', 21 | }, { 22 | instanceMethods: { 23 | myTestFunc: function () { 24 | return 'Test User'; 25 | }, 26 | }, 27 | }); 28 | 29 | // You can also associate mock models as well 30 | var GroupMock = DBConnectionMock.define('groups', { 31 | 'name': 'My Awesome Group', 32 | }); 33 | 34 | UserMock.belongsTo(GroupMock); 35 | 36 | // From there we can start using it like a normal model 37 | UserMock.findOne({ 38 | where: { 39 | username: 'my-user', 40 | }, 41 | }).then(function (user) { 42 | // `user` is a Sequelize Model-like object 43 | user.get('id'); // Auto-Incrementing ID available on all Models 44 | user.get('email'); // 'email@example.com'; Pulled from default values 45 | user.get('username'); // 'my-user'; Pulled from the `where` in the query 46 | 47 | user.myTestFunc(); // Will return 'Test User' as defined above 48 | 49 | user.getGroup(); // Will return a `GroupMock` object 50 | }); 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/images/logo.svg: -------------------------------------------------------------------------------- 1 | logo -------------------------------------------------------------------------------- /docs/images/logo.blue.svg: -------------------------------------------------------------------------------- 1 | logo.blue -------------------------------------------------------------------------------- /docs/api/utils.md: -------------------------------------------------------------------------------- 1 | # Utils 2 | 3 | Interface for some of the Sequelize Utilities exposed in the `Sequelize.Utils` object. 4 | 5 | 6 | 7 | 8 | ## uppercaseFirst(str) -> String 9 | 10 | Uppercase the first character in a string. Matches Sequelize functionality. 11 | 12 | ### Parameters 13 | 14 | Name | Type | Description 15 | --- | --- | --- 16 | str | String | String to uppercase the first character of 17 | 18 | 19 | ### Return 20 | `String`: modified string 21 | 22 | 23 | 24 | 25 | ## lowercaseFirst(str) -> String 26 | 27 | Lowercase the first character in a string. Matches Sequelize functionality. 28 | 29 | ### Parameters 30 | 31 | Name | Type | Description 32 | --- | --- | --- 33 | str | String | String to uppercase the first character of 34 | 35 | 36 | ### Return 37 | `String`: modified string 38 | 39 | 40 | 41 | 42 | ## singularize(str) -> String 43 | 44 | Returns the "singular" version of a word. As with Sequelize, this uses the [inflection 45 | library](https://github.com/dreamerslab/node.inflection) to accomplish this. 46 | 47 | ### Parameters 48 | 49 | Name | Type | Description 50 | --- | --- | --- 51 | str | String | Word to convert to its singular form 52 | 53 | 54 | ### Return 55 | `String`: singular version of the given word 56 | 57 | 58 | 59 | 60 | ## pluralize(str) -> String 61 | 62 | Returns the "plural" version of a word. As with Sequelize, this uses the [inflection 63 | library](https://github.com/dreamerslab/node.inflection) to accomplish this. 64 | 65 | ### Parameters 66 | 67 | Name | Type | Description 68 | --- | --- | --- 69 | str | String | Word to convert to its plural form 70 | 71 | 72 | ### Return 73 | `String`: plural version of the given word 74 | 75 | 76 | 77 | 78 | ## stack() 79 | 80 | Gets the current stack frame 81 | 82 | 83 | 84 | 85 | ## lodash 86 | 87 | Exposed version of the lodash library
**Alias** _ 88 | 89 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Interface for some of the Sequelize Utilities exposed in the `Sequelize.Utils` object. 5 | * 6 | * @name Utils 7 | * @fileOverview Utility function interface to match Sequelize 8 | */ 9 | 10 | var inflection = require('inflection'); 11 | 12 | /** 13 | * Uppercase the first character in a string. Matches Sequelize functionality. 14 | * 15 | * @param {String} str String to uppercase the first character of 16 | * @return {String} modified string 17 | **/ 18 | exports.uppercaseFirst = function (str) { 19 | return str[0].toUpperCase() + str.slice(1); 20 | }; 21 | 22 | /** 23 | * Lowercase the first character in a string. Matches Sequelize functionality. 24 | * 25 | * @param {String} str String to uppercase the first character of 26 | * @return {String} modified string 27 | **/ 28 | exports.lowercaseFirst = function (str) { 29 | return str[0].toLowerCase() + str.slice(1); 30 | }; 31 | 32 | /** 33 | * Returns the "singular" version of a word. As with Sequelize, this uses the [inflection 34 | * library](https://github.com/dreamerslab/node.inflection) to accomplish this. 35 | * 36 | * @param {String} str Word to convert to its singular form 37 | * @return {String} singular version of the given word 38 | **/ 39 | exports.singularize = function(str) { 40 | return inflection.singularize(str); 41 | }; 42 | 43 | /** 44 | * Returns the "plural" version of a word. As with Sequelize, this uses the [inflection 45 | * library](https://github.com/dreamerslab/node.inflection) to accomplish this. 46 | * 47 | * @param {String} str Word to convert to its plural form 48 | * @return {String} plural version of the given word 49 | **/ 50 | exports.pluralize = function(str) { 51 | return inflection.pluralize(str); 52 | }; 53 | 54 | /** 55 | * Gets the current stack frame 56 | * 57 | **/ 58 | exports.stack = function () { 59 | // Stash original stack prep 60 | var prepareStackTrace = Error.prepareStackTrace; 61 | Error.prepareStackTrace = function (_, s) { return s; }; 62 | var curr = {}; 63 | Error.captureStackTrace(curr, exports.stack); 64 | var stack = curr.stack; 65 | Error.prepareStackTrace = prepareStackTrace; 66 | return stack; 67 | }; 68 | 69 | /** 70 | * Exposed version of the lodash library 71 | * 72 | * @name lodash 73 | * @alias _ 74 | * @property 75 | **/ 76 | exports._ = exports.lodash = require('lodash'); 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sequelize Mock 2 | [![npm](https://img.shields.io/npm/v/sequelize-mock.svg)](https://www.npmjs.com/package/sequelize-mock) [![CircleCI](https://img.shields.io/circleci/project/BlinkUX/sequelize-mock.svg)](https://circleci.com/gh/BlinkUX/sequelize-mock) [![Coveralls](https://img.shields.io/coveralls/BlinkUX/sequelize-mock.svg)](https://coveralls.io/github/BlinkUX/sequelize-mock) [![MIT License](https://img.shields.io/github/license/BlinkUX/sequelize-mock.svg)](https://github.com/BlinkUX/sequelize-mock) [![Documentation Status](https://readthedocs.org/projects/sequelize-mock/badge/?version=stable)](http://sequelize-mock.readthedocs.io/en/stable/?badge=stable) 3 | 4 | A mocking interface designed for testing code that uses [Sequelize](http://sequelizejs.com). 5 | 6 | Documentation at [sequelize-mock.readthedocs.io](https://sequelize-mock.readthedocs.io/) 7 | 8 | ## Install 9 | 10 | ``` 11 | npm i sequelize-mock --save-dev 12 | ``` 13 | 14 | ## Getting Started 15 | 16 | The Mock Models created with this library function as drop in replacements for your unit testing. 17 | 18 | Start by importing the library 19 | 20 | ```javascript 21 | var SequelizeMock = require('sequelize-mock'); 22 | ``` 23 | 24 | Initialize the library as you would Sequelize 25 | 26 | ```javascript 27 | var DBConnectionMock = new SequelizeMock(); 28 | ``` 29 | 30 | Define your models 31 | 32 | ```javascript 33 | var UserMock = DBConnectionMock.define('users', { 34 | 'email': 'email@example.com', 35 | 'username': 'blink', 36 | 'picture': 'user-picture.jpg', 37 | }, { 38 | instanceMethods: { 39 | myTestFunc: function () { 40 | return 'Test User'; 41 | }, 42 | }, 43 | }); 44 | ``` 45 | 46 | Once Mock models have been defined, you can use them as drop-in replacements for your Sequelize model objects. Data is not retrieved from a database and instead is returned based on the setup of the mock objects, the query being made, and other applied or included information. 47 | 48 | For example, your code might look something like this 49 | 50 | ```javascript 51 | UserMock.findOne({ 52 | where: { 53 | username: 'my-user', 54 | }, 55 | }).then(function (user) { 56 | // `user` is a Sequelize Model-like object 57 | user.get('id'); // Auto-Incrementing ID available on all Models 58 | user.get('email'); // 'email@example.com'; Pulled from default values 59 | user.get('username'); // 'my-user'; Pulled from the `where` in the query 60 | 61 | user.myTestFunc(); // Will return 'Test User' as defined above 62 | }); 63 | ``` 64 | 65 | ## Contributing 66 | 67 | This library is under active development, so you should feel free to submit issues, questions, or pull requests. 68 | 69 | ## License 70 | 71 | Created by Blink UX and licensed under the MIT license. Check the LICENSE file for more information. 72 | -------------------------------------------------------------------------------- /docs/api/data-types.md: -------------------------------------------------------------------------------- 1 | # DataTypes 2 | 3 | These are the available DataTypes on the Sequelize class. You can access these on the 4 | class object as seen here. 5 | 6 | **Example** 7 | 8 | ```javascript 9 | var Sequelize = require('sequelize-mock'); 10 | var sequelize = new Sequelize(); 11 | 12 | Sequelize.STRING 13 | // OR ... 14 | sequelize.Sequelize.STRING 15 | ``` 16 | 17 | 18 | 19 | 20 | ## STRING 21 | 22 | Mock string data type 23 | 24 | 25 | 26 | 27 | ## CHAR 28 | 29 | Mock char data type 30 | 31 | 32 | 33 | 34 | ## TEXT 35 | 36 | Mock text data type 37 | 38 | 39 | 40 | 41 | ## INTEGER 42 | 43 | Mock integer data type 44 | 45 | 46 | 47 | 48 | ## BIGINT 49 | 50 | Mock big integer data type 51 | 52 | 53 | 54 | 55 | ## FLOAT 56 | 57 | Mock float data type 58 | 59 | 60 | 61 | 62 | ## REAL 63 | 64 | Mock real number data type 65 | 66 | 67 | 68 | 69 | ## DOUBLE 70 | 71 | Mock double data type 72 | 73 | 74 | 75 | 76 | ## DECIMAL 77 | 78 | Mock decimal data type 79 | 80 | 81 | 82 | 83 | ## BOOLEAN 84 | 85 | Mock boolean data type 86 | 87 | 88 | 89 | 90 | ## TIME 91 | 92 | Mock time data type 93 | 94 | 95 | 96 | 97 | ## DATE 98 | 99 | Mock date data type 100 | 101 | 102 | 103 | 104 | ## DATEONLY 105 | 106 | Mock date-only data type 107 | 108 | 109 | 110 | 111 | ## HSTORE 112 | 113 | Mock hstore data type 114 | 115 | 116 | 117 | 118 | ## JSON 119 | 120 | Mock JSON data type 121 | 122 | 123 | 124 | 125 | ## JSONB 126 | 127 | Mock JSONB data type 128 | 129 | 130 | 131 | 132 | ## NOW 133 | 134 | Mock now datetime data type 135 | 136 | 137 | 138 | 139 | ## BLOB 140 | 141 | Mock blob data type 142 | 143 | 144 | 145 | 146 | ## RANGE 147 | 148 | Mock range data type 149 | 150 | 151 | 152 | 153 | ## UUID 154 | 155 | Mock UUID data type 156 | 157 | 158 | 159 | 160 | ## UUIDV1 161 | 162 | Mock UUIDV1 data type 163 | 164 | 165 | 166 | 167 | ## UUIDV4 168 | 169 | Mock UUIDV4 data type 170 | 171 | 172 | 173 | 174 | ## VIRTUAL 175 | 176 | Mock virutal data type (even though all test types are technically virtual) 177 | 178 | 179 | 180 | 181 | ## ENUM 182 | 183 | Mock enum data type 184 | 185 | 186 | 187 | 188 | ## ARRAY 189 | 190 | Mock array data type 191 | 192 | 193 | 194 | 195 | ## GEOMETRY 196 | 197 | Mock geometry data type 198 | 199 | 200 | 201 | 202 | ## GEOGRAPHY 203 | 204 | Mock geography data type 205 | 206 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at dev@blinkux.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /test/utils.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var proxyquire = require('proxyquire').noCallThru(); 5 | 6 | var inflectionMock = { 7 | singularize: function (str) { return str; }, 8 | pluralize: function (str) { return str; }, 9 | }; 10 | 11 | var Utils = proxyquire('../src/utils', { 12 | 'inflection' : inflectionMock, 13 | }); 14 | 15 | describe('Utils', function () { 16 | 17 | describe('#uppercaseFirst', function () { 18 | it('should return the string with an uppercase first character', function () { 19 | Utils.uppercaseFirst('abc').should.equal('Abc'); 20 | }); 21 | it('should not modify an already uppercase character', function () { 22 | Utils.uppercaseFirst('ABC').should.equal('ABC'); 23 | }); 24 | it('should not modify number', function () { 25 | Utils.uppercaseFirst('123').should.equal('123'); 26 | }); 27 | }); 28 | 29 | describe('#lowercaseFirst', function () { 30 | it('should return the string with an lowercase first character', function () { 31 | Utils.lowercaseFirst('ABC').should.equal('aBC'); 32 | }); 33 | it('should not modify an already lowercase character', function () { 34 | Utils.lowercaseFirst('abc').should.equal('abc'); 35 | }); 36 | it('should not modify number', function () { 37 | Utils.lowercaseFirst('123').should.equal('123'); 38 | }); 39 | }); 40 | 41 | describe('#singularize', function () { 42 | var singularize; 43 | beforeEach(function () { 44 | singularize = inflectionMock.singularize; 45 | }); 46 | 47 | afterEach(function () { 48 | inflectionMock.singularize = singularize; 49 | }); 50 | 51 | it('should call into inflection (like Sequelize does)', function () { 52 | var count = 0; 53 | inflectionMock.singularize = function (str) { 54 | count++; 55 | return str; 56 | }; 57 | Utils.singularize('strings'); 58 | count.should.equal(1); 59 | }); 60 | }); 61 | 62 | describe('#pluralize', function () { 63 | var pluralize; 64 | beforeEach(function () { 65 | pluralize = inflectionMock.pluralize; 66 | }); 67 | 68 | afterEach(function () { 69 | inflectionMock.pluralize = pluralize; 70 | }); 71 | 72 | it('should call into inflection (like Sequelize does)', function () { 73 | var count = 0; 74 | inflectionMock.pluralize = function (str) { 75 | count++; 76 | return str; 77 | }; 78 | Utils.pluralize('string'); 79 | count.should.equal(1); 80 | }); 81 | }); 82 | 83 | describe('#stack', function () { 84 | var captureStack; 85 | beforeEach(function () { 86 | captureStack = Error.captureStackTrace; 87 | }); 88 | 89 | afterEach(function () { 90 | Error.captureStackTrace = captureStack; 91 | }); 92 | 93 | // TODO: This test is more integration than unit. Need to move this 94 | // code over to a different test suite once one exists. 95 | it('should capture and return the stack trace', function () { 96 | /* 97 | var arg1, arg2; 98 | Error.captureStackTrace = function (obj, fn) { 99 | arg1 = obj; 100 | arg2 = fn; 101 | obj.stack = 'bar'; 102 | }; 103 | */ 104 | 105 | var ret = Utils.stack(); 106 | 107 | /* 108 | // We need to restore nomality here so that the asserts and things can work properly 109 | Error.captureStackTrace = captureStack; 110 | 111 | should(arg1).be.Object(); 112 | should(arg2).be.Function(); 113 | 114 | ret.should.equal('bar'); 115 | */ 116 | 117 | ret.should.be.an.Array(); 118 | should(ret[0].getFileName).be.a.Function(); 119 | }); 120 | 121 | }); 122 | 123 | }); 124 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | v0.10.2 - 8ed75d2 - Dec 4th 2017 4 | * Fix for DataTypes not being exposed during `sequelize.import` calls 5 | * *DEV* Added .editorconfig file to normalize editors and minimize whitespace changes 6 | 7 | v0.10.1 - 43cc668 - Nov 20th 2017 8 | * Fix for relative file paths in the `sequelize.import` function 9 | 10 | v0.10.0 - 0a7f270f - Oct 24th 2017 11 | * Add `sequelize.import` support 12 | * Add `sequelize.$overrideImport` test functionality to allow overriding imported module paths 13 | * Add support for `Model.findAndCount()` and it's alias `Model.findAndCountAll()` (thanks to @TerryMooreII) 14 | 15 | v0.9.1 - 3aeaa05 - Sep 21st 2017 16 | * Add `authenticate()` to `sequelize` which always resolves (thanks to @vFederer) 17 | * Fix a few documentation issues 18 | 19 | v0.9.0 - c75d75e - Jul 28th 2017 20 | * Add DataType mock objects for use with any DataType funcitonality 21 | * Add support for conditional query result handling (thanks to @scinos) 22 | * Add support for `instance.get({ plain: true })` (thanks to @fredstrange) 23 | * Add support for `sequelize.model` and `sequelize.isDefined` (thanks to @Thylossus) 24 | * Fix setting `isNewRecord` on instances to false after saving (issue #19; thanks to @scinos) 25 | 26 | v0.8.1 - c2527de - May 22nd 2017 27 | * Fix creating Associations throws an error (issue #10) 28 | 29 | v0.8.0 - 60397ec - May 18th 2017 30 | * Add `$queueResult()`, `$queueFailure()`, and `$clearQueue()` test methods to `Sequelize` and `Model` objects 31 | * Add `QueryInterface` object to support test result mocking 32 | * Add `getQueryInterface()` to Sequelize instances which will get the associated `QueryInterface` object 33 | * Add getters/setters for accessing Instance data values via using the simple object syntax (e.g. `instance.foo = 'bar'`) 34 | * Add support for `hasPrimary` and `timestamps` options on Models 35 | * Add `instance.Model` reference to the calling `Model` object that the instance is based on 36 | * Change `Model.Instance` can now be directly used to create a mock `Instance` object of the given model 37 | * BREAKING The `query()` method for Sequelize instances will now throw instead of returning a rejected `Promise` by default. See the `$queueResult` or `$queueFailure` methods for getting proper returns from calls to this function 38 | * BREAKING The `Instance` object should now only be instantiated by going through a Model using either `model.build()` or `Model.Instance` 39 | * *DEV* Added HTML code coverage report to default `npm test` run 40 | 41 | v0.7.0 - bcfb924 - Feb 3rd 2017 42 | * Add `Model.bulkCreate()` support 43 | * Add `Instance.set()` Object parameter support 44 | * Add `Instance.get()` no parameter support to get a cloned Object of all of the values 45 | * Add `Instance.destroy()` will now set a `deletedAt` property to the current date 46 | * Add `Sequelize.Utils._` which points to the lodash library 47 | * Add `options` parameter in the `Sequelize` constructor 48 | * Add `getDialect()` to Sequelize instances which will return the value from the `options` passed into the constructor 49 | * Add `Sequelize.Instance` which points to the mock Instance class 50 | * Change `Model.destroy()` will return the value of `options.limit` if that value exists and is a number 51 | * BREAKING Removed `Model.generateTestModel()`, `Model.generateModelPromise()`, and `Model.generateModelsPromises()` 52 | * *DEV* Added documentation generation via `npm run docs-gen` 53 | 54 | v0.6.1 - d65cbc9 - Dec 7th 2016 55 | * Fix `Instance` initialization modifying the original passed in object 56 | 57 | v0.6.0 - 4315930 - Dec 6th 2016 58 | * Add Sequelize Error object mocks 59 | * Add `validate()` function + calls in the appropriate places 60 | * Add `$addValidationError(col[, message[, type]])` to Instances which will trigger a validation error on the next call to `.validate()` from any of places it can be called 61 | * Add `$removeValidationError(col)` to Instances which will remove any validation errors for that column 62 | * Add `$clearValidationErrors()` to Instances which removes all validation errors from all of the columns 63 | 64 | v0.5.1 - b9a34d2 - Nov 4th 2016 65 | * Add `Model.unscoped()` 66 | * Add `Model.insertOrUpdate()` as alias for `Model.upsert()` 67 | * Fix for `Sequelize.Utils.lowercaseFirst()` 68 | * Fix `Model.update()` affected rows return value to be array 69 | 70 | v0.5.0 - 3d436f7 - Oct 7th 2016 71 | * Initial Public Release 72 | -------------------------------------------------------------------------------- /docs/docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | The easiest way to get Sequelize Mock is to install it through NPM. Run the following command to install Sequelize Mock and save it to your developer dependencies. 4 | 5 | ```sh 6 | npm install --save-dev sequelize-mock 7 | ``` 8 | 9 | Alternatively you can install the library by [downloading the latest code](https://github.com/BlinkUX/sequelize-mock/releases). 10 | 11 | ## Make Fake DB Connection 12 | 13 | Because Sequelize Mock is meant to be a drop-in replacement for testing, it mirrors Sequelize in many ways including the base object's functionality. As such, you'll want to setup a fake DB connection, though you don't need any actual connection data. 14 | 15 | ```javascript 16 | var SequelizeMock = require('sequelize-mock'); 17 | 18 | var dbMock = new SequelizeMock(); 19 | ``` 20 | 21 | ## Creating Mock Objects 22 | 23 | Once you've installed the module, you can include it in your test code and define your Mock objects. Mock objects are defined with `db.define([tablename, ] defaultValues, options)`. 24 | 25 | ```javascript 26 | var UserMock = dbMock.define('user', { 27 | firstName: 'Jane', 28 | lastName: 'Doe', 29 | email: 'test@example.com' 30 | }, { 31 | instanceMethods: { 32 | getFullName: function () { 33 | return this.get('firstName') + ' ' + this.get('lastName'); 34 | }, 35 | }, 36 | }); 37 | ``` 38 | 39 | ## Swapping Model for Mocks 40 | 41 | ### Using `Sequelize.import()` 42 | 43 | In SequelizeMock, we provide the same `import()` functionality that Sequelize provides, with the added ability to override any given imported path with your mock paths. 44 | 45 | Using the `$overrideImport` method, you can simply define a mapping between your model and your mock object. 46 | 47 | ```javascript 48 | // If your Sequelize code looks like this 49 | sequelize.import('./users/model.js'); 50 | 51 | // Your test code can simply override the import so your code will function as expected 52 | sequelize.$overrideImport('./users/model.js', './users/mock.js'); 53 | 54 | // Now an import for your users model will actually import your user mock file instead 55 | sequelize.import('./users/model.js'); // Will load './users/mock.js' instead 56 | ``` 57 | 58 | **Note that relative paths are relative to the file calling the `import` function, and not to your test code.** 59 | 60 | ### Using `require()` 61 | 62 | There are a number of libraries out there that can be used to replace `require()` dependencies with mock objects. You can simply use one of these libraries to replace the Sequelize Mock object into your code and it should run exactly as you would expect. 63 | 64 | Here is an example of doing so with [proxyquire](https://www.npmjs.com/package/proxyquire) 65 | 66 | ```javascript 67 | var UserMock = dbMock.define('user', { 68 | username: 'testuser', 69 | }); 70 | 71 | var proxyquire = require('proxyquire'); 72 | 73 | var myModule = proxyquire('user.controller', { 74 | './user.model': UserMock 75 | }); 76 | ``` 77 | 78 | #### Some Mock Injection Libraries 79 | * [proxyquire](https://www.npmjs.com/package/proxyquire) 80 | * [mockery](https://www.npmjs.com/package/mockery) 81 | * [mock-require](https://www.npmjs.com/package/mock-require) 82 | 83 | ## Writing Tests 84 | 85 | Once you've got your mock models created, you're ready to start testing! Your mocks will function in almost the same way as your original models, with the exception that you don't need to rely on a database to be able to run tests. 86 | 87 | Here is a sample function I may want to test with my mock objects 88 | 89 | ```javascript 90 | exports.getUserEmail = function (userId) { 91 | return User.findOne({ 92 | where: { 93 | id: userId, 94 | }, 95 | }).then(function (user) { 96 | 97 | // Return user email in format for email client use 98 | return user.getFullName() + ' <' + user.get('email') + '>'; 99 | 100 | }) 101 | }; 102 | ``` 103 | 104 | For this function, a test might look something like this: 105 | 106 | ```javascript 107 | describe('#getUserEmail', function () { 108 | it("should return a user's email in NAME format", function (done) { 109 | myModule.getUserEmail(1).then(function (email) { 110 | 111 | // Given the defined Mock object above, the default values should be used for all the values 112 | email.should.equal('Jane Doe ') 113 | 114 | done(); 115 | 116 | }).catch(done); 117 | }); 118 | }); 119 | ``` 120 | -------------------------------------------------------------------------------- /docs/images/text.logo.svg: -------------------------------------------------------------------------------- 1 | text.logo -------------------------------------------------------------------------------- /docs/api/queryinterface.md: -------------------------------------------------------------------------------- 1 | # QueryInterface 2 | 3 | To support running mock queries, we use this object to queue results for queries. This 4 | is to provide full control over the objects that are returned and do so in a way that 5 | allows you to target specific code paths. 6 | 7 | Queries are queued in a "first in, last out" manner, so they should be inserted in the 8 | order your code would expect them in. The queue itself does no checking or validation 9 | for what types of objects are being passed around except in the case of failures 10 | (see `$queueFailure`). 11 | 12 | **Note:** This is not an equivalent mock of Sequelize's built in `QueryInterface` at 13 | the moment. The built in object for Sequelize is undocumented and is marked as `@private` 14 | in their code, meaning it is not likely something to be relied on. If this changes it 15 | can be mocked here. Functions have been prefixed with the mock prefix (`$`) for this 16 | reason. 17 | 18 | 19 | 20 | 21 | ## new QueryInterface([options]) 22 | 23 | The `QueryInterface` class is used to provide common mock query functionality. New 24 | instances of this class should mostly be created internally, however the functions on 25 | the class are exposed on objects utilize this class. 26 | 27 | ### Parameters 28 | 29 | Name | Type | Description 30 | --- | --- | --- 31 | [options] | Object | Options for the query interface to use 32 | [options.parent] | QueryInterface | Parent `QueryInterface` object to propagate up to 33 | [options.stopPropagation] | Boolean | Flag indicating if we should not propagate to the parent 34 | [options.createdDefault] | Boolean | Default value to be used for if something has been created if one is not passed in by the query. Defaults to true 35 | [options.fallbackFn] | Function | Default function to call as a fallback if nothing is left in the queue and a fallback function is not passed in with the query 36 | 37 | 38 | 39 | 40 | 41 | ## $queueResult(result, [options]) -> QueryInterface 42 | 43 | Queue a new success result from the mock database 44 | 45 | ### Parameters 46 | 47 | Name | Type | Description 48 | --- | --- | --- 49 | result | Any | The object or value to be returned as the result of a query 50 | [options] | Object | Options used when returning the result 51 | [options.wasCreated] | Boolean | Optional flag if a query requires a `created` value in the return indicating if the object was "created" in the DB 52 | [options.affectedRows] | Array.<Any> | Optional array of objects if the query requires an `affectedRows` return value 53 | 54 | 55 | ### Return 56 | `QueryInterface`: self 57 | 58 | 59 | 60 | 61 | ## $queueFailure(error, [options]) -> QueryInterface 62 | 63 | Queue a new error or failure result from the mock database. This will cause a query 64 | to be rejected with the given error/failure object. The error is converted into a 65 | `BaseError` object unless specified by the `options.convertNonErrors` parameter.
**Alias** $queueError 66 | 67 | ### Parameters 68 | 69 | Name | Type | Description 70 | --- | --- | --- 71 | error | Any | The object or value to be returned as the failure for a query 72 | [options] | Object | Options used when returning the result 73 | [options.convertNonErrors] | Boolean | Flag indicating if non `Error` objects should be allowed. Defaults to true 74 | 75 | 76 | ### Return 77 | `QueryInterface`: self 78 | 79 | 80 | 81 | 82 | ## $useHandler(handler) -> QueryInterface 83 | 84 | Adds a new query handler from the mock database 85 | 86 | ### Parameters 87 | 88 | Name | Type | Description 89 | --- | --- | --- 90 | handler | Function | The function that will be invoked with the query. 91 | 92 | 93 | ### Return 94 | `QueryInterface`: self 95 | 96 | 97 | 98 | 99 | ## $clearQueue([options]) -> QueryInterface 100 | 101 | Clears any queued query results
**Alias** $queueClear 102 | 103 | ### Parameters 104 | 105 | Name | Type | Description 106 | --- | --- | --- 107 | [options] | Object | Options used when returning the result 108 | [options.propagateClear] | Boolean | Propagate this clear up to any parent `QueryInterface`s. Defaults to false 109 | 110 | 111 | ### Return 112 | `QueryInterface`: self 113 | 114 | 115 | 116 | 117 | ## $clearHandlers([options]) -> QueryInterface 118 | 119 | Clears any handles
**Alias** $handlersClear 120 | 121 | ### Parameters 122 | 123 | Name | Type | Description 124 | --- | --- | --- 125 | [options] | Object | Options used when returning the result 126 | [options.propagateClear] | Boolean | Propagate this clear up to any parent `QueryInterface`s. Defaults to false 127 | 128 | 129 | ### Return 130 | `QueryInterface`: self 131 | 132 | 133 | 134 | 135 | ## $clearResults([options]) -> QueryInterface 136 | 137 | Clears any reesults (both handlers and queued results)
**Alias** $handlersClear 138 | 139 | ### Parameters 140 | 141 | Name | Type | Description 142 | --- | --- | --- 143 | [options] | Object | Options used when returning the result 144 | [options.propagateClear] | Boolean | Propagate this clear up to any parent `QueryInterface`s. Defaults to false 145 | 146 | 147 | ### Return 148 | `QueryInterface`: self 149 | 150 | 151 | 152 | 153 | ## $query([options]) -> Promise 154 | 155 | This is the mock method for getting results from the `QueryInterface`. This function 156 | will get the next result in the queue and return that wrapped in a promise. 157 | 158 | ### Parameters 159 | 160 | Name | Type | Description 161 | --- | --- | --- 162 | [options] | Object | Options used for this query 163 | [options.fallbackFn] | Function | A fallback function to run if there are no results queued 164 | [options.includeCreated] | Boolean | Flag indicating if a `created` value should be returned with the result for this query. Defaults to false 165 | [options.includeAffectedRows] | Boolean | Flag indicating if the query expects `affectedRows` in the returned result parameters. Defautls to false 166 | [options.stopPropagation] | Boolean | Flag indicating if result queue propagation should be stopped on this query. Defaults to false 167 | [options.query] | String | Name of the original query: "findOne", "findOrCreate", "upsert", etc. 168 | [options.queryOptions] | Object | Array with the arguments passed to the original query method 169 | 170 | 171 | ### Return 172 | `Promise`: resolved or rejected promise from the next item in the review queue 173 | 174 | -------------------------------------------------------------------------------- /docs/api/instance.md: -------------------------------------------------------------------------------- 1 | # Instance 2 | 3 | Instances are the individual results from a Sequelize call. In SequelizeMock, these objects 4 | have most of the same attributes as in the normal Sequelize Instance, however they also have 5 | a few additional bits of functionality. 6 | 7 | Values for instances are defined by defaults provided to the Model Mock on definition and 8 | they are overriden by values provided during queries. The major exceptions are the `id`, 9 | `createdAt`, and `updatedAt` values, which are available on all instances regardless of if 10 | they are in the query or default values. This is the same way Sequelize will add these to 11 | your instances. 12 | 13 | The `id` value is an auto-incrementing value with each query (unless provided as a part of 14 | your query). The `createdAt` and `updatedAt` values are set to the current timestamp at the 15 | time the instance is created. 16 | 17 | Additionally, there are a few additional test methods you can use to test functionality 18 | coming from Sequelize behavior. These methods are prefixed with a `$` so as to distinguish 19 | them from mocked native Sequelize functionality. Any test specific properties also include 20 | a double underscore (`__`) so that they are also uniquely distinguished from mock internals. 21 | 22 | 23 | 24 | 25 | ## new Instance(defaults, [obj]) 26 | 27 | Instance Mock Object. Creation of this object should almost always be handled through the 28 | `Model` class methods. In cases when you need to create an `Instance` directly, providing 29 | the `defaults` parameter should be enough, as the `obj` overrides parameter is optional. 30 | 31 | ### Parameters 32 | 33 | Name | Type | Description 34 | --- | --- | --- 35 | defaults | Object | The default values. This will come from the Model when created via that class 36 | [obj] | Object | Any overridden values that should be specific to this instance 37 | 38 | 39 | 40 | 41 | 42 | ### .dataValues 43 | 44 | As with Sequelize, we include a `dataValues` property which contains the values for the 45 | instance. As with Sequelize, you should use other methods to edit the values for any 46 | code that will also interact with Sequelize. 47 | 48 | For test code, when possible, we also recommend you use other means to validate. But 49 | this object is also available if needed.
**Alias** _values 50 | 51 | 52 | 53 | 54 | ## $addValidationError(col, [message], [type]) -> Instance 55 | 56 | Create a new validation error to be triggered the next time a validation would run. Any 57 | time Sequelize code would go to the database, it will trigger a check for any validation 58 | errors that should be thrown. This allows you to test any validation failures in a more 59 | unit-testing focused manner. 60 | 61 | Once a validation has occured, all validation errors will be emptied from the queue and 62 | returned in a single `ValidationError` object. 63 | 64 | If you do add validation errors for a test, be sure to clear the errors after each test 65 | so you don't fail your next test in case a validation does not occur. You can do so by 66 | calling the [`$clearValidationErrors`](#clearValidationErrors) method. 67 | 68 | **Example** 69 | 70 | ```javascript 71 | myUser.$addValidationError('email', 'Not a valid email address', 'InvalidEmail'); 72 | 73 | // ... 74 | 75 | myUser.save().then(function () {}, function (error) { 76 | // error will be a ValidationErrorItem 77 | error.errors[0].type === 'InvalidEmail'; 78 | }); 79 | ``` 80 | 81 | **See** 82 | 83 | - [clearValidationErrors](#clearValidationErrors) 84 | 85 | ### Parameters 86 | 87 | Name | Type | Description 88 | --- | --- | --- 89 | col | String | Field to add validation error for 90 | [message] | String | Message the error should contain 91 | [type] | String | Type of the validation error 92 | 93 | 94 | ### Return 95 | `Instance`: Instance object for chainability 96 | 97 | 98 | 99 | 100 | ## $removeValidationError(col) -> Instance 101 | 102 | Removes all validation errors that would be triggered against a specific column. 103 | 104 | ### Parameters 105 | 106 | Name | Type | Description 107 | --- | --- | --- 108 | col | String | Field to remove validation errors for 109 | 110 | 111 | ### Return 112 | `Instance`: The object for chainability 113 | 114 | 115 | 116 | 117 | ## $clearValidationErrors() -> Instance 118 | 119 | Removes all validation errors for every column. 120 | 121 | **Example** 122 | 123 | ```javascript 124 | beforeEach(function () { 125 | myUser = new Instance({ 'name': 'Test' }); 126 | }); 127 | 128 | afterEach(function () { 129 | // Recommended that you always run $clearValidationErrors after 130 | // each test so that you don't pollute your test data 131 | myUser.$clearValidationErrors(); 132 | }); 133 | ``` 134 | 135 | ### Return 136 | `Instance`: The object for chainability 137 | 138 | 139 | 140 | 141 | ## set(key, [val]) -> Instance 142 | 143 | Sets the value for the given key. Also accepts an object as the first parameter 144 | and it will loop through the key/value pairs and set each. 145 | 146 | ### Parameters 147 | 148 | Name | Type | Description 149 | --- | --- | --- 150 | key | String, Object | Key to set the value for. If `key` is an Object, each key/value pair will be set on the Instance 151 | [val] | Any | Any value to set on the Instance. If `key` is an Object, this parameter is ignored. 152 | 153 | 154 | ### Return 155 | `Instance`: The object for chainability 156 | 157 | 158 | 159 | 160 | ## get([key]) -> Any, Object 161 | 162 | If no key is provided, it will return all values on the Instance. 163 | 164 | If a key is provided, it will return that value 165 | 166 | ### Parameters 167 | 168 | Name | Type | Description 169 | --- | --- | --- 170 | [key] | String, Object | Key to get the value for 171 | 172 | 173 | ### Return 174 | `Any, Object`: either the value of the key, or all the values if there is no key or key is an object with plain set to true: {plain: true} 175 | 176 | 177 | 178 | 179 | ## validate() -> Promise.<ValidationErrorItem|undefined> 180 | 181 | Triggers validation. If there are errors added through `$addValidationError` they will 182 | be returned and the queue of validation errors will be cleared. 183 | 184 | As with Sequelize, this will **resolve** any validation errors, not throw the errors. 185 | 186 | ### Return 187 | `Promise.`: will resolve with any errors, or with nothing if there were no errors queued. 188 | 189 | 190 | 191 | 192 | ## save() -> Promise.<Instance> 193 | 194 | Because there is no database that it saves to, this will mainly trigger validation of 195 | the Instance and reject the promise if there are any validation errors. 196 | 197 | ### Return 198 | `Promise.`: Instance if there are no validation errors, otherwise it will reject the promise with those errors 199 | 200 | 201 | 202 | 203 | ## destroy() -> Promise 204 | 205 | This simply sets the `deletedAt` value and has no other effect on the mock Instance 206 | 207 | ### Return 208 | `Promise`: will always resolve as a successful destroy 209 | 210 | 211 | 212 | 213 | ## reload() -> Promise.<Instance> 214 | 215 | This has no effect on the Instance 216 | 217 | ### Return 218 | `Promise.`: will always resolve with the current instance 219 | 220 | 221 | 222 | 223 | ## update() -> Promise.<Instance> 224 | 225 | Acts as a `set()` then a `save()`. That means this function will also trigger validation 226 | and pass any errors along 227 | 228 | **See** 229 | 230 | - [set](#set) 231 | - [save](#save) 232 | 233 | ### Return 234 | `Promise.`: Promise from the save function 235 | 236 | 237 | 238 | 239 | ## toJSON() -> Object 240 | 241 | Returns all the values in a JSON representation.
**Alias** toJson 242 | 243 | ### Return 244 | `Object`: all the values on the object 245 | 246 | -------------------------------------------------------------------------------- /docs/api/errors.md: -------------------------------------------------------------------------------- 1 | # Errors 2 | 3 | Error objects are exposed via the base SequelizeMock object and can be used in much the same 4 | way that native Sequelize errors can be used. Because no queries are run, any errors that 5 | would normally contain a `sql` property include it with a value indicating this is test code 6 | 7 | *NOTE:* Error names are copied exactly from Sequelize in the off-chance code is relying on 8 | error name instead of equality or instanceof 9 | 10 | 11 | 12 | 13 | ## new BaseError() 14 | 15 | BaseError is the base class most other Sequelize Mock Error classes inherit from 16 | Mostly based off of [the Sequelize.js equivalent code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/errors.js#L20)
**Extends** Error
**Alias** Error 17 | 18 | 19 | 20 | 21 | ## new ValidationError(msg, [errors]) 22 | 23 | ValidationError is the class used for validation errors returned by Sequelize Mock. 24 | The most common way you will see this error is using the `$addValidationError` test 25 | helper method on instances. 26 | 27 | ValidationErrors contain an `errors` property which contains a list of the specific 28 | validation errors that were encountered. For example, if two validation errors were 29 | encountered, it will be an array of length two. Objects in this array are of type 30 | `ValidationErrorItem`.
**Extends** [BaseError](#BaseError) 31 | 32 | **See** 33 | 34 | - [ValidationErrorItem](#ValidationErrorItem) 35 | 36 | ### Parameters 37 | 38 | Name | Type | Description 39 | --- | --- | --- 40 | msg | String | Error Message 41 | [errors] | Array.<ValidationErrorItem> | Error Message 42 | 43 | 44 | 45 | 46 | 47 | ### .errors 48 | 49 | Array of all validation errors that were encountered with 50 | this validation 51 | 52 | 53 | 54 | 55 | ### #get(path) -> Array.<ValidationErrorItem> 56 | 57 | Get the validation error for a particular field 58 | 59 | #### Parameters 60 | 61 | Name | Type | Description 62 | --- | --- | --- 63 | path | String | Field you would like the list of validation errors for 64 | 65 | 66 | #### Return 67 | `Array.`: Array of validation errors for the given field 68 | 69 | 70 | 71 | 72 | ## new ValidationErrorItem(msg, type, path, value) 73 | 74 | A specific validation failure result 75 | 76 | **See** 77 | 78 | - ValidationError 79 | 80 | ### Parameters 81 | 82 | Name | Type | Description 83 | --- | --- | --- 84 | msg | String | Error Message 85 | type | String | Type of the error 86 | path | String | Field with the error 87 | value | String | Value with the error 88 | 89 | 90 | 91 | 92 | 93 | ## new DatabaseError(parentError) 94 | 95 | The generic base Database Error class
**Extends** [BaseError](#BaseError) 96 | 97 | ### Parameters 98 | 99 | Name | Type | Description 100 | --- | --- | --- 101 | parentError | Error | Original error that triggered this error 102 | 103 | 104 | 105 | 106 | 107 | ### .sql 108 | 109 | Ordinarily contains the SQL that triggered the error. 110 | However, because test code does not go to the DB and 111 | therefore does not have SQL, this will contain an SQL 112 | comment stating it is from Sequelize Mock. 113 | 114 | 115 | 116 | 117 | ## new TimeoutError() 118 | 119 | Database timeout error
**Extends** [DatabaseError](#DatabaseError) 120 | 121 | 122 | 123 | 124 | ## new UniqueConstraintError(opts) 125 | 126 | Unique constraint violation error
**Extends** [ValidationError](#ValidationError), [DatabaseError](#DatabaseError) 127 | 128 | ### Parameters 129 | 130 | Name | Type | Description 131 | --- | --- | --- 132 | opts | Object | Unique constraint error information 133 | opts.message | String | Error message 134 | [opts.errors] | Array.<ValidationErrorItem> | Errors that were encountered 135 | [opts.fields] | Array.<String> | Fields that violated the unique constraint 136 | 137 | 138 | 139 | 140 | 141 | ## new ForeignKeyConstraintError(opts) 142 | 143 | Foreign key constraint violation error
**Extends** [BaseError](#BaseError), [DatabaseError](#DatabaseError) 144 | 145 | ### Parameters 146 | 147 | Name | Type | Description 148 | --- | --- | --- 149 | opts | Object | Unique constraint error information 150 | opts.message | String | Error message 151 | [opts.fields] | Array.<String> | Fields that violated the unique constraint 152 | opts.table | String | Name of the table the failure is for 153 | opts.value | String | Invalid value 154 | opts.index | String | Name of the foreign key index that was violated 155 | 156 | 157 | 158 | 159 | 160 | ## new ExclusionConstraintError(opts) 161 | 162 | Exclusion constraint violation error
**Extends** [BaseError](#BaseError), [DatabaseError](#DatabaseError) 163 | 164 | ### Parameters 165 | 166 | Name | Type | Description 167 | --- | --- | --- 168 | opts | Object | Unique constraint error information 169 | opts.message | String | Error message 170 | [opts.fields] | Array.<String> | Fields that violated the unique constraint 171 | opts.table | String | Name of the table the failure is for 172 | opts.constraint | String | Name of the constraint that was violated 173 | 174 | 175 | 176 | 177 | 178 | ## new ConnectionError(parentError) 179 | 180 | Generic database connection error
**Extends** [BaseError](#BaseError) 181 | 182 | ### Parameters 183 | 184 | Name | Type | Description 185 | --- | --- | --- 186 | parentError | Error | Original error that triggered this error 187 | 188 | 189 | 190 | 191 | 192 | ## new ConnectionRefusedError(parentError) 193 | 194 | Database connection refused error
**Extends** [ConnectionError](#ConnectionError) 195 | 196 | ### Parameters 197 | 198 | Name | Type | Description 199 | --- | --- | --- 200 | parentError | Error | Original error that triggered this error 201 | 202 | 203 | 204 | 205 | 206 | ## new AccessDeniedError(parentError) 207 | 208 | Database access denied connection error
**Extends** [ConnectionError](#ConnectionError) 209 | 210 | ### Parameters 211 | 212 | Name | Type | Description 213 | --- | --- | --- 214 | parentError | Error | Original error that triggered this error 215 | 216 | 217 | 218 | 219 | 220 | ## new HostNotFoundError(parentError) 221 | 222 | Database host not found connection error
**Extends** [ConnectionError](#ConnectionError) 223 | 224 | ### Parameters 225 | 226 | Name | Type | Description 227 | --- | --- | --- 228 | parentError | Error | Original error that triggered this error 229 | 230 | 231 | 232 | 233 | 234 | ## new HostNotReachableError(parentError) 235 | 236 | Database host not reachable connection error
**Extends** [ConnectionError](#ConnectionError) 237 | 238 | ### Parameters 239 | 240 | Name | Type | Description 241 | --- | --- | --- 242 | parentError | Error | Original error that triggered this error 243 | 244 | 245 | 246 | 247 | 248 | ## new InvalidConnectionError(parentError) 249 | 250 | Database invalid connection error
**Extends** [ConnectionError](#ConnectionError) 251 | 252 | ### Parameters 253 | 254 | Name | Type | Description 255 | --- | --- | --- 256 | parentError | Error | Original error that triggered this error 257 | 258 | 259 | 260 | 261 | 262 | ## new ConnectionTimedOutError(parentError) 263 | 264 | Database connection timed out error
**Extends** [ConnectionError](#ConnectionError) 265 | 266 | ### Parameters 267 | 268 | Name | Type | Description 269 | --- | --- | --- 270 | parentError | Error | Original error that triggered this error 271 | 272 | 273 | 274 | 275 | 276 | ## new InstanceError(message) 277 | 278 | Error from a Sequelize (Mock) Instance
**Extends** [BaseError](#BaseError) 279 | 280 | ### Parameters 281 | 282 | Name | Type | Description 283 | --- | --- | --- 284 | message | Error | Error message 285 | 286 | 287 | -------------------------------------------------------------------------------- /docs/docs/mock-queries.md: -------------------------------------------------------------------------------- 1 | # Querying Mock Objects 2 | 3 | Sequelize Mock utilizes a special `QueryInterface` to be utilized for testing. This interface is provided and attached to give you full control over how results for queries may be returned and allows you to tweak the particulars of returned results as you need for your testing. 4 | 5 | When you call into a function that would run a DB query in standard Sequelize, this function instead calls into our special test `QueryInterface` object to get the results for the query. Query results can be either manually queued up based on what your code may expect next, automatically filled in from the `Model`'s defined default values, or dynamically generated. 6 | 7 | Each query will return the first available result from the list. 8 | 9 | 1. The value generated by a query handler 10 | 2. If not available, the next result queued for the object the query is being run on 11 | 3. If not available, it will return the next result queued for any parent object. For Models, this is the `Sequelize` object the Model was defined with (using `db.define`) 12 | 4. If not available and being called on a `Model`, it will return an automatically generated result based on the defaults of the `Model` being queried, *unless configured otherwise* 13 | 5. Any fallback function defined in the configuration for the object 14 | 15 | If none of the above resolve to a returnable result, a `EmptyQueryQueueError` excepction will be thrown. 16 | 17 | ## Configuration 18 | 19 | There are a few available configuration options that can be used to modify how query results are resolved. 20 | 21 | ### Sequelize Configuration 22 | 23 | When declaring a new `SequelizeMock` object, configuration can be passed in the `options` parameter. 24 | 25 | Option | Type | Description 26 | --- | --- | --- 27 | `autoQueryFallback` | Boolean | Flag indicating if defined `Model` objects should fallback to the automatic query result generation functionality. Defaults to true. Can be overridden by `Model` configuration 28 | `stopPropagation` | Boolean | Whether `Model`'s defined on this should propagate queries up to this object if they are not resolved by the `Model` themselves. Defaults to false. Can be overridden by `Model` configuration 29 | 30 | ### Model Configuration 31 | 32 | When declaring a new `Model`, configuration can be passed in through the `options` parameter. 33 | 34 | Option | Type | Description 35 | --- | --- | --- 36 | `autoQueryFallback` | Boolean | Flag indicating if `Model` should fallback to the automatic query result generation functionality. Defaults to true unless inherited from `SequelizeMock` object 37 | `stopPropagation` | Boolean | Whether queries should attempt to propagate up to the defining `SequelizeMock` if they are not resolved by the `Model` themselves. Defaults to false unless inherited from `SequelizeMock` object 38 | 39 | 40 | ## Query handlers 41 | 42 | Query results can be generated using query handlers. When multiple handlers are added to the QueryInterface, they will be called in order until one of them returns a valud result. If no handler returns a result, then QueryInterface will get the value from the list of queued results. 43 | 44 | The handler will receive two arguments 45 | 46 | - The name of the original query method: "findOne", "update", "destroy"... 47 | - The list of arguments passed to the original query method. 48 | 49 | Those arguments can be used to filter the results. For example: 50 | 51 | ```javascript 52 | User.$useHandler(function(query, queryOptions, done) { 53 | if (query === 'findOne') { 54 | if (queryOptions[0].where.id === 42) { 55 | // Result found, return it 56 | return User.build({ id: 42, name: 'foo' }); 57 | } else { 58 | // No results 59 | return null; 60 | } 61 | } 62 | }); 63 | 64 | User.findOne({where: {id: 42}}).then(function (user) { 65 | user.get('id'); // === 1337 66 | user.get('name'); // === 'foo' 67 | }); 68 | User.findOne({where: {id: 1}}).then(function (user) { 69 | user // === null 70 | }); 71 | ``` 72 | 73 | The handler must return a value with the result of the query, or `undefined` if the handler can't generate a value for that query. If a promise is returned, the status of that promise will be preserved by the query: 74 | 75 | ```javascript 76 | User.$useHandler(function(query, queryOptions, done) { 77 | if (query === "findOne") { 78 | return Promise.resolve(myValue); 79 | } else if (query === "destroy") { 80 | return Promise.reject(new Sequelize.Error("DB down")); 81 | } else { 82 | // This handler can handle this query 83 | return; 84 | } 85 | }); 86 | User.findOne().then(function (user) { 87 | user; // === myValue 88 | }); 89 | User.destroy().catch(function (error) { 90 | error.message; // === "DB down"; 91 | }); 92 | ``` 93 | 94 | Multiple handlers can be added. They are called in sequence until one of them returns a value: 95 | 96 | ```javascript 97 | User.$useHandler(function(query, queryOptions, done) { 98 | if (query === "findOne") return User.build({id: 1}); 99 | }); 100 | User.$useHandler(function(query, queryOptions, done) { 101 | if (query === "findById") return User.build({id: queryOptions[0]}); 102 | }); 103 | User.$useHandler(function(query, queryOptions, done) { 104 | if (query === "findOrCreate") return User.build({id:1000}); 105 | }); 106 | 107 | User.findOne().then(function (user) { 108 | user.get('id'); // === 1 109 | }); 110 | User.findById(123).then(function (user) { 111 | user.get('id'); // === 123 112 | }); 113 | User.findOrCreate().then(function (user) { 114 | user.get('id'); // === 1000 115 | }); 116 | ``` 117 | 118 | If a handler wants to return `undefined` as the actual result of the query, it must be wrapped in a promise: 119 | 120 | ```javascript 121 | User.$useHandler(function(query, queryOptions, done) { 122 | return Promise.resolve(undefined) 123 | }); 124 | User.$useHandler(function(query, queryOptions, done) { 125 | // This handler is never called because the previous handler returned a value (a promise) 126 | }); 127 | 128 | User.find().then(function (user) { 129 | typeof user; // === 'undefined' 130 | }); 131 | ``` 132 | 133 | ## Queued Results 134 | 135 | Query results can be queued against an object to be returned upon the triggering of a query based function. Results will be returned in the order they are added to the queue and can contain any values or objects. 136 | 137 | ```javascript 138 | User.$queueResult( User.build({ id: 1337 }) ); 139 | 140 | User.findOne().then(function (user) { 141 | user.get('id'); // === 1337 142 | }); 143 | ``` 144 | 145 | Results are returned without modification so this functionality can be used to return multiple rows for a single query. 146 | 147 | ```javascript 148 | // You can also return multiple rows for a single query this way 149 | User.$queueResult( [User.build(), User.build(), /* ... */] ); 150 | 151 | User.findAll().then(function (users) { 152 | users.length; // === length of the array above 153 | }); 154 | ``` 155 | 156 | Some functions require additional parameters or configuration. You can specify behavior by passing in the `options` parameter when queueing the results. These values will only be passed along when the function requires more than one value in the return, and will otherwise be ignored. 157 | 158 | ```javascript 159 | User.$queueResult( User.build(), { wasCreated: true } ); 160 | 161 | User.findOrCreate().spread(function (user, wasCreated) { 162 | wasCreated; // === true 163 | }); 164 | ``` 165 | 166 | #### Testing Errors 167 | 168 | You can also use this method to test for errors from the server. If the next queued item is a failure result, the query promise will be rejected with the given `Error`. 169 | 170 | ```javascript 171 | User.$queueFailure( 'My test error' ); 172 | 173 | User.findOne().catch(function (err) { 174 | err.message; // === 'My test error' 175 | }); 176 | ``` 177 | 178 | By default, if the objects passed in are not `Error` objects, they are converted to `Sequelize.Error` objects. You can disable this functionality if you need by passing in the option `{ convertNonErrors: false }`. 179 | 180 | ### Recommendations 181 | 182 | At the end of each test, it is highly recommended you clear the queue of pending query results and query handlers. This will help limit the number of false failures in your test results due to left over query results. 183 | 184 | ```javascript 185 | afterEach(function () { 186 | User.$clearResults(); 187 | 188 | // Equivalent to calling: 189 | // User.$clearQueue(); 190 | // User.$clearHandlers(); 191 | }) 192 | ``` 193 | 194 | ## Automated Results 195 | 196 | Automated results are generated based on a combination of the default values specified when declaring your mock `Model`s, any auto-generated values like `id` or `createdAt`, and the query being run. For example, for a `findOne()` query, it will use the `where` properties to build a new object to return to you. 197 | 198 | ```javascript 199 | User.findOne({ 200 | where: { 201 | name: 'Marty McFly' 202 | }, 203 | }).then(function (user) { 204 | user.get('id'); // === Auto-generated ID 205 | user.get('name'); // === 'Marty McFly' 206 | }); 207 | ``` 208 | -------------------------------------------------------------------------------- /docs/api/sequelize.md: -------------------------------------------------------------------------------- 1 | # Sequelize 2 | 3 | The mock class for the base Sequelize interface. 4 | 5 | 6 | 7 | 8 | ## new Sequelize([database], [username], [password], [options]) 9 | 10 | Sequelize Mock Object. This can be initialize much the same way that Sequelize itself 11 | is initialized. Any configuration or options is ignored, so it can be used as a drop-in 12 | replacement for Sequelize but does not have all the same functionality or features. 13 | 14 | ### Parameters 15 | 16 | Name | Type | Description 17 | --- | --- | --- 18 | [database] | String | Ignored for Mock objects, supported to match Sequelize 19 | [username] | String | Ignored for Mock objects, supported to match Sequelize 20 | [password] | String | Ignored for Mock objects, supported to match Sequelize 21 | [options] | String | Options object. Most default Sequelize options are ignored unless listed below. All, however, are available by accessing `sequelize.options` 22 | [options.dialect='mock'] | String | Dialect that the system will use. Avaible to be returned by `getDialect()` but has no other effect 23 | [options.autoQueryFallback] | Boolean | Flag inherited by defined Models indicating if we should try and generate results based on the query automatically 24 | [options.stopPropagation] | Boolean | Flag inherited by defined Models indicating if we should not propagate to the parent 25 | 26 | 27 | 28 | 29 | 30 | ### .options 31 | 32 | Options passed into the Sequelize initialization 33 | 34 | 35 | 36 | 37 | ### .importCache 38 | 39 | Used to cache and override model imports for easy mock model importing 40 | 41 | 42 | 43 | 44 | ### .models 45 | 46 | Models that have been defined in this Sequelize Mock instances 47 | 48 | 49 | 50 | 51 | ### .version 52 | 53 | Version number for the Mock library 54 | 55 | 56 | 57 | 58 | ## Sequelize 59 | 60 | Reference to the mock Sequelize class 61 | 62 | 63 | 64 | 65 | ## Utils 66 | 67 | Reference to the Util functions 68 | 69 | 70 | 71 | 72 | ## Promise 73 | 74 | Reference to the bluebird promise library 75 | 76 | 77 | 78 | 79 | ## QueryTypes 80 | 81 | Object containing all of the [Sequelize QueryTypes](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/query-types.js). 82 | 83 | 84 | 85 | 86 | ## Model 87 | 88 | Reference to the mock Model class 89 | 90 | 91 | 92 | 93 | ## Instance 94 | 95 | Reference to the mock Instance class 96 | 97 | 98 | 99 | 100 | ## $queueResult(result) -> Sequelize 101 | 102 | Queue a new query result to be returned by either the `query` method call or as a 103 | fallback from queries from `Model`s defined through the `define` method.
**Alias** $queueQueryResult, $qqr 104 | 105 | **See** 106 | 107 | - [query](#query) 108 | 109 | ### Parameters 110 | 111 | Name | Type | Description 112 | --- | --- | --- 113 | result | Any | The object or value to be returned as the result of a query 114 | 115 | 116 | ### Return 117 | `Sequelize`: self 118 | 119 | 120 | 121 | 122 | ## $queueFailure(error, [options]) -> Sequelize 123 | 124 | Queue a new query result to be returned by either the `query` method call or as a 125 | fallback from queries from `Model`s defined through the `define` method. This result 126 | is returned as a rejected promise for testing error handling.
**Alias** $queueQueryFailure, $queueError, $queueQueryError, $qqf 127 | 128 | **See** 129 | 130 | - [query](#query) 131 | 132 | ### Parameters 133 | 134 | Name | Type | Description 135 | --- | --- | --- 136 | error | Any | The object or value to be returned as the failure for a query 137 | [options] | Object | 138 | [options.convertNonErrors] | Boolean | Flag indicating if non `Error` objects should be allowed. Defaults to true 139 | 140 | 141 | ### Return 142 | `Sequelize`: self 143 | 144 | 145 | 146 | 147 | ## $clearQueue() -> Sequelize 148 | 149 | Clears any queued results from `$queueResult` or `$queueFailure`
**Alias** $queueClear, $queueQueryClear, $cqq, $qqc 150 | 151 | **See** 152 | 153 | - [$queueResult](#queueResult) 154 | - [$queueFailure](#queueFailure) 155 | 156 | ### Return 157 | `Sequelize`: self 158 | 159 | 160 | 161 | 162 | ## $overrideImport(importPath, overridePath) 163 | 164 | Overrides a path used for import 165 | 166 | **See** 167 | 168 | - [import](#import) 169 | 170 | ### Parameters 171 | 172 | Name | Type | Description 173 | --- | --- | --- 174 | importPath | String | The original path that import will be called with 175 | overridePath | String | The path that should actually be used for resolving. If this path is relative, it will be relative to the file calling the import function 176 | 177 | 178 | 179 | 180 | 181 | ## getDialect() -> String 182 | 183 | Returns the specified dialect 184 | 185 | ### Return 186 | `String`: The specified dialect 187 | 188 | 189 | 190 | 191 | ## getQueryInterface() -> QueryInterface 192 | 193 | Returns the current instance of `QueryInterface` 194 | 195 | **See** 196 | 197 | - [QueryInterface](./queryinterface.md) 198 | - [query](#query) 199 | 200 | ### Return 201 | `QueryInterface`: The instantiated `QueryInterface` object used for test `query` 202 | 203 | 204 | 205 | 206 | ## define(name, [obj={}], [opts]) -> Model 207 | 208 | Define a new mock Model. You should provide a name and a set of default values for this 209 | new Model. The default values will be used any time a new Instance of this model is 210 | created and will be overridden by any values provided specifically to that Instance. 211 | 212 | Additionally an options object can be passed in with an `instanceMethods` map. All of 213 | functions in this object will be added to any Instance of the Model that is created. 214 | 215 | All models are available by name via the `.models` property 216 | 217 | **Example** 218 | 219 | ```javascript 220 | sequelize.define('user', { 221 | 'name': 'Test User', 222 | 'email': 'test@example.com', 223 | 'joined': new Date(), 224 | }, { 225 | 'instanceMethods': { 226 | 'tenure': function () { return Date.now() - this.get('joined'); }, 227 | }, 228 | }); 229 | ``` 230 | 231 | **See** 232 | 233 | - Model 234 | 235 | ### Parameters 236 | 237 | Name | Type | Description 238 | --- | --- | --- 239 | name | String | Name of the mock Model 240 | [obj={}] | Object | Map of keys and their default values that will be used when querying against this object 241 | [opts] | Object | Options for the mock model 242 | [opts.instanceMethods] | Object | Map of function names and the functions to be run. These functions will be added to any instances of this Model type 243 | 244 | 245 | ### Return 246 | `Model`: Mock Model as defined by the name, default values, and options provided 247 | 248 | 249 | 250 | 251 | ## isDefined(name) -> Boolean 252 | 253 | Checks whether a model with the given name is defined. 254 | 255 | Uses the `.models` property for lookup. 256 | 257 | ### Parameters 258 | 259 | Name | Type | Description 260 | --- | --- | --- 261 | name | String | Name of the model 262 | 263 | 264 | ### Return 265 | `Boolean`: True if the model is defined, false otherwise 266 | 267 | 268 | 269 | 270 | ## import(path) -> Any 271 | 272 | Imports a given model from the provided file path. Files that are imported should 273 | export a function that accepts two parameters, this sequelize instance, and an object 274 | with all of the available datatypes 275 | 276 | Before importing any modules, it will remap any paths that were overridden using the 277 | `$overrideImport` test function. This method is most helpful when used to make the 278 | SequelizeMock framework import your mock models instead of the real ones in your test 279 | code. 280 | 281 | ### Parameters 282 | 283 | Name | Type | Description 284 | --- | --- | --- 285 | path | String | Path of the model to import. Can be relative or absolute 286 | 287 | 288 | ### Return 289 | `Any`: The result of evaluating the imported file's function 290 | 291 | 292 | 293 | 294 | ## model(name) -> Model 295 | 296 | Fetch a Model which is already defined. 297 | 298 | Uses the `.models` property for lookup. 299 | 300 | ### Parameters 301 | 302 | Name | Type | Description 303 | --- | --- | --- 304 | name | String | Name of the model 305 | 306 | 307 | ### Return 308 | `Model`: Mock model which was defined with the specified name 309 | 310 | 311 | 312 | 313 | ## query() -> Promise.<Any> 314 | 315 | Run a mock query against the `QueryInterface` associated with this Sequelize instance 316 | 317 | ### Return 318 | `Promise.`: The next result of a query as queued to the `QueryInterface` 319 | 320 | 321 | 322 | 323 | ## transaction([fn]) -> Promise 324 | 325 | This function will simulate the wrapping of a set of queries in a transaction. Because 326 | Sequelize Mock does not run any actual queries, there is no difference between code 327 | run through transactions and those that aren't. 328 | 329 | ### Parameters 330 | 331 | Name | Type | Description 332 | --- | --- | --- 333 | [fn] | Function | Optional function to run as a tranasction 334 | 335 | 336 | ### Return 337 | `Promise`: Promise that resolves the code is successfully run, otherwise it is rejected 338 | 339 | 340 | 341 | 342 | ## literal(arg) -> Any 343 | 344 | Simply returns the first argument passed in, unmodified. 345 | 346 | ### Parameters 347 | 348 | Name | Type | Description 349 | --- | --- | --- 350 | arg | Any | Value to return 351 | 352 | 353 | ### Return 354 | `Any`: value passed in 355 | 356 | 357 | 358 | 359 | ## authenticate() -> Promise 360 | 361 | Always returns a resolved promise 362 | 363 | ### Return 364 | `Promise`: will always resolve as a successful authentication 365 | 366 | -------------------------------------------------------------------------------- /src/queryinterface.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * To support running mock queries, we use this object to queue results for queries. This 5 | * is to provide full control over the objects that are returned and do so in a way that 6 | * allows you to target specific code paths. 7 | * 8 | * Queries are queued in a "first in, last out" manner, so they should be inserted in the 9 | * order your code would expect them in. The queue itself does no checking or validation 10 | * for what types of objects are being passed around except in the case of failures 11 | * (see `$queueFailure`). 12 | * 13 | * **Note:** This is not an equivalent mock of Sequelize's built in `QueryInterface` at 14 | * the moment. The built in object for Sequelize is undocumented and is marked as `@private` 15 | * in their code, meaning it is not likely something to be relied on. If this changes it 16 | * can be mocked here. Functions have been prefixed with the mock prefix (`$`) for this 17 | * reason. 18 | * 19 | * @name QueryInterface 20 | * @fileOverview The mock QueryInterface base that is used for returning results from queries for tests 21 | **/ 22 | 23 | var bluebird = require('bluebird'), 24 | _ = require('lodash'), 25 | Errors = require('./errors'); 26 | 27 | /** 28 | * The `QueryInterface` class is used to provide common mock query functionality. New 29 | * instances of this class should mostly be created internally, however the functions on 30 | * the class are exposed on objects utilize this class. 31 | * 32 | * @class QueryInterface 33 | * @constructor 34 | * @param {Object} [options] Options for the query interface to use 35 | * @param {QueryInterface} [options.parent] Parent `QueryInterface` object to propagate up to 36 | * @param {Boolean} [options.stopPropagation] Flag indicating if we should not propagate to the parent 37 | * @param {Boolean} [options.createdDefault] Default value to be used for if something has been created if one is not passed in by the query. Defaults to true 38 | * @param {Function} [options.fallbackFn] Default function to call as a fallback if nothing is left in the queue and a fallback function is not passed in with the query 39 | **/ 40 | function QueryInterface (options) { 41 | this.options = _.extend({ 42 | stopPropagation: false, 43 | createdDefault: true, 44 | fallbackFn: undefined, 45 | }, options || {}); 46 | this._results = []; 47 | this._handlers = []; 48 | } 49 | 50 | /** 51 | * Queue a new success result from the mock database 52 | * 53 | * @instance 54 | * @param {Any} result The object or value to be returned as the result of a query 55 | * @param {Object} [options] Options used when returning the result 56 | * @param {Boolean} [options.wasCreated] Optional flag if a query requires a `created` value in the return indicating if the object was "created" in the DB 57 | * @param {Array} [options.affectedRows] Optional array of objects if the query requires an `affectedRows` return value 58 | * @return {QueryInterface} self 59 | **/ 60 | QueryInterface.prototype.$queueResult = function (result, options) { 61 | this._results.push({ 62 | content: result, 63 | options: options || {}, 64 | type: 'Success', 65 | }); 66 | 67 | return this; 68 | }; 69 | 70 | /** 71 | * Queue a new error or failure result from the mock database. This will cause a query 72 | * to be rejected with the given error/failure object. The error is converted into a 73 | * `BaseError` object unless specified by the `options.convertNonErrors` parameter. 74 | * 75 | * @instance 76 | * @alias $queueError 77 | * @param {Any} error The object or value to be returned as the failure for a query 78 | * @param {Object} [options] Options used when returning the result 79 | * @param {Boolean} [options.convertNonErrors] Flag indicating if non `Error` objects should be allowed. Defaults to true 80 | * @return {QueryInterface} self 81 | **/ 82 | QueryInterface.prototype.$queueFailure = function (error, options) { 83 | // Rejections from Sequelize will almost always be errors, so we convert to an error by default 84 | if((!options || options.convertNonErrors !== false) && !(error instanceof Error)) { 85 | // Convert non-Error objects to BaseError objects if we haven't specified otherwise 86 | error = new Errors.BaseError(error); 87 | } 88 | 89 | this._results.push({ 90 | content: error, 91 | options: options || {}, 92 | type: 'Failure', 93 | }); 94 | 95 | return this; 96 | }; 97 | QueryInterface.prototype.$queueError = QueryInterface.prototype.$queueFailure; 98 | 99 | /** 100 | * Adds a new query handler from the mock database 101 | * 102 | * @instance 103 | * @param {Function} handler The function that will be invoked with the query. 104 | * @return {QueryInterface} self 105 | **/ 106 | QueryInterface.prototype.$useHandler = function (handler) { 107 | this._handlers.push(handler); 108 | return this; 109 | }; 110 | 111 | /** 112 | * Clears any queued query results 113 | * 114 | * @instance 115 | * @alias $queueClear 116 | * @param {Object} [options] Options used when returning the result 117 | * @param {Boolean} [options.propagateClear] Propagate this clear up to any parent `QueryInterface`s. Defaults to false 118 | * @return {QueryInterface} self 119 | **/ 120 | QueryInterface.prototype.$clearQueue = function (options) { 121 | options = options || {}; 122 | this._results = []; 123 | 124 | // If we should also clear any results that would be added through propagation 125 | // then we also need to trigger $clearQueue on any parent QueryInterface 126 | if(options.propagateClear && this.options.parent) { 127 | this.options.parent.$clearQueue(options); 128 | } 129 | 130 | return this; 131 | }; 132 | QueryInterface.prototype.$queueClear = QueryInterface.prototype.$clearQueue; 133 | 134 | /** 135 | * Clears any handles 136 | * 137 | * @instance 138 | * @alias $handlersClear 139 | * @param {Object} [options] Options used when returning the result 140 | * @param {Boolean} [options.propagateClear] Propagate this clear up to any parent `QueryInterface`s. Defaults to false 141 | * @return {QueryInterface} self 142 | **/ 143 | QueryInterface.prototype.$clearHandlers = function (options) { 144 | options = options || {}; 145 | this._handlers = []; 146 | 147 | // If we should also clear any handlers that would be added through propagation 148 | // then we also need to trigger $clearQueue on any parent QueryInterface 149 | if(options.propagateClear && this.options.parent) { 150 | this.options.parent.$clearHandlers(options); 151 | } 152 | 153 | return this; 154 | }; 155 | QueryInterface.prototype.$handlersClear = QueryInterface.prototype.$clearHandlers; 156 | 157 | /** 158 | * Clears any reesults (both handlers and queued results) 159 | * 160 | * @instance 161 | * @alias $handlersClear 162 | * @param {Object} [options] Options used when returning the result 163 | * @param {Boolean} [options.propagateClear] Propagate this clear up to any parent `QueryInterface`s. Defaults to false 164 | * @return {QueryInterface} self 165 | **/ 166 | QueryInterface.prototype.$clearResults = function (options) { 167 | this.$clearHandlers(options); 168 | this.$clearQueue(options); 169 | return this; 170 | }; 171 | QueryInterface.prototype.$resultsClear = QueryInterface.prototype.$clearResults; 172 | 173 | function resultsQueueHandler(qi, options) { 174 | return function(query, queryOptions) { 175 | var result = qi._results.shift(); 176 | if (!result) return; 177 | 178 | if(typeof result !== 'object' || !(result.type === 'Failure' || result.type === 'Success')) { 179 | throw new Errors.InvalidQueryResultError(); 180 | } 181 | 182 | if(result.type == 'Failure') { 183 | return bluebird.reject(result.content); 184 | } 185 | 186 | if(options.includeCreated) { 187 | var created = !!qi.options.createdDefault; 188 | if(typeof result.options.wasCreated !== 'undefined') { 189 | created = !!result.options.wasCreated; 190 | } 191 | 192 | return bluebird.resolve([result.content, created]); 193 | } 194 | if (options.includeAffectedRows) { 195 | var affectedRows = []; 196 | if(result.options.affectedRows instanceof Array) { 197 | affectedRows = result.options.affectedRows; 198 | } 199 | 200 | return bluebird.resolve([result.content, affectedRows]); 201 | } 202 | return bluebird.resolve(result.content); 203 | } 204 | } 205 | 206 | function propagationHandler(qi, options) { 207 | return function(query, queryOptions) { 208 | if (!options.stopPropagation && !qi.options.stopPropagation && qi.options.parent) { 209 | return qi.options.parent.$query(options); 210 | } 211 | } 212 | } 213 | 214 | function fallbackHandler(qi, options) { 215 | return function(query, queryOptions) { 216 | var fallbackFn = options.fallbackFn || qi.options.fallbackFn; 217 | if (fallbackFn) return fallbackFn(); 218 | } 219 | } 220 | 221 | /** 222 | * This is the mock method for getting results from the `QueryInterface`. This function 223 | * will get the next result in the queue and return that wrapped in a promise. 224 | * 225 | * @instance 226 | * @param {Object} [options] Options used for this query 227 | * @param {Function} [options.fallbackFn] A fallback function to run if there are no results queued 228 | * @param {Boolean} [options.includeCreated] Flag indicating if a `created` value should be returned with the result for this query. Defaults to false 229 | * @param {Boolean} [options.includeAffectedRows] Flag indicating if the query expects `affectedRows` in the returned result parameters. Defautls to false 230 | * @param {Boolean} [options.stopPropagation] Flag indicating if result queue propagation should be stopped on this query. Defaults to false 231 | * @param {String} [options.query] Name of the original query: "findOne", "findOrCreate", "upsert", etc. 232 | * @param {Object} [options.queryOptions] Array with the arguments passed to the original query method 233 | * @return {Promise} resolved or rejected promise from the next item in the review queue 234 | **/ 235 | QueryInterface.prototype.$query = function (options) { 236 | options = options || {}; 237 | 238 | var handlers = this._handlers.concat( 239 | resultsQueueHandler(this, options), 240 | propagationHandler(this, options), 241 | fallbackHandler(this, options), 242 | function() { 243 | throw new Errors.EmptyQueryQueueError(); 244 | } 245 | ) 246 | 247 | // Can't use promises to chain the handlers because they will convert any error thrown by the handlers to a rejected promise. 248 | var result; 249 | function processHandler(handler) { 250 | if (!handler) return; 251 | result = handler(options.query, options.queryOptions); 252 | if (typeof result === "undefined") { 253 | processHandler(handlers.shift()); 254 | }; 255 | } 256 | processHandler(handlers.shift()); 257 | 258 | // Always convert the result to a promise. If the promise was rejected, this method will return a rejected promise. 259 | return bluebird.resolve(result); 260 | }; 261 | 262 | module.exports = QueryInterface; 263 | -------------------------------------------------------------------------------- /test/sequelize.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var bluebird = require('bluebird'); 5 | var proxyquire = require('proxyquire').noCallThru(); 6 | 7 | var ModelMock = function () {}; 8 | var PathMock = { 9 | normalize: function (p) { return p }, 10 | resolve: function (p) { return p }, 11 | dirname: function (p) { return p }, 12 | }; 13 | 14 | var UtilsMock = { 15 | stack: function () { return {}; }, 16 | }; 17 | 18 | var PackageMock = { 19 | version: 'test', 20 | }; 21 | var ErrorMock = { 22 | 'BaseError': function () { return 'base error'; }, 23 | 'OtherError': function () { return 'base error'; }, 24 | }; 25 | var QueryInterfaceMock = function () {}; 26 | 27 | var lastImportTestCall; 28 | function importTestFunc() { 29 | lastImportTestCall = arguments; 30 | } 31 | 32 | var Sequelize = proxyquire('../src/sequelize', { 33 | 'path' : PathMock, 34 | './model' : ModelMock, 35 | './utils' : UtilsMock, 36 | './errors' : ErrorMock, 37 | './queryinterface' : QueryInterfaceMock, 38 | '../package.json' : PackageMock, 39 | './data-types' : function () {}, 40 | 41 | 'import-test' : importTestFunc, 42 | 'import-test-es6' : { default: importTestFunc }, 43 | }); 44 | 45 | describe('Sequelize', function () { 46 | 47 | it('should have top level constants on class', function () { 48 | Sequelize.should.have.property('version').which.is.equal('test'); 49 | Sequelize.should.have.property('options'); 50 | Sequelize.should.have.property('Utils').which.is.equal(UtilsMock); 51 | Sequelize.should.have.property('Promise').which.is.equal(bluebird); 52 | Sequelize.should.have.property('Model').which.is.equal(ModelMock); 53 | }); 54 | 55 | it('should have top level constants on instances of class', function () { 56 | var seq = new Sequelize(); 57 | seq.should.have.property('Utils').which.is.equal(UtilsMock); 58 | seq.should.have.property('Promise').which.is.equal(bluebird); 59 | seq.should.have.property('Model').which.is.equal(ModelMock); 60 | }); 61 | 62 | it('should have Error classes exposed on class and prototype', function () { 63 | Sequelize.should.have.property('OtherError').which.is.equal(ErrorMock.OtherError); 64 | Sequelize.prototype.should.have.property('OtherError').which.is.equal(ErrorMock.OtherError); 65 | }); 66 | 67 | it('should alias BaseError class', function () { 68 | Sequelize.should.have.property('Error').which.is.equal(ErrorMock.BaseError); 69 | Sequelize.prototype.should.have.property('Error').which.is.equal(ErrorMock.BaseError); 70 | }); 71 | 72 | describe('__constructor', function () { 73 | it('should default dialect to mock', function () { 74 | var seq = new Sequelize(); 75 | seq.options.dialect.should.equal('mock'); 76 | }); 77 | 78 | it('should support setting a dialect', function () { 79 | var seq = new Sequelize({ 80 | dialect: 'test', 81 | }); 82 | seq.options.dialect.should.equal('test'); 83 | }); 84 | 85 | it('should support options as second argument', function () { 86 | var seq = new Sequelize('database', { 87 | dialect: 'test', 88 | }); 89 | seq.options.dialect.should.equal('test'); 90 | }); 91 | 92 | it('should support options as third argument', function () { 93 | var seq = new Sequelize('database', 'user', { 94 | dialect: 'test', 95 | }); 96 | seq.options.dialect.should.equal('test'); 97 | }); 98 | 99 | it('should support options as fourth argument', function () { 100 | var seq = new Sequelize('database', 'user', 'password', { 101 | dialect: 'test', 102 | }); 103 | seq.options.dialect.should.equal('test'); 104 | }); 105 | }); 106 | 107 | describe('#$queueResult', function () { 108 | var seq; 109 | beforeEach(function () { 110 | seq = new Sequelize(); 111 | }); 112 | 113 | it('should queue a result against the QueryInterface', function () { 114 | var queue = []; 115 | seq.queryInterface = { 116 | $queueResult: function (res) { 117 | queue.push(res); 118 | } 119 | }; 120 | seq.$queueResult('foo'); 121 | queue.length.should.equal(1); 122 | queue[0].should.equal('foo'); 123 | }); 124 | }); 125 | 126 | describe('#$queueFailure', function () { 127 | var seq; 128 | beforeEach(function () { 129 | seq = new Sequelize(); 130 | }); 131 | 132 | it('should queue a result against the QueryInterface', function () { 133 | var queue = []; 134 | seq.queryInterface = { 135 | $queueFailure: function (res) { 136 | queue.push(res); 137 | } 138 | }; 139 | seq.$queueFailure('foo'); 140 | queue.length.should.equal(1); 141 | queue[0].should.equal('foo'); 142 | }); 143 | 144 | it('should pass along options to the QueryInterface', function () { 145 | var options; 146 | seq.queryInterface = { 147 | $queueFailure: function (res, opts) { 148 | options = opts; 149 | } 150 | }; 151 | seq.$queueFailure('foo', 'bar'); 152 | options.should.equal('bar'); 153 | }); 154 | }); 155 | 156 | describe('#$clearQueue', function () { 157 | var seq; 158 | beforeEach(function () { 159 | seq = new Sequelize(); 160 | }); 161 | 162 | it('should clear queue of results in the QueryInterface', function () { 163 | var run = 0; 164 | seq.queryInterface = { 165 | $clearQueue: function (res) { 166 | run++; 167 | } 168 | }; 169 | seq.$clearQueue(); 170 | run.should.equal(1); 171 | }); 172 | }); 173 | 174 | describe('#$overrideImport', function () { 175 | var seq; 176 | beforeEach(function () { 177 | seq = new Sequelize(); 178 | }); 179 | 180 | it('should override an import path in the importCache', function () { 181 | seq.importCache = {}; 182 | seq.$overrideImport('foo', 'bar'); 183 | seq.importCache.should.have.property('foo').which.is.exactly('bar'); 184 | }); 185 | }); 186 | 187 | describe('#getDialect', function () { 188 | it('should return the dialect set during initialization', function () { 189 | var seq = new Sequelize({ 190 | dialect: 'test' 191 | }); 192 | seq.getDialect().should.equal('test'); 193 | }); 194 | }); 195 | 196 | describe('#getQueryInterface', function () { 197 | it('should return the QueryInterface object', function () { 198 | var seq = new Sequelize(); 199 | seq.getQueryInterface().should.be.instanceof(QueryInterfaceMock); 200 | }); 201 | }); 202 | 203 | describe('#define', function () { 204 | it('should return a Mock Model', function () { 205 | var seq = new Sequelize(); 206 | seq.define().should.be.instanceOf(ModelMock); 207 | }); 208 | }); 209 | 210 | describe('#isDefined', function() { 211 | it('should return true if the model is defined', function() { 212 | var seq = new Sequelize(); 213 | seq.define('test', {}); 214 | 215 | seq.isDefined('test').should.be.true(); 216 | }); 217 | 218 | it('should return false if the model is not defined', function() { 219 | var seq = new Sequelize(); 220 | 221 | seq.isDefined('test').should.be.false() 222 | }); 223 | }); 224 | 225 | describe('#import', function () { 226 | var seq, resolve, stack; 227 | beforeEach(function () { 228 | seq = new Sequelize(); 229 | lastImportTestCall = null; 230 | resolve = PathMock.resolve; 231 | stack = UtilsMock.stack; 232 | }); 233 | 234 | afterEach(function () { 235 | PathMock.resolve = resolve; 236 | UtilsMock.stack = stack; 237 | }); 238 | 239 | it('should return an already imported model', function () { 240 | var findItem = {}; 241 | seq.importCache = { 242 | 'foo': findItem, 243 | }; 244 | seq.import('foo').should.be.exactly(findItem); 245 | }); 246 | 247 | it('should import a model from the given path', function () { 248 | seq.import('import-test'); 249 | should(lastImportTestCall).not.be.Null(); 250 | lastImportTestCall[0].should.be.exactly(seq); 251 | }); 252 | 253 | it('should turn a relative path into an absolute path', function () { 254 | var pathRun = 0; 255 | var stackRun = 0; 256 | PathMock.resolve = function () { pathRun++; return './bar'; }; 257 | UtilsMock.stack = function () { stackRun++; return [0, { getFileName: function () { return 'baz'; } } ] }; 258 | var findItem = {}; 259 | 260 | seq.importCache = { 261 | './bar': findItem, 262 | }; 263 | seq.import('./foo').should.be.exactly(findItem); 264 | pathRun.should.be.exactly(2); 265 | stackRun.should.be.exactly(1); 266 | }); 267 | 268 | it('should import a replaced model from an overridden import', function () { 269 | var findItem = {}; 270 | seq.importCache = { 271 | 'foo': 'bar', 272 | 'bar': findItem, 273 | }; 274 | seq.import('foo').should.be.exactly(findItem); 275 | }); 276 | 277 | it('should import an es6 model from the given path', function () { 278 | seq.import('import-test-es6'); 279 | should(lastImportTestCall).not.be.Null(); 280 | lastImportTestCall[0].should.be.exactly(seq); 281 | }); 282 | 283 | it('should import a model function as the second argument (for meteor compatibility)', function () { 284 | seq.import('import-test', importTestFunc); 285 | should(lastImportTestCall).not.be.Null(); 286 | lastImportTestCall[0].should.be.exactly(seq); 287 | }); 288 | }); 289 | 290 | describe('#model', function() { 291 | it('should return a previously defined Mock Model referenced its name', function() { 292 | var seq = new Sequelize(); 293 | var mock = seq.define('test', {}); 294 | seq.model('test').should.be.equal(mock); 295 | }); 296 | 297 | it('should throw an error if there is no model with the specified name', function() { 298 | var seq = new Sequelize(); 299 | var modelName = 'test'; 300 | var callModel = function() { 301 | seq.model(modelName); 302 | }; 303 | 304 | callModel.should.throw(Error); 305 | callModel.should.throw(modelName + ' has not been defined'); 306 | }); 307 | }); 308 | 309 | describe('#query', function () { 310 | it('should pass query along to QueryInterface', function () { 311 | var seq = new Sequelize(), 312 | run = 0; 313 | seq.queryInterface = { 314 | $query: function () { 315 | run++; 316 | return 'foo'; 317 | }, 318 | }; 319 | 320 | seq.query().should.equal('foo'); 321 | run.should.equal(1); 322 | }); 323 | }); 324 | 325 | describe('#transaction', function () { 326 | it('should run a passed in function', function (done) { 327 | var seq = new Sequelize(), 328 | count = 0; 329 | seq.transaction(function () { 330 | count++; 331 | return Promise.resolve(); 332 | }).then(function () { 333 | count.should.equal(1); 334 | done() 335 | }).catch(done); 336 | }); 337 | 338 | it('should return a promise object when no function is passed in', function (done) { 339 | var seq = new Sequelize(); 340 | seq.transaction().then(function (transaction) { 341 | should.exist(transaction); 342 | done() 343 | }).catch(done); 344 | }); 345 | }); 346 | 347 | describe('#literal', function () { 348 | it('should simply return the argument for the literal function', function () { 349 | var seq = new Sequelize(); 350 | seq.literal('Test').should.equal('Test'); 351 | var obj = {}; 352 | seq.literal(obj).should.equal(obj); 353 | }); 354 | }); 355 | 356 | describe('#authenticate', function () { 357 | it('should simply return a resolving promise', (done) => { 358 | var seq = new Sequelize(); 359 | seq.authenticate().then(done).catch(done); 360 | }) 361 | }); 362 | }); 363 | -------------------------------------------------------------------------------- /src/instance.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Instances are the individual results from a Sequelize call. In SequelizeMock, these objects 5 | * have most of the same attributes as in the normal Sequelize Instance, however they also have 6 | * a few additional bits of functionality. 7 | * 8 | * Values for instances are defined by defaults provided to the Model Mock on definition and 9 | * they are overriden by values provided during queries. The major exceptions are the `id`, 10 | * `createdAt`, and `updatedAt` values, which are available on all instances regardless of if 11 | * they are in the query or default values. This is the same way Sequelize will add these to 12 | * your instances. 13 | * 14 | * The `id` value is an auto-incrementing value with each query (unless provided as a part of 15 | * your query). The `createdAt` and `updatedAt` values are set to the current timestamp at the 16 | * time the instance is created. 17 | * 18 | * Additionally, there are a few additional test methods you can use to test functionality 19 | * coming from Sequelize behavior. These methods are prefixed with a `$` so as to distinguish 20 | * them from mocked native Sequelize functionality. Any test specific properties also include 21 | * a double underscore (`__`) so that they are also uniquely distinguished from mock internals. 22 | * 23 | * @name Instance 24 | * @fileOverview Instances of Models created by Model function calls. 25 | */ 26 | 27 | var bluebird = require('bluebird'), 28 | _ = require('lodash'), 29 | Errors = require('./errors'); 30 | 31 | var id = 0; 32 | 33 | /** 34 | * Instance Mock Object. Creation of this object should almost always be handled through the 35 | * `Model` class methods. In cases when you need to create an `Instance` directly, providing 36 | * the `defaults` parameter should be enough, as the `obj` overrides parameter is optional. 37 | * 38 | * @class Instance 39 | * @constructor 40 | * @param {Object} defaults The default values. This will come from the Model when created via that class 41 | * @param {Object} [obj] Any overridden values that should be specific to this instance 42 | **/ 43 | function fakeModelInstance (values, options) { 44 | this.options = options || {}; 45 | 46 | /** 47 | * As with Sequelize, we include a `dataValues` property which contains the values for the 48 | * instance. As with Sequelize, you should use other methods to edit the values for any 49 | * code that will also interact with Sequelize. 50 | * 51 | * For test code, when possible, we also recommend you use other means to validate. But 52 | * this object is also available if needed. 53 | * 54 | * @name dataValues 55 | * @alias _values 56 | * @member {Object} 57 | **/ 58 | this.dataValues = this._values = _.clone(values || {}); 59 | 60 | this.hasPrimaryKeys = this.Model.options.hasPrimaryKeys; 61 | if(this.hasPrimaryKeys) { 62 | this.dataValues.id = this.dataValues.id || (++id); 63 | } 64 | 65 | if(this.Model.options.timestamps) { 66 | this.dataValues.createdAt = this.dataValues.createdAt || new Date(); 67 | this.dataValues.updatedAt = this.dataValues.updatedAt || new Date(); 68 | } 69 | 70 | // Double underscore for test specific internal variables 71 | this.__validationErrors = []; 72 | 73 | // Add the items from the dataValues to be accessible via a simple `instance.value` call 74 | var self = this; 75 | _.each(this.dataValues, function (val, key) { 76 | 77 | Object.defineProperty(self, key, { 78 | get: function () { 79 | return fakeModelInstance.prototype.get.call(self, key); 80 | }, 81 | set: function (value) { 82 | fakeModelInstance.prototype.set.call(self, key, value); 83 | }, 84 | }); 85 | }); 86 | } 87 | 88 | /* Test Specific Functionality 89 | * 90 | */ 91 | /** 92 | * Create a new validation error to be triggered the next time a validation would run. Any 93 | * time Sequelize code would go to the database, it will trigger a check for any validation 94 | * errors that should be thrown. This allows you to test any validation failures in a more 95 | * unit-testing focused manner. 96 | * 97 | * Once a validation has occured, all validation errors will be emptied from the queue and 98 | * returned in a single `ValidationError` object. 99 | * 100 | * If you do add validation errors for a test, be sure to clear the errors after each test 101 | * so you don't fail your next test in case a validation does not occur. You can do so by 102 | * calling the [`$clearValidationErrors`](#clearValidationErrors) method. 103 | * 104 | * @example 105 | * myUser.$addValidationError('email', 'Not a valid email address', 'InvalidEmail'); 106 | * 107 | * // ... 108 | * 109 | * myUser.save().then(function () {}, function (error) { 110 | * // error will be a ValidationErrorItem 111 | * error.errors[0].type === 'InvalidEmail'; 112 | * }); 113 | * 114 | * @instance 115 | * @see {@link clearValidationErrors} 116 | * @param {String} col Field to add validation error for 117 | * @param {String} [message] Message the error should contain 118 | * @param {String} [type] Type of the validation error 119 | * @return {Instance} Instance object for chainability 120 | **/ 121 | fakeModelInstance.prototype.$addValidationError = function (col, message, type) { 122 | this.__validationErrors.push({ 123 | col: col, 124 | message: message, 125 | type: type, 126 | }) 127 | return this; 128 | }; 129 | 130 | /** 131 | * Removes all validation errors that would be triggered against a specific column. 132 | * 133 | * @instance 134 | * @param {String} col Field to remove validation errors for 135 | * @return {Instance} The object for chainability 136 | **/ 137 | fakeModelInstance.prototype.$removeValidationError = function (col) { 138 | this.__validationErrors = _.filter(this.__validationErrors, function (err) { 139 | // Filter to errors whose columns that are not the passed in column 140 | return err.col !== col; 141 | }); 142 | return this; 143 | }; 144 | 145 | /** 146 | * Removes all validation errors for every column. 147 | * 148 | * @example 149 | * beforeEach(function () { 150 | * myUser = new Instance({ 'name': 'Test' }); 151 | * }); 152 | * 153 | * afterEach(function () { 154 | * // Recommended that you always run $clearValidationErrors after 155 | * // each test so that you don't pollute your test data 156 | * myUser.$clearValidationErrors(); 157 | * }); 158 | * 159 | * @instance 160 | * @return {Instance} The object for chainability 161 | **/ 162 | fakeModelInstance.prototype.$clearValidationErrors = function () { 163 | this.__validationErrors = []; 164 | return this; 165 | }; 166 | 167 | /** 168 | * Sets the value for the given key. Also accepts an object as the first parameter 169 | * and it will loop through the key/value pairs and set each. 170 | * 171 | * @instance 172 | * @param {String|Object} key Key to set the value for. If `key` is an Object, each key/value pair will be set on the Instance 173 | * @param {Any} [val] Any value to set on the Instance. If `key` is an Object, this parameter is ignored. 174 | * @return {Instance} The object for chainability 175 | **/ 176 | fakeModelInstance.prototype.set = function(key, val) { 177 | if(typeof key === 'object' && key !== null) { 178 | // Loop over the object and perform a set for each key/value pair 179 | var self = this; 180 | _.each(key, function (value, key) { 181 | self._values[key] = value; 182 | }); 183 | } else { 184 | this._values[key] = val; 185 | } 186 | 187 | return this; 188 | }; 189 | 190 | /** 191 | * If no key is provided, it will return all values on the Instance. 192 | * 193 | * If a key is provided, it will return that value 194 | * 195 | * @instance 196 | * @param {String|Object} [key] Key to get the value for 197 | * @return {Any|Object} either the value of the key, or all the values if there is no key or key is an object with plain set to true: {plain: true} 198 | **/ 199 | fakeModelInstance.prototype.get = function (key) { 200 | if(!key || key.plain) { 201 | return _.clone(this._values); 202 | } 203 | else { 204 | return this._values[key]; 205 | } 206 | }; 207 | 208 | /** 209 | * Get plain value 210 | * @param {String} key Key yo get the value for 211 | * @return {Any} 212 | */ 213 | fakeModelInstance.prototype.getDataValue = function (key) { 214 | return this._values[key]; 215 | }; 216 | 217 | /** 218 | * Set plain value 219 | * @param {String} key Key yo get the value for 220 | * @param {Any} value 221 | */ 222 | fakeModelInstance.prototype.setDataValue = function (key, value) { 223 | this._values[key] = value; 224 | }; 225 | 226 | /** 227 | * Triggers validation. If there are errors added through `$addValidationError` they will 228 | * be returned and the queue of validation errors will be cleared. 229 | * 230 | * As with Sequelize, this will **resolve** any validation errors, not throw the errors. 231 | * 232 | * @instance 233 | * @return {Promise} will resolve with any errors, or with nothing if there were no errors queued. 234 | **/ 235 | fakeModelInstance.prototype.validate = function () { 236 | var self = this, 237 | validationError; 238 | 239 | // If we have a queued validation error, send that along with validation 240 | if(this.__validationErrors && this.__validationErrors.length) { 241 | var errors = _.map(this.__validationErrors, function (err) { 242 | return new Errors.ValidationErrorItem( 243 | err.message || ('Validation Error for value in column ' + err.col), 244 | err.type || 'Validation error', 245 | err.col, 246 | err.col ? self.get(err.col) : undefined 247 | ); 248 | }); 249 | validationError = new Errors.ValidationError(null, errors); 250 | this.$clearValidationErrors(); 251 | } 252 | 253 | return bluebird.resolve(validationError); 254 | }; 255 | 256 | /** 257 | * Because there is no database that it saves to, this will mainly trigger validation of 258 | * the Instance and reject the promise if there are any validation errors. 259 | * 260 | * @instance 261 | * @return {Promise} Instance if there are no validation errors, otherwise it will reject the promise with those errors 262 | **/ 263 | fakeModelInstance.prototype.save = function () { 264 | var self = this; 265 | return this.validate().then(function (err) { 266 | if(err) 267 | throw err; 268 | self.options.isNewRecord = false; 269 | self.isNewRecord = false; 270 | return self; 271 | }); 272 | }; 273 | 274 | /** 275 | * This simply sets the `deletedAt` value and has no other effect on the mock Instance 276 | * 277 | * @instance 278 | * @return {Promise} will always resolve as a successful destroy 279 | **/ 280 | fakeModelInstance.prototype.destroy = function () { 281 | this._values.deletedAt = new Date(); 282 | return bluebird.resolve(); 283 | }; 284 | 285 | /** 286 | * This has no effect on the Instance 287 | * 288 | * @instance 289 | * @return {Promise} will always resolve with the current instance 290 | **/ 291 | fakeModelInstance.prototype.reload = function () { 292 | return bluebird.resolve(this); 293 | }; 294 | 295 | /** 296 | * Acts as a `set()` then a `save()`. That means this function will also trigger validation 297 | * and pass any errors along 298 | * 299 | * @instance 300 | * @see {@link set} 301 | * @see {@link save} 302 | * @return {Promise} Promise from the save function 303 | **/ 304 | fakeModelInstance.prototype.update = function (obj) { 305 | for(var k in obj) 306 | this.set(k, obj[k]); 307 | return this.save(); 308 | }; 309 | 310 | /** 311 | * Returns all the values in a JSON representation. 312 | * 313 | * @method toJSON 314 | * @alias toJson 315 | * @instance 316 | * @return {Object} all the values on the object 317 | **/ 318 | fakeModelInstance.prototype.toJSON = fakeModelInstance.prototype.toJson = function () { 319 | return this.get(); 320 | }; 321 | 322 | module.exports = fakeModelInstance; 323 | -------------------------------------------------------------------------------- /scripts/doc-gen.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | fs = require('fs'), 5 | glob = require('glob'), 6 | _ = require('lodash'), 7 | dox = require('dox'), 8 | pkg = require('../package.json'); 9 | 10 | var DOCUMENTATION_ORDER = { 11 | 'Sequelize': 1, 12 | 'Model': 2, 13 | 'Instance': 3, 14 | 'DataTypes': 4, 15 | 'QueryInterface': 5, 16 | 'Errors': 6, 17 | 'Utils': 7, 18 | }; 19 | 20 | /** DocFile 21 | * 22 | * Wrapper for file that we are generativng documentation for. Will 23 | * be used to group rendering in meaningful ways 24 | * 25 | **/ 26 | function DocFile (file, contents) { 27 | this.uri = file; 28 | this.dir = path.dirname(file); 29 | this.filename = path.basename(file); 30 | this.name = this.filename.replace(/\.js$/, ''); 31 | this.contents = contents; 32 | this.comments = []; 33 | 34 | this.process(); 35 | } 36 | 37 | DocFile.prototype.process = function () { 38 | var rawComments = dox.parseComments(this.contents, { 39 | raw: true, 40 | skipSingleStar: true, 41 | }), 42 | self = this; 43 | 44 | this.comments = []; 45 | _.each(rawComments, function (rawComment) { 46 | if(rawComment.ctx && !rawComment.isPrivate) { 47 | self.comments.push( new DocComment(rawComment, self) ); 48 | } 49 | }); 50 | 51 | return this; 52 | }; 53 | 54 | DocFile.prototype.addComment = function (comment) { 55 | this.comments.push(comment); 56 | comment.setFile(this); 57 | }; 58 | 59 | DocFile.prototype.render = function () { 60 | this.markdown = _.map(this.comments, function (comment) { 61 | return (comment.render() || ''); 62 | }, '').join('\n\n'); 63 | return this.markdown; 64 | }; 65 | 66 | /** DocComment 67 | * 68 | * Documentation comment object meant for encapsulating the behavior 69 | * we want. 70 | * 71 | **/ 72 | function DocComment (data, file) { 73 | this.data = data; 74 | this.file = file; 75 | } 76 | 77 | DocComment.printableTypeList = function (item) { 78 | var types = item.types.join(', '); 79 | types = types.replace(/(<|>|&)/g, function (ch) { 80 | return '&#' + ch.charCodeAt(0) + ';'; 81 | }); 82 | return types; 83 | }; 84 | 85 | DocComment.prototype.setFile = function (file) { 86 | this.file = file; 87 | }; 88 | 89 | DocComment.prototype.getTags = function (filter) { 90 | if(!filter) 91 | return this.data.tags; 92 | return _.filter(this.data.tags, { type: filter }); 93 | }; 94 | 95 | DocComment.prototype.getTag = function (name) { 96 | return _.find(this.data.tags, { type: name }); 97 | }; 98 | 99 | DocComment.prototype.getName = function () { 100 | return (this.getTag('name') || '').string || 101 | (this.getTag('class') || '').string || 102 | (this.getTag('property') || '').string || 103 | (this.getTag('method') || '').string || 104 | this.data.ctx.name; 105 | }; 106 | 107 | DocComment.prototype.getParams = function () { 108 | return _.map( 109 | _.filter(this.getTags('param'), function (param) { 110 | return param.name.indexOf('.') < 0; 111 | }), 112 | function (param) { 113 | return param.name; 114 | }); 115 | }; 116 | 117 | DocComment.prototype.getType = function () { 118 | if(this.getTag('fileOverview') || this.getTag('file')) 119 | return 'file'; 120 | if(this.getTag('method')) 121 | return 'method'; 122 | return this.data.ctx.type; 123 | }; 124 | 125 | DocComment.prototype.render = function () { 126 | var output = '', 127 | type = this.getType(), 128 | parameters = this.getTags('param'), 129 | retType = this.getTag('return'), 130 | properties = this.getTags('prop'); 131 | 132 | var firstLevelHeader = '## ', 133 | methodPrefix = '', 134 | propertyPrefix = '', 135 | secondLevelHeader = '### ', 136 | thirdLevelHeader = '#### '; 137 | 138 | if(this.getTag('memberof') || this.getTag('member')) { 139 | firstLevelHeader = '### '; 140 | methodPrefix = '#'; 141 | propertyPrefix = '.'; 142 | secondLevelHeader = '#### '; 143 | thirdLevelHeader = '##### '; 144 | } 145 | 146 | function linkText(prefix, text, suffix) { 147 | if(arguments.length == 1) { 148 | text = prefix; 149 | prefix = ''; 150 | } 151 | prefix = prefix || ''; 152 | text = text || ''; 153 | suffix = suffix || ''; 154 | 155 | if(text.startsWith('{@link')) { 156 | text = text.slice(0, text.length - 1).replace(/\{@link\s*/, ''); 157 | var link = text, 158 | indexTextSplit = text.indexOf('|'); 159 | 160 | if(indexTextSplit > 0) { 161 | link = text.slice(0, indexTextSplit); 162 | // Remove the space or pipe 163 | text = text.slice(indexTextSplit + 1); 164 | } else { 165 | link = link.replace(/[^0-9a-zA-Z]/g, ''); 166 | text = text.replace(/]/g, ''); 167 | } 168 | 169 | if(!(link.startsWith('./') || link.startsWith('#') || link.match(/^https?:\/\//i))) { 170 | link = '#' + link; 171 | } 172 | 173 | return '[' + prefix + text + suffix + '](' + link + ')'; 174 | } 175 | return prefix + text + suffix; 176 | } 177 | 178 | if(type === 'file') { 179 | output += '# ' + this.getName(); 180 | output += '\n\n'; 181 | 182 | if(this.file) { 183 | this.file.name = this.getName(); 184 | } 185 | } else { 186 | output += '\n'; 187 | 188 | if(type === 'constructor' || type === 'method') { 189 | output += firstLevelHeader + methodPrefix + (type === 'constructor' ? 'new ' : '') + this.getName(); 190 | output += '(' + this.getParams().join(', ') + ')'; 191 | 192 | if( retType ) { 193 | output += ' -> ' + DocComment.printableTypeList(retType); 194 | } 195 | } else if (type === 'property') { 196 | output += firstLevelHeader + propertyPrefix + this.getName(); 197 | 198 | } else { 199 | output += firstLevelHeader + (type === 'constructor' ? 'new ' : '') + this.getName(); 200 | } 201 | 202 | output += '\n\n'; 203 | } 204 | 205 | output += this.data.description.full; 206 | 207 | if(this.getTags('extends').length) { 208 | output += '
**Extends** '; 209 | output += _.map(this.getTags('extends'), function (extendsItem) { 210 | return linkText(extendsItem.string); 211 | }).join(', '); 212 | output += ''; 213 | } 214 | 215 | if(this.getTags('alias').length) { 216 | output += '
**Alias** '; 217 | output += _.map(this.getTags('alias'), function (aliasItem) { 218 | return aliasItem.string; 219 | }).join(', '); 220 | output += ''; 221 | } 222 | 223 | if(this.getTags('example').length) { 224 | output += '\n\n'; 225 | output += '**Example**\n\n'; 226 | output += _.map(this.getTags('example'), function (extendsItem) { 227 | return '```javascript\n' + extendsItem.string + '\n```'; 228 | }).join(', '); 229 | output += ''; 230 | } 231 | 232 | if(this.getTags('see').length) { 233 | output += '\n\n'; 234 | output += '**See**\n\n'; 235 | output += _.map(this.getTags('see'), function (seeItem) { 236 | return ' - ' + linkText(seeItem.string); 237 | }).join('\n'); 238 | } 239 | 240 | if(type === 'constructor' || type === 'method') { 241 | if(parameters.length) { 242 | output += '\n\n'; 243 | output += secondLevelHeader + ' Parameters'; 244 | output += '\n\n'; 245 | output += 'Name | Type | Description\n'; 246 | output += '--- | --- | ---\n'; 247 | _.each(parameters, function (param) { 248 | output += param.name + ' | ' + DocComment.printableTypeList(param) + ' | ' + param.description; 249 | output += '\n'; 250 | }); 251 | } 252 | 253 | if(properties.length) { 254 | output += '\n\n'; 255 | output += secondLevelHeader + ' Properties\n'; 256 | _.each(properties, function (prop) { 257 | output += ' - ' + prop.string; 258 | output += '\n'; 259 | }); 260 | } 261 | 262 | if( retType ) { 263 | output += '\n\n'; 264 | output += secondLevelHeader + ' Return\n'; 265 | output += '`' + retType.types.join(', ').replace(/`/g, "'") + '`: ' + retType.description; 266 | } 267 | } 268 | 269 | this.markdown = output; 270 | 271 | return output + '\n\n'; 272 | }; 273 | 274 | /** Begin Doc Generation Script 275 | * 276 | * Begin script to process files to docs 277 | * 278 | **/ 279 | var STEP = 1; 280 | 281 | // Get all the files 282 | var getFiles = new Promise(function (resolve, reject) { 283 | console.log('STEP ' + (STEP++) + ': Listing JS Files'); 284 | glob(path.normalize(__dirname + '/../src') + '/**/!(index).js', function (err, files) { 285 | if(err) { 286 | return reject(err); 287 | } 288 | 289 | resolve(files); 290 | }); 291 | }); 292 | 293 | // Read the Files In 294 | var fileReader = getFiles.then(function (files) { 295 | console.log('STEP ' + (STEP++) + ': Read JS Files'); 296 | return Promise.all(_.map(files, function (file, index) { 297 | console.log('\t(' + (index + 1) + '/' + files.length + '): src/' + path.basename(file)); 298 | return new Promise(function (resolve, reject) { 299 | fs.readFile(file, { encoding: 'utf8' }, function (err, contents) { 300 | if(err) return reject(err); 301 | resolve(new DocFile(file, contents)); 302 | }); 303 | }); 304 | })); 305 | }); 306 | 307 | // Parse The Comments 308 | var renderFiles = fileReader.then(function (files) { 309 | console.log('STEP ' + (STEP++) + ': Render Markdown'); 310 | return _.map(files, function (file, index) { 311 | console.log('\t(' + (index + 1) + '/' + files.length + '): src/' + file.filename); 312 | 313 | file.render(); 314 | 315 | return file; 316 | }); 317 | }); 318 | 319 | // Save the markdown to a file 320 | var saveFiles = renderFiles.then(function (renderedFiles) { 321 | console.log('STEP ' + (STEP++) + ': Saving Files'); 322 | return Promise.all(_.map(renderedFiles, function (render, index) { 323 | var originalFilename = render.filename, 324 | mdName = originalFilename.replace(/js$/, 'md'), 325 | renderfile = path.normalize(__dirname + '/../docs/api/') + mdName; 326 | 327 | render.renderedFile = renderfile; 328 | render.renderedFileName = mdName; 329 | 330 | console.log('\t(' + (index + 1) + '/' + renderedFiles.length + '): docs/api/' + mdName); 331 | return new Promise(function (resolve, reject) { 332 | fs.writeFile( 333 | renderfile, 334 | render.markdown, 335 | function (err) { 336 | if(err) return reject(err); 337 | resolve(render); 338 | }); 339 | }); 340 | })); 341 | }); 342 | 343 | // Add Navigation to mkdocs.yml 344 | var saveFiles = renderFiles.then(function (renderedFiles) { 345 | console.log('STEP ' + (STEP++) + ': Add files to documentation navigation'); 346 | 347 | return new Promise(function (resolve, reject) { 348 | fs.readFile( 349 | path.normalize(__dirname + '/../') + 'mkdocs.yml', 350 | { encoding: 'utf8' }, 351 | function (err, contents) { 352 | if(err) return reject(err); 353 | 354 | var fileList = _.sortBy(renderedFiles, function (file) { 355 | return DOCUMENTATION_ORDER[file.name] || 1000; 356 | }); 357 | 358 | fileList = _.map(fileList, function (file) { 359 | return " - '" + file.name + "': 'api/" + file.renderedFileName + "'"; 360 | }).join('\n') 361 | 362 | contents = contents.replace(/### API PAGES START[^]+### API PAGES END/m, '### API PAGES START\n' + 363 | fileList + '\n' + 364 | '### API PAGES END'); 365 | 366 | fs.writeFile( 367 | path.normalize(__dirname + '/../') + 'mkdocs.yml', 368 | contents, 369 | function (err) { 370 | if(err) return reject(err); 371 | resolve(renderedFiles); 372 | } 373 | ); 374 | } 375 | ); 376 | }); 377 | 378 | return _.map(renderedFiles, function (render, index) { 379 | var originalFilename = render.filename, 380 | mdName = originalFilename.replace(/js$/, 'md'), 381 | renderfile = path.normalize(__dirname + '/../docs/api/') + mdName; 382 | 383 | console.log('\t(' + (index + 1) + '/' + renderedFiles.length + '): docs/api/' + mdName); 384 | return new Promise(function (resolve, reject) { 385 | fs.writeFile( 386 | renderfile, 387 | render.markdown, 388 | function (err) { 389 | if(err) return reject(err); 390 | resolve(); 391 | }); 392 | }); 393 | }); 394 | }); 395 | 396 | saveFiles.then(function () { 397 | console.log('SUCCESS'); 398 | }, function (errors) { 399 | console.error(errors.stack); 400 | }); 401 | -------------------------------------------------------------------------------- /test/instance.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var bluebird = require('bluebird'); 5 | var proxyquire = require('proxyquire').noCallThru(); 6 | 7 | var ErrorMock = { 8 | ValidationErrorItem : function (msg, type, path, value) { 9 | this.message = msg; 10 | this.type = type; 11 | this.path = path; 12 | this.value = value; 13 | }, 14 | ValidationError : function (msg, errors) { 15 | this.message = msg || 'Validation Error'; 16 | this.errors = errors || []; 17 | }, 18 | }; 19 | 20 | var Instance = proxyquire('../src/instance', { 21 | './errors' : ErrorMock, 22 | }); 23 | Instance.prototype.Model = { 24 | options: {}, 25 | }; 26 | 27 | describe('Instance', function () { 28 | 29 | var instGet, instSet; 30 | beforeEach(function () { 31 | instGet = Instance.prototype.get; 32 | instSet = Instance.prototype.set; 33 | }); 34 | afterEach(function () { 35 | Instance.prototype.get = instGet; 36 | Instance.prototype.set = instSet; 37 | Instance.prototype.Model.options = {}; 38 | }); 39 | 40 | describe('__constructor', function () { 41 | it('should assign an id, createdAt, and updatedAt value if the model has them', function () { 42 | Instance.prototype.Model.options = { 43 | hasPrimaryKeys: true, 44 | timestamps: true, 45 | }; 46 | var inst = new Instance(); 47 | 48 | inst._values.should.have.property('id'); 49 | inst._values.should.have.property('createdAt'); 50 | inst._values.should.have.property('updatedAt'); 51 | }); 52 | it('should not assign an id, createdAt, or updatedAt value if the model does not have them', function () { 53 | Instance.prototype.Model.options = { 54 | hasPrimaryKeys: false, 55 | timestamps: false, 56 | }; 57 | var inst = new Instance(); 58 | 59 | inst._values.should.not.have.property('id'); 60 | inst._values.should.not.have.property('createdAt'); 61 | inst._values.should.not.have.property('updatedAt'); 62 | }); 63 | it('should not modify the passed in object', function () { 64 | var values = { 65 | instKey: 'value', 66 | }; 67 | Instance.prototype.Model.options = { 68 | hasPrimaryKeys: true, 69 | }; 70 | var inst = new Instance(values); 71 | 72 | values.should.have.property('instKey'); 73 | values.should.not.have.property('id'); 74 | }); 75 | it('should not override passed in id, createdAt, and updatedAt values', function () { 76 | var inst = new Instance({ 77 | id: 5555, 78 | createdAt: 'Yesterday', 79 | updatedAt: 'Yesterday', 80 | }); 81 | 82 | inst._values.should.have.property('id').which.is.exactly(5555); 83 | inst._values.should.have.property('createdAt').which.is.exactly('Yesterday'); 84 | inst._values.should.have.property('updatedAt').which.is.exactly('Yesterday'); 85 | }); 86 | 87 | it('should assign any passed in values', function () { 88 | var inst = new Instance({ 89 | 'foo': 'bar', 90 | }); 91 | 92 | inst._values.should.have.property('foo').which.is.exactly('bar'); 93 | }); 94 | 95 | it('should create getters/setters for each of the properties', function () { 96 | var getRun = 0, 97 | setRun = 0; 98 | Instance.prototype.get = function () { 99 | getRun++; 100 | }; 101 | Instance.prototype.set = function () { 102 | setRun++; 103 | }; 104 | var inst = new Instance({ 105 | 'foo': 'bar', 106 | }); 107 | 108 | inst._values.should.have.property('foo').which.is.exactly('bar'); 109 | inst.foo; 110 | getRun.should.equal(1); 111 | inst.foo = 'baz'; 112 | setRun.should.equal(1); 113 | }); 114 | }); 115 | 116 | describe('#set', function () { 117 | it('should set the value of a property on the object given key, value', function () { 118 | var inst = new Instance(); 119 | 120 | inst._values.should.not.have.property('foo'); 121 | inst.set('foo', 'bar'); 122 | 123 | inst._values.should.have.property('foo').which.is.exactly('bar'); 124 | }); 125 | 126 | it('should set the values of a property on the object given object', function () { 127 | var inst = new Instance(); 128 | 129 | inst._values.should.not.have.property('foo'); 130 | inst._values.should.not.have.property('baz'); 131 | inst.set({ 132 | foo: 'bar', 133 | baz: 'bin' 134 | }); 135 | 136 | inst._values.should.have.property('foo').which.is.exactly('bar'); 137 | inst._values.should.have.property('baz').which.is.exactly('bin'); 138 | }); 139 | }); 140 | 141 | describe('#get', function () { 142 | it('should get the value of a property on the object', function () { 143 | var inst = new Instance(); 144 | inst._values.foo = 'bar'; 145 | 146 | inst.get('foo').should.be.exactly('bar'); 147 | }); 148 | 149 | it('should get the entire object if no paramater is provided', function () { 150 | var inst = new Instance(); 151 | inst._values.foo = 'bar'; 152 | 153 | inst.get().should.be.eql({foo: 'bar'}); 154 | }); 155 | 156 | 157 | it('should get the entire object if {plain: true} is passed', function () { 158 | var inst = new Instance(); 159 | inst._values.foo = 'bar'; 160 | 161 | inst.get().should.be.eql({foo: 'bar'}); 162 | }); 163 | }); 164 | 165 | describe('#getDataValue', function () { 166 | it('should get the value of a property on the object', function () { 167 | var inst = new Instance(); 168 | inst._values.foo = 'bar'; 169 | 170 | inst.getDataValue('foo').should.be.exactly('bar'); 171 | }); 172 | }); 173 | 174 | describe('#setDataValue', function () { 175 | it('should set the value of a property on the object', function () { 176 | var inst = new Instance(); 177 | inst._values.foo = 'bar'; 178 | inst.setDataValue('foo', 'baz'); 179 | inst._values.foo.should.be.exactly('baz'); 180 | }); 181 | }); 182 | 183 | describe('#validate', function () { 184 | it('should return no validation errors if no errors are set on the instances', function (done) { 185 | var inst = new Instance(); 186 | 187 | inst.validate().then(function (err) { 188 | should.not.exist(err); 189 | done(); 190 | }).catch(done); 191 | }); 192 | 193 | it('should return validation errors if added to the instance', function (done) { 194 | var inst = new Instance({ 'test': 'val' }); 195 | inst.$addValidationError('test', 'Test Error Message', 'mock test type'); 196 | 197 | inst.validate().then(function (err) { 198 | should.exist(err); 199 | err.errors.length.should.equal(1); 200 | err.errors[0].message.should.equal('Test Error Message'); 201 | err.errors[0].type.should.equal('mock test type'); 202 | err.errors[0].path.should.equal('test'); 203 | err.errors[0].value.should.equal('val'); 204 | done(); 205 | }).catch(done); 206 | }); 207 | 208 | it('should set default values for validation errors if missing information', function (done) { 209 | var inst = new Instance(); 210 | inst.$addValidationError(); 211 | 212 | inst.validate().then(function (err) { 213 | should.exist(err); 214 | err.errors.length.should.equal(1); 215 | err.errors[0].message.should.not.equal(''); 216 | err.errors[0].type.should.not.equal(''); 217 | should.not.exist(err.errors[0].path); 218 | should.not.exist(err.errors[0].value); 219 | done(); 220 | }).catch(done); 221 | }); 222 | }); 223 | 224 | describe('#save', function () { 225 | it('should return a promise object', function (done) { 226 | var inst = new Instance(); 227 | 228 | inst.save().then(function (passedIn) { 229 | passedIn.should.be.exactly(inst); 230 | done(); 231 | }).catch(done); 232 | }); 233 | 234 | it('should not save if there are validation errors', function (done) { 235 | var inst = new Instance(); 236 | inst.$addValidationError('test', 'Test Error Message', 'mock test type'); 237 | 238 | inst.save().then(function (passedIn) { 239 | done(new Error('Saved when there were validation errors instead of throwing')); 240 | }, function (err) { 241 | err.errors.length.should.equal(1); 242 | err.errors[0].message.should.equal('Test Error Message'); 243 | err.errors[0].type.should.equal('mock test type'); 244 | err.errors[0].path.should.equal('test'); 245 | done(); 246 | }).catch(done); 247 | }); 248 | 249 | it('should remove the flag for new records', function (done) { 250 | var inst = new Instance(); 251 | inst.options.isNewRecord = true; 252 | inst.isNewRecord = true; 253 | 254 | inst.save().then(function (passedIn) { 255 | passedIn.options.should.have.property('isNewRecord').which.is.exactly(false); 256 | passedIn.should.have.property('isNewRecord').which.is.exactly(false); 257 | done(); 258 | }).catch(done); 259 | }); 260 | }); 261 | 262 | describe('#destroy', function () { 263 | it('should return a promise object', function () { 264 | var inst = new Instance(); 265 | inst.destroy().should.be.instanceOf(bluebird); 266 | }); 267 | }); 268 | 269 | describe('#reload', function () { 270 | it('should return a promise object', function (done) { 271 | var inst = new Instance(); 272 | 273 | inst.reload().then(function (passedIn) { 274 | passedIn.should.be.exactly(inst); 275 | done(); 276 | }).catch(done); 277 | }); 278 | }); 279 | 280 | describe('#update', function () { 281 | it('should update values passed in', function () { 282 | var inst = new Instance(); 283 | 284 | inst._values.should.not.have.property('foo'); 285 | inst.update({ 286 | 'foo': 'bar' 287 | }); 288 | 289 | inst._values.should.have.property('foo').which.is.exactly('bar'); 290 | }); 291 | 292 | it('should return a promise object', function (done) { 293 | var inst = new Instance(); 294 | 295 | inst.update().then(function (passedIn) { 296 | passedIn.should.be.exactly(inst); 297 | done(); 298 | }).catch(done); 299 | }); 300 | }); 301 | 302 | describe('#toJSON', function () { 303 | it('should have the function aliased to toJson', function () { 304 | var inst = new Instance(); 305 | inst.should.have.property('toJSON'); 306 | inst.toJSON.should.be.exactly(inst.toJson); 307 | }); 308 | 309 | it('should return an object that is not an Instance', function () { 310 | var inst = new Instance(); 311 | inst.toJSON().should.not.be.an.instanceOf(Instance); 312 | }); 313 | 314 | it('should return an object with the values of the instance', function () { 315 | var inst = new Instance(); 316 | 317 | inst.set('foo', 'bar'); 318 | inst.set('baz', 'bin'); 319 | 320 | var json = inst.toJSON(); 321 | json.should.have.property('foo').which.is.exactly('bar'); 322 | json.should.have.property('baz').which.is.exactly('bin'); 323 | }); 324 | }); 325 | 326 | describe('#$addValidationError', function () { 327 | it('should add a validation error', function () { 328 | var inst = new Instance(); 329 | inst.$addValidationError('testCol', 'test validation message', 'test validation type'); 330 | inst.__validationErrors.length.should.equal(1); 331 | inst.__validationErrors[0].col.should.equal('testCol'); 332 | inst.__validationErrors[0].message.should.equal('test validation message'); 333 | inst.__validationErrors[0].type.should.equal('test validation type'); 334 | }); 335 | 336 | it('should allow multiple validation errors per column', function () { 337 | var inst = new Instance(); 338 | inst.$addValidationError('testCol', 'test validation message'); 339 | inst.$addValidationError('testCol', 'test validation message'); 340 | inst.__validationErrors.length.should.equal(2); 341 | }); 342 | }); 343 | 344 | describe('#$removeValidationError', function () { 345 | it('should remove a validation error', function () { 346 | var inst = new Instance(); 347 | 348 | inst.__validationErrors.push({col: 'testCol', message: '', type: ''}); 349 | inst.__validationErrors.length.should.equal(1); 350 | inst.$removeValidationError('testCol'); 351 | 352 | inst.__validationErrors.length.should.equal(0); 353 | }); 354 | 355 | it('should remove all validation errors for column', function () { 356 | var inst = new Instance(); 357 | 358 | inst.__validationErrors.push({col: 'testCol', message: '', type: ''}); 359 | inst.__validationErrors.push({col: 'testCol', message: '', type: ''}); 360 | inst.__validationErrors.length.should.equal(2); 361 | inst.$removeValidationError('testCol'); 362 | 363 | inst.__validationErrors.length.should.equal(0); 364 | }); 365 | 366 | it('should not remove validation errors for other columns', function () { 367 | var inst = new Instance(); 368 | 369 | inst.__validationErrors.push({col: 'testCol', message: '', type: ''}); 370 | inst.__validationErrors.push({col: 'testCol2', message: '', type: ''}); 371 | inst.__validationErrors.length.should.equal(2); 372 | inst.$removeValidationError('testCol'); 373 | 374 | inst.__validationErrors.length.should.equal(1); 375 | }); 376 | }); 377 | 378 | describe('#$clearValidationErrors', function () { 379 | it('should clear validation errors', function () { 380 | var inst = new Instance(); 381 | 382 | inst.__validationErrors.push({col: 'testCol', message: '', type: ''}); 383 | inst.__validationErrors.push({col: 'testCol2', message: '', type: ''}); 384 | inst.__validationErrors.push({col: 'testCol3', message: '', type: ''}); 385 | inst.__validationErrors.length.should.equal(3); 386 | inst.$clearValidationErrors(); 387 | 388 | inst.__validationErrors.length.should.equal(0); 389 | }); 390 | }); 391 | 392 | }); 393 | -------------------------------------------------------------------------------- /src/errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Error objects are exposed via the base SequelizeMock object and can be used in much the same 5 | * way that native Sequelize errors can be used. Because no queries are run, any errors that 6 | * would normally contain a `sql` property include it with a value indicating this is test code 7 | * 8 | * *NOTE:* Error names are copied exactly from Sequelize in the off-chance code is relying on 9 | * error name instead of equality or instanceof 10 | * 11 | * @name Errors 12 | * @fileOverview Error objects used by Sequelize and available via Sequelize Mock 13 | */ 14 | 15 | var util = require('util'); 16 | var _ = require('lodash'); 17 | 18 | /** 19 | * BaseError is the base class most other Sequelize Mock Error classes inherit from 20 | * Mostly based off of [the Sequelize.js equivalent code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/errors.js#L20) 21 | * 22 | * @class BaseError 23 | * @constructor 24 | * @alias Error 25 | * @extends Error 26 | */ 27 | exports.BaseError = function() { 28 | // Set basic error info 29 | var err = Error.apply(this, arguments); 30 | /* istanbul ignore next */ 31 | if (Error.captureStackTrace) 32 | Error.captureStackTrace(this, this.constructor); 33 | 34 | // Configure specifics 35 | this.message = err.message; 36 | // Copying name from sequelize just in case people rely on error name 37 | this.name = 'SequelizeBaseError'; 38 | }; 39 | // Extend Error 40 | util.inherits(exports.BaseError, Error); 41 | 42 | 43 | /** 44 | * ValidationError is the class used for validation errors returned by Sequelize Mock. 45 | * The most common way you will see this error is using the `$addValidationError` test 46 | * helper method on instances. 47 | * 48 | * ValidationErrors contain an `errors` property which contains a list of the specific 49 | * validation errors that were encountered. For example, if two validation errors were 50 | * encountered, it will be an array of length two. Objects in this array are of type 51 | * `ValidationErrorItem`. 52 | * 53 | * @class ValidationError 54 | * @constructor 55 | * @see {@link ValidationErrorItem} 56 | * @extends {@link BaseError} 57 | * @param {String} msg Error Message 58 | * @param {Array} [errors] Error Message 59 | */ 60 | exports.ValidationError = function (msg, errors) { 61 | exports.BaseError.apply(this, arguments); 62 | 63 | this.name = 'SequelizeValidationError'; 64 | this.message = msg || 'Validation Error'; 65 | /** 66 | * Array of all validation errors that were encountered with 67 | * this validation 68 | * 69 | * @member {Array} 70 | **/ 71 | this.errors = errors || []; 72 | }; 73 | // Extend BaseError 74 | util.inherits(exports.ValidationError, exports.BaseError); 75 | 76 | /** 77 | * Get the validation error for a particular field 78 | * 79 | * @memberof ValidationError 80 | * @instance 81 | * @param {String} path Field you would like the list of validation errors for 82 | * @return {Array} Array of validation errors for the given field 83 | */ 84 | exports.ValidationError.prototype.get = function (path) { 85 | return _.filter(this.errors, { path: path, }); 86 | }; 87 | 88 | /** 89 | * A specific validation failure result 90 | * 91 | * @class ValidationErrorItem 92 | * @constructor 93 | * @see ValidationError 94 | * @param {String} msg Error Message 95 | * @param {String} type Type of the error 96 | * @param {String} path Field with the error 97 | * @param {String} value Value with the error 98 | */ 99 | exports.ValidationErrorItem = function (msg, type, path, value) { 100 | this.message = msg; 101 | this.type = type; 102 | this.path = path; 103 | this.value = value; 104 | }; 105 | 106 | 107 | /** 108 | * The generic base Database Error class 109 | * 110 | * @class DatabaseError 111 | * @constructor 112 | * @extends {@link BaseError} 113 | * @param {Error} parentError Original error that triggered this error 114 | */ 115 | exports.DatabaseError = function (parentError) { 116 | this.parent = this.original = parentError; 117 | /** 118 | * Ordinarily contains the SQL that triggered the error. 119 | * However, because test code does not go to the DB and 120 | * therefore does not have SQL, this will contain an SQL 121 | * comment stating it is from Sequelize Mock. 122 | * 123 | * @name sql 124 | * @member {string} 125 | **/ 126 | this.sql = '/* Sequelize Mock; No SQL generated */'; 127 | }; 128 | // Extend BaseError 129 | util.inherits(exports.DatabaseError, exports.BaseError); 130 | 131 | 132 | /** 133 | * Database timeout error 134 | * 135 | * @class TimeoutError 136 | * @constructor 137 | * @extends {@link DatabaseError} 138 | */ 139 | exports.TimeoutError = function () { 140 | exports.BaseError.call(this, 'Query Timed Out'); 141 | exports.DatabaseError.call(this, this); 142 | 143 | this.name = 'SequelizeTimeoutError'; 144 | }; 145 | // Extend DatabaseError 146 | util.inherits(exports.TimeoutError, exports.DatabaseError); 147 | 148 | 149 | /** 150 | * Unique constraint violation error 151 | * 152 | * @class UniqueConstraintError 153 | * @constructor 154 | * @extends {@link ValidationError} 155 | * @extends {@link DatabaseError} 156 | * @param {Object} opts Unique constraint error information 157 | * @param {String} opts.message Error message 158 | * @param {Array} [opts.errors] Errors that were encountered 159 | * @param {Array} [opts.fields] Fields that violated the unique constraint 160 | */ 161 | exports.UniqueConstraintError = function (opts) { 162 | opts = opts || {}; 163 | 164 | exports.ValidationError.call(this, opts.message, opts.errors); 165 | exports.DatabaseError.call(this, this); 166 | 167 | this.name = 'SequelizeUniqueConstraintError'; 168 | this.fields = opts.fields || []; 169 | }; 170 | // Extend ValidationError 171 | util.inherits(exports.UniqueConstraintError, exports.ValidationError); 172 | 173 | 174 | /** 175 | * Foreign key constraint violation error 176 | * 177 | * @class ForeignKeyConstraintError 178 | * @constructor 179 | * @extends {@link BaseError} 180 | * @extends {@link DatabaseError} 181 | * @param {Object} opts Unique constraint error information 182 | * @param {String} opts.message Error message 183 | * @param {Array} [opts.fields] Fields that violated the unique constraint 184 | * @param {String} opts.table Name of the table the failure is for 185 | * @param {String} opts.value Invalid value 186 | * @param {String} opts.index Name of the foreign key index that was violated 187 | */ 188 | exports.ForeignKeyConstraintError = function (opts) { 189 | opts = opts || {}; 190 | 191 | exports.BaseError.call(this, opts.message); 192 | exports.DatabaseError.call(this, this); 193 | 194 | this.name = 'SequelizeForeignKeyConstraintError'; 195 | this.fields = opts.fields || []; 196 | this.table = opts.table; 197 | this.value = opts.value; 198 | this.index = opts.index; 199 | }; 200 | // Extend DatabaseError 201 | util.inherits(exports.ForeignKeyConstraintError, exports.DatabaseError); 202 | 203 | 204 | /** 205 | * Exclusion constraint violation error 206 | * 207 | * @class ExclusionConstraintError 208 | * @constructor 209 | * @extends {@link BaseError} 210 | * @extends {@link DatabaseError} 211 | * @param {Object} opts Unique constraint error information 212 | * @param {String} opts.message Error message 213 | * @param {Array} [opts.fields] Fields that violated the unique constraint 214 | * @param {String} opts.table Name of the table the failure is for 215 | * @param {String} opts.constraint Name of the constraint that was violated 216 | */ 217 | exports.ExclusionConstraintError = function (opts) { 218 | opts = opts || {}; 219 | 220 | exports.BaseError.call(this, opts.message); 221 | exports.DatabaseError.call(this, this); 222 | 223 | this.name = 'SequelizeExclusionConstraintError'; 224 | this.fields = opts.fields || []; 225 | this.table = opts.table; 226 | this.constraint = opts.constraint; 227 | }; 228 | // Extend DatabaseError 229 | util.inherits(exports.ExclusionConstraintError, exports.DatabaseError); 230 | 231 | 232 | /** 233 | * Generic database connection error 234 | * 235 | * @class ConnectionError 236 | * @constructor 237 | * @extends {@link BaseError} 238 | * @param {Error} parentError Original error that triggered this error 239 | */ 240 | exports.ConnectionError = function (parentError) { 241 | exports.BaseError.call(this, parentError ? parentError.message : 'Connection Error'); 242 | this.parent = this.original = parentError; 243 | this.name = 'SequelizeConnectionError'; 244 | }; 245 | // Extend BaseError 246 | util.inherits(exports.ConnectionError, exports.BaseError); 247 | 248 | 249 | /** 250 | * Database connection refused error 251 | * 252 | * @class ConnectionRefusedError 253 | * @constructor 254 | * @extends {@link ConnectionError} 255 | * @param {Error} parentError Original error that triggered this error 256 | */ 257 | exports.ConnectionRefusedError = function (parentError) { 258 | exports.ConnectionError.call(this, parentError); 259 | this.name = 'SequelizeConnectionRefusedError'; 260 | }; 261 | // Extend ConnectionError 262 | util.inherits(exports.ConnectionRefusedError, exports.ConnectionError); 263 | 264 | 265 | /** 266 | * Database access denied connection error 267 | * 268 | * @class AccessDeniedError 269 | * @constructor 270 | * @extends {@link ConnectionError} 271 | * @param {Error} parentError Original error that triggered this error 272 | */ 273 | exports.AccessDeniedError = function (parentError) { 274 | exports.ConnectionError.call(this, parentError); 275 | this.name = 'SequelizeAccessDeniedError'; 276 | }; 277 | // Extend ConnectionError 278 | util.inherits(exports.AccessDeniedError , exports.ConnectionError); 279 | 280 | 281 | /** 282 | * Database host not found connection error 283 | * 284 | * @class HostNotFoundError 285 | * @constructor 286 | * @extends {@link ConnectionError} 287 | * @param {Error} parentError Original error that triggered this error 288 | */ 289 | exports.HostNotFoundError = function (parentError) { 290 | exports.ConnectionError.call(this, parentError); 291 | this.name = 'SequelizeHostNotFoundError'; 292 | }; 293 | // Extend ConnectionError 294 | util.inherits(exports.HostNotFoundError , exports.ConnectionError); 295 | 296 | 297 | /** 298 | * Database host not reachable connection error 299 | * 300 | * @class HostNotReachableError 301 | * @constructor 302 | * @extends {@link ConnectionError} 303 | * @param {Error} parentError Original error that triggered this error 304 | */ 305 | exports.HostNotReachableError = function (parentError) { 306 | exports.ConnectionError.call(this, parentError); 307 | this.name = 'SequelizeHostNotReachableError'; 308 | }; 309 | // Extend ConnectionError 310 | util.inherits(exports.HostNotReachableError , exports.ConnectionError); 311 | 312 | 313 | /** 314 | * Database invalid connection error 315 | * 316 | * @class InvalidConnectionError 317 | * @constructor 318 | * @extends {@link ConnectionError} 319 | * @param {Error} parentError Original error that triggered this error 320 | */ 321 | exports.InvalidConnectionError = function (parentError) { 322 | exports.ConnectionError.call(this, parentError); 323 | this.name = 'SequelizeInvalidConnectionError'; 324 | }; 325 | // Extend ConnectionError 326 | util.inherits(exports.InvalidConnectionError , exports.ConnectionError); 327 | 328 | 329 | /** 330 | * Database connection timed out error 331 | * 332 | * @class ConnectionTimedOutError 333 | * @constructor 334 | * @extends {@link ConnectionError} 335 | * @param {Error} parentError Original error that triggered this error 336 | */ 337 | exports.ConnectionTimedOutError = function (parentError) { 338 | exports.ConnectionError.call(this, parentError); 339 | this.name = 'SequelizeConnectionTimedOutError'; 340 | }; 341 | // Extend ConnectionError 342 | util.inherits(exports.ConnectionTimedOutError , exports.ConnectionError); 343 | 344 | 345 | /** 346 | * Error from a Sequelize (Mock) Instance 347 | * 348 | * @class InstanceError 349 | * @constructor 350 | * @extends {@link BaseError} 351 | * @param {Error} message Error message 352 | */ 353 | exports.InstanceError = function (message) { 354 | exports.BaseError.apply(this, arguments); 355 | this.name = 'SequelizeInstanceError'; 356 | }; 357 | // Extend BaseError 358 | util.inherits(exports.InstanceError , exports.BaseError); 359 | 360 | /** 361 | * InvalidQueryError indicates that a query object was not formatted correctly. This most 362 | * likely means that you attempted to add a new query by pushing directly to the internal 363 | * object rather than going through `$queueResult` or `$queueFailure`. 364 | * 365 | * @class InvalidQueryError 366 | * @constructor 367 | * @extends BaseError 368 | * @private 369 | */ 370 | exports.InvalidQueryResultError = function (message) { 371 | exports.BaseError.call(this, message || 'Invalid query result was queued. Unable to complete mock query'); 372 | this.name = 'SequelizeMockInvalidQueryResultError'; 373 | }; 374 | // Extend ConnectionError 375 | util.inherits(exports.InvalidQueryResultError , exports.BaseError); 376 | 377 | /** 378 | * EmptyQueryQueueError indicates that there are currently no queued results and that no 379 | * automated fallback was provided or available for the calling function. 380 | * 381 | * @class EmptyQueryQueueError 382 | * @constructor 383 | * @extends BaseError 384 | * @private 385 | */ 386 | exports.EmptyQueryQueueError = function (message) { 387 | exports.BaseError.call(this, message || 'No query results are queued. Unexpected query attempted to be run'); 388 | this.name = 'SequelizeMockEmptyQueryQueueError'; 389 | }; 390 | // Extend ConnectionError 391 | util.inherits(exports.EmptyQueryQueueError , exports.BaseError); 392 | -------------------------------------------------------------------------------- /src/sequelize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * The mock class for the base Sequelize interface. 5 | * 6 | * @name Sequelize 7 | * @fileOverview Mock class for the base Sequelize class 8 | */ 9 | 10 | var path = require('path'), 11 | _ = require('lodash'), 12 | bluebird = require('bluebird'), 13 | Model = require('./model'), 14 | Instance = require('./instance'), 15 | Utils = require('./utils'), 16 | Errors = require('./errors'), 17 | DataTypes = require('./data-types')({}), 18 | QueryInterface = require('./queryinterface'); 19 | 20 | /** 21 | * Sequelize Mock Object. This can be initialize much the same way that Sequelize itself 22 | * is initialized. Any configuration or options is ignored, so it can be used as a drop-in 23 | * replacement for Sequelize but does not have all the same functionality or features. 24 | * 25 | * @class Sequelize 26 | * @constructor 27 | * @param {String} [database] Ignored for Mock objects, supported to match Sequelize 28 | * @param {String} [username] Ignored for Mock objects, supported to match Sequelize 29 | * @param {String} [password] Ignored for Mock objects, supported to match Sequelize 30 | * @param {String} [options] Options object. Most default Sequelize options are ignored unless listed below. All, however, are available by accessing `sequelize.options` 31 | * @param {String} [options.dialect='mock'] Dialect that the system will use. Avaible to be returned by `getDialect()` but has no other effect 32 | * @param {Boolean} [options.autoQueryFallback] Flag inherited by defined Models indicating if we should try and generate results based on the query automatically 33 | * @param {Boolean} [options.stopPropagation] Flag inherited by defined Models indicating if we should not propagate to the parent 34 | **/ 35 | function Sequelize(database, username, password, options) { 36 | if(typeof database == 'object') { 37 | options = database; 38 | } else if (typeof username == 'object') { 39 | options = username; 40 | } else if (typeof password == 'object') { 41 | options = password; 42 | } 43 | 44 | this.queryInterface = new QueryInterface( _.pick(options || {}, ['stopPropagation']) ); 45 | 46 | /** 47 | * Options passed into the Sequelize initialization 48 | * 49 | * @member Sequelize 50 | * @property 51 | **/ 52 | this.options = _.extend({ 53 | dialect: 'mock', 54 | }, options || {}); 55 | 56 | /** 57 | * Used to cache and override model imports for easy mock model importing 58 | * 59 | * @member Sequelize 60 | * @property 61 | **/ 62 | this.importCache = {}; 63 | 64 | /** 65 | * Models that have been defined in this Sequelize Mock instances 66 | * 67 | * @member Sequelize 68 | * @property 69 | **/ 70 | this.models = {}; 71 | } 72 | /** 73 | * Version number for the Mock library 74 | * 75 | * @member Sequelize 76 | * @property 77 | **/ 78 | Sequelize.version = require('../package.json').version; 79 | 80 | /** 81 | * Options object that exists on Sequelize but is not exposed in the API docs. Included 82 | * just in case someone tries to access it for some reason. 83 | * 84 | * @member Sequelize 85 | * @property 86 | * @private 87 | **/ 88 | Sequelize.options = {hooks: {}}; 89 | 90 | /** 91 | * Reference to the mock Sequelize class 92 | * 93 | * @property 94 | **/ 95 | Sequelize.prototype.Sequelize = Sequelize; 96 | 97 | /** 98 | * Reference to the Util functions 99 | * 100 | * @property 101 | **/ 102 | Sequelize.prototype.Utils = Sequelize.Utils = Utils; 103 | 104 | /** 105 | * Reference to the bluebird promise library 106 | * 107 | * @property 108 | **/ 109 | Sequelize.prototype.Promise = Sequelize.Promise = bluebird; 110 | 111 | /** 112 | * Object containing all of the [Sequelize QueryTypes](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/query-types.js). 113 | * 114 | * @property 115 | **/ 116 | Sequelize.QueryTypes = { 117 | SELECT: 'SELECT', 118 | INSERT: 'INSERT', 119 | UPDATE: 'UPDATE', 120 | BULKUPDATE: 'BULKUPDATE', 121 | BULKDELETE: 'BULKDELETE', 122 | DELETE: 'DELETE', 123 | UPSERT: 'UPSERT', 124 | VERSION: 'VERSION', 125 | SHOWTABLES: 'SHOWTABLES', 126 | SHOWINDEXES: 'SHOWINDEXES', 127 | DESCRIBE: 'DESCRIBE', 128 | RAW: 'RAW', 129 | FOREIGNKEYS: 'FOREIGNKEYS', 130 | }; 131 | 132 | /** 133 | * Reference to the mock Model class 134 | * 135 | * @property 136 | **/ 137 | Sequelize.prototype.Model = Sequelize.Model = Model; 138 | 139 | /** 140 | * Reference to the mock Instance class 141 | * 142 | * @property 143 | **/ 144 | Sequelize.prototype.Instance = Sequelize.Instance = Instance; 145 | 146 | /** 147 | * A reference to the Sequelize Error object. Also included are references to each of the 148 | * Error classes listed under the Error API header. 149 | * 150 | * @property Error 151 | **/ 152 | _.each(Errors, function (fn, name) { 153 | if(name == 'BaseError') { 154 | // Aliased to Error for some reason... 155 | name = 'Error'; 156 | } 157 | Sequelize.prototype[name] = Sequelize[name] = fn; 158 | }); 159 | 160 | // DATA TYPES 161 | require('./data-types')(Sequelize); 162 | 163 | /* Test Specific Functionality 164 | * 165 | */ 166 | /** 167 | * Queue a new query result to be returned by either the `query` method call or as a 168 | * fallback from queries from `Model`s defined through the `define` method. 169 | * 170 | * @see {@link query} 171 | * @alias $queueQueryResult 172 | * @alias $qqr 173 | * @param {Any} result The object or value to be returned as the result of a query 174 | * @return {Sequelize} self 175 | **/ 176 | Sequelize.prototype.$queueResult = function(result) { 177 | this.queryInterface.$queueResult(result); 178 | return this; 179 | }; 180 | Sequelize.prototype.$queueQueryResult = Sequelize.prototype.$qqr = Sequelize.prototype.$queueResult; 181 | 182 | /** 183 | * Queue a new query result to be returned by either the `query` method call or as a 184 | * fallback from queries from `Model`s defined through the `define` method. This result 185 | * is returned as a rejected promise for testing error handling. 186 | * 187 | * @see {@link query} 188 | * @alias $queueQueryFailure 189 | * @alias $queueError 190 | * @alias $queueQueryError 191 | * @alias $qqf 192 | * @param {Any} error The object or value to be returned as the failure for a query 193 | * @param {Object} [options] 194 | * @param {Boolean} [options.convertNonErrors] Flag indicating if non `Error` objects should be allowed. Defaults to true 195 | * @return {Sequelize} self 196 | **/ 197 | Sequelize.prototype.$queueFailure = function(error, options) { 198 | this.queryInterface.$queueFailure(error, options); 199 | return this; 200 | }; 201 | Sequelize.prototype.$queueError = 202 | Sequelize.prototype.$queueQueryError = 203 | Sequelize.prototype.$queueQueryFailure = 204 | Sequelize.prototype.$qqf = Sequelize.prototype.$queueFailure; 205 | 206 | /** 207 | * Clears any queued results from `$queueResult` or `$queueFailure` 208 | * 209 | * @see {@link $queueResult} 210 | * @see {@link $queueFailure} 211 | * @alias $queueClear 212 | * @alias $queueQueryClear 213 | * @alias $cqq 214 | * @alias $qqc 215 | * @return {Sequelize} self 216 | **/ 217 | Sequelize.prototype.$clearQueue = function() { 218 | this.queryInterface.$clearQueue(); 219 | return this; 220 | }; 221 | Sequelize.prototype.$queueClear = 222 | Sequelize.prototype.$queueQueryClear = 223 | Sequelize.prototype.$cqq = 224 | Sequelize.prototype.$qqc = Sequelize.prototype.$clearQueue; 225 | 226 | /** 227 | * Overrides a path used for import 228 | * 229 | * @see {@link import} 230 | * @param {String} importPath The original path that import will be called with 231 | * @param {String} overridePath The path that should actually be used for resolving. If this path is relative, it will be relative to the file calling the import function 232 | **/ 233 | Sequelize.prototype.$overrideImport = function (realPath, mockPath) { 234 | this.importCache[realPath] = mockPath; 235 | }; 236 | 237 | /* Mock Functionality 238 | * 239 | */ 240 | /** 241 | * Returns the specified dialect 242 | * 243 | * @return {String} The specified dialect 244 | */ 245 | Sequelize.prototype.getDialect = function() { 246 | return this.options.dialect; 247 | }; 248 | 249 | /** 250 | * Returns the current instance of `QueryInterface` 251 | * 252 | * @see {@link ./queryinterface.md|QueryInterface} 253 | * @see {@link query} 254 | * @return {QueryInterface} The instantiated `QueryInterface` object used for test `query` 255 | */ 256 | Sequelize.prototype.getQueryInterface = function() { 257 | return this.queryInterface; 258 | }; 259 | 260 | /** 261 | * Define a new mock Model. You should provide a name and a set of default values for this 262 | * new Model. The default values will be used any time a new Instance of this model is 263 | * created and will be overridden by any values provided specifically to that Instance. 264 | * 265 | * Additionally an options object can be passed in with an `instanceMethods` map. All of 266 | * functions in this object will be added to any Instance of the Model that is created. 267 | * 268 | * All models are available by name via the `.models` property 269 | * 270 | * @example 271 | * sequelize.define('user', { 272 | * 'name': 'Test User', 273 | * 'email': 'test@example.com', 274 | * 'joined': new Date(), 275 | * }, { 276 | * 'instanceMethods': { 277 | * 'tenure': function () { return Date.now() - this.get('joined'); }, 278 | * }, 279 | * }); 280 | * 281 | * @see Model 282 | * @param {String} name Name of the mock Model 283 | * @param {Object} [obj={}] Map of keys and their default values that will be used when querying against this object 284 | * @param {Object} [opts] Options for the mock model 285 | * @param {Object} [opts.instanceMethods] Map of function names and the functions to be run. These functions will be added to any instances of this Model type 286 | * @return {Model} Mock Model as defined by the name, default values, and options provided 287 | */ 288 | Sequelize.prototype.define = function (name, obj, opts) { 289 | opts = _.extend({ 290 | sequelize: this, 291 | }, opts || {}) 292 | 293 | var model = new Model(name, obj, opts); 294 | this.models[name] = model; 295 | return model; 296 | }; 297 | 298 | /** 299 | * Checks whether a model with the given name is defined. 300 | * 301 | * Uses the `.models` property for lookup. 302 | * 303 | * @see{@link define} 304 | * @see{@link models} 305 | * @param {String} name Name of the model 306 | * @return {Boolean} True if the model is defined, false otherwise 307 | */ 308 | Sequelize.prototype.isDefined = function (name) { 309 | return name in this.models && typeof this.models[name] !== 'undefined'; 310 | }; 311 | 312 | /** 313 | * Imports a given model from the provided file path. Files that are imported should 314 | * export a function that accepts two parameters, this sequelize instance, and an object 315 | * with all of the available datatypes 316 | * 317 | * Before importing any modules, it will remap any paths that were overridden using the 318 | * `$overrideImport` test function. This method is most helpful when used to make the 319 | * SequelizeMock framework import your mock models instead of the real ones in your test 320 | * code. 321 | * 322 | * @param {String} path Path of the model to import. Can be relative or absolute 323 | * @return {Any} The result of evaluating the imported file's function 324 | **/ 325 | Sequelize.prototype.import = function (importPath) { 326 | if(typeof this.importCache[importPath] === 'string') { 327 | importPath = this.importCache[importPath]; 328 | } 329 | 330 | if(path.normalize(importPath) !== path.resolve(importPath)) { 331 | // We're relative, and need the calling files location 332 | var callLoc = path.dirname(Utils.stack()[1].getFileName()); 333 | 334 | importPath = path.resolve(callLoc, importPath); 335 | } 336 | 337 | if(this.importCache[importPath] === 'string' || !this.importCache[importPath]) { 338 | var defineCall = arguments.length > 1 ? arguments[1] : require(importPath); 339 | if(typeof defineCall === 'object') { 340 | // ES6 module compatibility 341 | defineCall = defineCall.default; 342 | } 343 | this.importCache[importPath] = defineCall(this, DataTypes); 344 | } 345 | 346 | return this.importCache[importPath]; 347 | }; 348 | 349 | /** 350 | * Fetch a Model which is already defined. 351 | * 352 | * Uses the `.models` property for lookup. 353 | * 354 | * @see{@link define} 355 | * @see{@link models} 356 | * @param {String} name Name of the model 357 | * @return {Model} Mock model which was defined with the specified name 358 | * @throws {Error} Will throw an error if the model is not defined (that is, if sequelize#isDefined returns false) 359 | */ 360 | Sequelize.prototype.model = function (name) { 361 | if (!this.isDefined(name)) { 362 | throw new Error(name + ' has not been defined'); 363 | } 364 | 365 | return this.models[name]; 366 | } 367 | 368 | /** 369 | * Run a mock query against the `QueryInterface` associated with this Sequelize instance 370 | * 371 | * @return {Promise} The next result of a query as queued to the `QueryInterface` 372 | */ 373 | Sequelize.prototype.query = function () { 374 | return this.queryInterface.$query(); 375 | }; 376 | 377 | /** 378 | * This function will simulate the wrapping of a set of queries in a transaction. Because 379 | * Sequelize Mock does not run any actual queries, there is no difference between code 380 | * run through transactions and those that aren't. 381 | * 382 | * @param {Function} [fn] Optional function to run as a tranasction 383 | * @return {Promise} Promise that resolves the code is successfully run, otherwise it is rejected 384 | */ 385 | Sequelize.prototype.transaction = function (fn) { 386 | if(!fn) { 387 | fn = function (t) { 388 | return bluebird.resolve(t); 389 | }; 390 | } 391 | return new bluebird(function (resolve, reject) { 392 | // TODO Return mock transaction object 393 | return fn({}).then(resolve, reject); 394 | }); 395 | }; 396 | 397 | /** 398 | * Simply returns the first argument passed in, unmodified. 399 | * 400 | * @param {Any} arg Value to return 401 | * @return {Any} value passed in 402 | */ 403 | Sequelize.prototype.literal = function (arg) { 404 | return arg; 405 | }; 406 | 407 | /** 408 | * Always returns a resolved promise 409 | * 410 | * @return {Promise} will always resolve as a successful authentication 411 | */ 412 | Sequelize.prototype.authenticate = function() { 413 | return new bluebird(function (resolve) { 414 | return resolve(); 415 | }); 416 | }; 417 | 418 | module.exports = Sequelize; 419 | -------------------------------------------------------------------------------- /src/data-types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'), 4 | _ = require('lodash'); 5 | 6 | /** 7 | * These are the available DataTypes on the Sequelize class. You can access these on the 8 | * class object as seen here. 9 | * 10 | * @example 11 | * var Sequelize = require('sequelize-mock'); 12 | * var sequelize = new Sequelize(); 13 | * 14 | * Sequelize.STRING 15 | * // OR ... 16 | * sequelize.Sequelize.STRING 17 | * 18 | * @name DataTypes 19 | * @fileOverview Mock class for the base Sequelize class 20 | */ 21 | 22 | function noop() {} 23 | 24 | /** 25 | * Base abstract data type all other data types inherit from 26 | * 27 | * @class Abstract 28 | * @constructor 29 | * @private 30 | **/ 31 | function Abstract () { 32 | } 33 | 34 | Abstract.prototype.toString = 35 | /** 36 | * Returns the key this data type maps to in SQL 37 | * 38 | * @instance 39 | * @return {String} Key for this datatype 40 | * @private 41 | **/ 42 | Abstract.prototype.toSql = function () { 43 | return this.key; 44 | }; 45 | 46 | /** 47 | * Stringify a value for this data type 48 | * 49 | * @instance 50 | * @param {*} value The value to stringify 51 | * @param {Object} options Any options the stringify for this datatype accepts 52 | * @return {String} A string version value passed in 53 | * @private 54 | **/ 55 | Abstract.prototype.stringify = function (value, options) { 56 | if (this.$stringify) { 57 | return this.$stringify(value, options); 58 | } 59 | return value + ''; 60 | }; 61 | 62 | /** 63 | * Returns the key this data type maps to in SQL 64 | * 65 | * @instance 66 | * @return {String} Key for this datatype 67 | * @private 68 | **/ 69 | Abstract.inherits = function (Inheriting) { 70 | util.inherits(Inheriting, this); 71 | _.extend(Inheriting, this); 72 | 73 | return Inheriting; 74 | }; 75 | 76 | // In Sequelize these are mostly internal, but they are mapped here for parity 77 | Abstract.warn = noop; 78 | Abstract.prototype.dialectTypes = ''; 79 | 80 | /** 81 | * Mock string data type 82 | * 83 | * @property STRING 84 | */ 85 | var STRING = Abstract.inherits(function (length, binary) { 86 | if (!(this instanceof STRING)) { 87 | return new STRING(length, binary); 88 | } 89 | 90 | this.options = typeof length === 'object' && length ? length : { 91 | length: length || 255, 92 | binary: binary, 93 | }; 94 | }); 95 | STRING.prototype.key = STRING.key = 'STRING'; 96 | STRING.prototype.BINARY = STRING; 97 | 98 | /** 99 | * Mock char data type 100 | * 101 | * @property CHAR 102 | */ 103 | var CHAR = STRING.inherits(function (length, binary) { 104 | if (!(this instanceof CHAR)) { 105 | return new CHAR(length, binary); 106 | } 107 | STRING.apply(this, arguments); 108 | }); 109 | CHAR.prototype.key = CHAR.key = 'CHAR'; 110 | 111 | /** 112 | * Mock text data type 113 | * 114 | * @property TEXT 115 | */ 116 | var TEXT = Abstract.inherits(function(length) { 117 | if (!(this instanceof TEXT)) { 118 | return new TEXT(length); 119 | } 120 | 121 | this.options = typeof length === 'object' && length ? length : { 122 | length: length || '', 123 | }; 124 | this.key = (this.options.length || '').toUpperCase() + 'TEXT'; 125 | }); 126 | TEXT.prototype.key = TEXT.key = 'TEXT'; 127 | 128 | /** 129 | * Mock number data type for other number data types to inherit from 130 | * 131 | * @private 132 | * @property 133 | **/ 134 | var NUMBER = Abstract.inherits(function(options) { 135 | this.options = options; 136 | }); 137 | NUMBER.prototype.key = NUMBER.key = 'NUMBER'; 138 | NUMBER.prototype.ZEROFILL = NUMBER.prototype.UNSIGNED = NUMBER; 139 | 140 | NUMBER.prototype.$stringify = function (value) { 141 | if (isNaN(value)) { 142 | return "'NaN'"; 143 | } else if (!isFinite(value)) { 144 | return "'" + (value < 0 ? '-' : '') + "Infinity'"; 145 | } 146 | return value + ''; 147 | }; 148 | 149 | /** 150 | * Mock integer data type 151 | * 152 | * @property INTEGER 153 | */ 154 | var INTEGER = NUMBER.inherits(function(length) { 155 | if (!(this instanceof INTEGER)) { 156 | return new INTEGER(length); 157 | } 158 | 159 | NUMBER.call(this, typeof length === 'object' && length ? length : { 160 | length: length, 161 | }); 162 | }); 163 | INTEGER.prototype.key = INTEGER.key = 'INTEGER'; 164 | 165 | /** 166 | * Mock big integer data type 167 | * 168 | * @property BIGINT 169 | */ 170 | var BIGINT = NUMBER.inherits(function(length) { 171 | if (!(this instanceof BIGINT)) { 172 | return new BIGINT(length); 173 | } 174 | 175 | NUMBER.call(this, typeof length === 'object' && length ? length : { 176 | length: length, 177 | }); 178 | }); 179 | BIGINT.prototype.key = BIGINT.key = 'BIGINT'; 180 | 181 | /** 182 | * Mock float data type 183 | * 184 | * @property FLOAT 185 | */ 186 | var FLOAT = NUMBER.inherits(function(length, decimals) { 187 | if (!(this instanceof FLOAT)) { 188 | return new FLOAT(length, decimals); 189 | } 190 | 191 | NUMBER.call(this, typeof length === 'object' && length ? length : { 192 | length: length, 193 | decimals: decimals, 194 | }); 195 | }); 196 | FLOAT.prototype.key = FLOAT.key = 'FLOAT'; 197 | 198 | /** 199 | * Mock real number data type 200 | * 201 | * @property REAL 202 | */ 203 | var REAL = NUMBER.inherits(function(length, decimals) { 204 | if (!(this instanceof REAL)) { 205 | return new REAL(length, decimals); 206 | } 207 | 208 | NUMBER.call(this, typeof length === 'object' && length ? length : { 209 | length: length, 210 | decimals: decimals, 211 | }); 212 | }); 213 | REAL.prototype.key = REAL.key = 'REAL'; 214 | 215 | /** 216 | * Mock double data type 217 | * 218 | * @property DOUBLE 219 | */ 220 | var DOUBLE = NUMBER.inherits(function(length, decimals) { 221 | if (!(this instanceof DOUBLE)) { 222 | return new DOUBLE(length, decimals); 223 | } 224 | 225 | NUMBER.call(this, typeof length === 'object' && length ? length : { 226 | length: length, 227 | decimals: decimals, 228 | }); 229 | }); 230 | DOUBLE.prototype.key = DOUBLE.key = 'DOUBLE PRECISION'; 231 | 232 | /** 233 | * Mock decimal data type 234 | * 235 | * @property DECIMAL 236 | */ 237 | var DECIMAL = NUMBER.inherits(function(precision, scale) { 238 | if (!(this instanceof DECIMAL)) { 239 | return new DECIMAL(precision, scale); 240 | } 241 | 242 | NUMBER.call(this, typeof precision === 'object' && precision ? precision : { 243 | precision: precision, 244 | scale: scale, 245 | }); 246 | }); 247 | DECIMAL.prototype.key = DECIMAL.key = 'DECIMAL'; 248 | 249 | /** 250 | * Mock boolean data type 251 | * 252 | * @property BOOLEAN 253 | */ 254 | var BOOLEAN = Abstract.inherits(function () { 255 | if (!(this instanceof BOOLEAN)) { 256 | return new BOOLEAN(); 257 | } 258 | }); 259 | BOOLEAN.prototype.key = BOOLEAN.key = 'BOOLEAN'; 260 | 261 | /** 262 | * Mock time data type 263 | * 264 | * @property TIME 265 | */ 266 | var TIME = Abstract.inherits(function () { 267 | if (!(this instanceof TIME)) { 268 | return new TIME(); 269 | } 270 | }); 271 | TIME.prototype.key = TIME.key = 'TIME'; 272 | 273 | /** 274 | * Mock date data type 275 | * 276 | * @property DATE 277 | */ 278 | var DATE = Abstract.inherits(function (length) { 279 | if (!(this instanceof DATE)) { 280 | return new DATE(length); 281 | } 282 | 283 | this.options = typeof length === 'object' && length ? length : { 284 | length: length, 285 | }; 286 | }); 287 | DATE.prototype.key = DATE.key = 'DATE'; 288 | 289 | DATE.prototype.$stringify = function (date, options) { 290 | return !(date instanceof Date) ? '' : 291 | date.getFullYear() + '-' + _.padStart(date.getMonth() + 1, 2 , '0') + '-' + _.padStart(date.getDate(), 2, '0') + ' ' + 292 | _.padStart(date.getHours(), 2, '0') + ':' + _.padStart(date.getMinutes(), 2, '0') + ':' + 293 | _.padStart(date.getSeconds(), 2, '0') + '.' + _.padStart(date.getMilliseconds(), 3, '0') + ' Z'; 294 | }; 295 | 296 | /** 297 | * Mock date-only data type 298 | * 299 | * @property DATEONLY 300 | */ 301 | var DATEONLY = Abstract.inherits(function () { 302 | if (!(this instanceof DATEONLY)) { 303 | return new DATEONLY(); 304 | } 305 | }); 306 | DATEONLY.prototype.key = DATEONLY.key = 'DATEONLY'; 307 | 308 | /** 309 | * Mock hstore data type 310 | * 311 | * @property HSTORE 312 | */ 313 | var HSTORE = Abstract.inherits(function () { 314 | if (!(this instanceof HSTORE)) { 315 | return new HSTORE(); 316 | } 317 | }); 318 | HSTORE.prototype.key = HSTORE.key = 'HSTORE'; 319 | 320 | /** 321 | * Mock JSON data type 322 | * 323 | * @property JSON 324 | */ 325 | var JSONT = Abstract.inherits(function () { 326 | if (!(this instanceof JSONT)) { 327 | return new JSONT(); 328 | } 329 | }); 330 | JSONT.prototype.key = JSONT.key = 'JSON'; 331 | JSONT.prototype.$stringify = function (value) { 332 | return JSON.stringify(value); 333 | }; 334 | 335 | /** 336 | * Mock JSONB data type 337 | * 338 | * @property JSONB 339 | */ 340 | var JSONB = JSONT.inherits(function () { 341 | if (!(this instanceof JSONB)) { 342 | return new JSONB(); 343 | } 344 | }); 345 | JSONB.prototype.key = JSONB.key = 'JSONB'; 346 | 347 | /** 348 | * Mock now datetime data type 349 | * 350 | * @property NOW 351 | */ 352 | var NOW = Abstract.inherits(function () { 353 | if (!(this instanceof NOW)) { 354 | return new NOW(); 355 | } 356 | }); 357 | NOW.prototype.key = NOW.key = 'NOW'; 358 | 359 | /** 360 | * Mock blob data type 361 | * 362 | * @property BLOB 363 | */ 364 | var BLOB = Abstract.inherits(function (length) { 365 | if (!(this instanceof BLOB)) { 366 | return new BLOB(length); 367 | } 368 | 369 | this.options = typeof length === 'object' && length ? length : { 370 | length: length, 371 | }; 372 | }); 373 | BLOB.prototype.key = BLOB.key = 'BLOB'; 374 | BLOB.prototype.$stringify = function (value) { 375 | return value.toString('hex'); 376 | }; 377 | 378 | /** 379 | * Mock range data type 380 | * 381 | * @property RANGE 382 | */ 383 | var RANGE = Abstract.inherits(function (subtype) { 384 | if (!(this instanceof RANGE)) { 385 | return new RANGE(subtype); 386 | } 387 | 388 | this.options = typeof subtype === 'object' && subtype ? subtype : { 389 | subtype: new (subtype || INTEGER)(), 390 | }; 391 | }); 392 | RANGE.prototype.key = RANGE.key = 'RANGE'; 393 | 394 | /** 395 | * Mock UUID data type 396 | * 397 | * @property UUID 398 | */ 399 | var UUID = Abstract.inherits(function () { 400 | if (!(this instanceof UUID)) { 401 | return new UUID(); 402 | } 403 | }); 404 | UUID.prototype.key = UUID.key = 'UUID'; 405 | 406 | /** 407 | * Mock UUIDV1 data type 408 | * 409 | * @property UUIDV1 410 | */ 411 | var UUIDV1 = Abstract.inherits(function () { 412 | if (!(this instanceof UUIDV1)) { 413 | return new UUIDV1(); 414 | } 415 | }); 416 | UUIDV1.prototype.key = UUIDV1.key = 'UUIDV1'; 417 | 418 | /** 419 | * Mock UUIDV4 data type 420 | * 421 | * @property UUIDV4 422 | */ 423 | var UUIDV4 = Abstract.inherits(function () { 424 | if (!(this instanceof UUIDV4)) { 425 | return new UUIDV4(); 426 | } 427 | }); 428 | UUIDV4.prototype.key = UUIDV4.key = 'UUIDV4'; 429 | 430 | /** 431 | * Mock virutal data type (even though all test types are technically virtual) 432 | * 433 | * @property VIRTUAL 434 | */ 435 | var VIRTUAL = Abstract.inherits(function (subtype, fields) { 436 | if (!(this instanceof VIRTUAL)) { 437 | return new VIRTUAL(subtype, fields); 438 | } 439 | 440 | this.returnType = new (subtype || INTEGER)(); 441 | this.fields = fields; 442 | }); 443 | VIRTUAL.prototype.key = VIRTUAL.key = 'VIRTUAL'; 444 | 445 | /** 446 | * Mock enum data type 447 | * 448 | * @property ENUM 449 | */ 450 | var ENUM = Abstract.inherits(function (values) { 451 | if (!(this instanceof ENUM)) { 452 | return new ENUM(values); 453 | } 454 | 455 | this.options = typeof values === 'object' && !Array.isArray(values) && values ? values : { 456 | values: Array.isArray(values) ? values : Array.prototype.slice.call(arguments), 457 | }; 458 | this.values = this.options.values; 459 | }); 460 | ENUM.prototype.key = ENUM.key = 'ENUM'; 461 | 462 | /** 463 | * Mock array data type 464 | * 465 | * @property ARRAY 466 | */ 467 | var ARRAY = Abstract.inherits(function (subtype) { 468 | if (!(this instanceof ARRAY)) { 469 | return new ARRAY(subtype); 470 | } 471 | 472 | this.options = typeof subtype === 'object' && subtype ? subtype : { 473 | type: subtype, 474 | }; 475 | this.type = typeof this.options.type === 'function' ? new this.options.type() : this.options.type; 476 | }); 477 | ARRAY.prototype.key = ARRAY.key = 'ARRAY'; 478 | 479 | ARRAY.is = function (obj, type) { 480 | return obj.type instanceof type; 481 | }; 482 | 483 | /** 484 | * Mock geometry data type 485 | * 486 | * @property GEOMETRY 487 | */ 488 | var GEOMETRY = Abstract.inherits(function (subtype, srid) { 489 | if (!(this instanceof GEOMETRY)) { 490 | return new GEOMETRY(subtype, srid); 491 | } 492 | 493 | this.options = typeof subtype === 'object' && subtype ? subtype : { 494 | type: subtype, 495 | srid: srid, 496 | }; 497 | this.type = this.options.type; 498 | this.srid = this.options.srid; 499 | }); 500 | GEOMETRY.prototype.key = GEOMETRY.key = 'GEOMETRY'; 501 | 502 | /** 503 | * Mock geography data type 504 | * 505 | * @property GEOGRAPHY 506 | */ 507 | var GEOGRAPHY = Abstract.inherits(function (subtype, srid) { 508 | if (!(this instanceof GEOGRAPHY)) { 509 | return new GEOGRAPHY(subtype, srid); 510 | } 511 | 512 | this.options = typeof subtype === 'object' && subtype ? subtype : { 513 | type: subtype, 514 | srid: srid, 515 | }; 516 | this.type = this.options.type; 517 | this.srid = this.options.srid; 518 | }); 519 | GEOGRAPHY.prototype.key = GEOGRAPHY.key = 'GEOGRAPHY'; 520 | 521 | module.exports = function (Sequelize) { 522 | Sequelize.STRING = STRING; 523 | Sequelize.CHAR = CHAR; 524 | Sequelize.TEXT = TEXT; 525 | Sequelize.INTEGER = INTEGER; 526 | Sequelize.BIGINT = BIGINT; 527 | Sequelize.FLOAT = FLOAT; 528 | Sequelize.REAL = REAL; 529 | Sequelize.DOUBLE = DOUBLE; 530 | Sequelize.DECIMAL = DECIMAL; 531 | Sequelize.BOOLEAN = BOOLEAN; 532 | Sequelize.TIME = TIME; 533 | Sequelize.DATE = DATE; 534 | Sequelize.DATEONLY = DATEONLY; 535 | Sequelize.HSTORE = HSTORE; 536 | Sequelize.JSON = JSONT; 537 | Sequelize.JSONB = JSONB; 538 | Sequelize.NOW = NOW; 539 | Sequelize.BLOB = BLOB; 540 | Sequelize.RANGE = RANGE; 541 | Sequelize.UUID = UUID; 542 | Sequelize.UUIDV1 = UUIDV1; 543 | Sequelize.UUIDV4 = UUIDV4; 544 | Sequelize.VIRTUAL = VIRTUAL; 545 | Sequelize.ENUM = ENUM; 546 | Sequelize.ARRAY = ARRAY; 547 | Sequelize.GEOMETRY = GEOMETRY; 548 | Sequelize.GEOGRAPHY = GEOGRAPHY; 549 | 550 | return Sequelize; 551 | }; 552 | -------------------------------------------------------------------------------- /docs/api/model.md: -------------------------------------------------------------------------------- 1 | # Model 2 | 3 | This is the base mock Model class that will be the primary way to use Sequelize Mock. 4 | 5 | Mock Models should mostly behave as drop-in replacements for your Sequelize Models 6 | when running unit tests for your code. 7 | 8 | 9 | 10 | 11 | ## new Model(name, [defaults={}], [opts]) 12 | 13 | Model mock class. Models most often should be defined using the `sequelize.define()` 14 | function. 15 | 16 | ### Parameters 17 | 18 | Name | Type | Description 19 | --- | --- | --- 20 | name | Object | Name of the model 21 | [defaults={}] | Object | The default values to use when creating a new instance 22 | [opts] | Object | Options for the mock model 23 | [opts.instanceMethods] | Object | Map of function names and the functions to be run. These functions will be added to any instances of this Model type 24 | [opts.sequelize] | Object | Sequelize instance that this is tied to 25 | 26 | 27 | 28 | 29 | 30 | ### .options 31 | 32 | The current options for the model 33 | 34 | 35 | 36 | 37 | ### .name 38 | 39 | Name given to the model on initialization 40 | 41 | 42 | 43 | 44 | ### #Instance() 45 | 46 | The Model's copy of the Instance class used to build instances 47 | 48 | 49 | 50 | 51 | ### .$queryInterface 52 | 53 | QueryInterface used to run all queries against for models 54 | 55 | If this model is defined with the `Sequelize.define` method, this QueryInterface 56 | will reference the calling `Sequelize` instances QueryInterface when inheriting 57 | any options or propagating any queries. 58 | 59 | 60 | 61 | 62 | ## $queueResult(result, [options]) -> QueryInterface 63 | 64 | Queues a result for any query run against this model. This result will be wrapped 65 | in a Promise and resolved for most any method that would ordinarily run a query 66 | against the database. 67 | 68 | **Example** 69 | 70 | ```javascript 71 | UserMock.$queueResult(UserMock.build({ 72 | name: 'Alex', 73 | })); 74 | UserMock.findOne().then(function (result) { 75 | // `result` is the passed in built object 76 | result.get('name'); // 'Alex' 77 | }); 78 | 79 | // For `findOrCreate` there is an extra option that can be passed in 80 | UserMock.$queueResult(UserMock.build(), { wasCreated: false }); 81 | UserMock.findOrCreate({ 82 | // ... 83 | }).spread(function (user, created) { 84 | // created == false 85 | }); 86 | ``` 87 | 88 | **See** 89 | 90 | - [QueryInterface.$queueResult](./queryinterface.md#queueResult) 91 | 92 | ### Parameters 93 | 94 | Name | Type | Description 95 | --- | --- | --- 96 | result | Any | The object or value to be returned as the result of a query 97 | [options] | Object | Options used when returning the result 98 | [options.wasCreated] | Boolean | Optional flag if a query requires a `created` value in the return indicating if the object was "created" in the DB 99 | [options.affectedRows] | Array.<Any> | Optional array of objects if the query requires an `affectedRows` return value 100 | 101 | 102 | ### Return 103 | `QueryInterface`: model instance of QueryInterface 104 | 105 | 106 | 107 | 108 | ## $queueFailure(error, [options]) -> QueryInterface 109 | 110 | Queues an error/failure for any query run against this model. This error will be wrapped 111 | in a rejected Promise and be returned for most any method that would ordinarily run a 112 | query against the database.
**Alias** $queueError 113 | 114 | **Example** 115 | 116 | ```javascript 117 | UserMock.$queueFailure(new Error('My test error')); 118 | UserMock.findOne().catch(function (error) { 119 | error.message; // 'My test error' 120 | }); 121 | 122 | // Non error objects by default are converted to Sequelize.Error objects 123 | UserMock.$queueFailure('Another Test Error'); 124 | UserMock.findOne().catch(function (error) { 125 | error instanceof UserMock.sequelize.Error; // true 126 | }); 127 | ``` 128 | 129 | **See** 130 | 131 | - [QueryInterface.$queueFailure](./queryinterface.md#queueFailure) 132 | 133 | ### Parameters 134 | 135 | Name | Type | Description 136 | --- | --- | --- 137 | error | Any | The object or value to be returned as the failure for a query 138 | [options] | Object | Options used when returning the result 139 | [options.convertNonErrors] | Boolean | Flag indicating if non `Error` objects should be allowed. Defaults to true 140 | 141 | 142 | ### Return 143 | `QueryInterface`: model instance of QueryInterface 144 | 145 | 146 | 147 | 148 | ## $clearQueue([options]) -> QueryInterface 149 | 150 | Clears any queued results or failures for this Model.
**Alias** $queueClear 151 | 152 | **Example** 153 | 154 | ```javascript 155 | UserMock.$queueResult(UserMock.build()); 156 | // == 1 item in query queue 157 | UserMock.$queueFailure(new Error()); 158 | // == 2 items in query queue 159 | UserMock.$clearQueue(); 160 | // == 0 items in query queue 161 | ``` 162 | 163 | **See** 164 | 165 | - [QueryInterface.$clearQueue](./queryinterface.md#clearQueue) 166 | 167 | ### Parameters 168 | 169 | Name | Type | Description 170 | --- | --- | --- 171 | [options] | Object | Options used when returning the result 172 | [options.propagateClear] | Boolean | Propagate this clear up to any parent `QueryInterface`s. Defaults to false 173 | 174 | 175 | ### Return 176 | `QueryInterface`: model instance of QueryInterface 177 | 178 | 179 | 180 | 181 | ## sync() -> Promise.<Model> 182 | 183 | No-op that returns a promise with the current object 184 | 185 | ### Return 186 | `Promise.`: Self 187 | 188 | 189 | 190 | 191 | ## drop() -> Promise 192 | 193 | No-op that returns a promise that always resolves 194 | 195 | ### Return 196 | `Promise`: Promise that always resolves 197 | 198 | 199 | 200 | 201 | ## getTableName() -> String 202 | 203 | Returns the name of the model 204 | 205 | ### Return 206 | `String`: the name of the model 207 | 208 | 209 | 210 | 211 | ## unscoped() -> Model 212 | 213 | No-op that returns the current object 214 | 215 | ### Return 216 | `Model`: Self 217 | 218 | 219 | 220 | 221 | ## scope() -> Model 222 | 223 | No-op that returns the current object 224 | 225 | ### Return 226 | `Model`: Self 227 | 228 | 229 | 230 | 231 | ## findAll([options]) -> Promise.<Array.<Instance>> 232 | 233 | Executes a mock query to find all of the instances with any provided options. Without 234 | any other configuration, the default behavior when no queueud query result is present 235 | is to create an array of a single result based on the where query in the options and 236 | wraps it in a promise. 237 | 238 | To turn off this behavior, the `$autoQueryFallback` option on the model should be set 239 | to `false`. 240 | 241 | **Example** 242 | 243 | ```javascript 244 | // This is an example of the default behavior with no queued results 245 | // If there is a queued result or failure, that will be returned instead 246 | User.findAll({ 247 | where: { 248 | email: 'myEmail@example.com', 249 | }, 250 | }).then(function (results) { 251 | // results is an array of 1 252 | results[0].get('email') === 'myEmail@example.com'; // true 253 | }); 254 | ``` 255 | 256 | ### Parameters 257 | 258 | Name | Type | Description 259 | --- | --- | --- 260 | [options] | Object | Options for the findAll query 261 | [options.where] | Object | Values that any automatically created Instances should have 262 | 263 | 264 | ### Return 265 | `Promise.>`: result returned by the mock query 266 | 267 | 268 | 269 | 270 | ## findAndCount([options]) -> Promise.<Object> 271 | 272 | Executes a mock query to find all of the instances with any provided options and also return 273 | the count. Without any other configuration, the default behavior when no queueud query result 274 | is present is to create an array of a single result based on the where query in the options and 275 | wraps it in a promise. 276 | 277 | To turn off this behavior, the `$autoQueryFallback` option on the model should be set 278 | to `false`.
**Alias** findAndCountAll 279 | 280 | **Example** 281 | 282 | ```javascript 283 | // This is an example of the default behavior with no queued results 284 | // If there is a queued result or failure, that will be returned instead 285 | User.findAndCountAll({ 286 | where: { 287 | email: 'myEmail@example.com', 288 | }, 289 | }).then(function (results) { 290 | // results is an array of 1 291 | results.count = 1 292 | results.rows[0].get('email') === 'myEmail@example.com'; // true 293 | }); 294 | ``` 295 | 296 | ### Parameters 297 | 298 | Name | Type | Description 299 | --- | --- | --- 300 | [options] | Object | Options for the findAll query 301 | [options.where] | Object | Values that any automatically created Instances should have 302 | 303 | 304 | ### Return 305 | `Promise.`: result returned by the mock query 306 | 307 | 308 | 309 | 310 | ## findById(id) -> Promise.<Instance> 311 | 312 | Executes a mock query to find an instance with the given ID value. Without any other 313 | configuration, the default behavior when no queueud query result is present is to 314 | create a new Instance with the given id and wrap it in a promise. 315 | 316 | To turn off this behavior, the `$autoQueryFallback` option on the model should be set 317 | to `false`. 318 | 319 | ### Parameters 320 | 321 | Name | Type | Description 322 | --- | --- | --- 323 | id | Integer | ID of the instance 324 | 325 | 326 | ### Return 327 | `Promise.`: Promise that resolves with an instance with the given ID 328 | 329 | 330 | 331 | 332 | ## findOne([options]) -> Promise.<Instance> 333 | 334 | Executes a mock query to find an instance with the given infomation. Without any other 335 | configuration, the default behavior when no queueud query result is present is to 336 | build a new Instance with the given properties pulled from the where object in the 337 | options and wrap it in a promise.
**Alias** find 338 | 339 | **Example** 340 | 341 | ```javascript 342 | // This is an example of the default behavior with no queued results 343 | // If there is a queued result or failure, that will be returned instead 344 | User.find({ 345 | where: { 346 | email: 'myEmail@example.com', 347 | }, 348 | }).then(function (user) { 349 | user.get('email') === 'myEmail@example.com'; // true 350 | }); 351 | ``` 352 | 353 | **See** 354 | 355 | - [findAll](#findAll) 356 | 357 | ### Parameters 358 | 359 | Name | Type | Description 360 | --- | --- | --- 361 | [options] | Object | Map of values that the instance should have 362 | 363 | 364 | ### Return 365 | `Promise.`: Promise that resolves with an instance with the given properties 366 | 367 | 368 | 369 | 370 | ## max(field) -> Any 371 | 372 | Executes a mock query to find the max value of a field. Without any other 373 | configuration, the default behavior when no queueud query result is present 374 | is to return the default value for the given field 375 | 376 | ### Parameters 377 | 378 | Name | Type | Description 379 | --- | --- | --- 380 | field | String | Name of the field to return for 381 | 382 | 383 | ### Return 384 | `Any`: the default value for the given field 385 | 386 | 387 | 388 | 389 | ## min(field) -> Any 390 | 391 | Executes a mock query to find the min value of a field. Without any other 392 | configuration, the default behavior when no queueud query result is present 393 | is to return the default value for the given field 394 | 395 | ### Parameters 396 | 397 | Name | Type | Description 398 | --- | --- | --- 399 | field | String | Name of the field to return for 400 | 401 | 402 | ### Return 403 | `Any`: the default value for the given field 404 | 405 | 406 | 407 | 408 | ## sum(field) -> Any 409 | 410 | Executes a mock query to find the sum value of a field. Without any other 411 | configuration, the default behavior when no queueud query result is present 412 | is to return the default value for the given field 413 | 414 | ### Parameters 415 | 416 | Name | Type | Description 417 | --- | --- | --- 418 | field | String | Name of the field to return for 419 | 420 | 421 | ### Return 422 | `Any`: the default value for the given field 423 | 424 | 425 | 426 | 427 | ## build([values], [options]) -> Instance 428 | 429 | Builds a new Instance with the given properties 430 | 431 | ### Parameters 432 | 433 | Name | Type | Description 434 | --- | --- | --- 435 | [values] | Object | Map of values that the instance should have 436 | [options] | Object | Options for creating the instance 437 | [options.isNewRecord] | Object | Flag inidicating if this is a new mock record. Defaults to true 438 | 439 | 440 | ### Return 441 | `Instance`: a new instance with any given properties 442 | 443 | 444 | 445 | 446 | ## create(options) -> Promise.<Instance> 447 | 448 | Creates a new Instance with the given properties and triggers a save 449 | 450 | **See** 451 | 452 | - [build](#build) 453 | - Instance.save() 454 | 455 | ### Parameters 456 | 457 | Name | Type | Description 458 | --- | --- | --- 459 | options | Object | Map of values that the instance should have 460 | 461 | 462 | ### Return 463 | `Promise.`: a promise that resolves after saving a new instance with the given properties 464 | 465 | 466 | 467 | 468 | ## findOrCreate(options) -> Promise.<Array.<Instance, Boolean>> 469 | 470 | Executes a mock query to find or create an Instance with the given properties. Without 471 | any other configuration, the default behavior when no queueud query result is present 472 | is to trigger a create action based on the given properties from the where in 473 | the options object. 474 | 475 | **See** 476 | 477 | - [findAll](#findAll) 478 | - [create](#create) 479 | 480 | ### Parameters 481 | 482 | Name | Type | Description 483 | --- | --- | --- 484 | options | Object | Options for the query 485 | options.where | Object | Map of values that the instance should have 486 | 487 | 488 | ### Return 489 | `Promise.>`: Promise that includes the instance and whether or not it was created 490 | 491 | 492 | 493 | 494 | ## upsert(values) -> Promise.<Boolean> 495 | 496 | Executes a mock query to upsert an Instance with the given properties. Without any 497 | other configuration, the default behavior when no queueud query result is present is 498 | to return the `options.createdDefault` value indicating if a new item has been created
**Alias** insertOrUpdate 499 | 500 | ### Parameters 501 | 502 | Name | Type | Description 503 | --- | --- | --- 504 | values | Object | Values of the Instance being created 505 | 506 | 507 | ### Return 508 | `Promise.`: Promise that resolves with a boolean meant to indicate if something was inserted 509 | 510 | 511 | 512 | 513 | ## bulkCreate(set) -> Promise.<Array.<Instance>> 514 | 515 | Executes a mock query to create a set of new Instances in a bulk fashion. Without any 516 | other configuration, the default behavior when no queueud query result is present is 517 | to trigger a create on each item in a the given `set`. 518 | 519 | **See** 520 | 521 | - [create](#create) 522 | 523 | ### Parameters 524 | 525 | Name | Type | Description 526 | --- | --- | --- 527 | set | Array.<Object> | Set of values to create objects for 528 | 529 | 530 | ### Return 531 | `Promise.>`: Promise that contains all created Instances 532 | 533 | 534 | 535 | 536 | ## destroy([options]) -> Promise.<Integer> 537 | 538 | Executes a mock query to destroy a set of Instances. Without any other configuration, 539 | the default behavior when no queueud query result is present is to resolve with either 540 | the limit from the options or a 1. 541 | 542 | ### Parameters 543 | 544 | Name | Type | Description 545 | --- | --- | --- 546 | [options] | Object | Options for the query 547 | [options.limit] | Number | Number of rows that are deleted 548 | 549 | 550 | ### Return 551 | `Promise.`: Promise with number of deleted rows 552 | 553 | 554 | 555 | 556 | ## update(values, [options]) -> Promise.<Array> 557 | 558 | Executes a mock query to update a set of instances. Without any other configuration, 559 | the default behavior when no queueud query result is present is to create 1 new 560 | Instance that matches the where value from the first parameter and returns a Promise 561 | with an array of the count of affected rows (always 1) and the affected rows if the 562 | `returning` option is set to true 563 | 564 | ### Parameters 565 | 566 | Name | Type | Description 567 | --- | --- | --- 568 | values | Object | Values to build the Instance 569 | [options] | Object | Options to use for the update 570 | [options.returning] | Object | Whether or not to include the updated models in the return 571 | 572 | 573 | ### Return 574 | `Promise.`: Promise with an array of the number of affected rows and the affected rows themselves if `options.returning` is true 575 | 576 | -------------------------------------------------------------------------------- /test/errors.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var proxyquire = require('proxyquire').noCallThru(); 5 | 6 | var Errors = proxyquire('../src/errors', { 7 | // No require's we need to override 8 | }); 9 | 10 | describe('Errors', function () { 11 | 12 | describe('BaseError', function () { 13 | 14 | it('should set the message', function () { 15 | var err = new Errors.BaseError('Test message'); 16 | err.message.should.equal('Test message'); 17 | }); 18 | 19 | it('should have the same name as the Sequelize Error object', function () { 20 | var err = new Errors.BaseError(''); 21 | err.name.should.equal('SequelizeBaseError'); 22 | }); 23 | 24 | it('should inherit from the Error object', function () { 25 | var err = new Errors.BaseError(''); 26 | err.should.be.instanceOf(Error); 27 | }); 28 | 29 | }); 30 | 31 | describe('ValidationError', function () { 32 | 33 | it('should set the message', function () { 34 | var err = new Errors.ValidationError('Test message'); 35 | err.message.should.equal('Test message'); 36 | }); 37 | 38 | it('should store errors list', function () { 39 | var errorList = [1, 2, 3]; 40 | var err = new Errors.ValidationError('', errorList); 41 | err.errors.should.equal(errorList); 42 | }); 43 | 44 | it('should default to empty errors list', function () { 45 | var err = new Errors.ValidationError(''); 46 | err.errors.should.be.Array(); 47 | }); 48 | 49 | it('should have the same name as the Sequelize Error object', function () { 50 | var err = new Errors.ValidationError(''); 51 | err.name.should.equal('SequelizeValidationError'); 52 | }); 53 | 54 | it('should inherit from the BaseError object', function () { 55 | var err = new Errors.ValidationError(''); 56 | err.should.be.instanceOf(Errors.BaseError); 57 | }); 58 | 59 | describe('#get', function () { 60 | 61 | it('should find errors matching a given path', function () { 62 | var errorList = [{ path: 'abc' }, { path: 'def' }]; 63 | var err = new Errors.ValidationError('', errorList); 64 | err.get('abc').length.should.equal(1); 65 | err.get('def').length.should.equal(1); 66 | err.get('ghi').length.should.equal(0); 67 | }); 68 | 69 | }); 70 | 71 | }); 72 | 73 | describe('ValidationErrorItem', function () { 74 | 75 | it('should set each of the arguments on the object', function () { 76 | var err = new Errors.ValidationErrorItem('test message', 'test type', 'test path', 123); 77 | err.message.should.equal('test message'); 78 | err.type.should.equal('test type'); 79 | err.path.should.equal('test path'); 80 | err.value.should.equal(123); 81 | }); 82 | 83 | }); 84 | 85 | describe('DatabaseError', function () { 86 | 87 | it('should set the parent error', function () { 88 | var parent = {}; 89 | var err = new Errors.DatabaseError(parent); 90 | err.parent.should.equal(parent); 91 | err.original.should.equal(parent); 92 | }); 93 | 94 | it('should set a default sql value', function () { 95 | var err = new Errors.DatabaseError(); 96 | err.should.have.property('sql').which.is.a.String(); 97 | }); 98 | 99 | it('should inherit from the BaseError object', function () { 100 | var err = new Errors.DatabaseError(''); 101 | err.should.be.instanceOf(Errors.BaseError); 102 | }); 103 | 104 | }); 105 | 106 | describe('TimeoutError', function () { 107 | 108 | it('should set the message', function () { 109 | var err = new Errors.TimeoutError(); 110 | err.message.should.equal('Query Timed Out'); 111 | }); 112 | 113 | it('should set self as parent', function () { 114 | var err = new Errors.TimeoutError(); 115 | err.parent.should.equal(err); 116 | }); 117 | 118 | it('should have the same name as the Sequelize Error object', function () { 119 | var err = new Errors.TimeoutError(); 120 | err.name.should.equal('SequelizeTimeoutError'); 121 | }); 122 | 123 | it('should inherit from the DatabaseError object', function () { 124 | var err = new Errors.TimeoutError(); 125 | err.should.be.instanceOf(Errors.DatabaseError); 126 | }); 127 | 128 | }); 129 | 130 | describe('UniqueConstraintError', function () { 131 | 132 | it('should set the message', function () { 133 | var err = new Errors.UniqueConstraintError({ 134 | message: 'Test Message', 135 | }); 136 | err.message.should.equal('Test Message'); 137 | }); 138 | 139 | it('should set the errors list', function () { 140 | var errorList = [1,2,3]; 141 | var err = new Errors.UniqueConstraintError({ 142 | errors: errorList, 143 | }); 144 | err.errors.should.equal(errorList); 145 | }); 146 | 147 | it('should set the fields object', function () { 148 | var fieldsList = [1,2,3]; 149 | var err = new Errors.UniqueConstraintError({ 150 | fields: fieldsList 151 | }); 152 | err.fields.should.equal(fieldsList); 153 | }); 154 | 155 | it('should default to empty fields object', function () { 156 | var err = new Errors.UniqueConstraintError(); 157 | err.fields.should.be.Array(); 158 | }); 159 | 160 | it('should set self as parent', function () { 161 | var err = new Errors.UniqueConstraintError(); 162 | err.parent.should.equal(err); 163 | }); 164 | 165 | it('should have the same name as the Sequelize Error object', function () { 166 | var err = new Errors.UniqueConstraintError(); 167 | err.name.should.equal('SequelizeUniqueConstraintError'); 168 | }); 169 | 170 | it('should inherit from the ValidationError object', function () { 171 | var err = new Errors.UniqueConstraintError(); 172 | err.should.be.instanceOf(Errors.ValidationError); 173 | }); 174 | 175 | }); 176 | 177 | describe('ForeignKeyConstraintError', function () { 178 | 179 | it('should set the message', function () { 180 | var err = new Errors.ForeignKeyConstraintError({ 181 | message: 'Test Message', 182 | }); 183 | err.message.should.equal('Test Message'); 184 | }); 185 | 186 | it('should set the fields object', function () { 187 | var fieldsList = [1,2,3]; 188 | var err = new Errors.ForeignKeyConstraintError({ 189 | fields: fieldsList 190 | }); 191 | err.fields.should.equal(fieldsList); 192 | }); 193 | 194 | it('should default to empty fields object', function () { 195 | var err = new Errors.ForeignKeyConstraintError(); 196 | err.fields.should.be.Array(); 197 | }); 198 | 199 | it('should set the table value', function () { 200 | var err = new Errors.ForeignKeyConstraintError({ 201 | table: 'test table' 202 | }); 203 | err.table.should.equal('test table'); 204 | }); 205 | 206 | it('should set the value value', function () { 207 | var err = new Errors.ForeignKeyConstraintError({ 208 | value: 'test value' 209 | }); 210 | err.value.should.equal('test value'); 211 | }); 212 | 213 | it('should set the index value', function () { 214 | var err = new Errors.ForeignKeyConstraintError({ 215 | index: 'test index' 216 | }); 217 | err.index.should.equal('test index'); 218 | }); 219 | 220 | it('should set self as parent', function () { 221 | var err = new Errors.ForeignKeyConstraintError(); 222 | err.parent.should.equal(err); 223 | }); 224 | 225 | it('should have the same name as the Sequelize Error object', function () { 226 | var err = new Errors.ForeignKeyConstraintError(); 227 | err.name.should.equal('SequelizeForeignKeyConstraintError'); 228 | }); 229 | 230 | it('should inherit from the DatabaseError object', function () { 231 | var err = new Errors.ForeignKeyConstraintError(); 232 | err.should.be.instanceOf(Errors.DatabaseError); 233 | }); 234 | 235 | }); 236 | 237 | describe('ExclusionConstraintError', function () { 238 | 239 | it('should set the message', function () { 240 | var err = new Errors.ExclusionConstraintError({ 241 | message: 'Test Message', 242 | }); 243 | err.message.should.equal('Test Message'); 244 | }); 245 | 246 | it('should set the fields object', function () { 247 | var fieldsList = [1,2,3]; 248 | var err = new Errors.ExclusionConstraintError({ 249 | fields: fieldsList 250 | }); 251 | err.fields.should.equal(fieldsList); 252 | }); 253 | 254 | it('should default to empty fields object', function () { 255 | var err = new Errors.ExclusionConstraintError(); 256 | err.fields.should.be.Array(); 257 | }); 258 | 259 | it('should set the table value', function () { 260 | var err = new Errors.ExclusionConstraintError({ 261 | table: 'test table' 262 | }); 263 | err.table.should.equal('test table'); 264 | }); 265 | 266 | it('should set the constraint value', function () { 267 | var err = new Errors.ExclusionConstraintError({ 268 | constraint: 'test constraint' 269 | }); 270 | err.constraint.should.equal('test constraint'); 271 | }); 272 | 273 | it('should set self as parent', function () { 274 | var err = new Errors.ExclusionConstraintError(); 275 | err.parent.should.equal(err); 276 | }); 277 | 278 | it('should have the same name as the Sequelize Error object', function () { 279 | var err = new Errors.ExclusionConstraintError(); 280 | err.name.should.equal('SequelizeExclusionConstraintError'); 281 | }); 282 | 283 | it('should inherit from the DatabaseError object', function () { 284 | var err = new Errors.ExclusionConstraintError(); 285 | err.should.be.instanceOf(Errors.DatabaseError); 286 | }); 287 | 288 | }); 289 | 290 | describe('ConnectionError', function () { 291 | 292 | it('should set the message', function () { 293 | var parentError = { 294 | message: 'Test Message', 295 | }; 296 | var err = new Errors.ConnectionError(parentError); 297 | err.message.should.equal('Test Message'); 298 | }); 299 | 300 | it('should set parent', function () { 301 | var parentError = {}; 302 | var err = new Errors.ConnectionError(parentError); 303 | err.parent.should.equal(parentError); 304 | err.original.should.equal(parentError); 305 | }); 306 | 307 | it('should have the same name as the Sequelize Error object', function () { 308 | var err = new Errors.ConnectionError(); 309 | err.name.should.equal('SequelizeConnectionError'); 310 | }); 311 | 312 | it('should inherit from the BaseError object', function () { 313 | var err = new Errors.ConnectionError(); 314 | err.should.be.instanceOf(Errors.BaseError); 315 | }); 316 | 317 | }); 318 | 319 | describe('ConnectionRefusedError', function () { 320 | 321 | it('should set parent', function () { 322 | var parentError = {}; 323 | var err = new Errors.ConnectionRefusedError(parentError); 324 | err.parent.should.equal(parentError); 325 | err.original.should.equal(parentError); 326 | }); 327 | 328 | it('should have the same name as the Sequelize Error object', function () { 329 | var err = new Errors.ConnectionRefusedError(); 330 | err.name.should.equal('SequelizeConnectionRefusedError'); 331 | }); 332 | 333 | it('should inherit from the ConnectionError object', function () { 334 | var err = new Errors.ConnectionRefusedError(); 335 | err.should.be.instanceOf(Errors.ConnectionError); 336 | }); 337 | 338 | }); 339 | 340 | describe('AccessDeniedError', function () { 341 | 342 | it('should set parent', function () { 343 | var parentError = {}; 344 | var err = new Errors.AccessDeniedError(parentError); 345 | err.parent.should.equal(parentError); 346 | err.original.should.equal(parentError); 347 | }); 348 | 349 | it('should have the same name as the Sequelize Error object', function () { 350 | var err = new Errors.AccessDeniedError(); 351 | err.name.should.equal('SequelizeAccessDeniedError'); 352 | }); 353 | 354 | it('should inherit from the ConnectionError object', function () { 355 | var err = new Errors.AccessDeniedError(); 356 | err.should.be.instanceOf(Errors.ConnectionError); 357 | }); 358 | 359 | }); 360 | 361 | describe('HostNotFoundError', function () { 362 | 363 | it('should set parent', function () { 364 | var parentError = {}; 365 | var err = new Errors.HostNotFoundError(parentError); 366 | err.parent.should.equal(parentError); 367 | err.original.should.equal(parentError); 368 | }); 369 | 370 | it('should have the same name as the Sequelize Error object', function () { 371 | var err = new Errors.HostNotFoundError(); 372 | err.name.should.equal('SequelizeHostNotFoundError'); 373 | }); 374 | 375 | it('should inherit from the ConnectionError object', function () { 376 | var err = new Errors.HostNotFoundError(); 377 | err.should.be.instanceOf(Errors.ConnectionError); 378 | }); 379 | 380 | }); 381 | 382 | describe('HostNotReachableError', function () { 383 | 384 | it('should set parent', function () { 385 | var parentError = {}; 386 | var err = new Errors.HostNotReachableError(parentError); 387 | err.parent.should.equal(parentError); 388 | err.original.should.equal(parentError); 389 | }); 390 | 391 | it('should have the same name as the Sequelize Error object', function () { 392 | var err = new Errors.HostNotReachableError(); 393 | err.name.should.equal('SequelizeHostNotReachableError'); 394 | }); 395 | 396 | it('should inherit from the ConnectionError object', function () { 397 | var err = new Errors.HostNotReachableError(); 398 | err.should.be.instanceOf(Errors.ConnectionError); 399 | }); 400 | 401 | }); 402 | 403 | describe('InvalidConnectionError', function () { 404 | 405 | it('should set parent', function () { 406 | var parentError = {}; 407 | var err = new Errors.InvalidConnectionError(parentError); 408 | err.parent.should.equal(parentError); 409 | err.original.should.equal(parentError); 410 | }); 411 | 412 | it('should have the same name as the Sequelize Error object', function () { 413 | var err = new Errors.InvalidConnectionError(); 414 | err.name.should.equal('SequelizeInvalidConnectionError'); 415 | }); 416 | 417 | it('should inherit from the ConnectionError object', function () { 418 | var err = new Errors.InvalidConnectionError(); 419 | err.should.be.instanceOf(Errors.ConnectionError); 420 | }); 421 | 422 | }); 423 | 424 | describe('ConnectionTimedOutError', function () { 425 | 426 | it('should set parent', function () { 427 | var parentError = {}; 428 | var err = new Errors.ConnectionTimedOutError(parentError); 429 | err.parent.should.equal(parentError); 430 | err.original.should.equal(parentError); 431 | }); 432 | 433 | it('should have the same name as the Sequelize Error object', function () { 434 | var err = new Errors.ConnectionTimedOutError(); 435 | err.name.should.equal('SequelizeConnectionTimedOutError'); 436 | }); 437 | 438 | it('should inherit from the ConnectionError object', function () { 439 | var err = new Errors.ConnectionTimedOutError(); 440 | err.should.be.instanceOf(Errors.ConnectionError); 441 | }); 442 | 443 | }); 444 | 445 | describe('InstanceError', function () { 446 | 447 | it('should set message', function () { 448 | var err = new Errors.InstanceError('Test Message'); 449 | err.message.should.equal('Test Message'); 450 | }); 451 | 452 | it('should have the same name as the Sequelize Error object', function () { 453 | var err = new Errors.InstanceError(); 454 | err.name.should.equal('SequelizeInstanceError'); 455 | }); 456 | 457 | it('should inherit from the BaseError object', function () { 458 | var err = new Errors.InstanceError(); 459 | err.should.be.instanceOf(Errors.BaseError); 460 | }); 461 | 462 | }); 463 | 464 | describe('InvalidQueryResultError', function () { 465 | 466 | it('should set message', function () { 467 | var err = new Errors.InvalidQueryResultError('Test Message'); 468 | err.message.should.equal('Test Message'); 469 | }); 470 | 471 | it('should set a default message when none is provided', function () { 472 | var err = new Errors.InvalidQueryResultError(); 473 | err.message.should.not.be.empty(); 474 | }); 475 | 476 | it('should have a Sequelize Mock specific name', function () { 477 | var err = new Errors.InvalidQueryResultError(); 478 | err.name.should.equal('SequelizeMockInvalidQueryResultError'); 479 | }); 480 | 481 | it('should inherit from the BaseError object', function () { 482 | var err = new Errors.InvalidQueryResultError(); 483 | err.should.be.instanceOf(Errors.BaseError); 484 | }); 485 | 486 | }); 487 | 488 | describe('EmptyQueryQueueError', function () { 489 | 490 | it('should set message', function () { 491 | var err = new Errors.EmptyQueryQueueError('Test Message'); 492 | err.message.should.equal('Test Message'); 493 | }); 494 | 495 | it('should set a default message when none is provided', function () { 496 | var err = new Errors.EmptyQueryQueueError(); 497 | err.message.should.not.be.empty(); 498 | }); 499 | 500 | it('should have a Sequelize Mock specific name', function () { 501 | var err = new Errors.EmptyQueryQueueError(); 502 | err.name.should.equal('SequelizeMockEmptyQueryQueueError'); 503 | }); 504 | 505 | it('should inherit from the BaseError object', function () { 506 | var err = new Errors.EmptyQueryQueueError(); 507 | err.should.be.instanceOf(Errors.BaseError); 508 | }); 509 | 510 | }); 511 | 512 | }); 513 | --------------------------------------------------------------------------------