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.
- package/README.md +8 -15
- package/diagrams/how-it-works.excalidraw +1462 -0
- package/diagrams/how-it-works.mmd +9 -0
- package/diagrams/how-it-works.svg +2 -0
- package/dist/meadow-endpoints.js +572 -244
- package/dist/meadow-endpoints.js.map +1 -1
- package/dist/meadow-endpoints.min.js +18 -18
- package/dist/meadow-endpoints.min.js.map +1 -1
- package/docs/README.md +9 -25
- package/docs/_cover.md +9 -0
- package/docs/_sidebar.md +1 -0
- package/docs/_version.json +3 -3
- package/docs/architecture.md +119 -0
- package/docs/crud/README.md +6 -6
- package/docs/diagrams/behavior-injection-hook-points.excalidraw +677 -0
- package/docs/diagrams/behavior-injection-hook-points.mmd +9 -0
- package/docs/diagrams/behavior-injection-hook-points.svg +2 -0
- package/docs/diagrams/how-it-works.excalidraw +2777 -0
- package/docs/diagrams/how-it-works.mmd +16 -0
- package/docs/diagrams/how-it-works.svg +2 -0
- package/docs/diagrams/how-routes-are-generated.excalidraw +1237 -0
- package/docs/diagrams/how-routes-are-generated.mmd +13 -0
- package/docs/diagrams/how-routes-are-generated.svg +2 -0
- package/docs/diagrams/request-lifecycle.excalidraw +1897 -0
- package/docs/diagrams/request-lifecycle.mmd +22 -0
- package/docs/diagrams/request-lifecycle.svg +2 -0
- package/docs/diagrams/where-it-sits-in-the-stack.excalidraw +2068 -0
- package/docs/diagrams/where-it-sits-in-the-stack.mmd +19 -0
- package/docs/diagrams/where-it-sits-in-the-stack.svg +2 -0
- package/docs/index.html +2 -2
- package/docs/quickstart.md +151 -0
- package/docs/retold-catalog.json +155 -174
- package/docs/retold-keyword-index.json +6593 -2920
- package/package.json +8 -8
- package/source/Meadow-Endpoints.js +4 -0
- package/source/controller/Meadow-Endpoints-Controller-Base.js +40 -0
- package/source/endpoints/count/Meadow-Endpoint-Count.js +1 -0
- package/source/endpoints/count/Meadow-Endpoint-CountBy.js +1 -0
- package/source/endpoints/create/Meadow-Operation-Create.js +1 -0
- package/source/endpoints/delete/Meadow-Endpoint-Delete.js +1 -0
- package/source/endpoints/delete/Meadow-Endpoint-Undelete.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-Query.js +86 -0
- package/source/endpoints/read/Meadow-Endpoint-Read.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadDistinctList.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadLiteList.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadMax.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadSelectList.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-Reads.js +1 -0
- package/source/endpoints/read/Meadow-Endpoint-ReadsBy.js +1 -0
- package/source/endpoints/update/Meadow-Operation-Update.js +2 -0
- package/source/endpoints/upsert/Meadow-Operation-Upsert.js +1 -0
- package/test/MeadowEndpoints_SessionOverride_tests.js +75 -0
- package/test/MeadowEndpoints_basic_tests.js +97 -0
- 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.
|
|
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/
|
|
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/
|
|
47
|
+
"url": "https://github.com/fable-retold/meadow-endpoints/issues"
|
|
48
48
|
},
|
|
49
|
-
"homepage": "https://github.com/
|
|
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.
|
|
59
|
-
"quackage": "^1.
|
|
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.
|
|
66
|
+
"fable": "^3.1.78",
|
|
67
67
|
"fable-serviceproviderbase": "^3.0.19",
|
|
68
68
|
"JSONStream": "^1.3.5",
|
|
69
|
-
"meadow": "^2.0.
|
|
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} */
|
|
@@ -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)
|