├── 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 | # 
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 |
--------------------------------------------------------------------------------
/docs/images/logo.blue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 | [](https://www.npmjs.com/package/sequelize-mock) [](https://circleci.com/gh/BlinkUX/sequelize-mock) [](https://coveralls.io/github/BlinkUX/sequelize-mock) [](https://github.com/BlinkUX/sequelize-mock) [](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 |
--------------------------------------------------------------------------------
/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.