meadow-endpoints 4.0.22 → 4.0.24

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.
Files changed (54) hide show
  1. package/README.md +8 -15
  2. package/diagrams/how-it-works.excalidraw +1462 -0
  3. package/diagrams/how-it-works.mmd +9 -0
  4. package/diagrams/how-it-works.svg +2 -0
  5. package/dist/meadow-endpoints.js +572 -244
  6. package/dist/meadow-endpoints.js.map +1 -1
  7. package/dist/meadow-endpoints.min.js +18 -18
  8. package/dist/meadow-endpoints.min.js.map +1 -1
  9. package/docs/README.md +9 -25
  10. package/docs/_cover.md +9 -0
  11. package/docs/_sidebar.md +1 -0
  12. package/docs/_version.json +3 -3
  13. package/docs/architecture.md +119 -0
  14. package/docs/crud/README.md +6 -6
  15. package/docs/diagrams/behavior-injection-hook-points.excalidraw +677 -0
  16. package/docs/diagrams/behavior-injection-hook-points.mmd +9 -0
  17. package/docs/diagrams/behavior-injection-hook-points.svg +2 -0
  18. package/docs/diagrams/how-it-works.excalidraw +2777 -0
  19. package/docs/diagrams/how-it-works.mmd +16 -0
  20. package/docs/diagrams/how-it-works.svg +2 -0
  21. package/docs/diagrams/how-routes-are-generated.excalidraw +1237 -0
  22. package/docs/diagrams/how-routes-are-generated.mmd +13 -0
  23. package/docs/diagrams/how-routes-are-generated.svg +2 -0
  24. package/docs/diagrams/request-lifecycle.excalidraw +1897 -0
  25. package/docs/diagrams/request-lifecycle.mmd +22 -0
  26. package/docs/diagrams/request-lifecycle.svg +2 -0
  27. package/docs/diagrams/where-it-sits-in-the-stack.excalidraw +2068 -0
  28. package/docs/diagrams/where-it-sits-in-the-stack.mmd +19 -0
  29. package/docs/diagrams/where-it-sits-in-the-stack.svg +2 -0
  30. package/docs/index.html +2 -2
  31. package/docs/quickstart.md +151 -0
  32. package/docs/retold-catalog.json +155 -174
  33. package/docs/retold-keyword-index.json +6593 -2920
  34. package/package.json +8 -8
  35. package/source/Meadow-Endpoints.js +4 -0
  36. package/source/controller/Meadow-Endpoints-Controller-Base.js +40 -0
  37. package/source/endpoints/count/Meadow-Endpoint-Count.js +1 -0
  38. package/source/endpoints/count/Meadow-Endpoint-CountBy.js +1 -0
  39. package/source/endpoints/create/Meadow-Operation-Create.js +1 -0
  40. package/source/endpoints/delete/Meadow-Endpoint-Delete.js +1 -0
  41. package/source/endpoints/delete/Meadow-Endpoint-Undelete.js +1 -0
  42. package/source/endpoints/read/Meadow-Endpoint-Query.js +86 -0
  43. package/source/endpoints/read/Meadow-Endpoint-Read.js +1 -0
  44. package/source/endpoints/read/Meadow-Endpoint-ReadDistinctList.js +1 -0
  45. package/source/endpoints/read/Meadow-Endpoint-ReadLiteList.js +1 -0
  46. package/source/endpoints/read/Meadow-Endpoint-ReadMax.js +1 -0
  47. package/source/endpoints/read/Meadow-Endpoint-ReadSelectList.js +1 -0
  48. package/source/endpoints/read/Meadow-Endpoint-Reads.js +1 -0
  49. package/source/endpoints/read/Meadow-Endpoint-ReadsBy.js +1 -0
  50. package/source/endpoints/update/Meadow-Operation-Update.js +2 -0
  51. package/source/endpoints/upsert/Meadow-Operation-Upsert.js +1 -0
  52. package/test/MeadowEndpoints_SessionOverride_tests.js +75 -0
  53. package/test/MeadowEndpoints_basic_tests.js +97 -0
  54. package/dist/indoctrinate_content_staging/Indoctrinate-Catalog-AppData.json +0 -4808
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meadow-endpoints",
3
- "version": "4.0.22",
3
+ "version": "4.0.24",
4
4
  "description": "Automatic API endpoints for Meadow data.",
5
5
  "main": "source/Meadow-Endpoints.js",
6
6
  "scripts": {
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "repository": {
37
37
  "type": "git",
38
- "url": "https://github.com/stevenvelozo/meadow-endpoints.git"
38
+ "url": "https://github.com/fable-retold/meadow-endpoints.git"
39
39
  },
40
40
  "keywords": [
41
41
  "crud",
@@ -44,9 +44,9 @@
44
44
  "author": "Steven Velozo <steven@velozo.com> (http://velozo.com/)",
45
45
  "license": "MIT",
46
46
  "bugs": {
47
- "url": "https://github.com/stevenvelozo/meadow-endpoints/issues"
47
+ "url": "https://github.com/fable-retold/meadow-endpoints/issues"
48
48
  },
49
- "homepage": "https://github.com/stevenvelozo/meadow-endpoints",
49
+ "homepage": "https://github.com/fable-retold/meadow-endpoints",
50
50
  "devDependencies": {
51
51
  "alasql": "^4.17.2",
52
52
  "chance": "^1.1.13",
@@ -55,18 +55,18 @@
55
55
  "mysql2": "^3.22.1",
56
56
  "orator-serviceserver-restify": "^2.0.11",
57
57
  "papaparse": "^5.5.3",
58
- "pict-docuserve": "^1.3.2",
59
- "quackage": "^1.2.3",
58
+ "pict-docuserve": "^1.4.19",
59
+ "quackage": "^1.3.0",
60
60
  "supertest": "^7.2.2",
61
61
  "typescript": "^5.9.3",
62
62
  "why-is-node-running": "^3.2.2"
63
63
  },
64
64
  "dependencies": {
65
65
  "async": "3.2.6",
66
- "fable": "^3.1.75",
66
+ "fable": "^3.1.78",
67
67
  "fable-serviceproviderbase": "^3.0.19",
68
68
  "JSONStream": "^1.3.5",
69
- "meadow": "^2.0.41",
69
+ "meadow": "^2.0.46",
70
70
  "meadow-filter": "^1.0.10",
71
71
  "orator": "^6.1.2",
72
72
  "underscore": "^1.13.8"
@@ -73,6 +73,9 @@ class MeadowEndpoints
73
73
  Reads: require('./endpoints/read/Meadow-Endpoint-Reads.js'),
74
74
  ReadsBy: require('./endpoints/read/Meadow-Endpoint-ReadsBy.js'),
75
75
 
76
+ // Body-driven read: filter/pagination/mode travel in a JSON POST body
77
+ Query: require('./endpoints/read/Meadow-Endpoint-Query.js'),
78
+
76
79
  ReadSelectList: require('./endpoints/read/Meadow-Endpoint-ReadSelectList.js'),
77
80
  ReadLiteList: require('./endpoints/read/Meadow-Endpoint-ReadLiteList.js'),
78
81
  ReadDistinctList: require('./endpoints/read/Meadow-Endpoint-ReadDistinctList.js'),
@@ -183,6 +186,7 @@ class MeadowEndpoints
183
186
  }
184
187
  if (this._EnabledBehaviorSets.Reads)
185
188
  {
189
+ this.connectRoute(pServiceServer, 'postWithBodyParser', `s/Query`, this._Endpoints.Query, `the internal behavior _Endpoints.Query`);
186
190
  this.connectRoute(pServiceServer, 'get', `s`, this._Endpoints.Reads, `the internal behavior _Endpoints.Reads`);
187
191
  this.connectRoute(pServiceServer, 'get', `s/By/:ByField/:ByValue`, this._Endpoints.ReadsBy, `the internal behavior _Endpoints.ReadsBy`);
188
192
  this.connectRoute(pServiceServer, 'get', `s/By/:ByField/:ByValue/:Begin/:Cap`, this._Endpoints.ReadsBy, `the internal behavior _Endpoints.ReadsBy`);
@@ -155,6 +155,46 @@ class MeadowEndpointControllerBase
155
155
  {
156
156
  return this._SessionMarshaler.getSessionData(pRequest);
157
157
  }
158
+
159
+ /**
160
+ * Stamp the request's session onto a meadow query so a downstream provider
161
+ * can act under the caller's identity.
162
+ *
163
+ * Today only the MeadowEndpoints provider consumes this — when a beacon
164
+ * fronts a remote meadow-endpoints API it presents this session upstream
165
+ * (instead of its bound machine session) so the remote enforces row-level
166
+ * auth as the real caller. SQL providers ignore the parameter. The override
167
+ * rides the per-operation FoxHound query (concurrency-safe).
168
+ *
169
+ * No-op for the default/anonymous session (UserID 0 / SessionID 0x0000) —
170
+ * those carry no identity, so the provider keeps its existing behavior.
171
+ *
172
+ * @param {object} pRequestState - the request state (carries SessionData + Query)
173
+ * @returns {void}
174
+ */
175
+ stampSessionOverrideOnQuery(pRequestState)
176
+ {
177
+ if (!pRequestState || !pRequestState.Query || !pRequestState.SessionData)
178
+ {
179
+ return;
180
+ }
181
+ let tmpSession = pRequestState.SessionData;
182
+ let tmpSessionID = tmpSession.SessionID;
183
+ if (typeof(tmpSessionID) !== 'string' || tmpSessionID.length < 1 || tmpSessionID === '0x0000')
184
+ {
185
+ return;
186
+ }
187
+ if (pRequestState.Query.query && pRequestState.Query.query.parameters)
188
+ {
189
+ pRequestState.Query.query.parameters.MeadowEndpointsSessionOverride = (
190
+ {
191
+ SessionID: tmpSessionID,
192
+ CustomerID: tmpSession.CustomerID,
193
+ UserID: tmpSession.UserID,
194
+ UserRoleIndex: tmpSession.UserRoleIndex
195
+ });
196
+ }
197
+ }
158
198
  }
159
199
 
160
200
  module.exports = MeadowEndpointControllerBase;
@@ -11,6 +11,7 @@ const doAPIEndpointCount = function(pRequest, pResponse, fNext)
11
11
  (fStageComplete) =>
12
12
  {
13
13
  tmpRequestState.Query = this.DAL.query;
14
+ this.stampSessionOverrideOnQuery(tmpRequestState);
14
15
  if (typeof(pRequest.params.Filter) === 'string')
15
16
  {
16
17
  // If a filter has been passed in, parse it and add the values to the query.
@@ -11,6 +11,7 @@ const doAPIEndpointCountBy = function(pRequest, pResponse, fNext)
11
11
  (fStageComplete) =>
12
12
  {
13
13
  tmpRequestState.Query = this.DAL.query;
14
+ this.stampSessionOverrideOnQuery(tmpRequestState);
14
15
  tmpRequestState.Query.addFilter(pRequest.params.ByField, pRequest.params.ByValue, '=', 'AND', 'RequestByField');
15
16
  return fStageComplete();
16
17
  },
@@ -31,6 +31,7 @@ const doCreate = function(pRecord, pRequest, pRequestState, pResponse, fCallback
31
31
  {
32
32
  // Prepare create query
33
33
  tmpRequestState.Query = this.DAL.query;
34
+ this.stampSessionOverrideOnQuery(tmpRequestState);
34
35
  tmpRequestState.Query.setIDUser(tmpRequestState.SessionData.UserID);
35
36
  tmpRequestState.Query.addRecord(tmpRequestState.RecordToCreate);
36
37
  return fStageComplete();
@@ -36,6 +36,7 @@ const doAPIEndpointDelete = function(pRequest, pResponse, fNext)
36
36
  (fStageComplete) =>
37
37
  {
38
38
  tmpRequestState.Query = this.DAL.query;
39
+ this.stampSessionOverrideOnQuery(tmpRequestState);
39
40
  tmpRequestState.Query.addFilter(this.DAL.defaultIdentifier, tmpRequestState.IDRecord);
40
41
  tmpRequestState.Query.setIDUser(tmpRequestState.SessionData.UserID);
41
42
  return fStageComplete();
@@ -54,6 +54,7 @@ const doAPIEndpointUndelete = function(pRequest, pResponse, fNext)
54
54
  {
55
55
  // Now see if the record, with this identifier, for this user, exists with the deleted bit set to 1
56
56
  tmpRequestState.Query = this.DAL.query;
57
+ this.stampSessionOverrideOnQuery(tmpRequestState);
57
58
  tmpRequestState.Query.addFilter(this.DAL.defaultIdentifier, tmpIDRecord);
58
59
  tmpRequestState.Query.addFilter('Deleted', 1);
59
60
  tmpRequestState.Query.setIDUser(tmpRequestState.SessionData.UserID);
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Meadow Endpoint - Body-driven Read
3
+ *
4
+ * A single POST endpoint that carries the filter, pagination and read-mode
5
+ * selection in a JSON body instead of the URI. This sidesteps URI length
6
+ * limits hit by complex filters and large IN-lists, while reusing the exact
7
+ * GET read handlers (and therefore their behavior hooks, marshalling and
8
+ * response shapes) by mapping the body onto pRequest.params and delegating.
9
+ *
10
+ * Body envelope:
11
+ * {
12
+ * "Filter": "FBV~Genre~EQ~Books~...", // meadow-filter string
13
+ * "Begin": 0,
14
+ * "Cap": 250,
15
+ * "ExtraColumns": "ColumnA,ColumnB", // Lite read
16
+ * "Columns": "ColumnA,ColumnB", // Distinct read
17
+ * "Lite": true, "Distinct": true, "Count": true
18
+ * }
19
+ *
20
+ * The read mode is selected by the flags, resolved by precedence:
21
+ * Count > Distinct > Lite > Reads (the default). This lets a caller compose a
22
+ * read query (filter, pagination, Lite/Distinct shaping) and flip Count on to
23
+ * get the count of that same query.
24
+ */
25
+ const doReads = require('./Meadow-Endpoint-Reads.js');
26
+ const doReadLite = require('./Meadow-Endpoint-ReadLiteList.js');
27
+ const doReadDistinct = require('./Meadow-Endpoint-ReadDistinctList.js');
28
+ const doCount = require('../count/Meadow-Endpoint-Count.js');
29
+
30
+ // Body keys hydrated onto pRequest.params so the delegated GET handlers see
31
+ // the same inputs they read from the URI.
32
+ const PARAM_KEYS = [ 'Filter', 'Begin', 'Cap', 'ExtraColumns', 'Columns' ];
33
+
34
+ /**
35
+ * Resolve the read mode from the request body flags, by precedence:
36
+ * Count > Distinct > Lite > Reads (the default).
37
+ *
38
+ * @param {Record<string, any>} pBody - the parsed request body
39
+ *
40
+ * @return {string} one of 'Count', 'Distinct', 'Lite', 'Reads'
41
+ */
42
+ const resolveMode = function(pBody)
43
+ {
44
+ if (pBody.Count)
45
+ {
46
+ return 'Count';
47
+ }
48
+ if (pBody.Distinct)
49
+ {
50
+ return 'Distinct';
51
+ }
52
+ if (pBody.Lite)
53
+ {
54
+ return 'Lite';
55
+ }
56
+ return 'Reads';
57
+ };
58
+
59
+ const doAPIEndpointQuery = function(pRequest, pResponse, fNext)
60
+ {
61
+ const tmpBody = (pRequest.body && typeof(pRequest.body) === 'object') ? pRequest.body : {};
62
+ pRequest.params = (pRequest.params && typeof(pRequest.params) === 'object') ? pRequest.params : {};
63
+
64
+ for (let i = 0; i < PARAM_KEYS.length; i++)
65
+ {
66
+ if (typeof(tmpBody[PARAM_KEYS[i]]) !== 'undefined')
67
+ {
68
+ pRequest.params[PARAM_KEYS[i]] = tmpBody[PARAM_KEYS[i]];
69
+ }
70
+ }
71
+
72
+ switch (resolveMode(tmpBody))
73
+ {
74
+ case 'Count':
75
+ return doCount.call(this, pRequest, pResponse, fNext);
76
+ case 'Distinct':
77
+ return doReadDistinct.call(this, pRequest, pResponse, fNext);
78
+ case 'Lite':
79
+ return doReadLite.call(this, pRequest, pResponse, fNext);
80
+ case 'Reads':
81
+ default:
82
+ return doReads.call(this, pRequest, pResponse, fNext);
83
+ }
84
+ };
85
+
86
+ module.exports = doAPIEndpointQuery;
@@ -11,6 +11,7 @@ const doAPIEndpointRead = function(pRequest, pResponse, fNext)
11
11
  (fStageComplete) =>
12
12
  {
13
13
  tmpRequestState.Query = this.DAL.query;
14
+ this.stampSessionOverrideOnQuery(tmpRequestState);
14
15
  return fStageComplete();
15
16
  },
16
17
  fBehaviorInjector(`Read-PreOperation`),
@@ -15,6 +15,7 @@ const doAPIEndpointReadDistinct = function(pRequest, pResponse, fNext)
15
15
  (fStageComplete) =>
16
16
  {
17
17
  tmpRequestState.Query = this.DAL.query.setDistinct(true);
18
+ this.stampSessionOverrideOnQuery(tmpRequestState);
18
19
 
19
20
  /** @type {number | boolean} */
20
21
  let tmpCap = false;
@@ -14,6 +14,7 @@ const doAPIEndpointReadLite = function(pRequest, pResponse, fNext)
14
14
  (fStageComplete) =>
15
15
  {
16
16
  tmpRequestState.Query = this.DAL.query;
17
+ this.stampSessionOverrideOnQuery(tmpRequestState);
17
18
  // TODO: Limit the query to the columns we need for the templated expression
18
19
 
19
20
  /** @type {number | boolean} */
@@ -11,6 +11,7 @@ const doAPIEndpointReadMax = function(pRequest, pResponse, fNext)
11
11
  (fStageComplete) =>
12
12
  {
13
13
  tmpRequestState.Query = this.DAL.query;
14
+ this.stampSessionOverrideOnQuery(tmpRequestState);
14
15
  return fStageComplete();
15
16
  },
16
17
  (fStageComplete) =>
@@ -11,6 +11,7 @@ const doAPIEndpointReadSelectList = function(pRequest, pResponse, fNext)
11
11
  (fStageComplete) =>
12
12
  {
13
13
  tmpRequestState.Query = this.DAL.query;
14
+ this.stampSessionOverrideOnQuery(tmpRequestState);
14
15
 
15
16
  /** @type {number | boolean} */
16
17
  var tmpCap = false;
@@ -11,6 +11,7 @@ const doAPIEndpointReads = function(pRequest, pResponse, fNext)
11
11
  (fStageComplete) =>
12
12
  {
13
13
  tmpRequestState.Query = this.DAL.query;
14
+ this.stampSessionOverrideOnQuery(tmpRequestState);
14
15
 
15
16
  /** @type {number | boolean} */
16
17
  var tmpCap = false;
@@ -12,6 +12,7 @@ const doAPIEndpointReadsBy = function(pRequest, pResponse, fNext)
12
12
  (fStageComplete) =>
13
13
  {
14
14
  tmpRequestState.Query = this.DAL.query;
15
+ this.stampSessionOverrideOnQuery(tmpRequestState);
15
16
 
16
17
  /** @type {number | boolean} */
17
18
  var tmpCap = false;
@@ -33,6 +33,7 @@ const doUpdate = function(pRecordToModify, pRequest, pRequestState, pResponse, f
33
33
  else
34
34
  {
35
35
  tmpRequestState.Query = this.DAL.query;
36
+ this.stampSessionOverrideOnQuery(tmpRequestState);
36
37
 
37
38
  tmpRequestState.Query.addFilter(this.DAL.defaultIdentifier, tmpRequestState.RecordToModify[this.DAL.defaultIdentifier]);
38
39
 
@@ -64,6 +65,7 @@ const doUpdate = function(pRecordToModify, pRequest, pRequestState, pResponse, f
64
65
  (fStageComplete) =>
65
66
  {
66
67
  tmpRequestState.Query = this.DAL.query;
68
+ this.stampSessionOverrideOnQuery(tmpRequestState);
67
69
  return fStageComplete();
68
70
  },
69
71
  fBehaviorInjector(`Update-PreOperation`),
@@ -14,6 +14,7 @@ const doUpsert = function(pRecordToUpsert, pRequest, pRequestState, pResponse, f
14
14
  (fStageComplete) =>
15
15
  {
16
16
  tmpRequestState.Query = this.DAL.query;
17
+ this.stampSessionOverrideOnQuery(tmpRequestState);
17
18
 
18
19
  // Prepare to gather requirements for upserting
19
20
  tmpRequestState.Record = pRecordToUpsert;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Meadow Endpoints — per-request session override stamping.
3
+ *
4
+ * stampSessionOverrideOnQuery copies a real request session onto the meadow
5
+ * query (Query.query.parameters.MeadowEndpointsSessionOverride) so a
6
+ * downstream provider (the MeadowEndpoints provider, when a beacon fronts a
7
+ * remote API) can act under the caller's identity. The default/anonymous
8
+ * session is a no-op.
9
+ *
10
+ * npx mocha test/MeadowEndpoints_SessionOverride_tests.js -u tdd --exit
11
+ */
12
+
13
+ const Chai = require('chai');
14
+ const Expect = Chai.expect;
15
+
16
+ const libFable = require('fable');
17
+ const libMeadow = require('meadow');
18
+ const libMeadowEndpointsControllerBase = require('../source/controller/Meadow-Endpoints-Controller-Base.js');
19
+
20
+ const _BookSchema = require('../test_support/model/meadow_schema/BookStore-MeadowSchema-Book.json');
21
+
22
+ function buildController()
23
+ {
24
+ let tmpFable = new libFable({ Product: 'SessionOverrideTest', LogStreams: [ { streamtype: 'console', level: 'fatal' } ] });
25
+ let tmpMeadow = libMeadow.new(tmpFable, 'Book')
26
+ .setSchema(_BookSchema.Schema)
27
+ .setJsonSchema(_BookSchema.JsonSchema)
28
+ .setDefaultIdentifier(_BookSchema.DefaultIdentifier)
29
+ .setDefault(_BookSchema.DefaultObject);
30
+ return new libMeadowEndpointsControllerBase({ DAL: tmpMeadow, _ControllerOptions: {} });
31
+ }
32
+
33
+ function realSession()
34
+ {
35
+ return { SessionID: 'caller-session-xyz', CustomerID: 182, UserID: 5150, UserRoleIndex: 3, LoggedIn: true };
36
+ }
37
+
38
+ suite('Meadow-Endpoints session override stamping', () =>
39
+ {
40
+ test('a real session is stamped onto the query parameters', () =>
41
+ {
42
+ let tmpController = buildController();
43
+ let tmpRequestState = { SessionData: realSession(), Query: tmpController.DAL.query };
44
+ tmpController.stampSessionOverrideOnQuery(tmpRequestState);
45
+ let tmpOverride = tmpRequestState.Query.query.parameters.MeadowEndpointsSessionOverride;
46
+ Expect(tmpOverride).to.be.an('object');
47
+ Expect(tmpOverride.SessionID).to.equal('caller-session-xyz');
48
+ Expect(tmpOverride.CustomerID).to.equal(182);
49
+ Expect(tmpOverride.UserID).to.equal(5150);
50
+ });
51
+
52
+ test('the default/anonymous session (0x0000) is a no-op', () =>
53
+ {
54
+ let tmpController = buildController();
55
+ let tmpRequestState = { SessionData: { SessionID: '0x0000', UserID: 0 }, Query: tmpController.DAL.query };
56
+ tmpController.stampSessionOverrideOnQuery(tmpRequestState);
57
+ Expect(tmpRequestState.Query.query.parameters.MeadowEndpointsSessionOverride).to.equal(undefined);
58
+ });
59
+
60
+ test('a missing SessionID is a no-op', () =>
61
+ {
62
+ let tmpController = buildController();
63
+ let tmpRequestState = { SessionData: { UserID: 5 }, Query: tmpController.DAL.query };
64
+ tmpController.stampSessionOverrideOnQuery(tmpRequestState);
65
+ Expect(tmpRequestState.Query.query.parameters.MeadowEndpointsSessionOverride).to.equal(undefined);
66
+ });
67
+
68
+ test('missing Query or SessionData does not throw', () =>
69
+ {
70
+ let tmpController = buildController();
71
+ Expect(() => tmpController.stampSessionOverrideOnQuery({})).to.not.throw();
72
+ Expect(() => tmpController.stampSessionOverrideOnQuery({ SessionData: realSession() })).to.not.throw();
73
+ Expect(() => tmpController.stampSessionOverrideOnQuery(null)).to.not.throw();
74
+ });
75
+ });
@@ -505,6 +505,103 @@ suite
505
505
  }
506
506
  );
507
507
  test
508
+ (
509
+ "query: reads all records via JSON body (default mode)",
510
+ function (fDone)
511
+ {
512
+ _SuperTest
513
+ .post("1.0/Books/Query")
514
+ .send({})
515
+ .end(
516
+ (pError, pResponse) =>
517
+ {
518
+ Expect(pError).to.not.exist;
519
+ let tmpResult = JSON.parse(pResponse.text);
520
+ Expect(tmpResult).to.be.an("array");
521
+ Expect(tmpResult.length).to.be.at.least(5);
522
+ fDone();
523
+ }
524
+ );
525
+ }
526
+ );
527
+ test
528
+ (
529
+ "query: reads with filter and pagination in the body",
530
+ function (fDone)
531
+ {
532
+ _SuperTest
533
+ .post("1.0/Books/Query")
534
+ .send({ Filter: "FBV~Genre~LK~%Science%", Begin: 0, Cap: 1 })
535
+ .end(
536
+ (pError, pResponse) =>
537
+ {
538
+ let tmpResult = JSON.parse(pResponse.text);
539
+ Expect(tmpResult).to.be.an("array");
540
+ Expect(tmpResult.length).to.equal(1);
541
+ Expect(tmpResult[0].Genre).to.contain("Science");
542
+ fDone();
543
+ }
544
+ );
545
+ }
546
+ );
547
+ test
548
+ (
549
+ "query: Lite read via boolean flag",
550
+ function (fDone)
551
+ {
552
+ _SuperTest
553
+ .post("1.0/Books/Query")
554
+ .send({ Lite: true, Begin: 0, Cap: 3 })
555
+ .end(
556
+ (pError, pResponse) =>
557
+ {
558
+ let tmpResult = JSON.parse(pResponse.text);
559
+ Expect(tmpResult).to.be.an("array");
560
+ Expect(tmpResult.length).to.equal(3);
561
+ fDone();
562
+ }
563
+ );
564
+ }
565
+ );
566
+ test
567
+ (
568
+ "query: Distinct read via boolean flag and Columns",
569
+ function (fDone)
570
+ {
571
+ _SuperTest
572
+ .post("1.0/Books/Query")
573
+ .send({ Distinct: true, Columns: "Genre" })
574
+ .end(
575
+ (pError, pResponse) =>
576
+ {
577
+ let tmpResult = JSON.parse(pResponse.text);
578
+ Expect(tmpResult).to.be.an("array");
579
+ Expect(tmpResult.length).to.be.at.least(1);
580
+ fDone();
581
+ }
582
+ );
583
+ }
584
+ );
585
+ test
586
+ (
587
+ "query: Count flag takes precedence and honors the filter",
588
+ function (fDone)
589
+ {
590
+ _SuperTest
591
+ .post("1.0/Books/Query")
592
+ .send({ Lite: true, Count: true, Filter: "FBV~Genre~LK~%Science%" })
593
+ .end(
594
+ (pError, pResponse) =>
595
+ {
596
+ let tmpResult = JSON.parse(pResponse.text);
597
+ Expect(tmpResult).to.have.property("Count");
598
+ Expect(tmpResult.Count).to.be.at.least(3);
599
+ fDone();
600
+ }
601
+ );
602
+ }
603
+ );
604
+ test
508
605
  (
509
606
  'reads by: get records filtered by a field value',
510
607
  function (fDone)