meadow-endpoints 4.0.7 → 4.0.10
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/CONTRIBUTING.md +50 -0
- package/README.md +172 -51
- package/dist/indoctrinate_content_staging/Indoctrinate-Catalog-AppData.json +4548 -0
- package/docs/README.md +326 -0
- package/docs/_sidebar.md +37 -0
- package/docs/crud/README.md +220 -0
- package/docs/crud/count.md +168 -0
- package/docs/crud/create.md +194 -0
- package/docs/crud/delete.md +237 -0
- package/docs/crud/read.md +324 -0
- package/docs/crud/schema.md +349 -0
- package/docs/crud/update.md +203 -0
- package/docs/css/docuserve.css +73 -0
- package/docs/index.html +39 -0
- package/docs/retold-catalog.json +177 -0
- package/docs/retold-keyword-index.json +6720 -0
- package/package.json +21 -28
- package/source/endpoints/update/Meadow-Operation-Update.js +9 -1
- package/test/MeadowEndpoints_basic_tests.js +1586 -37
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unit tests for Meadow Endpoints
|
|
3
3
|
*
|
|
4
|
+
* Uses SQLite (better-sqlite3) as the provider so no external database server
|
|
5
|
+
* is required.
|
|
6
|
+
*
|
|
4
7
|
* @license MIT
|
|
5
8
|
*
|
|
6
9
|
* @author Steven Velozo <steven@velozo.com>
|
|
@@ -10,22 +13,170 @@ var Chai = require("chai");
|
|
|
10
13
|
var Expect = Chai.expect;
|
|
11
14
|
var Assert = Chai.assert;
|
|
12
15
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
16
|
+
const libFable = require('fable');
|
|
17
|
+
const libOrator = require('orator');
|
|
18
|
+
const libOratorServiceServerRestify = require('orator-serviceserver-restify');
|
|
19
|
+
const libMeadow = require('meadow');
|
|
20
|
+
const libMeadowEndpoints = require('../source/Meadow-Endpoints.js');
|
|
21
|
+
const libMeadowConnectionSQLite = require('meadow-connection-sqlite');
|
|
22
|
+
const libSuperTest = require('supertest');
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
const _BookSchema = require('../test_support/model/meadow_schema/BookStore-MeadowSchema-Book.json');
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
// -- Shared state for the test suite --
|
|
27
|
+
let _Fable = false;
|
|
28
|
+
let _Orator = false;
|
|
29
|
+
let _Meadow = false;
|
|
30
|
+
let _MeadowEndpoints = false;
|
|
31
|
+
let _SuperTest = false;
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
// Keep track of the API server port
|
|
34
|
+
const _APIServerPort = 9876;
|
|
35
|
+
const _BaseURL = `http://localhost:${_APIServerPort}/`;
|
|
23
36
|
|
|
24
37
|
suite
|
|
25
38
|
(
|
|
26
|
-
'Meadow-Endpoints
|
|
39
|
+
'Meadow-Endpoints',
|
|
27
40
|
() =>
|
|
28
41
|
{
|
|
42
|
+
// Set up Fable + Orator + SQLite + Meadow + Endpoints before all tests
|
|
43
|
+
suiteSetup
|
|
44
|
+
(
|
|
45
|
+
function (fDone)
|
|
46
|
+
{
|
|
47
|
+
this.timeout(30000);
|
|
48
|
+
|
|
49
|
+
let tmpSettings = {
|
|
50
|
+
Product: 'MeadowEndpointsTest',
|
|
51
|
+
ProductVersion: '1.0.0',
|
|
52
|
+
APIServerPort: _APIServerPort,
|
|
53
|
+
SQLite:
|
|
54
|
+
{
|
|
55
|
+
SQLiteFilePath: ':memory:'
|
|
56
|
+
},
|
|
57
|
+
LogStreams:
|
|
58
|
+
[
|
|
59
|
+
{
|
|
60
|
+
streamtype: 'console',
|
|
61
|
+
level: 'fatal'
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
MeadowEndpointsSessionDataSource: 'None'
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
_Fable = new libFable(tmpSettings);
|
|
68
|
+
|
|
69
|
+
// Register the Restify service server so Orator uses it
|
|
70
|
+
_Fable.serviceManager.addServiceType('OratorServiceServer', libOratorServiceServerRestify);
|
|
71
|
+
|
|
72
|
+
// Register and instantiate the SQLite connection provider
|
|
73
|
+
_Fable.serviceManager.addServiceType('MeadowSQLiteProvider', libMeadowConnectionSQLite);
|
|
74
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider');
|
|
75
|
+
|
|
76
|
+
_Fable.MeadowSQLiteProvider.connectAsync(
|
|
77
|
+
(pError) =>
|
|
78
|
+
{
|
|
79
|
+
if (pError)
|
|
80
|
+
{
|
|
81
|
+
return fDone(pError);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let tmpDB = _Fable.MeadowSQLiteProvider.db;
|
|
85
|
+
|
|
86
|
+
// Create the Book table
|
|
87
|
+
tmpDB.exec(
|
|
88
|
+
`CREATE TABLE IF NOT EXISTS Book (
|
|
89
|
+
IDBook INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
90
|
+
GUIDBook TEXT NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
|
|
91
|
+
CreateDate TEXT,
|
|
92
|
+
CreatingIDUser INTEGER NOT NULL DEFAULT 0,
|
|
93
|
+
UpdateDate TEXT,
|
|
94
|
+
UpdatingIDUser INTEGER NOT NULL DEFAULT 0,
|
|
95
|
+
Deleted INTEGER NOT NULL DEFAULT 0,
|
|
96
|
+
DeleteDate TEXT,
|
|
97
|
+
DeletingIDUser INTEGER NOT NULL DEFAULT 0,
|
|
98
|
+
Title TEXT NOT NULL DEFAULT '',
|
|
99
|
+
Type TEXT NOT NULL DEFAULT '',
|
|
100
|
+
Genre TEXT NOT NULL DEFAULT '',
|
|
101
|
+
ISBN TEXT NOT NULL DEFAULT '',
|
|
102
|
+
Language TEXT NOT NULL DEFAULT '',
|
|
103
|
+
ImageURL TEXT NOT NULL DEFAULT '',
|
|
104
|
+
PublicationYear INTEGER NOT NULL DEFAULT 0
|
|
105
|
+
);`
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Seed some test data
|
|
109
|
+
let tmpInsert = tmpDB.prepare(
|
|
110
|
+
`INSERT INTO Book (Title, Type, Genre, ISBN, Language, PublicationYear)
|
|
111
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
112
|
+
);
|
|
113
|
+
tmpInsert.run('Angels & Demons', 'Novel', 'Thriller', '978-0671027360', 'English', 2000);
|
|
114
|
+
tmpInsert.run('Dune', 'Novel', 'Science Fiction', '978-0441013593', 'English', 1965);
|
|
115
|
+
tmpInsert.run('Neuromancer', 'Novel', 'Science Fiction', '978-0441569595', 'English', 1984);
|
|
116
|
+
tmpInsert.run('Snow Crash', 'Novel', 'Science Fiction', '978-0553380958', 'English', 1992);
|
|
117
|
+
tmpInsert.run('The Da Vinci Code', 'Novel', 'Thriller', '978-0307474278', 'English', 2003);
|
|
118
|
+
|
|
119
|
+
// Create the Meadow DAL and endpoints
|
|
120
|
+
_Meadow = libMeadow.new(_Fable, 'Book')
|
|
121
|
+
.setProvider('SQLite')
|
|
122
|
+
.setSchema(_BookSchema.Schema)
|
|
123
|
+
.setJsonSchema(_BookSchema.JsonSchema)
|
|
124
|
+
.setDefaultIdentifier(_BookSchema.DefaultIdentifier)
|
|
125
|
+
.setDefault(_BookSchema.DefaultObject);
|
|
126
|
+
|
|
127
|
+
// Initialize Orator (creates and registers the Restify service server)
|
|
128
|
+
_Orator = new libOrator(_Fable, {});
|
|
129
|
+
_Orator.initialize(
|
|
130
|
+
() =>
|
|
131
|
+
{
|
|
132
|
+
// Wire up the Meadow Endpoints
|
|
133
|
+
_MeadowEndpoints = libMeadowEndpoints.new(_Meadow);
|
|
134
|
+
_MeadowEndpoints.connectRoutes(_Orator.serviceServer);
|
|
135
|
+
|
|
136
|
+
// Start listening for requests
|
|
137
|
+
_Orator.startService(
|
|
138
|
+
(pStartError) =>
|
|
139
|
+
{
|
|
140
|
+
if (pStartError)
|
|
141
|
+
{
|
|
142
|
+
return fDone(pStartError);
|
|
143
|
+
}
|
|
144
|
+
_SuperTest = libSuperTest(_BaseURL);
|
|
145
|
+
return fDone();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
suiteTeardown
|
|
153
|
+
(
|
|
154
|
+
function (fDone)
|
|
155
|
+
{
|
|
156
|
+
this.timeout(10000);
|
|
157
|
+
if (_Fable && _Fable.MeadowSQLiteProvider && _Fable.MeadowSQLiteProvider.db)
|
|
158
|
+
{
|
|
159
|
+
try { _Fable.MeadowSQLiteProvider.db.close(); } catch (pIgnore) { /* ignore */ }
|
|
160
|
+
}
|
|
161
|
+
if (_Orator && _Orator.serviceServer && _Orator.serviceServer.Active && _Orator.serviceServer.server)
|
|
162
|
+
{
|
|
163
|
+
// Directly close the restify server to avoid hanging on keep-alive connections
|
|
164
|
+
_Orator.serviceServer.server.close(() =>
|
|
165
|
+
{
|
|
166
|
+
_Orator.serviceServer.Active = false;
|
|
167
|
+
fDone();
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
else
|
|
171
|
+
{
|
|
172
|
+
fDone();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// ======================================================================
|
|
178
|
+
// Object Sanity
|
|
179
|
+
// ======================================================================
|
|
29
180
|
suite
|
|
30
181
|
(
|
|
31
182
|
'Object Sanity',
|
|
@@ -36,48 +187,1446 @@ suite
|
|
|
36
187
|
'The class should initialize itself into a happy little object.',
|
|
37
188
|
function (fDone)
|
|
38
189
|
{
|
|
39
|
-
Expect(
|
|
190
|
+
Expect(_MeadowEndpoints).to.be.an('object');
|
|
191
|
+
Expect(_MeadowEndpoints.DAL).to.be.an('object');
|
|
192
|
+
Expect(_MeadowEndpoints.DAL.scope).to.equal('Book');
|
|
193
|
+
Expect(_MeadowEndpoints.controller).to.be.an('object');
|
|
40
194
|
fDone();
|
|
41
195
|
}
|
|
42
196
|
);
|
|
43
197
|
test
|
|
44
198
|
(
|
|
45
|
-
'
|
|
46
|
-
function(fDone)
|
|
199
|
+
'The constructor should throw without a valid Meadow DAL.',
|
|
200
|
+
function (fDone)
|
|
47
201
|
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.end(
|
|
51
|
-
function (pError, pResponse)
|
|
52
|
-
{
|
|
53
|
-
var tmpResult = JSON.parse(pResponse.text);
|
|
54
|
-
Expect(tmpResult.Title).to.equal('Angels & Demons');
|
|
55
|
-
fDone();
|
|
56
|
-
}
|
|
57
|
-
);
|
|
202
|
+
Expect(() => { new libMeadowEndpoints(); }).to.throw();
|
|
203
|
+
fDone();
|
|
58
204
|
}
|
|
59
205
|
);
|
|
60
206
|
test
|
|
61
207
|
(
|
|
62
|
-
'
|
|
63
|
-
function(fDone)
|
|
208
|
+
'The MeadowEndpoints class should expose Meadow and BaseController.',
|
|
209
|
+
function (fDone)
|
|
64
210
|
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.
|
|
68
|
-
.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
);
|
|
211
|
+
Expect(libMeadowEndpoints.Meadow).to.be.an('object');
|
|
212
|
+
Expect(libMeadowEndpoints.Meadow.new).to.be.a('function');
|
|
213
|
+
Expect(libMeadowEndpoints.BaseController).to.be.a('function');
|
|
214
|
+
Expect(libMeadowEndpoints.new).to.be.a('function');
|
|
215
|
+
fDone();
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
test
|
|
219
|
+
(
|
|
220
|
+
'The endpoint version and prefix should be configured.',
|
|
221
|
+
function (fDone)
|
|
222
|
+
{
|
|
223
|
+
Expect(_MeadowEndpoints.EndpointVersion).to.equal('1.0');
|
|
224
|
+
Expect(_MeadowEndpoints.EndpointName).to.equal('Book');
|
|
225
|
+
Expect(_MeadowEndpoints.EndpointPrefix).to.equal('/1.0/Book');
|
|
226
|
+
fDone();
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
test
|
|
230
|
+
(
|
|
231
|
+
'Behavior sets should all be enabled by default.',
|
|
232
|
+
function (fDone)
|
|
233
|
+
{
|
|
234
|
+
Expect(_MeadowEndpoints._EnabledBehaviorSets.Create).to.equal(true);
|
|
235
|
+
Expect(_MeadowEndpoints._EnabledBehaviorSets.Read).to.equal(true);
|
|
236
|
+
Expect(_MeadowEndpoints._EnabledBehaviorSets.Reads).to.equal(true);
|
|
237
|
+
Expect(_MeadowEndpoints._EnabledBehaviorSets.Update).to.equal(true);
|
|
238
|
+
Expect(_MeadowEndpoints._EnabledBehaviorSets.Delete).to.equal(true);
|
|
239
|
+
Expect(_MeadowEndpoints._EnabledBehaviorSets.Count).to.equal(true);
|
|
240
|
+
Expect(_MeadowEndpoints._EnabledBehaviorSets.Schema).to.equal(true);
|
|
241
|
+
fDone();
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// ======================================================================
|
|
248
|
+
// Create Endpoints
|
|
249
|
+
// ======================================================================
|
|
250
|
+
suite
|
|
251
|
+
(
|
|
252
|
+
'Create',
|
|
253
|
+
() =>
|
|
254
|
+
{
|
|
255
|
+
test
|
|
256
|
+
(
|
|
257
|
+
'create: create a single record',
|
|
258
|
+
function (fDone)
|
|
259
|
+
{
|
|
260
|
+
_SuperTest
|
|
261
|
+
.post('1.0/Book')
|
|
262
|
+
.send({ Title: 'Batman is Batman', Genre: 'Comic', PublicationYear: 1939 })
|
|
263
|
+
.end(
|
|
264
|
+
(pError, pResponse) =>
|
|
265
|
+
{
|
|
266
|
+
Expect(pError).to.not.exist;
|
|
267
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
268
|
+
Expect(tmpResult.Title).to.equal('Batman is Batman');
|
|
269
|
+
Expect(tmpResult.Genre).to.equal('Comic');
|
|
270
|
+
Expect(tmpResult.PublicationYear).to.equal(1939);
|
|
271
|
+
Expect(tmpResult.IDBook).to.be.above(0);
|
|
272
|
+
Expect(tmpResult.GUIDBook).to.be.a('string');
|
|
273
|
+
Expect(tmpResult.Deleted).to.equal(0);
|
|
274
|
+
fDone();
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
);
|
|
279
|
+
test
|
|
280
|
+
(
|
|
281
|
+
'create: create a record with minimal fields',
|
|
282
|
+
function (fDone)
|
|
283
|
+
{
|
|
284
|
+
_SuperTest
|
|
285
|
+
.post('1.0/Book')
|
|
286
|
+
.send({ Title: 'Minimal Book' })
|
|
287
|
+
.end(
|
|
288
|
+
(pError, pResponse) =>
|
|
289
|
+
{
|
|
290
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
291
|
+
Expect(tmpResult.Title).to.equal('Minimal Book');
|
|
292
|
+
Expect(tmpResult.IDBook).to.be.above(0);
|
|
293
|
+
fDone();
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
test
|
|
299
|
+
(
|
|
300
|
+
'create: should handle an empty body gracefully',
|
|
301
|
+
function (fDone)
|
|
302
|
+
{
|
|
303
|
+
_SuperTest
|
|
304
|
+
.post('1.0/Book')
|
|
305
|
+
.send({})
|
|
306
|
+
.end(
|
|
307
|
+
(pError, pResponse) =>
|
|
308
|
+
{
|
|
309
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
310
|
+
// An empty body should still create a record with defaults
|
|
311
|
+
Expect(tmpResult.IDBook).to.be.above(0);
|
|
312
|
+
Expect(tmpResult.Title).to.equal('');
|
|
313
|
+
fDone();
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
test
|
|
319
|
+
(
|
|
320
|
+
'create bulk: create multiple records',
|
|
321
|
+
function (fDone)
|
|
322
|
+
{
|
|
323
|
+
_SuperTest
|
|
324
|
+
.post('1.0/Books')
|
|
325
|
+
.send([
|
|
326
|
+
{ Title: 'Bulk Book A', Genre: 'Fantasy' },
|
|
327
|
+
{ Title: 'Bulk Book B', Genre: 'Romance' },
|
|
328
|
+
{ Title: 'Bulk Book C', Genre: 'Horror' }
|
|
329
|
+
])
|
|
330
|
+
.end(
|
|
331
|
+
(pError, pResponse) =>
|
|
332
|
+
{
|
|
333
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
334
|
+
Expect(tmpResult).to.be.an('array');
|
|
335
|
+
Expect(tmpResult.length).to.equal(3);
|
|
336
|
+
Expect(tmpResult[0].Title).to.equal('Bulk Book A');
|
|
337
|
+
Expect(tmpResult[1].Title).to.equal('Bulk Book B');
|
|
338
|
+
Expect(tmpResult[2].Title).to.equal('Bulk Book C');
|
|
339
|
+
fDone();
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
// ======================================================================
|
|
348
|
+
// Read Endpoints
|
|
349
|
+
// ======================================================================
|
|
350
|
+
suite
|
|
351
|
+
(
|
|
352
|
+
'Read',
|
|
353
|
+
() =>
|
|
354
|
+
{
|
|
355
|
+
test
|
|
356
|
+
(
|
|
357
|
+
'read: get a specific record by ID',
|
|
358
|
+
function (fDone)
|
|
359
|
+
{
|
|
360
|
+
_SuperTest
|
|
361
|
+
.get('1.0/Book/1')
|
|
362
|
+
.end(
|
|
363
|
+
(pError, pResponse) =>
|
|
364
|
+
{
|
|
365
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
366
|
+
Expect(tmpResult.IDBook).to.equal(1);
|
|
367
|
+
Expect(tmpResult.Title).to.equal('Angels & Demons');
|
|
368
|
+
fDone();
|
|
369
|
+
}
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
test
|
|
374
|
+
(
|
|
375
|
+
'read: get a second record by ID',
|
|
376
|
+
function (fDone)
|
|
377
|
+
{
|
|
378
|
+
_SuperTest
|
|
379
|
+
.get('1.0/Book/2')
|
|
380
|
+
.end(
|
|
381
|
+
(pError, pResponse) =>
|
|
382
|
+
{
|
|
383
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
384
|
+
Expect(tmpResult.IDBook).to.equal(2);
|
|
385
|
+
Expect(tmpResult.Title).to.equal('Dune');
|
|
386
|
+
fDone();
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
);
|
|
391
|
+
test
|
|
392
|
+
(
|
|
393
|
+
'read: get a record that does not exist',
|
|
394
|
+
function (fDone)
|
|
395
|
+
{
|
|
396
|
+
_SuperTest
|
|
397
|
+
.get('1.0/Book/99999')
|
|
398
|
+
.end(
|
|
399
|
+
(pError, pResponse) =>
|
|
400
|
+
{
|
|
401
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
402
|
+
// Should return an error
|
|
403
|
+
Expect(tmpResult).to.have.property('Error');
|
|
404
|
+
fDone();
|
|
405
|
+
}
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
test
|
|
410
|
+
(
|
|
411
|
+
'reads: get all records with default pagination',
|
|
412
|
+
function (fDone)
|
|
413
|
+
{
|
|
414
|
+
_SuperTest
|
|
415
|
+
.get('1.0/Books')
|
|
416
|
+
.end(
|
|
417
|
+
(pError, pResponse) =>
|
|
418
|
+
{
|
|
419
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
420
|
+
Expect(tmpResult).to.be.an('array');
|
|
421
|
+
// We seeded 5 + created ~4 in prior tests
|
|
422
|
+
Expect(tmpResult.length).to.be.at.least(5);
|
|
423
|
+
fDone();
|
|
424
|
+
}
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
test
|
|
429
|
+
(
|
|
430
|
+
'reads: get records with pagination',
|
|
431
|
+
function (fDone)
|
|
432
|
+
{
|
|
433
|
+
_SuperTest
|
|
434
|
+
.get('1.0/Books/0/2')
|
|
435
|
+
.end(
|
|
436
|
+
(pError, pResponse) =>
|
|
437
|
+
{
|
|
438
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
439
|
+
Expect(tmpResult).to.be.an('array');
|
|
440
|
+
Expect(tmpResult.length).to.equal(2);
|
|
441
|
+
Expect(tmpResult[0].IDBook).to.equal(1);
|
|
442
|
+
fDone();
|
|
443
|
+
}
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
);
|
|
447
|
+
test
|
|
448
|
+
(
|
|
449
|
+
'reads: get records with offset pagination',
|
|
450
|
+
function (fDone)
|
|
451
|
+
{
|
|
452
|
+
_SuperTest
|
|
453
|
+
.get('1.0/Books/2/2')
|
|
454
|
+
.end(
|
|
455
|
+
(pError, pResponse) =>
|
|
456
|
+
{
|
|
457
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
458
|
+
Expect(tmpResult).to.be.an('array');
|
|
459
|
+
Expect(tmpResult.length).to.equal(2);
|
|
460
|
+
Expect(tmpResult[0].IDBook).to.equal(3);
|
|
461
|
+
fDone();
|
|
462
|
+
}
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
);
|
|
466
|
+
test
|
|
467
|
+
(
|
|
468
|
+
'reads: get records with filter (contains)',
|
|
469
|
+
function (fDone)
|
|
470
|
+
{
|
|
471
|
+
_SuperTest
|
|
472
|
+
.get('1.0/Books/FilteredTo/FBV~Genre~LK~%25Science%25')
|
|
473
|
+
.end(
|
|
474
|
+
(pError, pResponse) =>
|
|
475
|
+
{
|
|
476
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
477
|
+
Expect(tmpResult).to.be.an('array');
|
|
478
|
+
Expect(tmpResult.length).to.be.at.least(3);
|
|
479
|
+
tmpResult.forEach((pRecord) =>
|
|
480
|
+
{
|
|
481
|
+
Expect(pRecord.Genre).to.contain('Science');
|
|
482
|
+
});
|
|
483
|
+
fDone();
|
|
484
|
+
}
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
test
|
|
489
|
+
(
|
|
490
|
+
'reads: get records with filter and pagination',
|
|
491
|
+
function (fDone)
|
|
492
|
+
{
|
|
493
|
+
_SuperTest
|
|
494
|
+
.get('1.0/Books/FilteredTo/FBV~Genre~LK~%25Science%25/0/1')
|
|
495
|
+
.end(
|
|
496
|
+
(pError, pResponse) =>
|
|
497
|
+
{
|
|
498
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
499
|
+
Expect(tmpResult).to.be.an('array');
|
|
500
|
+
Expect(tmpResult.length).to.equal(1);
|
|
501
|
+
Expect(tmpResult[0].Genre).to.contain('Science');
|
|
502
|
+
fDone();
|
|
503
|
+
}
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
test
|
|
508
|
+
(
|
|
509
|
+
'reads by: get records filtered by a field value',
|
|
510
|
+
function (fDone)
|
|
511
|
+
{
|
|
512
|
+
_SuperTest
|
|
513
|
+
.get('1.0/Books/By/Genre/Thriller')
|
|
514
|
+
.end(
|
|
515
|
+
(pError, pResponse) =>
|
|
516
|
+
{
|
|
517
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
518
|
+
Expect(tmpResult).to.be.an('array');
|
|
519
|
+
Expect(tmpResult.length).to.be.at.least(2);
|
|
520
|
+
tmpResult.forEach((pRecord) =>
|
|
521
|
+
{
|
|
522
|
+
Expect(pRecord.Genre).to.equal('Thriller');
|
|
523
|
+
});
|
|
524
|
+
fDone();
|
|
525
|
+
}
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
);
|
|
529
|
+
test
|
|
530
|
+
(
|
|
531
|
+
'reads by: with pagination',
|
|
532
|
+
function (fDone)
|
|
533
|
+
{
|
|
534
|
+
_SuperTest
|
|
535
|
+
.get('1.0/Books/By/Genre/Thriller/0/1')
|
|
536
|
+
.end(
|
|
537
|
+
(pError, pResponse) =>
|
|
538
|
+
{
|
|
539
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
540
|
+
Expect(tmpResult).to.be.an('array');
|
|
541
|
+
Expect(tmpResult.length).to.equal(1);
|
|
542
|
+
Expect(tmpResult[0].Genre).to.equal('Thriller');
|
|
543
|
+
fDone();
|
|
544
|
+
}
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
// ======================================================================
|
|
552
|
+
// Specialized Read Endpoints
|
|
553
|
+
// ======================================================================
|
|
554
|
+
suite
|
|
555
|
+
(
|
|
556
|
+
'Specialized Read',
|
|
557
|
+
() =>
|
|
558
|
+
{
|
|
559
|
+
test
|
|
560
|
+
(
|
|
561
|
+
'select list: get Hash/Value pairs',
|
|
562
|
+
function (fDone)
|
|
563
|
+
{
|
|
564
|
+
_SuperTest
|
|
565
|
+
.get('1.0/BookSelect')
|
|
566
|
+
.end(
|
|
567
|
+
(pError, pResponse) =>
|
|
568
|
+
{
|
|
569
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
570
|
+
Expect(tmpResult).to.be.an('array');
|
|
571
|
+
Expect(tmpResult.length).to.be.at.least(5);
|
|
572
|
+
Expect(tmpResult[0]).to.have.property('Hash');
|
|
573
|
+
Expect(tmpResult[0]).to.have.property('Value');
|
|
574
|
+
fDone();
|
|
575
|
+
}
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
);
|
|
579
|
+
test
|
|
580
|
+
(
|
|
581
|
+
'select list: with pagination',
|
|
582
|
+
function (fDone)
|
|
583
|
+
{
|
|
584
|
+
_SuperTest
|
|
585
|
+
.get('1.0/BookSelect/0/2')
|
|
586
|
+
.end(
|
|
587
|
+
(pError, pResponse) =>
|
|
588
|
+
{
|
|
589
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
590
|
+
Expect(tmpResult).to.be.an('array');
|
|
591
|
+
Expect(tmpResult.length).to.equal(2);
|
|
592
|
+
fDone();
|
|
593
|
+
}
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
);
|
|
597
|
+
test
|
|
598
|
+
(
|
|
599
|
+
'select list: with filter',
|
|
600
|
+
function (fDone)
|
|
601
|
+
{
|
|
602
|
+
_SuperTest
|
|
603
|
+
.get('1.0/BookSelect/FilteredTo/FBV~Genre~EQ~Thriller')
|
|
604
|
+
.end(
|
|
605
|
+
(pError, pResponse) =>
|
|
606
|
+
{
|
|
607
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
608
|
+
Expect(tmpResult).to.be.an('array');
|
|
609
|
+
Expect(tmpResult.length).to.be.at.least(2);
|
|
610
|
+
tmpResult.forEach((pRecord) =>
|
|
611
|
+
{
|
|
612
|
+
Expect(pRecord).to.have.property('Hash');
|
|
613
|
+
Expect(pRecord).to.have.property('Value');
|
|
614
|
+
});
|
|
615
|
+
fDone();
|
|
616
|
+
}
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
);
|
|
620
|
+
test
|
|
621
|
+
(
|
|
622
|
+
'lite list: get minimal columns',
|
|
623
|
+
function (fDone)
|
|
624
|
+
{
|
|
625
|
+
_SuperTest
|
|
626
|
+
.get('1.0/Books/Lite')
|
|
627
|
+
.end(
|
|
628
|
+
(pError, pResponse) =>
|
|
629
|
+
{
|
|
630
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
631
|
+
Expect(tmpResult).to.be.an('array');
|
|
632
|
+
Expect(tmpResult.length).to.be.at.least(5);
|
|
633
|
+
fDone();
|
|
634
|
+
}
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
);
|
|
638
|
+
test
|
|
639
|
+
(
|
|
640
|
+
'lite list: with pagination',
|
|
641
|
+
function (fDone)
|
|
642
|
+
{
|
|
643
|
+
_SuperTest
|
|
644
|
+
.get('1.0/Books/Lite/0/3')
|
|
645
|
+
.end(
|
|
646
|
+
(pError, pResponse) =>
|
|
647
|
+
{
|
|
648
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
649
|
+
Expect(tmpResult).to.be.an('array');
|
|
650
|
+
Expect(tmpResult.length).to.equal(3);
|
|
651
|
+
fDone();
|
|
652
|
+
}
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
);
|
|
656
|
+
test
|
|
657
|
+
(
|
|
658
|
+
'max: get the maximum value for a column',
|
|
659
|
+
function (fDone)
|
|
660
|
+
{
|
|
661
|
+
_SuperTest
|
|
662
|
+
.get('1.0/Book/Max/PublicationYear')
|
|
663
|
+
.end(
|
|
664
|
+
(pError, pResponse) =>
|
|
665
|
+
{
|
|
666
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
667
|
+
// ReadMax returns a single record (not an array)
|
|
668
|
+
Expect(tmpResult).to.be.an('object');
|
|
669
|
+
Expect(tmpResult).to.have.property('PublicationYear');
|
|
670
|
+
// The max publication year in our seed data is 2003
|
|
671
|
+
Expect(tmpResult.PublicationYear).to.be.at.least(2003);
|
|
672
|
+
fDone();
|
|
673
|
+
}
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
// ======================================================================
|
|
681
|
+
// Update Endpoints
|
|
682
|
+
// ======================================================================
|
|
683
|
+
suite
|
|
684
|
+
(
|
|
685
|
+
'Update',
|
|
686
|
+
() =>
|
|
687
|
+
{
|
|
688
|
+
test
|
|
689
|
+
(
|
|
690
|
+
'update: update a single record',
|
|
691
|
+
function (fDone)
|
|
692
|
+
{
|
|
693
|
+
_SuperTest
|
|
694
|
+
.put('1.0/Book')
|
|
695
|
+
.send({ IDBook: 1, Title: 'Angels & Demons (Updated)' })
|
|
696
|
+
.end(
|
|
697
|
+
(pError, pResponse) =>
|
|
698
|
+
{
|
|
699
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
700
|
+
Expect(tmpResult.IDBook).to.equal(1);
|
|
701
|
+
Expect(tmpResult.Title).to.equal('Angels & Demons (Updated)');
|
|
702
|
+
fDone();
|
|
703
|
+
}
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
);
|
|
707
|
+
test
|
|
708
|
+
(
|
|
709
|
+
'update: verify the update persisted',
|
|
710
|
+
function (fDone)
|
|
711
|
+
{
|
|
712
|
+
_SuperTest
|
|
713
|
+
.get('1.0/Book/1')
|
|
714
|
+
.end(
|
|
715
|
+
(pError, pResponse) =>
|
|
716
|
+
{
|
|
717
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
718
|
+
Expect(tmpResult.Title).to.equal('Angels & Demons (Updated)');
|
|
719
|
+
fDone();
|
|
720
|
+
}
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
);
|
|
724
|
+
test
|
|
725
|
+
(
|
|
726
|
+
'update: fail without a valid record ID',
|
|
727
|
+
function (fDone)
|
|
728
|
+
{
|
|
729
|
+
_SuperTest
|
|
730
|
+
.put('1.0/Book')
|
|
731
|
+
.send({ Title: 'No ID Provided' })
|
|
732
|
+
.end(
|
|
733
|
+
(pError, pResponse) =>
|
|
734
|
+
{
|
|
735
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
736
|
+
Expect(tmpResult).to.have.property('Error');
|
|
737
|
+
fDone();
|
|
738
|
+
}
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
);
|
|
742
|
+
test
|
|
743
|
+
(
|
|
744
|
+
'update: fail for a non-existent record',
|
|
745
|
+
function (fDone)
|
|
746
|
+
{
|
|
747
|
+
_SuperTest
|
|
748
|
+
.put('1.0/Book')
|
|
749
|
+
.send({ IDBook: 99999, Title: 'Ghost Record' })
|
|
750
|
+
.end(
|
|
751
|
+
(pError, pResponse) =>
|
|
752
|
+
{
|
|
753
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
754
|
+
Expect(tmpResult).to.have.property('Error');
|
|
755
|
+
fDone();
|
|
756
|
+
}
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
);
|
|
760
|
+
test
|
|
761
|
+
(
|
|
762
|
+
'bulk update: update multiple records',
|
|
763
|
+
function (fDone)
|
|
764
|
+
{
|
|
765
|
+
_SuperTest
|
|
766
|
+
.put('1.0/Books')
|
|
767
|
+
.send([
|
|
768
|
+
{ IDBook: 2, Title: 'Dune (Updated)' },
|
|
769
|
+
{ IDBook: 3, Title: 'Neuromancer (Updated)' }
|
|
770
|
+
])
|
|
771
|
+
.end(
|
|
772
|
+
(pError, pResponse) =>
|
|
773
|
+
{
|
|
774
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
775
|
+
Expect(tmpResult).to.be.an('array');
|
|
776
|
+
Expect(tmpResult.length).to.equal(2);
|
|
777
|
+
Expect(tmpResult[0].Title).to.equal('Dune (Updated)');
|
|
778
|
+
Expect(tmpResult[1].Title).to.equal('Neuromancer (Updated)');
|
|
779
|
+
fDone();
|
|
780
|
+
}
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
);
|
|
784
|
+
test
|
|
785
|
+
(
|
|
786
|
+
'upsert: update an existing record via upsert',
|
|
787
|
+
function (fDone)
|
|
788
|
+
{
|
|
789
|
+
_SuperTest
|
|
790
|
+
.put('1.0/Book/Upsert')
|
|
791
|
+
.send({ IDBook: 4, Title: 'Snow Crash (Upserted)' })
|
|
792
|
+
.end(
|
|
793
|
+
(pError, pResponse) =>
|
|
794
|
+
{
|
|
795
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
796
|
+
Expect(tmpResult.IDBook).to.equal(4);
|
|
797
|
+
Expect(tmpResult.Title).to.equal('Snow Crash (Upserted)');
|
|
798
|
+
fDone();
|
|
799
|
+
}
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
);
|
|
803
|
+
test
|
|
804
|
+
(
|
|
805
|
+
'upsert: create a new record via upsert',
|
|
806
|
+
function (fDone)
|
|
807
|
+
{
|
|
808
|
+
_SuperTest
|
|
809
|
+
.put('1.0/Book/Upsert')
|
|
810
|
+
.send({ IDBook: 0, Title: 'Upserted New Book', Genre: 'Upsert' })
|
|
811
|
+
.end(
|
|
812
|
+
(pError, pResponse) =>
|
|
813
|
+
{
|
|
814
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
815
|
+
Expect(tmpResult.Title).to.equal('Upserted New Book');
|
|
816
|
+
Expect(tmpResult.IDBook).to.be.above(0);
|
|
817
|
+
fDone();
|
|
818
|
+
}
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
// ======================================================================
|
|
826
|
+
// Delete Endpoints
|
|
827
|
+
// ======================================================================
|
|
828
|
+
suite
|
|
829
|
+
(
|
|
830
|
+
'Delete',
|
|
831
|
+
() =>
|
|
832
|
+
{
|
|
833
|
+
let _DeletedRecordID = 0;
|
|
834
|
+
|
|
835
|
+
test
|
|
836
|
+
(
|
|
837
|
+
'delete: create a record to delete',
|
|
838
|
+
function (fDone)
|
|
839
|
+
{
|
|
840
|
+
_SuperTest
|
|
841
|
+
.post('1.0/Book')
|
|
842
|
+
.send({ Title: 'To Be Deleted', Genre: 'Temp' })
|
|
843
|
+
.end(
|
|
844
|
+
(pError, pResponse) =>
|
|
845
|
+
{
|
|
846
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
847
|
+
_DeletedRecordID = tmpResult.IDBook;
|
|
848
|
+
Expect(_DeletedRecordID).to.be.above(0);
|
|
849
|
+
fDone();
|
|
850
|
+
}
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
);
|
|
854
|
+
test
|
|
855
|
+
(
|
|
856
|
+
'delete: delete a record by URL parameter',
|
|
857
|
+
function (fDone)
|
|
858
|
+
{
|
|
859
|
+
_SuperTest
|
|
860
|
+
.delete(`1.0/Book/${_DeletedRecordID}`)
|
|
861
|
+
.end(
|
|
862
|
+
(pError, pResponse) =>
|
|
863
|
+
{
|
|
864
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
865
|
+
Expect(tmpResult).to.have.property('Count');
|
|
866
|
+
Expect(tmpResult.Count).to.equal(1);
|
|
867
|
+
fDone();
|
|
868
|
+
}
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
);
|
|
872
|
+
test
|
|
873
|
+
(
|
|
874
|
+
'delete: the deleted record should not be readable',
|
|
875
|
+
function (fDone)
|
|
876
|
+
{
|
|
877
|
+
_SuperTest
|
|
878
|
+
.get(`1.0/Book/${_DeletedRecordID}`)
|
|
879
|
+
.end(
|
|
880
|
+
(pError, pResponse) =>
|
|
881
|
+
{
|
|
882
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
883
|
+
// Record should not be found (soft-deleted)
|
|
884
|
+
Expect(tmpResult).to.have.property('Error');
|
|
885
|
+
fDone();
|
|
886
|
+
}
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
);
|
|
890
|
+
test
|
|
891
|
+
(
|
|
892
|
+
'undelete: restore a soft-deleted record',
|
|
893
|
+
function (fDone)
|
|
894
|
+
{
|
|
895
|
+
_SuperTest
|
|
896
|
+
.get(`1.0/Book/Undelete/${_DeletedRecordID}`)
|
|
897
|
+
.end(
|
|
898
|
+
(pError, pResponse) =>
|
|
899
|
+
{
|
|
900
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
901
|
+
Expect(tmpResult).to.have.property('Count');
|
|
902
|
+
Expect(tmpResult.Count).to.equal(1);
|
|
903
|
+
fDone();
|
|
904
|
+
}
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
test
|
|
909
|
+
(
|
|
910
|
+
'undelete: the restored record should be readable again',
|
|
911
|
+
function (fDone)
|
|
912
|
+
{
|
|
913
|
+
_SuperTest
|
|
914
|
+
.get(`1.0/Book/${_DeletedRecordID}`)
|
|
915
|
+
.end(
|
|
916
|
+
(pError, pResponse) =>
|
|
917
|
+
{
|
|
918
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
919
|
+
Expect(tmpResult.IDBook).to.equal(_DeletedRecordID);
|
|
920
|
+
Expect(tmpResult.Title).to.equal('To Be Deleted');
|
|
921
|
+
fDone();
|
|
922
|
+
}
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
);
|
|
926
|
+
test
|
|
927
|
+
(
|
|
928
|
+
'delete: delete and verify zero count for non-existent ID',
|
|
929
|
+
function (fDone)
|
|
930
|
+
{
|
|
931
|
+
_SuperTest
|
|
932
|
+
.delete('1.0/Book/99999')
|
|
933
|
+
.end(
|
|
934
|
+
(pError, pResponse) =>
|
|
935
|
+
{
|
|
936
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
937
|
+
// Non-existent record should error
|
|
938
|
+
Expect(tmpResult).to.have.property('Error');
|
|
939
|
+
fDone();
|
|
940
|
+
}
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
);
|
|
946
|
+
|
|
947
|
+
// ======================================================================
|
|
948
|
+
// Count Endpoints
|
|
949
|
+
// ======================================================================
|
|
950
|
+
suite
|
|
951
|
+
(
|
|
952
|
+
'Count',
|
|
953
|
+
() =>
|
|
954
|
+
{
|
|
955
|
+
test
|
|
956
|
+
(
|
|
957
|
+
'count: get total record count',
|
|
958
|
+
function (fDone)
|
|
959
|
+
{
|
|
960
|
+
_SuperTest
|
|
961
|
+
.get('1.0/Books/Count')
|
|
962
|
+
.end(
|
|
963
|
+
(pError, pResponse) =>
|
|
964
|
+
{
|
|
965
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
966
|
+
Expect(tmpResult).to.have.property('Count');
|
|
967
|
+
Expect(tmpResult.Count).to.be.at.least(5);
|
|
968
|
+
fDone();
|
|
969
|
+
}
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
);
|
|
973
|
+
test
|
|
974
|
+
(
|
|
975
|
+
'count by: count by a field value',
|
|
976
|
+
function (fDone)
|
|
977
|
+
{
|
|
978
|
+
_SuperTest
|
|
979
|
+
.get('1.0/Books/Count/By/Genre/Thriller')
|
|
980
|
+
.end(
|
|
981
|
+
(pError, pResponse) =>
|
|
982
|
+
{
|
|
983
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
984
|
+
Expect(tmpResult).to.have.property('Count');
|
|
985
|
+
Expect(tmpResult.Count).to.be.at.least(2);
|
|
986
|
+
fDone();
|
|
987
|
+
}
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
);
|
|
991
|
+
test
|
|
992
|
+
(
|
|
993
|
+
'count: count with filter',
|
|
994
|
+
function (fDone)
|
|
995
|
+
{
|
|
996
|
+
_SuperTest
|
|
997
|
+
.get('1.0/Books/Count/FilteredTo/FBV~Genre~LK~%25Science%25')
|
|
998
|
+
.end(
|
|
999
|
+
(pError, pResponse) =>
|
|
1000
|
+
{
|
|
1001
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1002
|
+
Expect(tmpResult).to.have.property('Count');
|
|
1003
|
+
Expect(tmpResult.Count).to.be.at.least(3);
|
|
1004
|
+
fDone();
|
|
1005
|
+
}
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
);
|
|
1011
|
+
|
|
1012
|
+
// ======================================================================
|
|
1013
|
+
// Schema Endpoints
|
|
1014
|
+
// ======================================================================
|
|
1015
|
+
suite
|
|
1016
|
+
(
|
|
1017
|
+
'Schema',
|
|
1018
|
+
() =>
|
|
1019
|
+
{
|
|
1020
|
+
test
|
|
1021
|
+
(
|
|
1022
|
+
'schema: get the JSON schema',
|
|
1023
|
+
function (fDone)
|
|
1024
|
+
{
|
|
1025
|
+
_SuperTest
|
|
1026
|
+
.get('1.0/Book/Schema')
|
|
1027
|
+
.end(
|
|
1028
|
+
(pError, pResponse) =>
|
|
1029
|
+
{
|
|
1030
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1031
|
+
Expect(tmpResult).to.have.property('title');
|
|
1032
|
+
Expect(tmpResult.title).to.equal('Book');
|
|
1033
|
+
Expect(tmpResult).to.have.property('properties');
|
|
1034
|
+
Expect(tmpResult.properties).to.have.property('IDBook');
|
|
1035
|
+
Expect(tmpResult.properties).to.have.property('Title');
|
|
1036
|
+
Expect(tmpResult.properties).to.have.property('Genre');
|
|
1037
|
+
fDone();
|
|
1038
|
+
}
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
);
|
|
1042
|
+
test
|
|
1043
|
+
(
|
|
1044
|
+
'new: get a default empty record',
|
|
1045
|
+
function (fDone)
|
|
1046
|
+
{
|
|
1047
|
+
_SuperTest
|
|
1048
|
+
.get('1.0/Book/Schema/New')
|
|
1049
|
+
.end(
|
|
1050
|
+
(pError, pResponse) =>
|
|
1051
|
+
{
|
|
1052
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1053
|
+
Expect(tmpResult).to.have.property('IDBook');
|
|
1054
|
+
Expect(tmpResult.IDBook).to.equal(0);
|
|
1055
|
+
Expect(tmpResult).to.have.property('Title');
|
|
1056
|
+
Expect(tmpResult.Title).to.equal('');
|
|
1057
|
+
Expect(tmpResult).to.have.property('PublicationYear');
|
|
1058
|
+
Expect(tmpResult.PublicationYear).to.equal(0);
|
|
1059
|
+
fDone();
|
|
1060
|
+
}
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
);
|
|
1064
|
+
test
|
|
1065
|
+
(
|
|
1066
|
+
'validate: validate a valid record',
|
|
1067
|
+
function (fDone)
|
|
1068
|
+
{
|
|
1069
|
+
_SuperTest
|
|
1070
|
+
.post('1.0/Book/Schema/Validate')
|
|
1071
|
+
.send({
|
|
1072
|
+
IDBook: 1,
|
|
1073
|
+
Title: 'Test Book',
|
|
1074
|
+
Genre: 'Test'
|
|
1075
|
+
})
|
|
1076
|
+
.end(
|
|
1077
|
+
(pError, pResponse) =>
|
|
1078
|
+
{
|
|
1079
|
+
// Validation should succeed
|
|
1080
|
+
Expect(pResponse.status).to.equal(200);
|
|
1081
|
+
fDone();
|
|
1082
|
+
}
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
);
|
|
1088
|
+
|
|
1089
|
+
// ======================================================================
|
|
1090
|
+
// Behavior Injection
|
|
1091
|
+
// ======================================================================
|
|
1092
|
+
suite
|
|
1093
|
+
(
|
|
1094
|
+
'Behavior Injection',
|
|
1095
|
+
() =>
|
|
1096
|
+
{
|
|
1097
|
+
test
|
|
1098
|
+
(
|
|
1099
|
+
'setBehavior: register and trigger a Read-PostOperation hook',
|
|
1100
|
+
function (fDone)
|
|
1101
|
+
{
|
|
1102
|
+
// Register a post-read hook that adds a custom field
|
|
1103
|
+
_MeadowEndpoints.controller.BehaviorInjection.setBehavior('Read-PostOperation',
|
|
1104
|
+
(pRequest, pRequestState, fCallback) =>
|
|
1105
|
+
{
|
|
1106
|
+
pRequestState.Record.CustomField = 'injected';
|
|
1107
|
+
return fCallback();
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
_SuperTest
|
|
1111
|
+
.get('1.0/Book/1')
|
|
1112
|
+
.end(
|
|
1113
|
+
(pError, pResponse) =>
|
|
1114
|
+
{
|
|
1115
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1116
|
+
Expect(tmpResult.CustomField).to.equal('injected');
|
|
1117
|
+
|
|
1118
|
+
// Clean up the behavior
|
|
1119
|
+
delete _MeadowEndpoints.controller.BehaviorInjection._BehaviorFunctions['Read-PostOperation'];
|
|
1120
|
+
fDone();
|
|
1121
|
+
}
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
);
|
|
1125
|
+
test
|
|
1126
|
+
(
|
|
1127
|
+
'setBehavior: register and trigger a Reads-QueryConfiguration hook',
|
|
1128
|
+
function (fDone)
|
|
1129
|
+
{
|
|
1130
|
+
// Register a query config hook that limits to Genre=Thriller
|
|
1131
|
+
_MeadowEndpoints.controller.BehaviorInjection.setBehavior('Reads-QueryConfiguration',
|
|
1132
|
+
(pRequest, pRequestState, fCallback) =>
|
|
1133
|
+
{
|
|
1134
|
+
pRequestState.Query.addFilter('Genre', 'Thriller');
|
|
1135
|
+
return fCallback();
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
_SuperTest
|
|
1139
|
+
.get('1.0/Books')
|
|
1140
|
+
.end(
|
|
1141
|
+
(pError, pResponse) =>
|
|
1142
|
+
{
|
|
1143
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1144
|
+
Expect(tmpResult).to.be.an('array');
|
|
1145
|
+
tmpResult.forEach((pRecord) =>
|
|
1146
|
+
{
|
|
1147
|
+
Expect(pRecord.Genre).to.equal('Thriller');
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
// Clean up the behavior
|
|
1151
|
+
delete _MeadowEndpoints.controller.BehaviorInjection._BehaviorFunctions['Reads-QueryConfiguration'];
|
|
1152
|
+
fDone();
|
|
1153
|
+
}
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
);
|
|
1157
|
+
test
|
|
1158
|
+
(
|
|
1159
|
+
'setBehavior: Create-PreOperation hook can modify the record',
|
|
1160
|
+
function (fDone)
|
|
1161
|
+
{
|
|
1162
|
+
// Register a pre-create hook that sets a default genre
|
|
1163
|
+
_MeadowEndpoints.controller.BehaviorInjection.setBehavior('Create-PreOperation',
|
|
1164
|
+
(pRequest, pRequestState, fCallback) =>
|
|
1165
|
+
{
|
|
1166
|
+
if (!pRequestState.RecordToCreate.Genre || pRequestState.RecordToCreate.Genre === '')
|
|
1167
|
+
{
|
|
1168
|
+
pRequestState.RecordToCreate.Genre = 'DefaultGenre';
|
|
1169
|
+
}
|
|
1170
|
+
return fCallback();
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
_SuperTest
|
|
1174
|
+
.post('1.0/Book')
|
|
1175
|
+
.send({ Title: 'Hook Default Genre' })
|
|
1176
|
+
.end(
|
|
1177
|
+
(pError, pResponse) =>
|
|
1178
|
+
{
|
|
1179
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1180
|
+
Expect(tmpResult.Genre).to.equal('DefaultGenre');
|
|
1181
|
+
|
|
1182
|
+
// Clean up
|
|
1183
|
+
delete _MeadowEndpoints.controller.BehaviorInjection._BehaviorFunctions['Create-PreOperation'];
|
|
1184
|
+
fDone();
|
|
1185
|
+
}
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
);
|
|
1189
|
+
test
|
|
1190
|
+
(
|
|
1191
|
+
'setBehavior: Create-PreOperation hook can reject a request',
|
|
1192
|
+
function (fDone)
|
|
1193
|
+
{
|
|
1194
|
+
// Register a pre-create hook that rejects records without a title
|
|
1195
|
+
_MeadowEndpoints.controller.BehaviorInjection.setBehavior('Create-PreOperation',
|
|
1196
|
+
(pRequest, pRequestState, fCallback) =>
|
|
1197
|
+
{
|
|
1198
|
+
if (!pRequestState.RecordToCreate.Title || pRequestState.RecordToCreate.Title === '')
|
|
1199
|
+
{
|
|
1200
|
+
let tmpError = new Error('Title is required');
|
|
1201
|
+
tmpError.StatusCode = 400;
|
|
1202
|
+
return fCallback(tmpError);
|
|
1203
|
+
}
|
|
1204
|
+
return fCallback();
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
_SuperTest
|
|
1208
|
+
.post('1.0/Book')
|
|
1209
|
+
.send({ Genre: 'No Title Provided' })
|
|
1210
|
+
.end(
|
|
1211
|
+
(pError, pResponse) =>
|
|
1212
|
+
{
|
|
1213
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1214
|
+
Expect(tmpResult).to.have.property('Error');
|
|
1215
|
+
|
|
1216
|
+
// Clean up
|
|
1217
|
+
delete _MeadowEndpoints.controller.BehaviorInjection._BehaviorFunctions['Create-PreOperation'];
|
|
1218
|
+
fDone();
|
|
1219
|
+
}
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
);
|
|
1223
|
+
test
|
|
1224
|
+
(
|
|
1225
|
+
'setBehavior: Delete-PreOperation hook can prevent deletion',
|
|
1226
|
+
function (fDone)
|
|
1227
|
+
{
|
|
1228
|
+
// Register a pre-delete hook that blocks deletion
|
|
1229
|
+
_MeadowEndpoints.controller.BehaviorInjection.setBehavior('Delete-PreOperation',
|
|
1230
|
+
(pRequest, pRequestState, fCallback) =>
|
|
1231
|
+
{
|
|
1232
|
+
let tmpError = new Error('Deletion is blocked');
|
|
1233
|
+
tmpError.StatusCode = 403;
|
|
1234
|
+
return fCallback(tmpError);
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
_SuperTest
|
|
1238
|
+
.delete('1.0/Book/1')
|
|
1239
|
+
.end(
|
|
1240
|
+
(pError, pResponse) =>
|
|
1241
|
+
{
|
|
1242
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1243
|
+
Expect(tmpResult).to.have.property('Error');
|
|
1244
|
+
|
|
1245
|
+
// Clean up
|
|
1246
|
+
delete _MeadowEndpoints.controller.BehaviorInjection._BehaviorFunctions['Delete-PreOperation'];
|
|
1247
|
+
fDone();
|
|
1248
|
+
}
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
);
|
|
1252
|
+
test
|
|
1253
|
+
(
|
|
1254
|
+
'setBehavior: Count-QueryConfiguration hook modifies count query',
|
|
1255
|
+
function (fDone)
|
|
1256
|
+
{
|
|
1257
|
+
// Register a hook that adds a filter to the count query
|
|
1258
|
+
_MeadowEndpoints.controller.BehaviorInjection.setBehavior('Count-QueryConfiguration',
|
|
1259
|
+
(pRequest, pRequestState, fCallback) =>
|
|
1260
|
+
{
|
|
1261
|
+
pRequestState.Query.addFilter('Genre', 'Thriller');
|
|
1262
|
+
return fCallback();
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
_SuperTest
|
|
1266
|
+
.get('1.0/Books/Count')
|
|
1267
|
+
.end(
|
|
1268
|
+
(pError, pResponse) =>
|
|
1269
|
+
{
|
|
1270
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1271
|
+
Expect(tmpResult.Count).to.be.at.least(1);
|
|
1272
|
+
// The count should be less than total records
|
|
1273
|
+
Expect(tmpResult.Count).to.be.below(20);
|
|
1274
|
+
|
|
1275
|
+
// Clean up
|
|
1276
|
+
delete _MeadowEndpoints.controller.BehaviorInjection._BehaviorFunctions['Count-QueryConfiguration'];
|
|
1277
|
+
fDone();
|
|
1278
|
+
}
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
);
|
|
1282
|
+
test
|
|
1283
|
+
(
|
|
1284
|
+
'setBehavior: Schema-PostOperation hook can modify schema',
|
|
1285
|
+
function (fDone)
|
|
1286
|
+
{
|
|
1287
|
+
_MeadowEndpoints.controller.BehaviorInjection.setBehavior('Schema-PostOperation',
|
|
1288
|
+
(pRequest, pRequestState, fCallback) =>
|
|
1289
|
+
{
|
|
1290
|
+
if (pRequestState.JSONSchema && pRequestState.JSONSchema.properties)
|
|
1291
|
+
{
|
|
1292
|
+
pRequestState.JSONSchema.customMeta = 'injected-metadata';
|
|
1293
|
+
}
|
|
1294
|
+
return fCallback();
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
_SuperTest
|
|
1298
|
+
.get('1.0/Book/Schema')
|
|
1299
|
+
.end(
|
|
1300
|
+
(pError, pResponse) =>
|
|
1301
|
+
{
|
|
1302
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1303
|
+
Expect(tmpResult.customMeta).to.equal('injected-metadata');
|
|
1304
|
+
|
|
1305
|
+
// Clean up
|
|
1306
|
+
delete _MeadowEndpoints.controller.BehaviorInjection._BehaviorFunctions['Schema-PostOperation'];
|
|
1307
|
+
fDone();
|
|
1308
|
+
}
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
);
|
|
1312
|
+
test
|
|
1313
|
+
(
|
|
1314
|
+
'setBehavior: New-PostOperation hook can modify defaults',
|
|
1315
|
+
function (fDone)
|
|
1316
|
+
{
|
|
1317
|
+
_MeadowEndpoints.controller.BehaviorInjection.setBehavior('New-PostOperation',
|
|
1318
|
+
(pRequest, pRequestState, fCallback) =>
|
|
1319
|
+
{
|
|
1320
|
+
pRequestState.EmptyEntityRecord.Genre = 'DefaultFromHook';
|
|
1321
|
+
pRequestState.EmptyEntityRecord.Language = 'English';
|
|
1322
|
+
return fCallback();
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
_SuperTest
|
|
1326
|
+
.get('1.0/Book/Schema/New')
|
|
1327
|
+
.end(
|
|
1328
|
+
(pError, pResponse) =>
|
|
1329
|
+
{
|
|
1330
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1331
|
+
Expect(tmpResult.Genre).to.equal('DefaultFromHook');
|
|
1332
|
+
Expect(tmpResult.Language).to.equal('English');
|
|
1333
|
+
|
|
1334
|
+
// Clean up
|
|
1335
|
+
delete _MeadowEndpoints.controller.BehaviorInjection._BehaviorFunctions['New-PostOperation'];
|
|
1336
|
+
fDone();
|
|
1337
|
+
}
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
);
|
|
1341
|
+
test
|
|
1342
|
+
(
|
|
1343
|
+
'setBehavior: Update-PostOperation hook runs after update',
|
|
1344
|
+
function (fDone)
|
|
1345
|
+
{
|
|
1346
|
+
let _HookCalled = false;
|
|
1347
|
+
_MeadowEndpoints.controller.BehaviorInjection.setBehavior('Update-PostOperation',
|
|
1348
|
+
(pRequest, pRequestState, fCallback) =>
|
|
1349
|
+
{
|
|
1350
|
+
_HookCalled = true;
|
|
1351
|
+
pRequestState.Record.HookWasHere = true;
|
|
1352
|
+
return fCallback();
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
_SuperTest
|
|
1356
|
+
.put('1.0/Book')
|
|
1357
|
+
.send({ IDBook: 5, Title: 'The Da Vinci Code (Hooked)' })
|
|
1358
|
+
.end(
|
|
1359
|
+
(pError, pResponse) =>
|
|
1360
|
+
{
|
|
1361
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1362
|
+
Expect(_HookCalled).to.equal(true);
|
|
1363
|
+
Expect(tmpResult.Title).to.equal('The Da Vinci Code (Hooked)');
|
|
1364
|
+
|
|
1365
|
+
// Clean up
|
|
1366
|
+
delete _MeadowEndpoints.controller.BehaviorInjection._BehaviorFunctions['Update-PostOperation'];
|
|
1367
|
+
fDone();
|
|
1368
|
+
}
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
);
|
|
1374
|
+
|
|
1375
|
+
// ======================================================================
|
|
1376
|
+
// Session Management
|
|
1377
|
+
// ======================================================================
|
|
1378
|
+
suite
|
|
1379
|
+
(
|
|
1380
|
+
'Session Management',
|
|
1381
|
+
() =>
|
|
1382
|
+
{
|
|
1383
|
+
test
|
|
1384
|
+
(
|
|
1385
|
+
'header session: use x-trusted-session header',
|
|
1386
|
+
function (fDone)
|
|
1387
|
+
{
|
|
1388
|
+
// Temporarily switch session source
|
|
1389
|
+
let tmpOriginalSource = _MeadowEndpoints.controller.settings.MeadowEndpointsSessionDataSource;
|
|
1390
|
+
_MeadowEndpoints.controller.settings.MeadowEndpointsSessionDataSource = 'Header';
|
|
1391
|
+
|
|
1392
|
+
let tmpHookTriggered = false;
|
|
1393
|
+
_MeadowEndpoints.controller.BehaviorInjection.setBehavior('Read-PreOperation',
|
|
1394
|
+
(pRequest, pRequestState, fCallback) =>
|
|
1395
|
+
{
|
|
1396
|
+
tmpHookTriggered = true;
|
|
1397
|
+
Expect(pRequestState.SessionData.UserID).to.equal(42);
|
|
1398
|
+
Expect(pRequestState.SessionData.CustomerID).to.equal(100);
|
|
1399
|
+
return fCallback();
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
_SuperTest
|
|
1403
|
+
.get('1.0/Book/1')
|
|
1404
|
+
.set('x-trusted-session', JSON.stringify({ UserID: 42, CustomerID: 100 }))
|
|
1405
|
+
.end(
|
|
1406
|
+
(pError, pResponse) =>
|
|
1407
|
+
{
|
|
1408
|
+
Expect(tmpHookTriggered).to.equal(true);
|
|
1409
|
+
|
|
1410
|
+
// Clean up
|
|
1411
|
+
_MeadowEndpoints.controller.settings.MeadowEndpointsSessionDataSource = tmpOriginalSource;
|
|
1412
|
+
delete _MeadowEndpoints.controller.BehaviorInjection._BehaviorFunctions['Read-PreOperation'];
|
|
1413
|
+
fDone();
|
|
1414
|
+
}
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
);
|
|
1420
|
+
|
|
1421
|
+
// ======================================================================
|
|
1422
|
+
// Endpoint Configuration
|
|
1423
|
+
// ======================================================================
|
|
1424
|
+
suite
|
|
1425
|
+
(
|
|
1426
|
+
'Endpoint Configuration',
|
|
1427
|
+
() =>
|
|
1428
|
+
{
|
|
1429
|
+
test
|
|
1430
|
+
(
|
|
1431
|
+
'setBehaviorEndpoint: should accept custom endpoint functions',
|
|
1432
|
+
function (fDone)
|
|
1433
|
+
{
|
|
1434
|
+
// setBehaviorEndpoint should be a function and accept a custom function
|
|
1435
|
+
Expect(_MeadowEndpoints.setBehaviorEndpoint).to.be.a('function');
|
|
1436
|
+
let tmpResult = _MeadowEndpoints.setBehaviorEndpoint('CustomEndpoint', function () {});
|
|
1437
|
+
Expect(tmpResult).to.equal(_MeadowEndpoints); // Should return this for chaining
|
|
1438
|
+
fDone();
|
|
1439
|
+
}
|
|
1440
|
+
);
|
|
1441
|
+
test
|
|
1442
|
+
(
|
|
1443
|
+
'setBehaviorEndpoint: should ignore non-function endpoints',
|
|
1444
|
+
function (fDone)
|
|
1445
|
+
{
|
|
1446
|
+
let tmpResult = _MeadowEndpoints.setBehaviorEndpoint('BadEndpoint', 'not a function');
|
|
1447
|
+
Expect(tmpResult).to.equal(_MeadowEndpoints);
|
|
1448
|
+
fDone();
|
|
1449
|
+
}
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
);
|
|
1453
|
+
|
|
1454
|
+
// ======================================================================
|
|
1455
|
+
// Full CRUD Lifecycle
|
|
1456
|
+
// ======================================================================
|
|
1457
|
+
suite
|
|
1458
|
+
(
|
|
1459
|
+
'Full CRUD Lifecycle',
|
|
1460
|
+
() =>
|
|
1461
|
+
{
|
|
1462
|
+
let _LifecycleRecordID = 0;
|
|
1463
|
+
|
|
1464
|
+
test
|
|
1465
|
+
(
|
|
1466
|
+
'lifecycle: create a record',
|
|
1467
|
+
function (fDone)
|
|
1468
|
+
{
|
|
1469
|
+
_SuperTest
|
|
1470
|
+
.post('1.0/Book')
|
|
1471
|
+
.send({ Title: 'Lifecycle Test', Genre: 'Test', ISBN: '000-0000000000', PublicationYear: 2024 })
|
|
1472
|
+
.end(
|
|
1473
|
+
(pError, pResponse) =>
|
|
1474
|
+
{
|
|
1475
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1476
|
+
_LifecycleRecordID = tmpResult.IDBook;
|
|
1477
|
+
Expect(tmpResult.Title).to.equal('Lifecycle Test');
|
|
1478
|
+
Expect(_LifecycleRecordID).to.be.above(0);
|
|
1479
|
+
fDone();
|
|
1480
|
+
}
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
);
|
|
1484
|
+
test
|
|
1485
|
+
(
|
|
1486
|
+
'lifecycle: read the created record',
|
|
1487
|
+
function (fDone)
|
|
1488
|
+
{
|
|
1489
|
+
_SuperTest
|
|
1490
|
+
.get(`1.0/Book/${_LifecycleRecordID}`)
|
|
1491
|
+
.end(
|
|
1492
|
+
(pError, pResponse) =>
|
|
1493
|
+
{
|
|
1494
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1495
|
+
Expect(tmpResult.IDBook).to.equal(_LifecycleRecordID);
|
|
1496
|
+
Expect(tmpResult.Title).to.equal('Lifecycle Test');
|
|
1497
|
+
Expect(tmpResult.Genre).to.equal('Test');
|
|
1498
|
+
Expect(tmpResult.ISBN).to.equal('000-0000000000');
|
|
1499
|
+
Expect(tmpResult.PublicationYear).to.equal(2024);
|
|
1500
|
+
fDone();
|
|
1501
|
+
}
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
);
|
|
1505
|
+
test
|
|
1506
|
+
(
|
|
1507
|
+
'lifecycle: update the record',
|
|
1508
|
+
function (fDone)
|
|
1509
|
+
{
|
|
1510
|
+
_SuperTest
|
|
1511
|
+
.put('1.0/Book')
|
|
1512
|
+
.send({ IDBook: _LifecycleRecordID, Title: 'Lifecycle Test (Updated)', PublicationYear: 2025 })
|
|
1513
|
+
.end(
|
|
1514
|
+
(pError, pResponse) =>
|
|
1515
|
+
{
|
|
1516
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1517
|
+
Expect(tmpResult.IDBook).to.equal(_LifecycleRecordID);
|
|
1518
|
+
Expect(tmpResult.Title).to.equal('Lifecycle Test (Updated)');
|
|
1519
|
+
Expect(tmpResult.PublicationYear).to.equal(2025);
|
|
1520
|
+
fDone();
|
|
1521
|
+
}
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
);
|
|
1525
|
+
test
|
|
1526
|
+
(
|
|
1527
|
+
'lifecycle: verify update via read',
|
|
1528
|
+
function (fDone)
|
|
1529
|
+
{
|
|
1530
|
+
_SuperTest
|
|
1531
|
+
.get(`1.0/Book/${_LifecycleRecordID}`)
|
|
1532
|
+
.end(
|
|
1533
|
+
(pError, pResponse) =>
|
|
1534
|
+
{
|
|
1535
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1536
|
+
Expect(tmpResult.Title).to.equal('Lifecycle Test (Updated)');
|
|
1537
|
+
Expect(tmpResult.PublicationYear).to.equal(2025);
|
|
1538
|
+
fDone();
|
|
1539
|
+
}
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
);
|
|
1543
|
+
test
|
|
1544
|
+
(
|
|
1545
|
+
'lifecycle: count should include the record',
|
|
1546
|
+
function (fDone)
|
|
1547
|
+
{
|
|
1548
|
+
_SuperTest
|
|
1549
|
+
.get('1.0/Books/Count/By/Genre/Test')
|
|
1550
|
+
.end(
|
|
1551
|
+
(pError, pResponse) =>
|
|
1552
|
+
{
|
|
1553
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1554
|
+
Expect(tmpResult.Count).to.be.at.least(1);
|
|
1555
|
+
fDone();
|
|
1556
|
+
}
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
);
|
|
1560
|
+
test
|
|
1561
|
+
(
|
|
1562
|
+
'lifecycle: delete the record',
|
|
1563
|
+
function (fDone)
|
|
1564
|
+
{
|
|
1565
|
+
_SuperTest
|
|
1566
|
+
.delete(`1.0/Book/${_LifecycleRecordID}`)
|
|
1567
|
+
.end(
|
|
1568
|
+
(pError, pResponse) =>
|
|
1569
|
+
{
|
|
1570
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1571
|
+
Expect(tmpResult.Count).to.equal(1);
|
|
1572
|
+
fDone();
|
|
1573
|
+
}
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
);
|
|
1577
|
+
test
|
|
1578
|
+
(
|
|
1579
|
+
'lifecycle: verify record is gone',
|
|
1580
|
+
function (fDone)
|
|
1581
|
+
{
|
|
1582
|
+
_SuperTest
|
|
1583
|
+
.get(`1.0/Book/${_LifecycleRecordID}`)
|
|
1584
|
+
.end(
|
|
1585
|
+
(pError, pResponse) =>
|
|
1586
|
+
{
|
|
1587
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1588
|
+
Expect(tmpResult).to.have.property('Error');
|
|
1589
|
+
fDone();
|
|
1590
|
+
}
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
);
|
|
1594
|
+
test
|
|
1595
|
+
(
|
|
1596
|
+
'lifecycle: undelete the record',
|
|
1597
|
+
function (fDone)
|
|
1598
|
+
{
|
|
1599
|
+
_SuperTest
|
|
1600
|
+
.get(`1.0/Book/Undelete/${_LifecycleRecordID}`)
|
|
1601
|
+
.end(
|
|
1602
|
+
(pError, pResponse) =>
|
|
1603
|
+
{
|
|
1604
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1605
|
+
Expect(tmpResult.Count).to.equal(1);
|
|
1606
|
+
fDone();
|
|
1607
|
+
}
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
);
|
|
1611
|
+
test
|
|
1612
|
+
(
|
|
1613
|
+
'lifecycle: verify record is back',
|
|
1614
|
+
function (fDone)
|
|
1615
|
+
{
|
|
1616
|
+
_SuperTest
|
|
1617
|
+
.get(`1.0/Book/${_LifecycleRecordID}`)
|
|
1618
|
+
.end(
|
|
1619
|
+
(pError, pResponse) =>
|
|
1620
|
+
{
|
|
1621
|
+
let tmpResult = JSON.parse(pResponse.text);
|
|
1622
|
+
Expect(tmpResult.IDBook).to.equal(_LifecycleRecordID);
|
|
1623
|
+
Expect(tmpResult.Title).to.equal('Lifecycle Test (Updated)');
|
|
1624
|
+
fDone();
|
|
1625
|
+
}
|
|
1626
|
+
);
|
|
78
1627
|
}
|
|
79
1628
|
);
|
|
80
1629
|
}
|
|
81
1630
|
);
|
|
82
1631
|
}
|
|
83
|
-
);
|
|
1632
|
+
);
|