@tryghost/express-bookshelf-jsonapi 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +164 -0
- package/index.js +3 -0
- package/lib/api.js +41 -0
- package/lib/apiware/action.js +47 -0
- package/lib/apiware/destroy.js +35 -0
- package/lib/apiware/format.js +54 -0
- package/lib/apiware/index.js +76 -0
- package/lib/apiware/noop.js +5 -0
- package/lib/apiware/params-data.js +12 -0
- package/lib/apiware/params-fields.js +14 -0
- package/lib/apiware/params-filter.js +23 -0
- package/lib/apiware/params-include.js +22 -0
- package/lib/apiware/params-page.js +36 -0
- package/lib/apiware/params-sort.js +16 -0
- package/lib/apiware/payload.js +52 -0
- package/lib/apiware/query.js +28 -0
- package/lib/ebja.js +24 -0
- package/lib/endpoint.js +67 -0
- package/lib/format.js +33 -0
- package/lib/http.js +71 -0
- package/lib/plugin.js +40 -0
- package/lib/request.js +35 -0
- package/lib/resource.js +16 -0
- package/lib/response.js +33 -0
- package/lib/stack.js +30 -0
- package/lib/vendor/jsonapi-mapper.js +14 -0
- package/lib/vendor/jsonapi-query-parser.js +2 -0
- package/package.json +40 -0
- package/test/apiware/payload.test.js +230 -0
- package/test/stack.test.js +126 -0
- package/test/stacks/action.test.js +199 -0
- package/test/stacks/destroy.test.js +171 -0
- package/test/stacks/query.test.js +198 -0
- package/test/structure.test.js +352 -0
- package/test/utils.js +22 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
var sinon = require('sinon');
|
|
2
|
+
|
|
3
|
+
var stack = require('../lib/stack');
|
|
4
|
+
|
|
5
|
+
var sandbox = sinon.createSandbox();
|
|
6
|
+
|
|
7
|
+
describe('Stack handler', function () {
|
|
8
|
+
var req, res;
|
|
9
|
+
|
|
10
|
+
beforeEach(function () {
|
|
11
|
+
req = {a: true};
|
|
12
|
+
res = {b: false};
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(function () {
|
|
16
|
+
sandbox.restore();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('Errors with no arguments', function () {
|
|
20
|
+
expect(stack.handle).toThrow(TypeError);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('Works with an empty array, returning req and res', function () {
|
|
24
|
+
return new Promise(function (resolve) {
|
|
25
|
+
var req = {a: true};
|
|
26
|
+
var res = {b: false};
|
|
27
|
+
stack.handle([], req, res, function finished() {
|
|
28
|
+
expect(arguments).toHaveLength(3);
|
|
29
|
+
expect(arguments[0]).toBeFalsy();
|
|
30
|
+
expect(arguments[1]).toEqual(req);
|
|
31
|
+
expect(arguments[2]).toEqual(res);
|
|
32
|
+
resolve();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('Calls the functions in order', function () {
|
|
38
|
+
return new Promise(function (resolve) {
|
|
39
|
+
var a = sandbox.stub().callsArg(2);
|
|
40
|
+
var b = sandbox.stub().callsArg(2);
|
|
41
|
+
var testStack = [a, b];
|
|
42
|
+
|
|
43
|
+
stack.handle(testStack, req, res, function finished() {
|
|
44
|
+
expect(a.calledOnce).toBe(true);
|
|
45
|
+
expect(a.calledWith(req, res)).toBe(true);
|
|
46
|
+
expect(b.calledOnce).toBe(true);
|
|
47
|
+
expect(b.calledWith(req, res)).toBe(true);
|
|
48
|
+
expect(a.calledBefore(b)).toBe(true);
|
|
49
|
+
|
|
50
|
+
expect(arguments).toHaveLength(3);
|
|
51
|
+
expect(arguments[0]).toBeFalsy();
|
|
52
|
+
expect(arguments[1]).toEqual(req);
|
|
53
|
+
expect(arguments[2]).toEqual(res);
|
|
54
|
+
resolve();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('Returns req with an error if a stack method errors', function () {
|
|
60
|
+
return new Promise(function (resolve) {
|
|
61
|
+
var a = sandbox.stub().callsArg(2);
|
|
62
|
+
var b = sandbox.stub().callsArgWith(2, new Error('I broked!'));
|
|
63
|
+
var testStack = [a, b];
|
|
64
|
+
|
|
65
|
+
stack.handle(testStack, req, res, function finished() {
|
|
66
|
+
expect(a.calledOnce).toBe(true);
|
|
67
|
+
expect(a.calledWith(req, res)).toBe(true);
|
|
68
|
+
expect(b.calledOnce).toBe(true);
|
|
69
|
+
expect(b.calledWith(req, res)).toBe(true);
|
|
70
|
+
expect(a.calledBefore(b)).toBe(true);
|
|
71
|
+
|
|
72
|
+
expect(arguments).toHaveLength(3);
|
|
73
|
+
expect(arguments[0]).toBeTruthy();
|
|
74
|
+
expect(arguments[0].message).toEqual('I broked!');
|
|
75
|
+
expect(arguments[1]).toEqual(req);
|
|
76
|
+
expect(arguments[2]).toEqual(res);
|
|
77
|
+
|
|
78
|
+
resolve();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('Does not call methods after a stack method errors', function () {
|
|
84
|
+
return new Promise(function (resolve) {
|
|
85
|
+
var a = sandbox.stub().callsArg(2);
|
|
86
|
+
var b = sandbox.stub().callsArgWith(2, new Error('I broked!'));
|
|
87
|
+
var c = sandbox.stub().callsArg(2);
|
|
88
|
+
var testStack = [a, b, c];
|
|
89
|
+
|
|
90
|
+
stack.handle(testStack, req, res, function finished() {
|
|
91
|
+
expect(a.calledOnce).toBe(true);
|
|
92
|
+
expect(a.calledWith(req, res)).toBe(true);
|
|
93
|
+
expect(b.calledOnce).toBe(true);
|
|
94
|
+
expect(b.calledWith(req, res)).toBe(true);
|
|
95
|
+
expect(c.called).toBe(false);
|
|
96
|
+
expect(a.calledBefore(b)).toBe(true);
|
|
97
|
+
|
|
98
|
+
expect(arguments).toHaveLength(3);
|
|
99
|
+
expect(arguments[0]).toBeTruthy();
|
|
100
|
+
expect(arguments[0].message).toEqual('I broked!');
|
|
101
|
+
expect(arguments[1]).toEqual(req);
|
|
102
|
+
expect(arguments[2]).toEqual(res);
|
|
103
|
+
|
|
104
|
+
resolve();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('Does not call methods if callback method errors', function () {
|
|
110
|
+
return new Promise(function (resolve) {
|
|
111
|
+
var a = sandbox.stub().callsArg(2);
|
|
112
|
+
var b = sandbox.stub().callsArg(2);
|
|
113
|
+
var testStack = [a, b];
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
stack.handle(testStack, req, res, function finished() {
|
|
117
|
+
throw new Error('HAHAHAHAH');
|
|
118
|
+
});
|
|
119
|
+
} catch {
|
|
120
|
+
expect(a.calledOnce).toBe(true);
|
|
121
|
+
expect(b.calledOnce).toBe(true);
|
|
122
|
+
resolve();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
var sinon = require('sinon');
|
|
2
|
+
var utils = require('../utils');
|
|
3
|
+
var _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
var apiware = require('../../lib/apiware');
|
|
6
|
+
var stack = require('../../lib/stack');
|
|
7
|
+
var errors = require('ghost-ignition').errors;
|
|
8
|
+
|
|
9
|
+
var sandbox = sinon.createSandbox();
|
|
10
|
+
|
|
11
|
+
describe('Stack: action', function () {
|
|
12
|
+
var actionStack, req, res;
|
|
13
|
+
|
|
14
|
+
afterEach(function () {
|
|
15
|
+
sandbox.restore();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
beforeEach(function () {
|
|
20
|
+
var fakeModel = utils.fakeModel({
|
|
21
|
+
getOne: sandbox.stub().resolves(),
|
|
22
|
+
otherMethod: sandbox.stub().resolves({id: 'cde'})
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
req = {
|
|
26
|
+
model: fakeModel,
|
|
27
|
+
options: {},
|
|
28
|
+
params: {},
|
|
29
|
+
query: {
|
|
30
|
+
data: {},
|
|
31
|
+
options: {}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
res = {
|
|
35
|
+
exec: []
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Create a spy version of the stack
|
|
39
|
+
actionStack = {};
|
|
40
|
+
_.forEach(apiware.action, function (value, key) {
|
|
41
|
+
actionStack[key] = sandbox.spy(value);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('Calls the methods in order', function () {
|
|
46
|
+
return new Promise(function (resolve, reject) {
|
|
47
|
+
stack.handle(_.values(actionStack), req, res, function finishing(err) {
|
|
48
|
+
if (err) {
|
|
49
|
+
return reject(err);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
expect(actionStack.validate.calledOnce).toBe(true);
|
|
53
|
+
expect(actionStack.payload.calledOnce).toBe(true);
|
|
54
|
+
expect(actionStack.permissions.calledOnce).toBe(true);
|
|
55
|
+
expect(actionStack.action.calledOnce).toBe(true);
|
|
56
|
+
expect(actionStack.query.calledOnce).toBe(true);
|
|
57
|
+
expect(actionStack.process.calledOnce).toBe(true);
|
|
58
|
+
expect(actionStack.format.calledOnce).toBe(true);
|
|
59
|
+
|
|
60
|
+
sinon.assert.callOrder(
|
|
61
|
+
actionStack.validate,
|
|
62
|
+
actionStack.payload,
|
|
63
|
+
actionStack.permissions,
|
|
64
|
+
actionStack.action,
|
|
65
|
+
actionStack.query,
|
|
66
|
+
actionStack.process,
|
|
67
|
+
actionStack.format
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
resolve();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('Calls no action and the getOne query method by default', function () {
|
|
76
|
+
return new Promise(function (resolve, reject) {
|
|
77
|
+
stack.handle(_.values(actionStack), req, res, function finishing(err, apiReq, apiRes) {
|
|
78
|
+
if (err) {
|
|
79
|
+
return reject(err);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
expect(Array.isArray(apiRes.exec)).toBe(true);
|
|
83
|
+
expect(apiRes.exec).toHaveLength(1);
|
|
84
|
+
expect(apiRes.exec[0]).toHaveProperty('method', 'getOne');
|
|
85
|
+
expect(apiRes.exec[0]).toHaveProperty('payload');
|
|
86
|
+
expect(apiRes.exec[0].payload).toHaveProperty('data');
|
|
87
|
+
expect(apiRes.exec[0].payload).toHaveProperty('options');
|
|
88
|
+
|
|
89
|
+
resolve();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('Can set the action method', function () {
|
|
95
|
+
return new Promise(function (resolve, reject) {
|
|
96
|
+
req.options.actionMethod = 'otherMethod';
|
|
97
|
+
|
|
98
|
+
stack.handle(_.values(actionStack), req, res, function finishing(err, apiReq, apiRes) {
|
|
99
|
+
if (err) {
|
|
100
|
+
return reject(err);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
expect(Array.isArray(apiRes.exec)).toBe(true);
|
|
104
|
+
expect(apiRes.exec).toHaveLength(2);
|
|
105
|
+
expect(apiRes.exec[0]).toHaveProperty('method', 'otherMethod');
|
|
106
|
+
expect(apiRes.exec[0]).toHaveProperty('payload');
|
|
107
|
+
|
|
108
|
+
expect(apiRes.exec[1]).toHaveProperty('method', 'getOne');
|
|
109
|
+
expect(apiRes.exec[1]).toHaveProperty('payload');
|
|
110
|
+
expect(apiRes.exec[1].payload).toHaveProperty('data');
|
|
111
|
+
expect(apiRes.exec[1].payload).toHaveProperty('options');
|
|
112
|
+
|
|
113
|
+
expect(req.model.otherMethod.calledOnce).toBe(true);
|
|
114
|
+
expect(req.model.otherMethod.calledWith(apiRes.exec[0].payload)).toBe(true);
|
|
115
|
+
expect(actionStack.action.calledOnce).toBe(true);
|
|
116
|
+
expect(actionStack.query.calledOnce).toBe(true);
|
|
117
|
+
|
|
118
|
+
resolve();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('Passes through the identifier, if there is one', function () {
|
|
124
|
+
return new Promise(function (resolve, reject) {
|
|
125
|
+
req.params.identifier = 'cba';
|
|
126
|
+
req.options.actionMethod = 'otherMethod';
|
|
127
|
+
|
|
128
|
+
stack.handle(_.values(actionStack), req, res, function finishing(err, apiReq, apiRes) {
|
|
129
|
+
if (err) {
|
|
130
|
+
return reject(err);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
expect(Array.isArray(apiRes.exec)).toBe(true);
|
|
134
|
+
expect(apiRes.exec).toHaveLength(2);
|
|
135
|
+
expect(apiRes.exec[0]).toHaveProperty('method', 'otherMethod');
|
|
136
|
+
expect(apiRes.exec[0]).toHaveProperty('payload');
|
|
137
|
+
expect(apiRes.exec[0].payload).toHaveProperty('id', 'cba');
|
|
138
|
+
|
|
139
|
+
expect(apiRes.exec[1]).toHaveProperty('method', 'getOne');
|
|
140
|
+
expect(apiRes.exec[1]).toHaveProperty('payload');
|
|
141
|
+
expect(apiRes.exec[1].payload).toHaveProperty('data');
|
|
142
|
+
expect(apiRes.exec[1].payload).toHaveProperty('options');
|
|
143
|
+
|
|
144
|
+
expect(req.model.otherMethod.calledOnce).toBe(true);
|
|
145
|
+
expect(req.model.otherMethod.calledWith(apiRes.exec[0].payload)).toBe(true);
|
|
146
|
+
expect(actionStack.action.calledOnce).toBe(true);
|
|
147
|
+
expect(actionStack.query.calledOnce).toBe(true);
|
|
148
|
+
|
|
149
|
+
resolve();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('catches bookshelf no rows errors and converts them to ignition errors', function () {
|
|
155
|
+
return new Promise(function (resolve, reject) {
|
|
156
|
+
req.options.actionMethod = 'otherMethod';
|
|
157
|
+
req.model.otherMethod.rejects(new req.model.NoRowsUpdatedError());
|
|
158
|
+
|
|
159
|
+
stack.handle(_.values(actionStack), _.cloneDeep(req), _.cloneDeep(res), function finishing(err, apiReq, apiRes) {
|
|
160
|
+
if (err) {
|
|
161
|
+
utils.expectIgnitionError(err, 'NotFoundError');
|
|
162
|
+
|
|
163
|
+
expect(req.model.otherMethod.calledOnce).toBe(true);
|
|
164
|
+
expect(req.model.otherMethod.calledWith(apiRes.exec[0].payload)).toBe(true);
|
|
165
|
+
expect(actionStack.action.calledOnce).toBe(true);
|
|
166
|
+
expect(actionStack.query.called).toBe(false);
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
return resolve();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
reject(new Error('This should have thrown an error'));
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('catches but does not convert unknown errors', function () {
|
|
178
|
+
return new Promise(function (resolve, reject) {
|
|
179
|
+
req.options.actionMethod = 'otherMethod';
|
|
180
|
+
req.model.otherMethod.rejects(new Error('Unknown'));
|
|
181
|
+
|
|
182
|
+
stack.handle(_.values(actionStack), _.cloneDeep(req), _.cloneDeep(res), function finishing(err, apiReq, apiRes) {
|
|
183
|
+
if (err) {
|
|
184
|
+
expect(err).not.toBeInstanceOf(errors.IgnitionError);
|
|
185
|
+
expect(err.message).toEqual('Unknown');
|
|
186
|
+
|
|
187
|
+
expect(req.model.otherMethod.calledOnce).toBe(true);
|
|
188
|
+
expect(req.model.otherMethod.calledWith(apiRes.exec[0].payload)).toBe(true);
|
|
189
|
+
expect(actionStack.action.calledOnce).toBe(true);
|
|
190
|
+
expect(actionStack.query.called).toBe(false);
|
|
191
|
+
|
|
192
|
+
return resolve();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
reject(new Error('This should have thrown an error'));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
var sinon = require('sinon');
|
|
2
|
+
var utils = require('../utils');
|
|
3
|
+
var _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
var apiware = require('../../lib/apiware');
|
|
6
|
+
var stack = require('../../lib/stack');
|
|
7
|
+
var errors = require('ghost-ignition').errors;
|
|
8
|
+
|
|
9
|
+
var sandbox = sinon.createSandbox();
|
|
10
|
+
|
|
11
|
+
describe('Stack: destroy', function () {
|
|
12
|
+
var destroyStack, req, res;
|
|
13
|
+
|
|
14
|
+
afterEach(function () {
|
|
15
|
+
sandbox.restore();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
beforeEach(function () {
|
|
19
|
+
var fakeModel = utils.fakeModel({
|
|
20
|
+
destroy: sandbox.stub().resolves(),
|
|
21
|
+
otherMethod: sandbox.stub().resolves()
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
req = {
|
|
25
|
+
model: fakeModel,
|
|
26
|
+
params: {identifier: 'abc'},
|
|
27
|
+
options: {},
|
|
28
|
+
query: {
|
|
29
|
+
data: {},
|
|
30
|
+
options: {}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
res = {
|
|
34
|
+
exec: []
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Create a spy version of the stack
|
|
38
|
+
destroyStack = {};
|
|
39
|
+
_.forEach(apiware.destroy, function (value, key) {
|
|
40
|
+
destroyStack[key] = sandbox.spy(value);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('Calls the methods in order', function () {
|
|
45
|
+
return new Promise(function (resolve, reject) {
|
|
46
|
+
stack.handle(_.values(destroyStack), req, res, function finishing(err, apiReq, apiRes) {
|
|
47
|
+
if (err) {
|
|
48
|
+
return reject(err);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
expect(Array.isArray(apiRes.exec)).toBe(true);
|
|
52
|
+
expect(apiRes.exec).toHaveLength(1);
|
|
53
|
+
expect(apiRes.exec[0]).toHaveProperty('method', 'destroy');
|
|
54
|
+
expect(apiRes.exec[0]).toHaveProperty('payload');
|
|
55
|
+
expect(apiRes.exec[0].payload).toHaveProperty('id', 'abc');
|
|
56
|
+
|
|
57
|
+
expect(destroyStack.permissions.calledOnce).toBe(true);
|
|
58
|
+
expect(destroyStack.permissions.calledWith(req, res)).toBe(true);
|
|
59
|
+
expect(destroyStack.destroy.calledOnce).toBe(true);
|
|
60
|
+
expect(destroyStack.destroy.calledWith(req, res)).toBe(true);
|
|
61
|
+
expect(destroyStack.destroy.calledAfter(destroyStack.permissions)).toBe(true);
|
|
62
|
+
|
|
63
|
+
resolve();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('Calls the model.destroy method by default', function () {
|
|
69
|
+
return new Promise(function (resolve, reject) {
|
|
70
|
+
stack.handle(_.values(destroyStack), req, res, function finishing(err, apiReq, apiRes) {
|
|
71
|
+
if (err) {
|
|
72
|
+
return reject(err);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
expect(Array.isArray(apiRes.exec)).toBe(true);
|
|
76
|
+
expect(apiRes.exec).toHaveLength(1);
|
|
77
|
+
expect(apiRes.exec[0]).toHaveProperty('method', 'destroy');
|
|
78
|
+
expect(apiRes.exec[0]).toHaveProperty('payload');
|
|
79
|
+
expect(apiRes.exec[0].payload).toHaveProperty('id', 'abc');
|
|
80
|
+
|
|
81
|
+
expect(req.model.destroy.calledOnce).toBe(true);
|
|
82
|
+
expect(req.model.destroy.calledWith(apiRes.exec[0].payload)).toBe(true);
|
|
83
|
+
|
|
84
|
+
resolve();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
it('Can override the model.destroy method', function () {
|
|
91
|
+
return new Promise(function (resolve, reject) {
|
|
92
|
+
req.options.destroyMethod = 'otherMethod';
|
|
93
|
+
|
|
94
|
+
stack.handle(_.values(destroyStack), req, res, function finishing(err, apiReq, apiRes) {
|
|
95
|
+
if (err) {
|
|
96
|
+
return reject(err);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
expect(Array.isArray(apiRes.exec)).toBe(true);
|
|
100
|
+
expect(apiRes.exec).toHaveLength(1);
|
|
101
|
+
expect(apiRes.exec[0]).toHaveProperty('method', 'otherMethod');
|
|
102
|
+
expect(apiRes.exec[0]).toHaveProperty('payload');
|
|
103
|
+
expect(apiRes.exec[0].payload).toHaveProperty('id', 'abc');
|
|
104
|
+
|
|
105
|
+
expect(req.model.destroy.called).toBe(false);
|
|
106
|
+
expect(req.model.otherMethod.calledOnce).toBe(true);
|
|
107
|
+
expect(req.model.otherMethod.calledWith(apiRes.exec[0].payload)).toBe(true);
|
|
108
|
+
|
|
109
|
+
resolve();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('catches bookshelf no rows errors and converts them to ignition errors', function () {
|
|
115
|
+
return new Promise(function (resolve, reject) {
|
|
116
|
+
req.model.destroy.rejects(new req.model.NoRowsDeletedError());
|
|
117
|
+
|
|
118
|
+
stack.handle(_.values(destroyStack), _.cloneDeep(req), _.cloneDeep(res), function finishing(err) {
|
|
119
|
+
if (err) {
|
|
120
|
+
utils.expectIgnitionError(err, 'NotFoundError');
|
|
121
|
+
|
|
122
|
+
expect(destroyStack.permissions.calledOnce).toBe(true);
|
|
123
|
+
expect(destroyStack.destroy.calledOnce).toBe(true);
|
|
124
|
+
|
|
125
|
+
return resolve();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
reject(new Error('This should have thrown an error'));
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('catches but does not convert unknown errors', function () {
|
|
134
|
+
return new Promise(function (resolve, reject) {
|
|
135
|
+
req.model.destroy.rejects(new Error('Unknown'));
|
|
136
|
+
|
|
137
|
+
stack.handle(_.values(destroyStack), _.cloneDeep(req), _.cloneDeep(res), function finishing(err) {
|
|
138
|
+
if (err) {
|
|
139
|
+
expect(err).not.toBeInstanceOf(errors.IgnitionError);
|
|
140
|
+
expect(err.message).toEqual('Unknown');
|
|
141
|
+
|
|
142
|
+
expect(destroyStack.permissions.calledOnce).toBe(true);
|
|
143
|
+
expect(destroyStack.destroy.calledOnce).toBe(true);
|
|
144
|
+
|
|
145
|
+
return resolve();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
reject(new Error('This should have thrown an error'));
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('Errors if there is no identifier', function () {
|
|
154
|
+
return new Promise(function (resolve, reject) {
|
|
155
|
+
delete req.params.identifier;
|
|
156
|
+
|
|
157
|
+
stack.handle(_.values(destroyStack), _.cloneDeep(req), _.cloneDeep(res), function finishing(err) {
|
|
158
|
+
if (err) {
|
|
159
|
+
utils.expectIgnitionError(err, 'BadRequestError', /identifier/);
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
expect(destroyStack.permissions.calledOnce).toBe(true);
|
|
163
|
+
expect(destroyStack.destroy.calledOnce).toBe(true);
|
|
164
|
+
return resolve();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
reject(new Error('This should have thrown an error'));
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
var sinon = require('sinon');
|
|
2
|
+
var utils = require('../utils');
|
|
3
|
+
var _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
var apiware = require('../../lib/apiware');
|
|
6
|
+
var stack = require('../../lib/stack');
|
|
7
|
+
var errors = require('ghost-ignition').errors;
|
|
8
|
+
|
|
9
|
+
var sandbox = sinon.createSandbox();
|
|
10
|
+
|
|
11
|
+
describe('Stack: query', function () {
|
|
12
|
+
var queryStack, req, res;
|
|
13
|
+
|
|
14
|
+
afterEach(function () {
|
|
15
|
+
sandbox.restore();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
beforeEach(function () {
|
|
20
|
+
var fakeModel = utils.fakeModel({
|
|
21
|
+
getOne: sandbox.stub().resolves(),
|
|
22
|
+
getPage: sandbox.stub().resolves(),
|
|
23
|
+
otherMethod: sandbox.stub().resolves()
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
req = {
|
|
27
|
+
model: fakeModel,
|
|
28
|
+
options: {},
|
|
29
|
+
params: {
|
|
30
|
+
queryData: {
|
|
31
|
+
page: {}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
query: {
|
|
35
|
+
data: {},
|
|
36
|
+
options: {}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
res = {
|
|
40
|
+
exec: []
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Create a spy version of the stack
|
|
44
|
+
queryStack = {};
|
|
45
|
+
_.forEach(apiware.query, function (value, key) {
|
|
46
|
+
queryStack[key] = sandbox.spy(value);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('Calls the methods in order', function () {
|
|
51
|
+
return new Promise(function (resolve, reject) {
|
|
52
|
+
stack.handle(_.values(queryStack), req, res, function finishing(err) {
|
|
53
|
+
if (err) {
|
|
54
|
+
return reject(err);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
expect(queryStack.validate.calledOnce).toBe(true);
|
|
58
|
+
expect(queryStack.paramsData.calledOnce).toBe(true);
|
|
59
|
+
expect(queryStack.paramsInclude.calledOnce).toBe(true);
|
|
60
|
+
expect(queryStack.paramsPage.calledOnce).toBe(true);
|
|
61
|
+
expect(queryStack.paramsFilter.calledOnce).toBe(true);
|
|
62
|
+
expect(queryStack.paramsFields.calledOnce).toBe(true);
|
|
63
|
+
expect(queryStack.paramsSort.calledOnce).toBe(true);
|
|
64
|
+
expect(queryStack.permissions.calledOnce).toBe(true);
|
|
65
|
+
expect(queryStack.query.calledOnce).toBe(true);
|
|
66
|
+
expect(queryStack.process.calledOnce).toBe(true);
|
|
67
|
+
expect(queryStack.format.calledOnce).toBe(true);
|
|
68
|
+
|
|
69
|
+
sinon.assert.callOrder(
|
|
70
|
+
queryStack.validate,
|
|
71
|
+
queryStack.paramsData,
|
|
72
|
+
queryStack.paramsInclude,
|
|
73
|
+
queryStack.paramsPage,
|
|
74
|
+
queryStack.paramsFilter,
|
|
75
|
+
queryStack.paramsFields,
|
|
76
|
+
queryStack.paramsSort,
|
|
77
|
+
queryStack.permissions,
|
|
78
|
+
queryStack.query,
|
|
79
|
+
queryStack.process,
|
|
80
|
+
queryStack.format
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
resolve();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('Calls the getOne query method by default', function () {
|
|
89
|
+
return new Promise(function (resolve, reject) {
|
|
90
|
+
stack.handle(_.values(queryStack), req, res, function finishing(err, apiReq, apiRes) {
|
|
91
|
+
if (err) {
|
|
92
|
+
return reject(err);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
expect(Array.isArray(apiRes.exec)).toBe(true);
|
|
96
|
+
expect(apiRes.exec).toHaveLength(1);
|
|
97
|
+
expect(apiRes.exec[0]).toHaveProperty('method', 'getOne');
|
|
98
|
+
expect(apiRes.exec[0]).toHaveProperty('payload');
|
|
99
|
+
expect(apiRes.exec[0].payload).toHaveProperty('data');
|
|
100
|
+
expect(apiRes.exec[0].payload).toHaveProperty('options');
|
|
101
|
+
|
|
102
|
+
resolve();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('Can set the query method', function () {
|
|
108
|
+
return new Promise(function (resolve, reject) {
|
|
109
|
+
req.options.queryMethod = 'otherMethod';
|
|
110
|
+
|
|
111
|
+
stack.handle(_.values(queryStack), req, res, function finishing(err, apiReq, apiRes) {
|
|
112
|
+
if (err) {
|
|
113
|
+
return reject(err);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
expect(apiRes.exec[0]).toHaveProperty('method', 'otherMethod');
|
|
117
|
+
expect(apiRes.exec[0]).toHaveProperty('payload');
|
|
118
|
+
expect(apiRes.exec[0].payload).toHaveProperty('data');
|
|
119
|
+
expect(apiRes.exec[0].payload).toHaveProperty('options');
|
|
120
|
+
|
|
121
|
+
expect(req.model.otherMethod.calledOnce).toBe(true);
|
|
122
|
+
expect(req.model.otherMethod.calledWith(apiRes.exec[0].payload)).toBe(true);
|
|
123
|
+
expect(queryStack.query.calledOnce).toBe(true);
|
|
124
|
+
|
|
125
|
+
resolve();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('Passes through the identifier, if there is one', function () {
|
|
131
|
+
return new Promise(function (resolve, reject) {
|
|
132
|
+
req.params.identifier = 'cba';
|
|
133
|
+
|
|
134
|
+
stack.handle(_.values(queryStack), req, res, function finishing(err, apiReq, apiRes) {
|
|
135
|
+
if (err) {
|
|
136
|
+
return reject(err);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
expect(Array.isArray(apiRes.exec)).toBe(true);
|
|
140
|
+
expect(apiRes.exec).toHaveLength(1);
|
|
141
|
+
expect(apiRes.exec[0]).toHaveProperty('method', 'getOne');
|
|
142
|
+
expect(apiRes.exec[0]).toHaveProperty('payload');
|
|
143
|
+
expect(apiRes.exec[0].payload).toHaveProperty('data');
|
|
144
|
+
expect(apiRes.exec[0].payload).toHaveProperty('options');
|
|
145
|
+
expect(apiRes.exec[0].payload.data).toHaveProperty('id', 'cba');
|
|
146
|
+
|
|
147
|
+
expect(req.model.getOne.calledOnce).toBe(true);
|
|
148
|
+
expect(req.model.getOne.calledWith(apiRes.exec[0].payload)).toBe(true);
|
|
149
|
+
expect(queryStack.query.calledOnce).toBe(true);
|
|
150
|
+
|
|
151
|
+
resolve();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('catches bookshelf no rows errors and converts them to ignition errors', function () {
|
|
157
|
+
return new Promise(function (resolve, reject) {
|
|
158
|
+
req.model.getOne.rejects(new req.model.NotFoundError());
|
|
159
|
+
|
|
160
|
+
stack.handle(_.values(queryStack), _.cloneDeep(req), _.cloneDeep(res), function finishing(err, apiReq, apiRes) {
|
|
161
|
+
if (err) {
|
|
162
|
+
utils.expectIgnitionError(err, 'NotFoundError');
|
|
163
|
+
|
|
164
|
+
expect(req.model.getOne.calledOnce).toBe(true);
|
|
165
|
+
expect(req.model.getOne.calledWith(apiRes.exec[0].payload)).toBe(true);
|
|
166
|
+
expect(queryStack.query.called).toBe(true);
|
|
167
|
+
expect(queryStack.process.called).toBe(false);
|
|
168
|
+
|
|
169
|
+
return resolve();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
reject(new Error('This should have thrown an error'));
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('catches but does not convert unknown errors', function () {
|
|
178
|
+
return new Promise(function (resolve, reject) {
|
|
179
|
+
req.model.getOne.rejects(new Error('Unknown'));
|
|
180
|
+
|
|
181
|
+
stack.handle(_.values(queryStack), _.cloneDeep(req), _.cloneDeep(res), function finishing(err, apiReq, apiRes) {
|
|
182
|
+
if (err) {
|
|
183
|
+
expect(err).not.toBeInstanceOf(errors.IgnitionError);
|
|
184
|
+
expect(err.message).toEqual('Unknown');
|
|
185
|
+
|
|
186
|
+
expect(req.model.getOne.calledOnce).toBe(true);
|
|
187
|
+
expect(req.model.getOne.calledWith(apiRes.exec[0].payload)).toBe(true);
|
|
188
|
+
expect(queryStack.query.called).toBe(true);
|
|
189
|
+
expect(queryStack.process.called).toBe(false);
|
|
190
|
+
|
|
191
|
+
return resolve();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
reject(new Error('This should have thrown an error'));
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|