jsgui3-server 0.0.147 → 0.0.149
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/.github/workflows/control-scan-manifest-check.yml +31 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +44 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +45 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +42 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +43 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +41 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +44 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f9dee4ec18a96e09bee06bae.js +39 -0
- package/README.md +85 -3
- package/admin-ui/client.js +213 -0
- package/admin-ui/server.js +104 -0
- package/client/controls/auto-observable.js +207 -0
- package/dev-status.svg +139 -0
- package/docs/api-reference.md +301 -43
- package/docs/books/admin-ui/01-introduction.md +32 -0
- package/docs/books/admin-ui/02-architecture.md +92 -0
- package/docs/books/admin-ui/03-controls.md +194 -0
- package/docs/books/admin-ui/04-implementation-plan.md +62 -0
- package/docs/books/admin-ui/README.md +26 -0
- package/docs/books/jsgui3-bundling-research-book/00-table-of-contents.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/01-pipeline-and-runtime-semantics.md +34 -0
- package/docs/books/jsgui3-bundling-research-book/02-javascript-bundling-core.md +36 -0
- package/docs/books/jsgui3-bundling-research-book/03-style-extraction-and-css-compilation.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/04-static-publishing-and-delivery.md +39 -0
- package/docs/books/jsgui3-bundling-research-book/05-current-limits-and-size-bloat-vectors.md +25 -0
- package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +77 -0
- package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +63 -0
- package/docs/books/jsgui3-bundling-research-book/08-test-and-verification-methodology.md +43 -0
- package/docs/books/jsgui3-bundling-research-book/09-roadmap-and-rollout.md +42 -0
- package/docs/books/jsgui3-bundling-research-book/10-further-research-strategies-and-upgrades.md +211 -0
- package/docs/books/jsgui3-bundling-research-book/README.md +35 -0
- package/docs/bundling-system-deep-dive.md +9 -4
- package/docs/comprehensive-documentation.md +49 -18
- package/docs/configuration-reference.md +152 -27
- package/docs/core/README.md +19 -0
- package/docs/core/jsgui3-server-core-book/00-table-of-contents.md +21 -0
- package/docs/core/jsgui3-server-core-book/01-startup-readiness-state-machine.md +41 -0
- package/docs/core/jsgui3-server-core-book/02-resource-abstraction-and-lifecycle.md +92 -0
- package/docs/core/jsgui3-server-core-book/03-resource-pool-and-event-topology.md +47 -0
- package/docs/core/jsgui3-server-core-book/04-sse-publisher-semantics.md +41 -0
- package/docs/core/jsgui3-server-core-book/05-serve-factory-resource-wiring.md +46 -0
- package/docs/core/jsgui3-server-core-book/06-e2e-testing-methodology.md +48 -0
- package/docs/core/jsgui3-server-core-book/07-defect-detection-and-hardening-loop.md +47 -0
- package/docs/publishers-guide.md +59 -4
- package/docs/resources-guide.md +184 -35
- package/docs/simple-server-api-design.md +72 -17
- package/docs/system-architecture.md +18 -14
- package/examples/controls/15) window, observable SSE/server.js +6 -1
- package/examples/controls/19) window, auto observable ui/client.js +125 -0
- package/examples/controls/19) window, auto observable ui/server.js +73 -0
- package/examples/controls/20) window, task manager app/README.md +133 -0
- package/examples/controls/20) window, task manager app/client.js +797 -0
- package/examples/controls/20) window, task manager app/server.js +178 -0
- package/examples/controls/6) window, color_palette/client.js +165 -68
- package/examples/controls/9) window, date picker/client.js +362 -76
- package/examples/controls/9b) window, shared data.model mirrored date pickers/client.js +104 -83
- package/examples/jsgui3-html/06) theming/client.js +22 -1
- package/examples/jsgui3-html/10) binding-debugger/client.js +137 -1
- package/http/responders/static/Static_Route_HTTP_Responder.js +52 -34
- package/lab/experiments/capture-color-controls.js +196 -0
- package/lab/results/screenshots/color-controls/full_page.png +0 -0
- package/lab/results/screenshots/color-controls/section_1_color_grid_12x12.png +0 -0
- package/lab/results/screenshots/color-controls/section_2_color_grid_4x2.png +0 -0
- package/lab/results/screenshots/color-controls/section_3_color_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_4_palette_comparison.png +0 -0
- package/lab/results/screenshots/color-controls/section_5_raw_swatches.png +0 -0
- package/lab/results/screenshots/color-controls/section_6_optimized_crayola.png +0 -0
- package/lab/results/screenshots/color-controls/section_7_pastel_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_8_extended_144.png +0 -0
- package/lab/screenshot-utils.js +248 -0
- package/module.js +11 -4
- package/package.json +14 -4
- package/publishers/Publishers.js +4 -3
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +5 -5
- package/publishers/http-observable-publisher.js +8 -0
- package/publishers/http-sse-publisher.js +341 -0
- package/publishers/http-webpage-publisher.js +13 -3
- package/publishers/http-webpageorsite-publisher.js +18 -0
- package/resources/process-resource.js +950 -0
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +164 -46
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +18 -7
- package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +829 -0
- package/resources/remote-process-resource.js +355 -0
- package/resources/server-resource-pool.js +354 -41
- package/serve-factory.js +441 -259
- package/server.js +161 -16
- package/tests/README.md +66 -4
- package/tests/admin-ui-render.test.js +24 -0
- package/tests/assigners.test.js +56 -40
- package/tests/bundling-default-control-elimination.puppeteer.test.js +260 -0
- package/tests/configuration-validation.test.js +21 -18
- package/tests/content-analysis.test.js +7 -6
- package/tests/control-optimizer-cache-behavior.test.js +52 -0
- package/tests/control-scan-manifest-regression.test.js +144 -0
- package/tests/end-to-end.test.js +15 -14
- package/tests/error-handling.test.js +222 -179
- package/tests/fixtures/bundling-default-button-client.js +37 -0
- package/tests/fixtures/bundling-default-window-client.js +34 -0
- package/tests/fixtures/control_scan_manifest_expectations.json +48 -0
- package/tests/fixtures/resource-monitor-client.js +319 -0
- package/tests/helpers/puppeteer-e2e-harness.js +317 -0
- package/tests/http-sse-publisher.test.js +136 -0
- package/tests/performance.test.js +69 -65
- package/tests/process-resource.test.js +138 -0
- package/tests/publishers.test.js +7 -7
- package/tests/remote-process-resource.test.js +160 -0
- package/tests/sass-controls.e2e.test.js +7 -1
- package/tests/serve-resources.test.js +270 -0
- package/tests/serve.test.js +120 -50
- package/tests/server-resource-pool.test.js +106 -0
- package/tests/small-controls-bundle-size.test.js +252 -0
- package/tests/test-runner.js +13 -1
- package/tests/window-examples.puppeteer.test.js +204 -1
- package/tests/window-resource-integration.puppeteer.test.js +585 -0
- package/tests/temp_invalid.js +0 -7
- package/tests/temp_invalid_utf8.js +0 -1
- package/tests/temp_malformed.js +0 -10
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const { describe, it, afterEach, after } = require('mocha');
|
|
4
|
+
const EventEmitter = require('events');
|
|
5
|
+
const { Resource } = require('jsgui3-html');
|
|
6
|
+
|
|
7
|
+
const fake_webpage_publisher_path = require.resolve('../publishers/http-webpage-publisher');
|
|
8
|
+
const fake_website_publisher_path = require.resolve('../publishers/http-website-publisher');
|
|
9
|
+
const original_webpage_publisher_module = require.cache[fake_webpage_publisher_path];
|
|
10
|
+
const original_website_publisher_module = require.cache[fake_website_publisher_path];
|
|
11
|
+
|
|
12
|
+
class Fake_Publisher_Base extends EventEmitter {
|
|
13
|
+
constructor(html_route, html_body) {
|
|
14
|
+
super();
|
|
15
|
+
const body_buffer = Buffer.from(html_body, 'utf8');
|
|
16
|
+
this.route = html_route;
|
|
17
|
+
this.response_headers = {
|
|
18
|
+
identity: {
|
|
19
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
20
|
+
'Content-Length': body_buffer.length
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
this.response_buffers = {
|
|
24
|
+
identity: body_buffer
|
|
25
|
+
};
|
|
26
|
+
this.type = 'html';
|
|
27
|
+
this.extension = 'html';
|
|
28
|
+
|
|
29
|
+
setImmediate(() => {
|
|
30
|
+
this.emit('ready', {
|
|
31
|
+
_arr: [{
|
|
32
|
+
type: this.type,
|
|
33
|
+
extension: this.extension,
|
|
34
|
+
route: this.route,
|
|
35
|
+
response_headers: this.response_headers,
|
|
36
|
+
response_buffers: this.response_buffers
|
|
37
|
+
}]
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
handle_http(req, res) {
|
|
43
|
+
const body_buffer = this.response_buffers.identity;
|
|
44
|
+
res.writeHead(200, this.response_headers.identity);
|
|
45
|
+
res.end(body_buffer);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
meets_requirements() {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
start(callback) {
|
|
53
|
+
if (typeof callback === 'function') {
|
|
54
|
+
callback(null, true);
|
|
55
|
+
}
|
|
56
|
+
return Promise.resolve(true);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
stop(callback) {
|
|
60
|
+
if (typeof callback === 'function') {
|
|
61
|
+
callback(null, true);
|
|
62
|
+
}
|
|
63
|
+
return Promise.resolve(true);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class Fake_Webpage_Publisher extends Fake_Publisher_Base {
|
|
68
|
+
constructor(options = {}) {
|
|
69
|
+
const webpage = options.webpage || {};
|
|
70
|
+
const route = webpage.path || '/';
|
|
71
|
+
const title = webpage.title || webpage.name || 'Fake Page';
|
|
72
|
+
const html_body = `<html><head><title>${title}</title></head><body>${route}</body></html>`;
|
|
73
|
+
super(route, html_body);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class Fake_Website_Publisher extends Fake_Publisher_Base {
|
|
78
|
+
constructor(options = {}) {
|
|
79
|
+
const title = (options.website && options.website.name) || 'Fake Site';
|
|
80
|
+
const html_body = `<html><head><title>${title}</title></head><body>site</body></html>`;
|
|
81
|
+
super('/*', html_body);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
require.cache[fake_webpage_publisher_path] = { exports: Fake_Webpage_Publisher };
|
|
86
|
+
require.cache[fake_website_publisher_path] = { exports: Fake_Website_Publisher };
|
|
87
|
+
|
|
88
|
+
const Server = require('../server');
|
|
89
|
+
const { get_free_port } = require('../port-utils');
|
|
90
|
+
|
|
91
|
+
class In_Process_Test_Resource extends Resource {
|
|
92
|
+
constructor(spec = {}) {
|
|
93
|
+
super(spec);
|
|
94
|
+
this.current_state = 'stopped';
|
|
95
|
+
this.start_calls = 0;
|
|
96
|
+
this.stop_calls = 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
start(callback) {
|
|
100
|
+
this.start_calls += 1;
|
|
101
|
+
const previous_state = this.current_state;
|
|
102
|
+
this.current_state = 'running';
|
|
103
|
+
this.raise('state_change', {
|
|
104
|
+
from: previous_state,
|
|
105
|
+
to: this.current_state,
|
|
106
|
+
timestamp: Date.now()
|
|
107
|
+
});
|
|
108
|
+
if (typeof callback === 'function') callback(null, true);
|
|
109
|
+
return Promise.resolve(true);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
stop(callback) {
|
|
113
|
+
this.stop_calls += 1;
|
|
114
|
+
const previous_state = this.current_state;
|
|
115
|
+
this.current_state = 'stopped';
|
|
116
|
+
this.raise('state_change', {
|
|
117
|
+
from: previous_state,
|
|
118
|
+
to: this.current_state,
|
|
119
|
+
timestamp: Date.now()
|
|
120
|
+
});
|
|
121
|
+
if (typeof callback === 'function') callback(null, true);
|
|
122
|
+
return Promise.resolve(true);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get status() {
|
|
126
|
+
return {
|
|
127
|
+
state: this.current_state
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
get_abstract() {
|
|
132
|
+
return {
|
|
133
|
+
name: this.name,
|
|
134
|
+
state: this.current_state
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const get_http_response = (port, path_name) => {
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
const request = http.get({
|
|
142
|
+
hostname: '127.0.0.1',
|
|
143
|
+
port,
|
|
144
|
+
path: path_name
|
|
145
|
+
}, (response) => {
|
|
146
|
+
let body_text = '';
|
|
147
|
+
response.setEncoding('utf8');
|
|
148
|
+
response.on('data', (chunk) => {
|
|
149
|
+
body_text += chunk;
|
|
150
|
+
});
|
|
151
|
+
response.on('end', () => {
|
|
152
|
+
resolve({
|
|
153
|
+
response,
|
|
154
|
+
body_text
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
request.on('error', reject);
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
describe('Server.serve resources integration', function() {
|
|
163
|
+
this.timeout(20000);
|
|
164
|
+
|
|
165
|
+
const started_servers = [];
|
|
166
|
+
|
|
167
|
+
after(() => {
|
|
168
|
+
if (original_webpage_publisher_module) {
|
|
169
|
+
require.cache[fake_webpage_publisher_path] = original_webpage_publisher_module;
|
|
170
|
+
} else {
|
|
171
|
+
delete require.cache[fake_webpage_publisher_path];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (original_website_publisher_module) {
|
|
175
|
+
require.cache[fake_website_publisher_path] = original_website_publisher_module;
|
|
176
|
+
} else {
|
|
177
|
+
delete require.cache[fake_website_publisher_path];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
delete require.cache[require.resolve('../server')];
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
afterEach(async () => {
|
|
184
|
+
while (started_servers.length > 0) {
|
|
185
|
+
const server_instance = started_servers.pop();
|
|
186
|
+
await new Promise((resolve) => {
|
|
187
|
+
server_instance.close(() => resolve());
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('supports in-process resource instances and stops them on server close', async () => {
|
|
193
|
+
const port = await get_free_port();
|
|
194
|
+
const in_process_resource = new In_Process_Test_Resource({
|
|
195
|
+
name: 'in_process_resource'
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const server_instance = await Server.serve({
|
|
199
|
+
host: '127.0.0.1',
|
|
200
|
+
port,
|
|
201
|
+
api: {
|
|
202
|
+
ping: () => 'pong'
|
|
203
|
+
},
|
|
204
|
+
resources: {
|
|
205
|
+
in_process_resource
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
started_servers.push(server_instance);
|
|
209
|
+
|
|
210
|
+
assert.strictEqual(in_process_resource.start_calls, 1);
|
|
211
|
+
assert.strictEqual(in_process_resource.status.state, 'running');
|
|
212
|
+
|
|
213
|
+
const { response, body_text } = await get_http_response(port, '/api/ping');
|
|
214
|
+
assert.strictEqual(response.statusCode, 200);
|
|
215
|
+
assert.strictEqual(body_text, 'pong');
|
|
216
|
+
|
|
217
|
+
await new Promise((resolve) => server_instance.close(() => resolve()));
|
|
218
|
+
started_servers.pop();
|
|
219
|
+
|
|
220
|
+
assert.strictEqual(in_process_resource.stop_calls >= 1, true);
|
|
221
|
+
assert.strictEqual(in_process_resource.status.state, 'stopped');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('supports direct process resources with consistent status API', async () => {
|
|
225
|
+
const port = await get_free_port();
|
|
226
|
+
let server_instance = null;
|
|
227
|
+
|
|
228
|
+
server_instance = await Server.serve({
|
|
229
|
+
host: '127.0.0.1',
|
|
230
|
+
port,
|
|
231
|
+
api: {
|
|
232
|
+
process_status: () => {
|
|
233
|
+
const process_resource = server_instance.resource_pool.get_resource('worker_process');
|
|
234
|
+
return process_resource ? process_resource.status : null;
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
resources: {
|
|
238
|
+
worker_process: {
|
|
239
|
+
type: 'process',
|
|
240
|
+
command: process.execPath,
|
|
241
|
+
args: ['-e', 'setInterval(() => {}, 1000);']
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
started_servers.push(server_instance);
|
|
246
|
+
|
|
247
|
+
const process_resource = server_instance.resource_pool.get_resource('worker_process');
|
|
248
|
+
assert(process_resource, 'Expected worker_process resource in the pool');
|
|
249
|
+
|
|
250
|
+
const process_status = process_resource.status;
|
|
251
|
+
assert.strictEqual(process_status.state, 'running');
|
|
252
|
+
assert.strictEqual(typeof process_status.processManager.type, 'string');
|
|
253
|
+
assert.strictEqual(process_status.processManager.type, 'direct');
|
|
254
|
+
assert(Number.isFinite(process_status.pid), 'Expected running process PID');
|
|
255
|
+
|
|
256
|
+
const { response, body_text } = await get_http_response(port, '/api/process_status');
|
|
257
|
+
assert.strictEqual(response.statusCode, 200);
|
|
258
|
+
|
|
259
|
+
const parsed_status = JSON.parse(body_text);
|
|
260
|
+
assert.strictEqual(parsed_status.state, 'running');
|
|
261
|
+
assert.strictEqual(typeof parsed_status.restartCount, 'number');
|
|
262
|
+
assert('memoryUsage' in parsed_status);
|
|
263
|
+
assert('lastHealthCheck' in parsed_status);
|
|
264
|
+
|
|
265
|
+
await new Promise((resolve) => server_instance.close(() => resolve()));
|
|
266
|
+
started_servers.pop();
|
|
267
|
+
|
|
268
|
+
assert.strictEqual(process_resource.status.state, 'stopped');
|
|
269
|
+
});
|
|
270
|
+
});
|
package/tests/serve.test.js
CHANGED
|
@@ -6,12 +6,14 @@ const EventEmitter = require('events');
|
|
|
6
6
|
|
|
7
7
|
const dummy_client_path = require.resolve('./dummy-client.js');
|
|
8
8
|
|
|
9
|
-
const fake_webpage_publisher_path = require.resolve('../publishers/http-webpage-publisher');
|
|
10
|
-
const fake_website_publisher_path = require.resolve('../publishers/http-website-publisher');
|
|
9
|
+
const fake_webpage_publisher_path = require.resolve('../publishers/http-webpage-publisher');
|
|
10
|
+
const fake_website_publisher_path = require.resolve('../publishers/http-website-publisher');
|
|
11
|
+
const original_webpage_publisher_module = require.cache[fake_webpage_publisher_path];
|
|
12
|
+
const original_website_publisher_module = require.cache[fake_website_publisher_path];
|
|
11
13
|
|
|
12
|
-
class Fake_Publisher_Base extends EventEmitter {
|
|
13
|
-
constructor(html_route, html_body) {
|
|
14
|
-
super();
|
|
14
|
+
class Fake_Publisher_Base extends EventEmitter {
|
|
15
|
+
constructor(html_route, html_body) {
|
|
16
|
+
super();
|
|
15
17
|
this.html_route = html_route;
|
|
16
18
|
this.html_body = html_body;
|
|
17
19
|
const buffer = Buffer.from(this.html_body, 'utf8');
|
|
@@ -40,27 +42,48 @@ class Fake_Publisher_Base extends EventEmitter {
|
|
|
40
42
|
};
|
|
41
43
|
this.type = 'html';
|
|
42
44
|
this.extension = 'html';
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
const ready_delay_ms = Number(this.constructor.ready_delay_ms) || 0;
|
|
46
|
+
const emit_ready = () => {
|
|
47
|
+
this.emit('ready', {
|
|
48
|
+
_arr: [{
|
|
49
|
+
type: this.type,
|
|
50
|
+
extension: this.extension,
|
|
51
|
+
route: this.route,
|
|
52
|
+
response_headers: this.response_headers,
|
|
53
|
+
response_buffers: this.response_buffers
|
|
54
|
+
}]
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (ready_delay_ms > 0) {
|
|
59
|
+
setTimeout(emit_ready, ready_delay_ms);
|
|
60
|
+
} else {
|
|
61
|
+
setImmediate(emit_ready);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
55
64
|
|
|
56
|
-
handle_http(req, res) {
|
|
57
|
-
res.writeHead(200, {
|
|
58
|
-
'Content-Type': 'text/html; charset=utf-8',
|
|
59
|
-
'Content-Length': Buffer.byteLength(this.html_body, 'utf8')
|
|
60
|
-
});
|
|
61
|
-
res.end(this.html_body);
|
|
62
|
-
}
|
|
63
|
-
|
|
65
|
+
handle_http(req, res) {
|
|
66
|
+
res.writeHead(200, {
|
|
67
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
68
|
+
'Content-Length': Buffer.byteLength(this.html_body, 'utf8')
|
|
69
|
+
});
|
|
70
|
+
res.end(this.html_body);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
meets_requirements() {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
start(callback) {
|
|
78
|
+
if (typeof callback === 'function') callback(null, true);
|
|
79
|
+
return Promise.resolve(true);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
stop(callback) {
|
|
83
|
+
if (typeof callback === 'function') callback(null, true);
|
|
84
|
+
return Promise.resolve(true);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
64
87
|
|
|
65
88
|
class Fake_Webpage_Publisher extends Fake_Publisher_Base {
|
|
66
89
|
constructor(opts = {}) {
|
|
@@ -72,14 +95,17 @@ class Fake_Webpage_Publisher extends Fake_Publisher_Base {
|
|
|
72
95
|
}
|
|
73
96
|
}
|
|
74
97
|
|
|
75
|
-
class Fake_Website_Publisher extends Fake_Publisher_Base {
|
|
98
|
+
class Fake_Website_Publisher extends Fake_Publisher_Base {
|
|
76
99
|
constructor(opts = {}) {
|
|
77
100
|
const route = '/*';
|
|
78
101
|
const title = (opts.website && opts.website.name) || 'Test Website';
|
|
79
102
|
const body = `<html><head><title>${title}</title></head><body><div class="dummy-control">website</div></body></html>`;
|
|
80
103
|
super(route, body);
|
|
81
104
|
}
|
|
82
|
-
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Fake_Webpage_Publisher.ready_delay_ms = 0;
|
|
108
|
+
Fake_Website_Publisher.ready_delay_ms = 0;
|
|
83
109
|
|
|
84
110
|
require.cache[fake_webpage_publisher_path] = { exports: Fake_Webpage_Publisher };
|
|
85
111
|
require.cache[fake_website_publisher_path] = { exports: Fake_Website_Publisher };
|
|
@@ -120,16 +146,35 @@ const get_http_response = (port, route_path = '/') => new Promise((resolve, reje
|
|
|
120
146
|
req.on('error', reject);
|
|
121
147
|
});
|
|
122
148
|
|
|
123
|
-
describe('Server.serve', function() {
|
|
124
|
-
this.timeout(10000);
|
|
125
|
-
let server_instance;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
149
|
+
describe('Server.serve', function() {
|
|
150
|
+
this.timeout(10000);
|
|
151
|
+
let server_instance;
|
|
152
|
+
|
|
153
|
+
after(() => {
|
|
154
|
+
if (original_webpage_publisher_module) {
|
|
155
|
+
require.cache[fake_webpage_publisher_path] = original_webpage_publisher_module;
|
|
156
|
+
} else {
|
|
157
|
+
delete require.cache[fake_webpage_publisher_path];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (original_website_publisher_module) {
|
|
161
|
+
require.cache[fake_website_publisher_path] = original_website_publisher_module;
|
|
162
|
+
} else {
|
|
163
|
+
delete require.cache[fake_website_publisher_path];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
delete require.cache[require.resolve('../server')];
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
afterEach(async () => {
|
|
170
|
+
Fake_Webpage_Publisher.ready_delay_ms = 0;
|
|
171
|
+
Fake_Website_Publisher.ready_delay_ms = 0;
|
|
172
|
+
|
|
173
|
+
if (server_instance) {
|
|
174
|
+
await new Promise(resolve => server_instance.close(resolve));
|
|
175
|
+
server_instance = null;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
133
178
|
|
|
134
179
|
it('should serve a simple control', async () => {
|
|
135
180
|
const port = await get_free_port();
|
|
@@ -195,16 +240,41 @@ describe('Server.serve', function() {
|
|
|
195
240
|
assert.strictEqual(body, 'ok');
|
|
196
241
|
});
|
|
197
242
|
|
|
198
|
-
it('should return 404 for unknown routes', async () => {
|
|
199
|
-
const port = await get_free_port();
|
|
200
|
-
server_instance = await Server.serve({
|
|
201
|
-
Ctrl: Dummy_Control,
|
|
202
|
-
src_path_client_js: dummy_client_path,
|
|
203
|
-
host: '127.0.0.1',
|
|
204
|
-
port
|
|
205
|
-
});
|
|
206
|
-
const { res, body } = await get_http_response(port, '/missing');
|
|
207
|
-
assert.strictEqual(res.statusCode, 404);
|
|
208
|
-
assert.strictEqual(body, 'Not Found');
|
|
209
|
-
});
|
|
210
|
-
|
|
243
|
+
it('should return 404 for unknown routes', async () => {
|
|
244
|
+
const port = await get_free_port();
|
|
245
|
+
server_instance = await Server.serve({
|
|
246
|
+
Ctrl: Dummy_Control,
|
|
247
|
+
src_path_client_js: dummy_client_path,
|
|
248
|
+
host: '127.0.0.1',
|
|
249
|
+
port
|
|
250
|
+
});
|
|
251
|
+
const { res, body } = await get_http_response(port, '/missing');
|
|
252
|
+
assert.strictEqual(res.statusCode, 404);
|
|
253
|
+
assert.strictEqual(body, 'Not Found');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('waits for webpage publisher readiness before resolving serve()', async () => {
|
|
257
|
+
Fake_Webpage_Publisher.ready_delay_ms = 2600;
|
|
258
|
+
|
|
259
|
+
const port = await get_free_port();
|
|
260
|
+
const started_at = Date.now();
|
|
261
|
+
|
|
262
|
+
server_instance = await Server.serve({
|
|
263
|
+
Ctrl: Dummy_Control,
|
|
264
|
+
src_path_client_js: dummy_client_path,
|
|
265
|
+
host: '127.0.0.1',
|
|
266
|
+
port,
|
|
267
|
+
readyTimeoutMs: 12000
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const elapsed_ms = Date.now() - started_at;
|
|
271
|
+
assert(
|
|
272
|
+
elapsed_ms >= 2400,
|
|
273
|
+
`Expected serve() to wait for delayed readiness, elapsed=${elapsed_ms}ms`
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
const { res, body } = await get_http_response(port, '/');
|
|
277
|
+
assert.strictEqual(res.statusCode, 200);
|
|
278
|
+
assert(body.includes('<div class="dummy-control"'));
|
|
279
|
+
});
|
|
280
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { describe, it } = require('mocha');
|
|
3
|
+
const { Resource } = require('jsgui3-html');
|
|
4
|
+
|
|
5
|
+
const Server_Resource_Pool = require('../resources/server-resource-pool');
|
|
6
|
+
|
|
7
|
+
class Dummy_Managed_Resource extends Resource {
|
|
8
|
+
constructor(spec = {}) {
|
|
9
|
+
super(spec);
|
|
10
|
+
this.current_state = spec.initial_state || 'stopped';
|
|
11
|
+
this.start_calls = 0;
|
|
12
|
+
this.stop_calls = 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
start(callback) {
|
|
16
|
+
const previous_state = this.current_state;
|
|
17
|
+
this.start_calls += 1;
|
|
18
|
+
this.current_state = 'running';
|
|
19
|
+
this.raise('state_change', {
|
|
20
|
+
from: previous_state,
|
|
21
|
+
to: this.current_state,
|
|
22
|
+
timestamp: Date.now()
|
|
23
|
+
});
|
|
24
|
+
if (typeof callback === 'function') callback(null, true);
|
|
25
|
+
return Promise.resolve(true);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
stop(callback) {
|
|
29
|
+
const previous_state = this.current_state;
|
|
30
|
+
this.stop_calls += 1;
|
|
31
|
+
this.current_state = 'stopped';
|
|
32
|
+
this.raise('state_change', {
|
|
33
|
+
from: previous_state,
|
|
34
|
+
to: this.current_state,
|
|
35
|
+
timestamp: Date.now()
|
|
36
|
+
});
|
|
37
|
+
if (typeof callback === 'function') callback(null, true);
|
|
38
|
+
return Promise.resolve(true);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get status() {
|
|
42
|
+
return {
|
|
43
|
+
state: this.current_state
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get_abstract() {
|
|
48
|
+
return {
|
|
49
|
+
name: this.name,
|
|
50
|
+
state: this.current_state
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('Server_Resource_Pool lifecycle', function() {
|
|
56
|
+
it('forwards resource state events and supports removal with stop()', async () => {
|
|
57
|
+
const resource_pool = new Server_Resource_Pool();
|
|
58
|
+
const managed_resource = new Dummy_Managed_Resource({
|
|
59
|
+
name: 'managed_resource_one'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
resource_pool.add(managed_resource);
|
|
63
|
+
|
|
64
|
+
const forwarded_events = [];
|
|
65
|
+
resource_pool.on('resource_state_change', (event_data) => {
|
|
66
|
+
forwarded_events.push(event_data);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await managed_resource.start();
|
|
70
|
+
assert(forwarded_events.some((event_data) => event_data.resourceName === 'managed_resource_one' && event_data.to === 'running'));
|
|
71
|
+
|
|
72
|
+
const did_remove = await resource_pool.remove('managed_resource_one');
|
|
73
|
+
assert.strictEqual(did_remove, true);
|
|
74
|
+
assert.strictEqual(managed_resource.stop_calls > 0, true);
|
|
75
|
+
assert.strictEqual(resource_pool.has_resource('managed_resource_one'), false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('supports stop-all and summary aggregation', async () => {
|
|
79
|
+
const resource_pool = new Server_Resource_Pool();
|
|
80
|
+
|
|
81
|
+
const resource_one = new Dummy_Managed_Resource({
|
|
82
|
+
name: 'managed_resource_a',
|
|
83
|
+
initial_state: 'running'
|
|
84
|
+
});
|
|
85
|
+
const resource_two = new Dummy_Managed_Resource({
|
|
86
|
+
name: 'managed_resource_b',
|
|
87
|
+
initial_state: 'crashed'
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
resource_pool.add(resource_one);
|
|
91
|
+
resource_pool.add(resource_two);
|
|
92
|
+
|
|
93
|
+
const by_constructor = resource_pool.get_resources_by_type(Dummy_Managed_Resource);
|
|
94
|
+
const by_name = resource_pool.get_resources_by_type('Dummy_Managed_Resource');
|
|
95
|
+
assert.strictEqual(by_constructor.length >= 2, true);
|
|
96
|
+
assert.strictEqual(by_name.length >= 2, true);
|
|
97
|
+
|
|
98
|
+
const summary_before_stop = resource_pool.summary;
|
|
99
|
+
assert(summary_before_stop.byType.Dummy_Managed_Resource);
|
|
100
|
+
assert(summary_before_stop.byType.Dummy_Managed_Resource.some((entry) => entry.name === 'managed_resource_a'));
|
|
101
|
+
|
|
102
|
+
await resource_pool.stop();
|
|
103
|
+
assert.strictEqual(resource_one.stop_calls > 0, true);
|
|
104
|
+
assert.strictEqual(resource_two.stop_calls > 0, true);
|
|
105
|
+
});
|
|
106
|
+
});
|