@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,352 @@
|
|
|
1
|
+
var sinon = require('sinon');
|
|
2
|
+
var _ = require('lodash');
|
|
3
|
+
|
|
4
|
+
// This is what calling models require
|
|
5
|
+
var ebja = require('../index');
|
|
6
|
+
var stack = require('../lib/stack');
|
|
7
|
+
var http = require('../lib/http');
|
|
8
|
+
var apiware = require('../lib/apiware');
|
|
9
|
+
|
|
10
|
+
var sandbox = sinon.createSandbox();
|
|
11
|
+
|
|
12
|
+
describe('Structure: Method Signatures', function () {
|
|
13
|
+
|
|
14
|
+
afterEach(function () {
|
|
15
|
+
sandbox.restore();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('EBJA', function () {
|
|
19
|
+
it('exposes a plugin', function () {
|
|
20
|
+
expect(Object.prototype.hasOwnProperty.call(ebja, 'plugin')).toBe(true);
|
|
21
|
+
expect(typeof ebja.plugin).toBe('function');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('plugin also registers plugins if they are not yet registered', function () {
|
|
25
|
+
var fakeBookshelf = {
|
|
26
|
+
Model: {
|
|
27
|
+
extend: sandbox.spy()
|
|
28
|
+
},
|
|
29
|
+
plugin: sandbox.spy()
|
|
30
|
+
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
ebja.plugin(fakeBookshelf);
|
|
34
|
+
|
|
35
|
+
expect(fakeBookshelf.plugin.calledTwice).toBe(true);
|
|
36
|
+
expect(fakeBookshelf.plugin.firstCall.calledWith(require('bookshelf-jsonapi-params'))).toBe(true);
|
|
37
|
+
expect(fakeBookshelf.plugin.secondCall.calledWith('pagination')).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('plugin does not register plugins if they are already registered', function () {
|
|
41
|
+
var fakeBookshelf = {
|
|
42
|
+
Model: {
|
|
43
|
+
extend: sandbox.stub(),
|
|
44
|
+
fetchJsonApi: '',
|
|
45
|
+
fetchPage: ''
|
|
46
|
+
},
|
|
47
|
+
plugin: sandbox.spy()
|
|
48
|
+
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
ebja.plugin(fakeBookshelf);
|
|
52
|
+
|
|
53
|
+
expect(fakeBookshelf.plugin.called).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('calls extend with correct properties', function () {
|
|
57
|
+
var fakeBookshelf = {
|
|
58
|
+
Model: {
|
|
59
|
+
extend: sandbox.stub().returnsThis()
|
|
60
|
+
},
|
|
61
|
+
plugin: sandbox.spy()
|
|
62
|
+
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
ebja.plugin(fakeBookshelf);
|
|
66
|
+
|
|
67
|
+
expect(fakeBookshelf.Model.extend.calledOnce).toBe(true);
|
|
68
|
+
expect(fakeBookshelf.Model.extend.firstCall.args[0]).toEqual({jsonapi: true});
|
|
69
|
+
expect(fakeBookshelf.Model.extend.firstCall.args[1]).toHaveProperty('getOne');
|
|
70
|
+
expect(fakeBookshelf.Model.extend.firstCall.args[1]).toHaveProperty('getPage');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('API', function () {
|
|
75
|
+
it('throws error with no config', function () {
|
|
76
|
+
function setupEbja() {
|
|
77
|
+
return ebja();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
expect(setupEbja).toThrow(Error);
|
|
81
|
+
expect(setupEbja).toThrow(/requires configuration/);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('throws error with no baseUrl', function () {
|
|
85
|
+
function setupEbja() {
|
|
86
|
+
return ebja({
|
|
87
|
+
models: []
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
expect(setupEbja).toThrow(Error);
|
|
91
|
+
expect(setupEbja).toThrow(/requires a baseUrl/);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('throws error with no models', function () {
|
|
95
|
+
function setupEbja() {
|
|
96
|
+
return ebja({
|
|
97
|
+
baseUrl: 'thing'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
expect(setupEbja).toThrow(Error);
|
|
101
|
+
expect(setupEbja).toThrow(/requires bookshelf models/);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('can setup with all config values', function () {
|
|
105
|
+
function setupEbja() {
|
|
106
|
+
return ebja({
|
|
107
|
+
baseUrl: 'thing',
|
|
108
|
+
models: []
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
expect(setupEbja).not.toThrow();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('setupa api exposes public methods', function () {
|
|
116
|
+
var api = ebja({
|
|
117
|
+
baseUrl: 'thing',
|
|
118
|
+
models: []
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(Object.prototype.hasOwnProperty.call(api, 'Endpoint')).toBe(true);
|
|
122
|
+
expect(Object.prototype.hasOwnProperty.call(api, 'Resource')).toBe(true);
|
|
123
|
+
|
|
124
|
+
expect(typeof api.Endpoint).toBe('function');
|
|
125
|
+
expect(api.Endpoint).toHaveProperty('name', 'registerEndpoint');
|
|
126
|
+
expect(typeof api.Resource).toBe('function');
|
|
127
|
+
expect(api.Resource).toHaveProperty('name', 'registerResource');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('Endpoint', function () {
|
|
132
|
+
var api;
|
|
133
|
+
beforeEach(function () {
|
|
134
|
+
api = ebja({
|
|
135
|
+
baseUrl: 'thing',
|
|
136
|
+
models: []
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
afterEach(function () {
|
|
141
|
+
sandbox.restore();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('throws error with no options', function () {
|
|
145
|
+
function setupEndpoint() {
|
|
146
|
+
api.Endpoint();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
expect(setupEndpoint).toThrow(Error);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('can register an endpoint with empty options object', function () {
|
|
153
|
+
function setupEndpoint() {
|
|
154
|
+
return api.Endpoint({});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
expect(setupEndpoint).not.toThrow();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('an endpoint is a handler function', function () {
|
|
161
|
+
var ep = api.Endpoint({});
|
|
162
|
+
expect(typeof ep).toBe('function');
|
|
163
|
+
expect(ep).toHaveProperty('name', 'handler');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('endpoint handler function throws error without arguments', function () {
|
|
167
|
+
var ep = api.Endpoint({});
|
|
168
|
+
expect(ep).toThrow(Error);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('endpoint handler can be called as middleware', function () {
|
|
172
|
+
return new Promise(function (resolve) {
|
|
173
|
+
var options = {request: {}, response: {}};
|
|
174
|
+
var ep = api.Endpoint(options);
|
|
175
|
+
var req = {__proto__: require('http').IncomingMessage.prototype};
|
|
176
|
+
var res = {};
|
|
177
|
+
var next = function callback() {
|
|
178
|
+
expect(httpStub.calledOnce).toBe(true);
|
|
179
|
+
expect(httpStub.calledWith(api, options, req, res, next)).toBe(true);
|
|
180
|
+
expect(queueStub.calledOnce).toBe(true);
|
|
181
|
+
expect(queueStub.calledWith(_.values(apiware.query))).toBe(true);
|
|
182
|
+
|
|
183
|
+
resolve();
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
var queueStub = sandbox.stub(stack, 'handle').callsArg(3);
|
|
187
|
+
var httpStub = sandbox.stub(http, 'adapter').returns([{}, {}, next]);
|
|
188
|
+
|
|
189
|
+
ep(req, res, next);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('Resource', function () {
|
|
195
|
+
var api;
|
|
196
|
+
beforeEach(function () {
|
|
197
|
+
api = ebja({
|
|
198
|
+
baseUrl: 'thing',
|
|
199
|
+
models: []
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
afterEach(function () {
|
|
204
|
+
sandbox.restore();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('throws error with no options', function () {
|
|
208
|
+
function setupResource() {
|
|
209
|
+
api.Resource();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
expect(setupResource).toThrow(Error);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('can register an endpoint with empty options object', function () {
|
|
216
|
+
function setupResource() {
|
|
217
|
+
return api.Resource({});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
expect(setupResource).not.toThrow();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('resource is just an object with an Endpoint function', function () {
|
|
224
|
+
var rs = api.Resource({});
|
|
225
|
+
|
|
226
|
+
expect(typeof rs).toBe('object');
|
|
227
|
+
expect(rs).toHaveProperty('Endpoint');
|
|
228
|
+
expect(typeof rs.Endpoint).toBe('function');
|
|
229
|
+
expect(rs.Endpoint).toHaveProperty('name', 'registerEndpoint');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('Resource.Endpoint', function () {
|
|
233
|
+
var rs;
|
|
234
|
+
beforeEach(function () {
|
|
235
|
+
rs = api.Resource({});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
it('throws error with no options', function () {
|
|
240
|
+
function setupEndpoint() {
|
|
241
|
+
rs.Endpoint();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
expect(setupEndpoint).toThrow(Error);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('can register an endpoint with empty options object', function () {
|
|
248
|
+
function setupEndpoint() {
|
|
249
|
+
return rs.Endpoint({});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
expect(setupEndpoint).not.toThrow();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('an endpoint is a handler function', function () {
|
|
256
|
+
var ep = rs.Endpoint({});
|
|
257
|
+
expect(typeof ep).toBe('function');
|
|
258
|
+
expect(ep).toHaveProperty('name', 'handler');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('endpoint handler function throws error without arguments', function () {
|
|
262
|
+
var ep = rs.Endpoint({});
|
|
263
|
+
expect(ep).toThrow(Error);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('endpoint handler can be called as middleware', function () {
|
|
267
|
+
return new Promise(function (resolve) {
|
|
268
|
+
var options = {request: {}, response: {}};
|
|
269
|
+
var ep = rs.Endpoint(options);
|
|
270
|
+
var req = {__proto__: require('http').IncomingMessage.prototype};
|
|
271
|
+
var res = {};
|
|
272
|
+
var next = function callback() {
|
|
273
|
+
expect(httpStub.calledOnce).toBe(true);
|
|
274
|
+
expect(httpStub.calledWith(api, options, req, res, next)).toBe(true);
|
|
275
|
+
expect(queueStub.calledOnce).toBe(true);
|
|
276
|
+
|
|
277
|
+
resolve();
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
var queueStub = sandbox.stub(stack, 'handle').callsArg(3);
|
|
281
|
+
var httpStub = sandbox.stub(http, 'adapter').returns([{}, {}, next]);
|
|
282
|
+
|
|
283
|
+
ep(req, res, next);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('APIware stacks', function () {
|
|
290
|
+
it('exports query, action and destroy', function () {
|
|
291
|
+
expect(typeof apiware).toBe('object');
|
|
292
|
+
expect(apiware).toHaveProperty('query');
|
|
293
|
+
expect(apiware).toHaveProperty('action');
|
|
294
|
+
expect(apiware).toHaveProperty('destroy');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('query stack is correct', function () {
|
|
298
|
+
expect(typeof apiware.query).toBe('object');
|
|
299
|
+
var keys = Object.keys(apiware.query);
|
|
300
|
+
expect(keys[0]).toBe('validate');
|
|
301
|
+
expect(apiware.query.validate.name).toBe('noop');
|
|
302
|
+
expect(keys[1]).toBe('paramsData');
|
|
303
|
+
expect(apiware.query.paramsData.name).toBe('paramsData');
|
|
304
|
+
expect(keys[2]).toBe('paramsInclude');
|
|
305
|
+
expect(apiware.query.paramsInclude.name).toBe('paramsInclude');
|
|
306
|
+
expect(keys[3]).toBe('paramsPage');
|
|
307
|
+
expect(apiware.query.paramsPage.name).toBe('paramsPage');
|
|
308
|
+
expect(keys[4]).toBe('paramsFilter');
|
|
309
|
+
expect(apiware.query.paramsFilter.name).toBe('paramsFilter');
|
|
310
|
+
expect(keys[5]).toBe('paramsFields');
|
|
311
|
+
expect(apiware.query.paramsFields.name).toBe('paramsFields');
|
|
312
|
+
expect(keys[6]).toBe('paramsSort');
|
|
313
|
+
expect(apiware.query.paramsSort.name).toBe('paramsSort');
|
|
314
|
+
expect(keys[7]).toBe('permissions');
|
|
315
|
+
expect(apiware.query.permissions.name).toBe('noop');
|
|
316
|
+
expect(keys[8]).toBe('query');
|
|
317
|
+
expect(apiware.query.query.name).toBe('query');
|
|
318
|
+
expect(keys[9]).toBe('process');
|
|
319
|
+
expect(apiware.query.process.name).toBe('noop');
|
|
320
|
+
expect(keys[10]).toBe('format');
|
|
321
|
+
expect(apiware.query.format.name).toBe('format');
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('action stack is correct', function () {
|
|
325
|
+
expect(typeof apiware.action).toBe('object');
|
|
326
|
+
var keys = Object.keys(apiware.action);
|
|
327
|
+
expect(keys[0]).toBe('validate');
|
|
328
|
+
expect(apiware.action.validate.name).toBe('noop');
|
|
329
|
+
expect(keys[1]).toBe('payload');
|
|
330
|
+
expect(apiware.action.payload.name).toBe('payload');
|
|
331
|
+
expect(keys[2]).toBe('permissions');
|
|
332
|
+
expect(apiware.action.permissions.name).toBe('noop');
|
|
333
|
+
expect(keys[3]).toBe('action');
|
|
334
|
+
expect(apiware.action.action.name).toBe('action');
|
|
335
|
+
expect(keys[4]).toBe('query');
|
|
336
|
+
expect(apiware.action.query.name).toBe('query');
|
|
337
|
+
expect(keys[5]).toBe('process');
|
|
338
|
+
expect(apiware.action.process.name).toBe('noop');
|
|
339
|
+
expect(keys[6]).toBe('format');
|
|
340
|
+
expect(apiware.action.format.name).toBe('format');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('destroy stack is correct', function () {
|
|
344
|
+
expect(typeof apiware.destroy).toBe('object');
|
|
345
|
+
var keys = Object.keys(apiware.destroy);
|
|
346
|
+
expect(keys[0]).toBe('permissions');
|
|
347
|
+
expect(apiware.destroy.permissions.name).toBe('noop');
|
|
348
|
+
expect(keys[1]).toBe('destroy');
|
|
349
|
+
expect(apiware.destroy.destroy.name).toBe('destroy');
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
});
|
package/test/utils.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
var utils = exports;
|
|
2
|
+
var _ = require('lodash');
|
|
3
|
+
// This is the same util used by bookshelf
|
|
4
|
+
var createError = require('create-error');
|
|
5
|
+
var errors = require('ghost-ignition').errors;
|
|
6
|
+
|
|
7
|
+
utils.fakeModel = function (options) {
|
|
8
|
+
return _.merge({}, {
|
|
9
|
+
NotFoundError: createError('NotFoundError'),
|
|
10
|
+
NoRowsDeletedError: createError('NoRowsDeletedError'),
|
|
11
|
+
NoRowsUpdatedError: createError('NoRowsUpdatedError'),
|
|
12
|
+
}, options);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
utils.expectIgnitionError = function (error, type, messagePattern) {
|
|
16
|
+
expect(error).toBeInstanceOf(errors.IgnitionError);
|
|
17
|
+
expect(error).toHaveProperty('errorType', type);
|
|
18
|
+
expect(error).toHaveProperty('message');
|
|
19
|
+
if (messagePattern) {
|
|
20
|
+
expect(error.message).toMatch(messagePattern);
|
|
21
|
+
}
|
|
22
|
+
};
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig, mergeConfig } from 'vitest/config';
|
|
2
|
+
import rootConfig from '../../vitest.config';
|
|
3
|
+
|
|
4
|
+
// Override: legacy package, coverage is below the root thresholds.
|
|
5
|
+
// Tests were originally mocha with no coverage gate — match that behavior.
|
|
6
|
+
export default mergeConfig(
|
|
7
|
+
rootConfig,
|
|
8
|
+
defineConfig({
|
|
9
|
+
test: {
|
|
10
|
+
coverage: {
|
|
11
|
+
thresholds: {
|
|
12
|
+
lines: 0,
|
|
13
|
+
functions: 0,
|
|
14
|
+
branches: 0,
|
|
15
|
+
statements: 0,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
);
|