@flancer32/teq-web 0.3.0 → 0.4.0

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/test/dev/app.mjs DELETED
@@ -1,65 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
- /**
4
- * A test script to emulate an app that uses the web server.
5
- */
6
- import {dirname, join} from 'node:path';
7
- import {fileURLToPath} from 'node:url';
8
- import {readFileSync} from 'node:fs';
9
- import Container from '@teqfw/di';
10
-
11
- // VARS
12
- /* Resolve a path to the root folder. */
13
- const url = new URL(import.meta.url);
14
- const script = fileURLToPath(url);
15
- const cur = dirname(script);
16
- const root = join(cur, '..', '..');
17
-
18
- // Create a new instance of the container
19
- const container = new Container();
20
-
21
- // Get the resolver from the container
22
- const resolver = container.getResolver();
23
- resolver.addNamespaceRoot('Fl32_Web_', join(root, 'src'));
24
- resolver.addNamespaceRoot('App_', join(cur, 'app'));
25
-
26
- // init the app (add the handlers to the Dispatcher)
27
- /** @type {function} */
28
- const appStart = await container.get('App_Plugin_Start$');
29
- await appStart();
30
-
31
- // order handlers in the Dispatcher
32
- /** @type {Fl32_Web_Back_Dispatcher} */
33
- const dispatcher = await container.get('Fl32_Web_Back_Dispatcher$');
34
- dispatcher.orderHandlers();
35
-
36
- // configure and run the server
37
- /** @type {Fl32_Web_Back_Server} */
38
- const server = await container.get('Fl32_Web_Back_Server$');
39
- /** @type {typeof Fl32_Web_Back_Enum_Server_Type} */
40
- const SERVER_TYPE = await container.get('Fl32_Web_Back_Enum_Server_Type$');
41
- /** @type {Fl32_Web_Back_Server_Config} */
42
- const factConfig = await container.get('Fl32_Web_Back_Server_Config$');
43
-
44
- // Read TLS certificates
45
- const certsDir = join(cur, '..', 'certs');
46
- const key = readFileSync(join(certsDir, 'key.pem'), 'utf8');
47
- const cert = readFileSync(join(certsDir, 'cert.pem'), 'utf8');
48
- let ca;
49
- try {
50
- ca = readFileSync(join(certsDir, 'ca.pem'), 'utf8');
51
- } catch (e) {
52
- // CA certificate is optional
53
- }
54
-
55
- console.log('Starting HTTPS server on port 3443...');
56
- const cfg = factConfig.create({
57
- port: 3443,
58
- type: SERVER_TYPE.HTTPS,
59
- tls: {
60
- key,
61
- cert,
62
- ca
63
- }
64
- });
65
- await server.start(cfg);
Binary file
@@ -1,11 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1.0">
6
- <title>@flancer32/teq-web</title>
7
- </head>
8
- <body>
9
- <p>Hello, from `@flancer32/teq-web`!</p>
10
- </body>
11
- </html>
@@ -1,106 +0,0 @@
1
- # AI Agent Unit Testing Instructions for @flancer32/teq-web
2
-
3
- This document outlines project-specific guidelines for writing and maintaining unit tests in the `@flancer32/teq-web` plugin. Follow these rules to ensure consistency and reliability.
4
-
5
- ---
6
-
7
- ## 1. Test File Location & Naming
8
-
9
- * Mirror `src/` structure under `test/unit/`, using the same path and filenames.
10
- * Use `.test.mjs` suffix for test files, e.g. `Config.test.mjs`, `Kahn.test.mjs`.
11
- * Top-level `describe` must reference the full DI key:
12
-
13
- ```js
14
- describe('Fl32_Web_Back_Handler_Static_A_Config', () => { /* ... */ });
15
- ```
16
-
17
- ## 2. Dependency Injection via DI Container
18
-
19
- * Always call `buildTestContainer()` from `test/unit/common.js` and then:
20
-
21
- ```js
22
- /** @type {Fl32_Web_Back_Handler_Static_A_Config} */
23
- const config = await container.get('Fl32_Web_Back_Handler_Static_A_Config$');
24
- ```
25
- * **Do not** import production modules (`node:path`, etc.) directly—register or mock everything through the container.
26
- * Use `container.register(depId, mock)` to override dependencies in test mode.
27
-
28
- ## 3. Asynchronous Subject Retrieval
29
-
30
- * Declare your `it` or `beforeEach` callbacks as `async` if you call `await container.get(...)`.
31
- * Always `await container.get('Your_Service_Key$')` to obtain the actual instance before invoking its methods.
32
-
33
- ## 4. Testing Patterns
34
-
35
- * Use Node’s built-in test runner and assertion library:
36
-
37
- ```js
38
- import { describe, it } from 'node:test';
39
- import assert from 'node:assert/strict';
40
- ```
41
- * **Success cases**:
42
-
43
- * `assert.strictEqual(actual, expected)` for primitives.
44
- * `assert.deepStrictEqual(actual, expected)` for objects/arrays.
45
- * **Error cases**:
46
-
47
- * `assert.throws(() => fn(), /message/)` matching a key fragment of the error.
48
-
49
- ## 5. DTO Shape & Defaults
50
-
51
- * Pass a full DTO to `factory.create(dto)`.
52
- * To test fallback/default logic, supply `[]` for optional arrays.
53
-
54
- ## 6. Comments & Documentation
55
-
56
- * All inline comments must be in **English**.
57
- * Comment only non-trivial logic; don’t restate obvious assertions.
58
-
59
- ## 7. Mocks & Helpers
60
-
61
- * Use plain JS objects or small factory functions for mocks.
62
- * No external mocking libraries—rely on `@teqfw/di` test mode.
63
-
64
- ## 8. Maintenance
65
-
66
- * One behavior per `it` block—keep tests focused and concise.
67
- * Update tests when API or default constants change.
68
- * Ensure CI runs all tests automatically.
69
-
70
- ---
71
-
72
- ## Test Mode Support in `@teqfw/di`
73
-
74
- When you enable test mode, you can inject or override any dependency—built-in or custom—without touching production code:
75
-
76
- ```js
77
- const container = buildTestContainer();
78
- container.enableTestMode();
79
-
80
- // override a service or Node builtin
81
- container.register('Fl32_Web_Back_Logger$', mockLogger);
82
- container.register('node:fs', { /* mock fs.promises.stat… */});
83
- ```
84
-
85
- * **register** vs. **registerInstance**
86
- Use `container.register(depId, instanceOrFactory)` to bind mocks. Avoid `registerInstance`, which is not part of the public test-mode API.
87
-
88
- ### Mocking Node.js Built-ins
89
-
90
- ```js
91
- // simulate filesystem behavior
92
- container.register('node:fs', {
93
- promises: {
94
- stat: async (p) => { /* … */ }
95
- },
96
- createReadStream: (p) => { /* … */ }
97
- });
98
-
99
- // adjust path logic
100
- container.register('node:path', {
101
- join: (...parts) => parts.join('/'),
102
- resolve: (p) => `/abs/${p}`
103
- });
104
- ```
105
-
106
- These overrides are injected into every module that asks for `node:fs` or `node:path`, enabling isolated, deterministic tests without side effects.
@@ -1,150 +0,0 @@
1
- import {describe, it, beforeEach} from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import {buildTestContainer} from '../common.js';
4
-
5
- /** Collects execution order */
6
- let log;
7
- /** DI container for each test */
8
- let container;
9
- /** Dispatcher stages enum */
10
- let STAGE;
11
- /** Respond helper mock */
12
- let respond;
13
-
14
- beforeEach(async () => {
15
- log = [];
16
- container = buildTestContainer();
17
-
18
- // Mock logger to keep tests quiet
19
- container.register('Fl32_Web_Back_Logger$', {
20
- info: () => {},
21
- error: () => {},
22
- exception: () => {},
23
- });
24
- // Keep handler order as provided
25
- container.register('Fl32_Web_Back_Helper_Order_Kahn$', {sort: arr => arr});
26
-
27
- // Respond helper stub
28
- respond = {
29
- isWritable: res => !res.headersSent && !res.writableEnded,
30
- code404_NotFound: ({res}) => { res.code = 404; res.headersSent = true; },
31
- code500_InternalServerError: ({res}) => { res.code = 500; res.headersSent = true; },
32
- };
33
- container.register('Fl32_Web_Back_Helper_Respond$', respond);
34
-
35
- STAGE = await container.get('Fl32_Web_Back_Enum_Stage$');
36
- });
37
-
38
- function pre(name) {
39
- return {
40
- getRegistrationInfo: () => ({name, stage: STAGE.PRE}),
41
- handle: async () => { log.push(name); },
42
- };
43
- }
44
-
45
- function proc(name, opts = {}) {
46
- const {handled = true, throwErr = false, send = true} = opts;
47
- return {
48
- getRegistrationInfo: () => ({name, stage: STAGE.PROCESS}),
49
- handle: async (req, res) => {
50
- log.push(name);
51
- if (throwErr) throw new Error('boom');
52
- if (send) res.headersSent = true;
53
- return handled;
54
- },
55
- };
56
- }
57
-
58
- function post(name) {
59
- return {
60
- getRegistrationInfo: () => ({name, stage: STAGE.POST}),
61
- handle: async () => { log.push(name); },
62
- };
63
- }
64
-
65
- describe('Fl32_Web_Back_Dispatcher', () => {
66
- it('calls pre-handlers even when a process handler fails', async () => {
67
- const dispatcher = await container.get('Fl32_Web_Back_Dispatcher$');
68
- dispatcher.addHandler(pre('pre'));
69
- dispatcher.addHandler(proc('proc', {throwErr: true, send: false}));
70
- dispatcher.orderHandlers();
71
-
72
- const req = {url: '/'}; const res = {};
73
- await dispatcher.onEventRequest(req, res);
74
-
75
- assert.strictEqual(log[0], 'pre');
76
- });
77
-
78
- it('executes post-handlers after a successful process handler', async () => {
79
- const dispatcher = await container.get('Fl32_Web_Back_Dispatcher$');
80
- dispatcher.addHandler(pre('pre'));
81
- dispatcher.addHandler(proc('proc'));
82
- dispatcher.addHandler(post('post'));
83
- dispatcher.orderHandlers();
84
-
85
- const req = {url: '/'}; const res = {};
86
- await dispatcher.onEventRequest(req, res);
87
-
88
- assert.deepStrictEqual(log, ['pre', 'proc', 'post']);
89
- });
90
-
91
- it('returns 500 if a process handler throws', async () => {
92
- const dispatcher = await container.get('Fl32_Web_Back_Dispatcher$');
93
- dispatcher.addHandler(proc('proc', {throwErr: true, send: false}));
94
- dispatcher.orderHandlers();
95
-
96
- const req = {url: '/'}; const res = {};
97
- await dispatcher.onEventRequest(req, res);
98
-
99
- assert.strictEqual(res.code, 500);
100
- });
101
-
102
- it('returns 404 if no process handler handles the request', async () => {
103
- const dispatcher = await container.get('Fl32_Web_Back_Dispatcher$');
104
- dispatcher.addHandler(pre('pre'));
105
- dispatcher.addHandler(proc('p1', {handled: false, send: false}));
106
- dispatcher.addHandler(post('post'));
107
- dispatcher.orderHandlers();
108
-
109
- const req = {url: '/missing'}; const res = {};
110
- await dispatcher.onEventRequest(req, res);
111
-
112
- assert.strictEqual(res.code, 404);
113
- assert.deepStrictEqual(log, ['pre', 'p1', 'post']);
114
- });
115
-
116
- it('orders handlers according to before/after dependencies', async () => {
117
- const localLog = [];
118
- const container2 = buildTestContainer();
119
-
120
- container2.register('Fl32_Web_Back_Logger$', {
121
- info: () => {},
122
- error: () => {},
123
- exception: () => {},
124
- });
125
-
126
- const respond2 = {
127
- isWritable: res => !res.headersSent && !res.writableEnded,
128
- code404_NotFound: ({res}) => { res.code = 404; res.headersSent = true; },
129
- code500_InternalServerError: ({res}) => { res.code = 500; res.headersSent = true; },
130
- };
131
- container2.register('Fl32_Web_Back_Helper_Respond$', respond2);
132
-
133
- const STAGE2 = await container2.get('Fl32_Web_Back_Enum_Stage$');
134
- const mk = (name, after = []) => ({
135
- getRegistrationInfo: () => ({name, stage: STAGE2.PRE, after}),
136
- handle: async () => { localLog.push(name); },
137
- });
138
-
139
- const dispatcher = await container2.get('Fl32_Web_Back_Dispatcher$');
140
- dispatcher.addHandler(mk('a', ['c']));
141
- dispatcher.addHandler(mk('b', ['a']));
142
- dispatcher.addHandler(mk('c'));
143
- dispatcher.orderHandlers();
144
-
145
- await dispatcher.onEventRequest({}, {});
146
-
147
- assert.deepStrictEqual(localLog, ['c', 'a', 'b']);
148
- });
149
- });
150
-
@@ -1,40 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'assert';
3
- import {buildTestContainer} from '../../../common.js';
4
-
5
- test.describe('Fl32_Web_Back_Dto_Handler_Source', () => {
6
- test('should create valid config DTO with casted fields', async () => {
7
- const container = buildTestContainer();
8
- container.register('Fl32_Web_Back_Helper_Cast$', {
9
- string: (d) => typeof d === 'string' ? d : undefined,
10
- stringArrayMap: (d) => d,
11
- array: (d, item) => Array.isArray(d) ? d.map(item) : [],
12
- });
13
- const factory = await container.get('Fl32_Web_Back_Dto_Handler_Source$');
14
- const dto = factory.create({
15
- root: '/abs/path',
16
- prefix: '/src/',
17
- allow: { vue: ['dist/vue.global.js'] },
18
- defaults: ['index.html'],
19
- });
20
- assert.strictEqual(dto.root, '/abs/path');
21
- assert.strictEqual(dto.prefix, '/src/');
22
- assert.deepStrictEqual(dto.allow, { vue: ['dist/vue.global.js'] });
23
- assert.deepStrictEqual(dto.defaults, ['index.html']);
24
- });
25
-
26
- test('should return undefined fields if values are invalid', async () => {
27
- const container = buildTestContainer();
28
- container.register('Fl32_Web_Back_Helper_Cast$', {
29
- string: () => undefined,
30
- stringArrayMap: () => ({}),
31
- array: () => [],
32
- });
33
- const factory = await container.get('Fl32_Web_Back_Dto_Handler_Source$');
34
- const dto = factory.create({});
35
- assert.strictEqual(dto.root, undefined);
36
- assert.strictEqual(dto.prefix, undefined);
37
- assert.deepStrictEqual(dto.allow, {});
38
- assert.deepStrictEqual(dto.defaults, []);
39
- });
40
- });
@@ -1,22 +0,0 @@
1
- import {describe, it, beforeEach} from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import {buildTestContainer} from '../../../common.js';
4
-
5
- describe('Fl32_Web_Back_Handler_Pre_Log', () => {
6
- let container;
7
- const log = [];
8
-
9
- beforeEach(() => {
10
- log.length = 0;
11
- container = buildTestContainer();
12
- container.register('Fl32_Web_Back_Logger$', {
13
- debug: (msg) => log.push(msg),
14
- });
15
- });
16
-
17
- it('logs method and url', async () => {
18
- const handler = await container.get('Fl32_Web_Back_Handler_Pre_Log$');
19
- await handler.handle({method: 'GET', url: '/path'});
20
- assert.deepStrictEqual(log, ['GET /path']);
21
- });
22
- });
@@ -1,52 +0,0 @@
1
- import {describe, it} from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import {buildTestContainer} from '../../../../common.js';
4
-
5
- describe('Fl32_Web_Back_Handler_Static_A_Config', () => {
6
- it('normalizes root, prefix, allow and defaults', async () => {
7
- const container = buildTestContainer();
8
- /** @type {Fl32_Web_Back_Handler_Static_A_Config} */
9
- const factory = await container.get('Fl32_Web_Back_Handler_Static_A_Config$');
10
-
11
- const dto = {
12
- root: './r',
13
- prefix: '/p',
14
- allow: {'.': ['.']},
15
- defaults: []
16
- };
17
- const cfg = factory.create(dto);
18
-
19
- // root is resolved via node:path from the container
20
- const {resolve} = await import('node:path');
21
- assert.strictEqual(cfg.root, resolve('./r'));
22
-
23
- // prefix is normalized to always end with a slash
24
- assert.strictEqual(cfg.prefix, '/p/');
25
-
26
- // allowed extensions are preserved
27
- assert.deepStrictEqual(cfg.allow, {'.': ['.']});
28
-
29
- // defaults fallback to built-in list when empty
30
- assert.deepStrictEqual(
31
- cfg.defaults,
32
- ['index.html', 'index.htm', 'index.txt']
33
- );
34
- });
35
-
36
- it('throws on invalid data', async () => {
37
- const container = buildTestContainer();
38
- const factory = await container.get('Fl32_Web_Back_Handler_Static_A_Config$');
39
-
40
- // missing root should throw an error about root
41
- assert.throws(
42
- () => factory.create({prefix: '/'}),
43
- /Field 'root' must be a string/
44
- );
45
-
46
- // non-string prefix should throw an error about prefix
47
- assert.throws(
48
- () => factory.create({root: 'a', prefix: 5}),
49
- /Field 'prefix' must be a string/
50
- );
51
- });
52
- });
@@ -1,60 +0,0 @@
1
- import {describe, it, beforeEach} from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import {buildTestContainer} from '../../../../common.js';
4
-
5
- describe('Fl32_Web_Back_Handler_Static_A_Fallback', () => {
6
- /** @type {{ promises: { stat: (p: string) => Promise<any> }, _add: (p: string, isFile: boolean) => void }} */
7
- let mockFs;
8
-
9
- beforeEach(() => {
10
- const storage = new Map();
11
-
12
- mockFs = {
13
- promises: {
14
- stat: async p => {
15
- // normalize path: backslashes → slashes, remove duplicate and trailing slash
16
- const key = p.replace(/\\/g, '/').replace(/\/+/g, '/').replace(/\/$/, '');
17
- if (!storage.has(key)) throw new Error('ENOENT');
18
- return storage.get(key);
19
- }
20
- },
21
- /** Adds a file or directory into the mock storage */
22
- _add: (p, isFile) => {
23
- const key = p.replace(/\\/g, '/').replace(/\/+/g, '/').replace(/\/$/, '');
24
- storage.set(key, {
25
- isFile: () => isFile,
26
- isDirectory: () => !isFile
27
- });
28
- }
29
- };
30
- });
31
-
32
- function addFile(p) { mockFs._add(p, true); }
33
-
34
- function addDir(p) { mockFs._add(p, false); }
35
-
36
- /** Creates and returns a configured Fallback instance */
37
- async function getFallback() {
38
- const container = buildTestContainer();
39
- container.register('node:fs', mockFs);
40
- /** @type {Fl32_Web_Back_Handler_Static_A_Fallback} */
41
- return container.get('Fl32_Web_Back_Handler_Static_A_Fallback$');
42
- }
43
-
44
- it('returns index file for a directory', async () => {
45
- addDir('/d');
46
- addFile('/d/index.html');
47
-
48
- const fb = await getFallback();
49
- const result = await fb.apply('/d', ['index.html']);
50
- assert.strictEqual(result, '/d/index.html');
51
- });
52
-
53
- it('returns null when nothing found', async () => {
54
- addDir('/x');
55
-
56
- const fb = await getFallback();
57
- const result = await fb.apply('/x', ['a.html']);
58
- assert.strictEqual(result, null);
59
- });
60
- });