retold-data-service 2.1.2 → 2.1.5
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/BUILDING-AND-PUBLISHING.md +2 -2
- package/Dockerfile +1 -1
- package/README.md +12 -27
- package/build-all.js +66 -0
- package/diagrams/architecture.excalidraw +2966 -0
- package/diagrams/architecture.mmd +17 -0
- package/diagrams/architecture.svg +2 -0
- package/docs/README.md +12 -12
- package/docs/_brand.json +18 -0
- package/docs/_cover.md +1 -1
- package/docs/_topbar.md +1 -1
- package/docs/_version.json +3 -3
- package/docs/api/reference.md +8 -8
- package/docs/architecture.md +6 -84
- package/docs/diagrams/component-diagram.excalidraw +2807 -0
- package/docs/diagrams/component-diagram.mmd +14 -0
- package/docs/diagrams/component-diagram.svg +2 -0
- package/docs/diagrams/component-stack.excalidraw +1169 -0
- package/docs/diagrams/component-stack.mmd +6 -0
- package/docs/diagrams/component-stack.svg +2 -0
- package/docs/diagrams/hook-execution-order.excalidraw +3230 -0
- package/docs/diagrams/hook-execution-order.mmd +19 -0
- package/docs/diagrams/hook-execution-order.svg +2 -0
- package/docs/diagrams/initialization-flow.excalidraw +1800 -0
- package/docs/diagrams/initialization-flow.mmd +22 -0
- package/docs/diagrams/initialization-flow.svg +2 -0
- package/docs/index.html +6 -7
- package/docs/lifecycle-hooks.md +2 -21
- package/docs/retold-catalog.json +141 -141
- package/docs/retold-keyword-index.json +6818 -1608
- package/package.json +130 -96
- package/source/services/RetoldDataService-Brand.js +13 -0
- package/source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader.js +65 -15
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Layout.js +28 -74
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Load.js +17 -17
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-SettingsPanel.js +62 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Shell.js +142 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-StatusBar.js +125 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-StatusDetail.js +89 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-TopBar-Nav.js +42 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-TopBar-User.js +48 -0
- package/source/services/comprehension-loader/web/comprehension-loader.js +5415 -6183
- package/source/services/comprehension-loader/web/comprehension-loader.js.map +1 -1
- package/source/services/comprehension-loader/web/comprehension-loader.min.js +75 -1
- package/source/services/comprehension-loader/web/comprehension-loader.min.js.map +1 -1
- package/source/services/comprehension-loader/web/favicons/favicon-dark.svg +13 -0
- package/source/services/comprehension-loader/web/favicons/favicon-light.svg +13 -0
- package/source/services/comprehension-loader/web/favicons/favicon.svg +13 -0
- package/source/services/comprehension-loader/web/index.html +3 -0
- package/source/services/comprehension-loader/web/pict.min.js +12 -0
- package/source/services/data-cloner/DataCloner-Command-Headless.js +2 -1
- package/source/services/data-cloner/DataCloner-Command-Sync.js +110 -75
- package/source/services/data-cloner/pict-app/Pict-Application-DataCloner.js +70 -47
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Export.js +3 -3
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Layout.js +40 -86
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-SettingsPanel.js +61 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Shell.js +136 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-StatusBar.js +117 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-StatusDetail.js +81 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Sync.js +18 -18
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-TopBar-Nav.js +42 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-TopBar-User.js +48 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-ViewData.js +2 -2
- package/source/services/data-cloner/web/data-cloner.js +5772 -7986
- package/source/services/data-cloner/web/data-cloner.js.map +1 -1
- package/source/services/data-cloner/web/data-cloner.min.js +75 -1
- package/source/services/data-cloner/web/data-cloner.min.js.map +1 -1
- package/source/services/data-cloner/web/favicons/favicon-dark.svg +13 -0
- package/source/services/data-cloner/web/favicons/favicon-light.svg +13 -0
- package/source/services/data-cloner/web/favicons/favicon.svg +13 -0
- package/source/services/data-cloner/web/favicons/favicons/favicon-dark.svg +13 -0
- package/source/services/data-cloner/web/favicons/favicons/favicon-light.svg +13 -0
- package/source/services/data-cloner/web/favicons/favicons/favicon.svg +13 -0
- package/source/services/data-cloner/web/index.html +3 -0
- package/source/services/data-cloner/web/pict.min.js +12 -0
- package/test/Bundles_smoke_tests.js +43 -0
- package/test/ComprehensionLoader_smoke_tests.js +95 -0
- package/test/DataCloner-RuntimeOverrides_tests.js +344 -0
- package/test/DataCloner_smoke_tests.js +87 -0
- package/docs/css/docuserve.css +0 -327
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bundle smoke tests — verifies `npm run build` produced both browser bundles
|
|
5
|
+
* and each has the expected window-global identifier.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const libFS = require('fs');
|
|
9
|
+
const libPath = require('path');
|
|
10
|
+
const libChai = require('chai');
|
|
11
|
+
|
|
12
|
+
const Expect = libChai.expect;
|
|
13
|
+
|
|
14
|
+
suite('Built bundles', function ()
|
|
15
|
+
{
|
|
16
|
+
test('comprehension-loader bundle is present and non-trivial', function ()
|
|
17
|
+
{
|
|
18
|
+
let tmpPath = libPath.resolve(__dirname, '../source/services/comprehension-loader/web/comprehension-loader.js');
|
|
19
|
+
let tmpStat = libFS.statSync(tmpPath);
|
|
20
|
+
Expect(tmpStat.size).to.be.greaterThan(10000, 'bundle should be > 10KB');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('data-cloner bundle is present and non-trivial', function ()
|
|
24
|
+
{
|
|
25
|
+
let tmpPath = libPath.resolve(__dirname, '../source/services/data-cloner/web/data-cloner.js');
|
|
26
|
+
let tmpStat = libFS.statSync(tmpPath);
|
|
27
|
+
Expect(tmpStat.size).to.be.greaterThan(10000, 'bundle should be > 10KB');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('comprehension-loader bundle exposes ComprehensionLoaderApplication', function ()
|
|
31
|
+
{
|
|
32
|
+
let tmpPath = libPath.resolve(__dirname, '../source/services/comprehension-loader/web/comprehension-loader.js');
|
|
33
|
+
let tmpSrc = libFS.readFileSync(tmpPath, 'utf8');
|
|
34
|
+
Expect(tmpSrc).to.include('ComprehensionLoaderApplication');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('data-cloner bundle exposes DataClonerApplication', function ()
|
|
38
|
+
{
|
|
39
|
+
let tmpPath = libPath.resolve(__dirname, '../source/services/data-cloner/web/data-cloner.js');
|
|
40
|
+
let tmpSrc = libFS.readFileSync(tmpPath, 'utf8');
|
|
41
|
+
Expect(tmpSrc).to.include('DataClonerApplication');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ComprehensionLoader bootstrap smoke tests.
|
|
5
|
+
*
|
|
6
|
+
* Verifies: (1) the Application class loads, (2) instantiation registers
|
|
7
|
+
* all expected views without throwing, (3) the Shell view's render()
|
|
8
|
+
* actually emits HTML into the application container.
|
|
9
|
+
*
|
|
10
|
+
* The async lifecycle (status polling, persistence load, etc.) is NOT
|
|
11
|
+
* exercised here — that requires real fetch + localStorage backends and
|
|
12
|
+
* isn't what a "smoke" check should hang on. Constructor + a single
|
|
13
|
+
* shell-render pass is enough to prove the wiring compiles and runs.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { JSDOM } = require('jsdom');
|
|
17
|
+
|
|
18
|
+
const Chai = require('chai');
|
|
19
|
+
const Expect = Chai.expect;
|
|
20
|
+
|
|
21
|
+
const libPict = require('pict');
|
|
22
|
+
const libApp = require('../source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader.js');
|
|
23
|
+
|
|
24
|
+
suite('Comprehension Loader — bootstrap smoke', function ()
|
|
25
|
+
{
|
|
26
|
+
setup(function ()
|
|
27
|
+
{
|
|
28
|
+
// Each test gets its own fresh DOM. Multiple smoke-test files share
|
|
29
|
+
// the global namespace; setting up here (not at module load) means
|
|
30
|
+
// the DOM matches the suite that's currently running.
|
|
31
|
+
let tmpDOM = new JSDOM(
|
|
32
|
+
'<!doctype html><html><body>'
|
|
33
|
+
+ '<div id="ComprehensionLoader-Application-Container"></div>'
|
|
34
|
+
+ '<style id="PICT-CSS"></style>'
|
|
35
|
+
+ '</body></html>',
|
|
36
|
+
{ url: 'http://localhost/' });
|
|
37
|
+
global.window = tmpDOM.window;
|
|
38
|
+
global.document = tmpDOM.window.document;
|
|
39
|
+
global.localStorage = tmpDOM.window.localStorage;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('Application class loads as a function with a default_configuration', function ()
|
|
43
|
+
{
|
|
44
|
+
Expect(libApp).to.be.a('function');
|
|
45
|
+
Expect(libApp.default_configuration).to.be.an('object');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('Instantiation registers all theme + shell views without throwing', function ()
|
|
49
|
+
{
|
|
50
|
+
let tmpPict = new libPict({ LogStreams: [{ streamtype: 'null', level: 'error' }] });
|
|
51
|
+
let tmpInstance = new libApp(tmpPict, libApp.default_configuration);
|
|
52
|
+
Expect(tmpInstance).to.exist;
|
|
53
|
+
|
|
54
|
+
let tmpExpectedViews =
|
|
55
|
+
[
|
|
56
|
+
'Pict-Section-Modal',
|
|
57
|
+
'ComprehensionLoader-Layout',
|
|
58
|
+
'ComprehensionLoader-Session',
|
|
59
|
+
'ComprehensionLoader-Schema',
|
|
60
|
+
'ComprehensionLoader-Source',
|
|
61
|
+
'ComprehensionLoader-Load',
|
|
62
|
+
'ComprehensionLoader-StatusHistogram',
|
|
63
|
+
'ComprehensionLoader-Shell',
|
|
64
|
+
'ComprehensionLoader-TopBar-Nav',
|
|
65
|
+
'ComprehensionLoader-TopBar-User',
|
|
66
|
+
'ComprehensionLoader-StatusBar',
|
|
67
|
+
'ComprehensionLoader-StatusDetail',
|
|
68
|
+
'ComprehensionLoader-SettingsPanel'
|
|
69
|
+
];
|
|
70
|
+
for (let i = 0; i < tmpExpectedViews.length; i++)
|
|
71
|
+
{
|
|
72
|
+
Expect(tmpPict.views[tmpExpectedViews[i]],
|
|
73
|
+
'expected view ' + tmpExpectedViews[i] + ' to be registered').to.exist;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Expect(tmpPict.providers['Theme-Section'],
|
|
77
|
+
'Theme-Section provider should be registered').to.exist;
|
|
78
|
+
Expect(tmpPict.providers.ComprehensionLoader).to.exist;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('Shell view renders HTML into the application container', function ()
|
|
82
|
+
{
|
|
83
|
+
let tmpPict = new libPict({ LogStreams: [{ streamtype: 'null', level: 'error' }] });
|
|
84
|
+
new libApp(tmpPict, libApp.default_configuration);
|
|
85
|
+
|
|
86
|
+
// Render only the shell — avoids triggering the live-status poll setInterval.
|
|
87
|
+
tmpPict.views['ComprehensionLoader-Shell'].render();
|
|
88
|
+
|
|
89
|
+
let tmpContainer = global.document.getElementById('ComprehensionLoader-Application-Container');
|
|
90
|
+
Expect(tmpContainer).to.not.be.null;
|
|
91
|
+
Expect(tmpContainer.innerHTML.length).to.be.greaterThan(30,
|
|
92
|
+
'application container should have non-trivial HTML after shell render');
|
|
93
|
+
Expect(tmpContainer.innerHTML).to.include('ComprehensionLoader-Shell-Mount');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for DataCloner-Command-Sync runtime override application.
|
|
3
|
+
*
|
|
4
|
+
* Pure-function tests for `applyRuntimeSyncOverrides` and the
|
|
5
|
+
* `_RuntimeSyncProperties` coercion map. No server boot required.
|
|
6
|
+
*
|
|
7
|
+
* @license MIT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var Chai = require('chai');
|
|
11
|
+
var Expect = Chai.expect;
|
|
12
|
+
|
|
13
|
+
var libSyncCommand = require('../source/services/data-cloner/DataCloner-Command-Sync.js');
|
|
14
|
+
var _RuntimeSyncProperties = libSyncCommand._RuntimeSyncProperties;
|
|
15
|
+
var applyRuntimeSyncOverrides = libSyncCommand.applyRuntimeSyncOverrides;
|
|
16
|
+
|
|
17
|
+
// ---- Test helpers ----
|
|
18
|
+
|
|
19
|
+
var fMakeMeadowSync = (pEntityNames) =>
|
|
20
|
+
{
|
|
21
|
+
var tmpEntities = {};
|
|
22
|
+
for (var i = 0; i < pEntityNames.length; i++)
|
|
23
|
+
{
|
|
24
|
+
tmpEntities[pEntityNames[i]] = {};
|
|
25
|
+
}
|
|
26
|
+
// Orchestrator-level properties present here are the ones that get mirrored
|
|
27
|
+
// from the request body (BackSyncTimeLimit, DateTimePrecisionMS, TrueUpPageSize).
|
|
28
|
+
return {
|
|
29
|
+
BackSyncTimeLimit: 30000,
|
|
30
|
+
DateTimePrecisionMS: 1000,
|
|
31
|
+
TrueUpPageSize: 500,
|
|
32
|
+
MeadowSyncEntities: tmpEntities
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
var fMakeLog = () =>
|
|
37
|
+
{
|
|
38
|
+
var tmpEntries = [];
|
|
39
|
+
return {
|
|
40
|
+
info: (pMsg) => tmpEntries.push({ level: 'info', msg: pMsg }),
|
|
41
|
+
warn: (pMsg) => tmpEntries.push({ level: 'warn', msg: pMsg }),
|
|
42
|
+
Entries: tmpEntries,
|
|
43
|
+
InfosWithText: (pText) => tmpEntries.filter((p) => p.level === 'info' && p.msg.indexOf(pText) > -1),
|
|
44
|
+
Warns: () => tmpEntries.filter((p) => p.level === 'warn'),
|
|
45
|
+
Infos: () => tmpEntries.filter((p) => p.level === 'info')
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
suite
|
|
50
|
+
(
|
|
51
|
+
'DataCloner Runtime Sync Overrides',
|
|
52
|
+
function()
|
|
53
|
+
{
|
|
54
|
+
suite
|
|
55
|
+
(
|
|
56
|
+
'_RuntimeSyncProperties coercion map',
|
|
57
|
+
function()
|
|
58
|
+
{
|
|
59
|
+
test('BackSyncTimeLimit: accepts positive integer', function()
|
|
60
|
+
{
|
|
61
|
+
Expect(_RuntimeSyncProperties.BackSyncTimeLimit(60000)).to.equal(60000);
|
|
62
|
+
});
|
|
63
|
+
test('BackSyncTimeLimit: coerces numeric string', function()
|
|
64
|
+
{
|
|
65
|
+
Expect(_RuntimeSyncProperties.BackSyncTimeLimit('1800000')).to.equal(1800000);
|
|
66
|
+
});
|
|
67
|
+
test('BackSyncTimeLimit: rejects zero', function()
|
|
68
|
+
{
|
|
69
|
+
Expect(_RuntimeSyncProperties.BackSyncTimeLimit(0)).to.equal(null);
|
|
70
|
+
});
|
|
71
|
+
test('BackSyncTimeLimit: rejects negative', function()
|
|
72
|
+
{
|
|
73
|
+
Expect(_RuntimeSyncProperties.BackSyncTimeLimit(-100)).to.equal(null);
|
|
74
|
+
});
|
|
75
|
+
test('BackSyncTimeLimit: rejects non-numeric string', function()
|
|
76
|
+
{
|
|
77
|
+
Expect(_RuntimeSyncProperties.BackSyncTimeLimit('garbage')).to.equal(null);
|
|
78
|
+
});
|
|
79
|
+
test('MaxRecordsPerEntity: rejects zero (matches pre-refactor semantics)', function()
|
|
80
|
+
{
|
|
81
|
+
Expect(_RuntimeSyncProperties.MaxRecordsPerEntity(0)).to.equal(null);
|
|
82
|
+
});
|
|
83
|
+
test('MaxRecordsPerEntity: accepts positive', function()
|
|
84
|
+
{
|
|
85
|
+
Expect(_RuntimeSyncProperties.MaxRecordsPerEntity(500)).to.equal(500);
|
|
86
|
+
});
|
|
87
|
+
test('DateTimePrecisionMS: allows zero (any integer)', function()
|
|
88
|
+
{
|
|
89
|
+
Expect(_RuntimeSyncProperties.DateTimePrecisionMS(0)).to.equal(0);
|
|
90
|
+
});
|
|
91
|
+
test('DateTimePrecisionMS: rejects NaN', function()
|
|
92
|
+
{
|
|
93
|
+
Expect(_RuntimeSyncProperties.DateTimePrecisionMS('nope')).to.equal(null);
|
|
94
|
+
});
|
|
95
|
+
test('TrueUpPageSize: accepts positive integer', function()
|
|
96
|
+
{
|
|
97
|
+
Expect(_RuntimeSyncProperties.TrueUpPageSize(250)).to.equal(250);
|
|
98
|
+
});
|
|
99
|
+
test('UseAdvancedIDPagination: truthy → true', function()
|
|
100
|
+
{
|
|
101
|
+
Expect(_RuntimeSyncProperties.UseAdvancedIDPagination(1)).to.equal(true);
|
|
102
|
+
Expect(_RuntimeSyncProperties.UseAdvancedIDPagination('yes')).to.equal(true);
|
|
103
|
+
});
|
|
104
|
+
test('UseAdvancedIDPagination: falsy → false', function()
|
|
105
|
+
{
|
|
106
|
+
Expect(_RuntimeSyncProperties.UseAdvancedIDPagination(0)).to.equal(false);
|
|
107
|
+
Expect(_RuntimeSyncProperties.UseAdvancedIDPagination('')).to.equal(false);
|
|
108
|
+
Expect(_RuntimeSyncProperties.UseAdvancedIDPagination(null)).to.equal(false);
|
|
109
|
+
});
|
|
110
|
+
test('SyncDeletedRecords: coerces to boolean', function()
|
|
111
|
+
{
|
|
112
|
+
Expect(_RuntimeSyncProperties.SyncDeletedRecords(true)).to.equal(true);
|
|
113
|
+
Expect(_RuntimeSyncProperties.SyncDeletedRecords(false)).to.equal(false);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
suite
|
|
119
|
+
(
|
|
120
|
+
'applyRuntimeSyncOverrides — global (top-level) body keys',
|
|
121
|
+
function()
|
|
122
|
+
{
|
|
123
|
+
test('Empty body leaves everything alone', function()
|
|
124
|
+
{
|
|
125
|
+
var tmpSync = fMakeMeadowSync(['Document', 'Attachment']);
|
|
126
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
127
|
+
var tmpLog = fMakeLog();
|
|
128
|
+
applyRuntimeSyncOverrides({}, tmpSync, tmpClone, tmpLog);
|
|
129
|
+
Expect(tmpSync.BackSyncTimeLimit).to.equal(30000);
|
|
130
|
+
Expect(tmpSync.MeadowSyncEntities.Document).to.deep.equal({});
|
|
131
|
+
Expect(tmpSync.MeadowSyncEntities.Attachment).to.deep.equal({});
|
|
132
|
+
Expect(tmpClone.SyncDeletedRecords).to.equal(false);
|
|
133
|
+
Expect(tmpLog.Entries).to.have.lengthOf(0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('BackSyncTimeLimit mirrors to orchestrator and every entity', function()
|
|
137
|
+
{
|
|
138
|
+
var tmpSync = fMakeMeadowSync(['Document', 'Attachment']);
|
|
139
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
140
|
+
var tmpLog = fMakeLog();
|
|
141
|
+
applyRuntimeSyncOverrides({ BackSyncTimeLimit: 60000 }, tmpSync, tmpClone, tmpLog);
|
|
142
|
+
Expect(tmpSync.BackSyncTimeLimit).to.equal(60000);
|
|
143
|
+
Expect(tmpSync.MeadowSyncEntities.Document.BackSyncTimeLimit).to.equal(60000);
|
|
144
|
+
Expect(tmpSync.MeadowSyncEntities.Attachment.BackSyncTimeLimit).to.equal(60000);
|
|
145
|
+
// No per-entity overrides → no info log lines
|
|
146
|
+
Expect(tmpLog.Infos()).to.have.lengthOf(0);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('Numeric-string BackSyncTimeLimit is coerced to integer', function()
|
|
150
|
+
{
|
|
151
|
+
var tmpSync = fMakeMeadowSync(['Document']);
|
|
152
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
153
|
+
var tmpLog = fMakeLog();
|
|
154
|
+
applyRuntimeSyncOverrides({ BackSyncTimeLimit: '1800000' }, tmpSync, tmpClone, tmpLog);
|
|
155
|
+
Expect(tmpSync.MeadowSyncEntities.Document.BackSyncTimeLimit).to.equal(1800000);
|
|
156
|
+
Expect(typeof tmpSync.MeadowSyncEntities.Document.BackSyncTimeLimit).to.equal('number');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('Invalid BackSyncTimeLimit leaves everything unchanged', function()
|
|
160
|
+
{
|
|
161
|
+
var tmpSync = fMakeMeadowSync(['Document']);
|
|
162
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
163
|
+
var tmpLog = fMakeLog();
|
|
164
|
+
applyRuntimeSyncOverrides({ BackSyncTimeLimit: 'oops' }, tmpSync, tmpClone, tmpLog);
|
|
165
|
+
Expect(tmpSync.BackSyncTimeLimit).to.equal(30000); // original
|
|
166
|
+
Expect(tmpSync.MeadowSyncEntities.Document).to.deep.equal({});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('SyncDeletedRecords mirrors to cloneState and entities', function()
|
|
170
|
+
{
|
|
171
|
+
var tmpSync = fMakeMeadowSync(['Document', 'Attachment']);
|
|
172
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
173
|
+
var tmpLog = fMakeLog();
|
|
174
|
+
applyRuntimeSyncOverrides({ SyncDeletedRecords: true }, tmpSync, tmpClone, tmpLog);
|
|
175
|
+
Expect(tmpClone.SyncDeletedRecords).to.equal(true);
|
|
176
|
+
Expect(tmpSync.MeadowSyncEntities.Document.SyncDeletedRecords).to.equal(true);
|
|
177
|
+
Expect(tmpSync.MeadowSyncEntities.Attachment.SyncDeletedRecords).to.equal(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('SyncDeletedRecords explicit false is honored', function()
|
|
181
|
+
{
|
|
182
|
+
var tmpSync = fMakeMeadowSync(['Document']);
|
|
183
|
+
tmpSync.MeadowSyncEntities.Document.SyncDeletedRecords = true;
|
|
184
|
+
var tmpClone = { SyncDeletedRecords: true };
|
|
185
|
+
var tmpLog = fMakeLog();
|
|
186
|
+
applyRuntimeSyncOverrides({ SyncDeletedRecords: false }, tmpSync, tmpClone, tmpLog);
|
|
187
|
+
Expect(tmpClone.SyncDeletedRecords).to.equal(false);
|
|
188
|
+
Expect(tmpSync.MeadowSyncEntities.Document.SyncDeletedRecords).to.equal(false);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('Orchestrator mirror only fires for keys present on orchestrator', function()
|
|
192
|
+
{
|
|
193
|
+
// UseAdvancedIDPagination is NOT a property the orchestrator carries
|
|
194
|
+
// — it only flows to entities. Verify we don't accidentally add it
|
|
195
|
+
// to the orchestrator.
|
|
196
|
+
var tmpSync = fMakeMeadowSync(['Document']);
|
|
197
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
198
|
+
var tmpLog = fMakeLog();
|
|
199
|
+
applyRuntimeSyncOverrides({ UseAdvancedIDPagination: true }, tmpSync, tmpClone, tmpLog);
|
|
200
|
+
Expect(tmpSync.hasOwnProperty('UseAdvancedIDPagination')).to.equal(false);
|
|
201
|
+
Expect(tmpSync.MeadowSyncEntities.Document.UseAdvancedIDPagination).to.equal(true);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
suite
|
|
207
|
+
(
|
|
208
|
+
'applyRuntimeSyncOverrides — per-entity SyncEntityOptions',
|
|
209
|
+
function()
|
|
210
|
+
{
|
|
211
|
+
test('Per-entity override targets only the named entity', function()
|
|
212
|
+
{
|
|
213
|
+
var tmpSync = fMakeMeadowSync(['Document', 'Attachment']);
|
|
214
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
215
|
+
var tmpLog = fMakeLog();
|
|
216
|
+
applyRuntimeSyncOverrides({
|
|
217
|
+
SyncEntityOptions: { Document: { BackSyncTimeLimit: 600000 } }
|
|
218
|
+
}, tmpSync, tmpClone, tmpLog);
|
|
219
|
+
Expect(tmpSync.MeadowSyncEntities.Document.BackSyncTimeLimit).to.equal(600000);
|
|
220
|
+
Expect(tmpSync.MeadowSyncEntities.Attachment.BackSyncTimeLimit).to.equal(undefined);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('Per-entity override emits one info log per entity with applied keys', function()
|
|
224
|
+
{
|
|
225
|
+
var tmpSync = fMakeMeadowSync(['Document']);
|
|
226
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
227
|
+
var tmpLog = fMakeLog();
|
|
228
|
+
applyRuntimeSyncOverrides({
|
|
229
|
+
SyncEntityOptions: { Document: { BackSyncTimeLimit: 600000 } }
|
|
230
|
+
}, tmpSync, tmpClone, tmpLog);
|
|
231
|
+
var tmpInfo = tmpLog.Infos();
|
|
232
|
+
Expect(tmpInfo).to.have.lengthOf(1);
|
|
233
|
+
Expect(tmpInfo[0].msg).to.include('Document');
|
|
234
|
+
Expect(tmpInfo[0].msg).to.include('BackSyncTimeLimit=600000');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('Per-entity wins over global', function()
|
|
238
|
+
{
|
|
239
|
+
var tmpSync = fMakeMeadowSync(['Document', 'Attachment']);
|
|
240
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
241
|
+
var tmpLog = fMakeLog();
|
|
242
|
+
applyRuntimeSyncOverrides({
|
|
243
|
+
BackSyncTimeLimit: 30000,
|
|
244
|
+
SyncEntityOptions: { Document: { BackSyncTimeLimit: 600000 } }
|
|
245
|
+
}, tmpSync, tmpClone, tmpLog);
|
|
246
|
+
Expect(tmpSync.MeadowSyncEntities.Document.BackSyncTimeLimit).to.equal(600000);
|
|
247
|
+
Expect(tmpSync.MeadowSyncEntities.Attachment.BackSyncTimeLimit).to.equal(30000);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('Multiple per-entity overrides apply independently', function()
|
|
251
|
+
{
|
|
252
|
+
var tmpSync = fMakeMeadowSync(['Document', 'Attachment', 'Note']);
|
|
253
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
254
|
+
var tmpLog = fMakeLog();
|
|
255
|
+
applyRuntimeSyncOverrides({
|
|
256
|
+
BackSyncTimeLimit: 30000,
|
|
257
|
+
SyncEntityOptions: {
|
|
258
|
+
Document: { BackSyncTimeLimit: 600000 },
|
|
259
|
+
Attachment: { BackSyncTimeLimit: 120000 }
|
|
260
|
+
}
|
|
261
|
+
}, tmpSync, tmpClone, tmpLog);
|
|
262
|
+
Expect(tmpSync.MeadowSyncEntities.Document.BackSyncTimeLimit).to.equal(600000);
|
|
263
|
+
Expect(tmpSync.MeadowSyncEntities.Attachment.BackSyncTimeLimit).to.equal(120000);
|
|
264
|
+
Expect(tmpSync.MeadowSyncEntities.Note.BackSyncTimeLimit).to.equal(30000);
|
|
265
|
+
Expect(tmpLog.Infos()).to.have.lengthOf(2);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('Unknown entity name produces a warn, not a throw', function()
|
|
269
|
+
{
|
|
270
|
+
var tmpSync = fMakeMeadowSync(['Document']);
|
|
271
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
272
|
+
var tmpLog = fMakeLog();
|
|
273
|
+
applyRuntimeSyncOverrides({
|
|
274
|
+
SyncEntityOptions: { ImaginaryTable: { BackSyncTimeLimit: 60000 } }
|
|
275
|
+
}, tmpSync, tmpClone, tmpLog);
|
|
276
|
+
var tmpWarn = tmpLog.Warns();
|
|
277
|
+
Expect(tmpWarn).to.have.lengthOf(1);
|
|
278
|
+
Expect(tmpWarn[0].msg).to.include('ImaginaryTable');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('Unknown property inside SyncEntityOptions[entity] is ignored', function()
|
|
282
|
+
{
|
|
283
|
+
var tmpSync = fMakeMeadowSync(['Document']);
|
|
284
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
285
|
+
var tmpLog = fMakeLog();
|
|
286
|
+
applyRuntimeSyncOverrides({
|
|
287
|
+
SyncEntityOptions: { Document: { GarbageKey: 'whatever', BackSyncTimeLimit: 60000 } }
|
|
288
|
+
}, tmpSync, tmpClone, tmpLog);
|
|
289
|
+
Expect(tmpSync.MeadowSyncEntities.Document.BackSyncTimeLimit).to.equal(60000);
|
|
290
|
+
Expect(tmpSync.MeadowSyncEntities.Document.GarbageKey).to.equal(undefined);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('Per-entity override with no valid keys produces no log line', function()
|
|
294
|
+
{
|
|
295
|
+
var tmpSync = fMakeMeadowSync(['Document']);
|
|
296
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
297
|
+
var tmpLog = fMakeLog();
|
|
298
|
+
applyRuntimeSyncOverrides({
|
|
299
|
+
SyncEntityOptions: { Document: { BackSyncTimeLimit: 'garbage' } }
|
|
300
|
+
}, tmpSync, tmpClone, tmpLog);
|
|
301
|
+
Expect(tmpLog.Infos()).to.have.lengthOf(0);
|
|
302
|
+
Expect(tmpSync.MeadowSyncEntities.Document.BackSyncTimeLimit).to.equal(undefined);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('Non-object SyncEntityOptions is ignored (no throw)', function()
|
|
306
|
+
{
|
|
307
|
+
var tmpSync = fMakeMeadowSync(['Document']);
|
|
308
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
309
|
+
var tmpLog = fMakeLog();
|
|
310
|
+
applyRuntimeSyncOverrides({
|
|
311
|
+
SyncEntityOptions: 'oops',
|
|
312
|
+
BackSyncTimeLimit: 60000
|
|
313
|
+
}, tmpSync, tmpClone, tmpLog);
|
|
314
|
+
Expect(tmpSync.MeadowSyncEntities.Document.BackSyncTimeLimit).to.equal(60000);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test('Null SyncEntityOptions is ignored (no throw)', function()
|
|
318
|
+
{
|
|
319
|
+
var tmpSync = fMakeMeadowSync(['Document']);
|
|
320
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
321
|
+
var tmpLog = fMakeLog();
|
|
322
|
+
applyRuntimeSyncOverrides({ SyncEntityOptions: null, BackSyncTimeLimit: 60000 }, tmpSync, tmpClone, tmpLog);
|
|
323
|
+
Expect(tmpSync.MeadowSyncEntities.Document.BackSyncTimeLimit).to.equal(60000);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('Per-entity SyncDeletedRecords does NOT mirror to cloneState', function()
|
|
327
|
+
{
|
|
328
|
+
// Per-entity overrides target a specific entity; cloneState is a
|
|
329
|
+
// cross-cutting "is delete-sync enabled at all?" flag and should
|
|
330
|
+
// only be mirrored from the global value.
|
|
331
|
+
var tmpSync = fMakeMeadowSync(['Document', 'Attachment']);
|
|
332
|
+
var tmpClone = { SyncDeletedRecords: false };
|
|
333
|
+
var tmpLog = fMakeLog();
|
|
334
|
+
applyRuntimeSyncOverrides({
|
|
335
|
+
SyncEntityOptions: { Document: { SyncDeletedRecords: true } }
|
|
336
|
+
}, tmpSync, tmpClone, tmpLog);
|
|
337
|
+
Expect(tmpClone.SyncDeletedRecords).to.equal(false);
|
|
338
|
+
Expect(tmpSync.MeadowSyncEntities.Document.SyncDeletedRecords).to.equal(true);
|
|
339
|
+
Expect(tmpSync.MeadowSyncEntities.Attachment.SyncDeletedRecords).to.equal(undefined);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DataCloner bootstrap smoke tests — parallel to the ComprehensionLoader
|
|
5
|
+
* smoke suite. See that file for rationale.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { JSDOM } = require('jsdom');
|
|
9
|
+
|
|
10
|
+
const Chai = require('chai');
|
|
11
|
+
const Expect = Chai.expect;
|
|
12
|
+
|
|
13
|
+
const libPict = require('pict');
|
|
14
|
+
const libApp = require('../source/services/data-cloner/pict-app/Pict-Application-DataCloner.js');
|
|
15
|
+
|
|
16
|
+
suite('Data Cloner — bootstrap smoke', function ()
|
|
17
|
+
{
|
|
18
|
+
setup(function ()
|
|
19
|
+
{
|
|
20
|
+
let tmpDOM = new JSDOM(
|
|
21
|
+
'<!doctype html><html><body>'
|
|
22
|
+
+ '<div id="DataCloner-Application-Container"></div>'
|
|
23
|
+
+ '<style id="PICT-CSS"></style>'
|
|
24
|
+
+ '</body></html>',
|
|
25
|
+
{ url: 'http://localhost/' });
|
|
26
|
+
global.window = tmpDOM.window;
|
|
27
|
+
global.document = tmpDOM.window.document;
|
|
28
|
+
global.localStorage = tmpDOM.window.localStorage;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('Application class loads as a function with a default_configuration', function ()
|
|
32
|
+
{
|
|
33
|
+
Expect(libApp).to.be.a('function');
|
|
34
|
+
Expect(libApp.default_configuration).to.be.an('object');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('Instantiation registers all theme + shell views without throwing', function ()
|
|
38
|
+
{
|
|
39
|
+
let tmpPict = new libPict({ LogStreams: [{ streamtype: 'null', level: 'error' }] });
|
|
40
|
+
let tmpInstance = new libApp(tmpPict, libApp.default_configuration);
|
|
41
|
+
Expect(tmpInstance).to.exist;
|
|
42
|
+
|
|
43
|
+
let tmpExpectedViews =
|
|
44
|
+
[
|
|
45
|
+
'Pict-Section-Modal',
|
|
46
|
+
'DataCloner-Layout',
|
|
47
|
+
'DataCloner-Connection',
|
|
48
|
+
'DataCloner-Session',
|
|
49
|
+
'DataCloner-Schema',
|
|
50
|
+
'DataCloner-Deploy',
|
|
51
|
+
'DataCloner-Sync',
|
|
52
|
+
'DataCloner-Export',
|
|
53
|
+
'DataCloner-ViewData',
|
|
54
|
+
'DataCloner-StatusHistogram',
|
|
55
|
+
'PictSection-ConnectionForm',
|
|
56
|
+
'DataCloner-Shell',
|
|
57
|
+
'DataCloner-TopBar-Nav',
|
|
58
|
+
'DataCloner-TopBar-User',
|
|
59
|
+
'DataCloner-StatusBar',
|
|
60
|
+
'DataCloner-StatusDetail',
|
|
61
|
+
'DataCloner-SettingsPanel'
|
|
62
|
+
];
|
|
63
|
+
for (let i = 0; i < tmpExpectedViews.length; i++)
|
|
64
|
+
{
|
|
65
|
+
Expect(tmpPict.views[tmpExpectedViews[i]],
|
|
66
|
+
'expected view ' + tmpExpectedViews[i] + ' to be registered').to.exist;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
Expect(tmpPict.providers['Theme-Section'],
|
|
70
|
+
'Theme-Section provider should be registered').to.exist;
|
|
71
|
+
Expect(tmpPict.providers.DataCloner).to.exist;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('Shell view renders HTML into the application container', function ()
|
|
75
|
+
{
|
|
76
|
+
let tmpPict = new libPict({ LogStreams: [{ streamtype: 'null', level: 'error' }] });
|
|
77
|
+
new libApp(tmpPict, libApp.default_configuration);
|
|
78
|
+
|
|
79
|
+
tmpPict.views['DataCloner-Shell'].render();
|
|
80
|
+
|
|
81
|
+
let tmpContainer = global.document.getElementById('DataCloner-Application-Container');
|
|
82
|
+
Expect(tmpContainer).to.not.be.null;
|
|
83
|
+
Expect(tmpContainer.innerHTML.length).to.be.greaterThan(30,
|
|
84
|
+
'application container should have non-trivial HTML after shell render');
|
|
85
|
+
Expect(tmpContainer.innerHTML).to.include('DataCloner-Shell-Mount');
|
|
86
|
+
});
|
|
87
|
+
});
|