@flancer32/teq-web 0.3.1 → 0.5.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/README.md +236 -127
  3. package/ai/AGENTS.md +36 -0
  4. package/ai/abstractions.md +75 -0
  5. package/ai/examples/minimal-server.md +78 -0
  6. package/ai/overview.md +27 -0
  7. package/ai/rules.md +41 -0
  8. package/package.json +43 -28
  9. package/src/Back/Api/Handler.mjs +26 -0
  10. package/src/Back/{Defaults.js → Defaults.mjs} +6 -1
  11. package/src/Back/Dto/Info.mjs +66 -0
  12. package/src/Back/Dto/{Handler/Source.js → Source.mjs} +26 -23
  13. package/src/Back/Enum/Server/Type.mjs +13 -0
  14. package/src/Back/Enum/Stage.mjs +13 -0
  15. package/src/Back/Handler/Pre/Log.mjs +59 -0
  16. package/src/Back/Handler/Static/A/{Config.js → Config.mjs} +14 -3
  17. package/src/Back/Handler/Static/A/{Fallback.js → Fallback.mjs} +16 -4
  18. package/src/Back/Handler/Static/A/{FileService.js → FileService.mjs} +31 -14
  19. package/src/Back/Handler/Static/A/{Registry.js → Registry.mjs} +18 -6
  20. package/src/Back/Handler/Static/A/{Resolver.js → Resolver.mjs} +13 -2
  21. package/src/Back/Handler/Static.mjs +83 -0
  22. package/src/Back/Helper/Cast.mjs +116 -0
  23. package/src/Back/Helper/{Mime.js → Mime.mjs} +2 -0
  24. package/src/Back/Helper/Order/Kahn.mjs +69 -0
  25. package/src/Back/Helper/{Respond.js → Respond.mjs} +14 -3
  26. package/src/Back/Logger.mjs +57 -0
  27. package/src/Back/PipelineEngine.mjs +228 -0
  28. package/src/Back/Server/Config/{Tls.js → Tls.mjs} +35 -33
  29. package/src/Back/Server/Config.mjs +69 -0
  30. package/src/Back/{Server.js → Server.mjs} +35 -24
  31. package/types.d.ts +30 -0
  32. package/.github/workflows/npm-publish.yml +0 -48
  33. package/AGENTS.md +0 -101
  34. package/eslint.config.js +0 -37
  35. package/src/AGENTS.md +0 -108
  36. package/src/Back/Api/Handler.js +0 -26
  37. package/src/Back/Dispatcher.js +0 -115
  38. package/src/Back/Dto/Handler/Info.js +0 -68
  39. package/src/Back/Enum/Server/Type.js +0 -12
  40. package/src/Back/Enum/Stage.js +0 -10
  41. package/src/Back/Handler/Pre/Log.js +0 -45
  42. package/src/Back/Handler/Static.js +0 -63
  43. package/src/Back/Helper/Cast.js +0 -114
  44. package/src/Back/Helper/Order/Kahn.js +0 -66
  45. package/src/Back/Logger.js +0 -53
  46. package/src/Back/Server/Config.js +0 -69
  47. package/teqfw.json +0 -8
  48. package/test/accept/ExternalServer.test.mjs +0 -66
  49. package/test/accept/Server.test.mjs +0 -203
  50. package/test/certs/ca.pem +0 -19
  51. package/test/certs/cert.pem +0 -19
  52. package/test/certs/key.pem +0 -28
  53. package/test/dev/app/Plugin/Start.js +0 -40
  54. package/test/dev/app.mjs +0 -65
  55. package/test/dev/web/favicon.ico +0 -0
  56. package/test/dev/web/index.html +0 -11
  57. package/test/unit/AGENTS.md +0 -106
  58. package/test/unit/Back/Dispatcher.test.mjs +0 -150
  59. package/test/unit/Back/Dto/Handler/Source.test.mjs +0 -40
  60. package/test/unit/Back/Handler/Pre/Log.test.mjs +0 -22
  61. package/test/unit/Back/Handler/Static/A/Config.test.mjs +0 -52
  62. package/test/unit/Back/Handler/Static/A/Fallback.test.mjs +0 -60
  63. package/test/unit/Back/Handler/Static/A/FileService.test.mjs +0 -225
  64. package/test/unit/Back/Handler/Static/A/Registry.test.mjs +0 -83
  65. package/test/unit/Back/Handler/Static/A/Resolver.test.mjs +0 -73
  66. package/test/unit/Back/Handler/Static/Static.test.mjs +0 -235
  67. package/test/unit/Back/Helper/Order/Kahn.test.mjs +0 -59
  68. package/test/unit/Back/Helper/Respond.test.mjs +0 -83
  69. package/test/unit/Back/Server.test.mjs +0 -112
  70. package/test/unit/common.js +0 -22
package/test/certs/ca.pem DELETED
@@ -1,19 +0,0 @@
1
- -----BEGIN CERTIFICATE-----
2
- MIIDCTCCAfGgAwIBAgIUBpZ2v9fA8XfqVF83Nw5byUa0ylswDQYJKoZIhvcNAQEL
3
- BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDYwODA5MDA1N1oXDTI2MDYw
4
- ODA5MDA1N1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
5
- AAOCAQ8AMIIBCgKCAQEAymikxJnbPFs3H/rRzetPjUdOPVuMOTfkz5oBDcP7s/2W
6
- O7135UPbDGzClYyYJvwAZm9i1Vivzm6KrNDZR0lawqYVf6COGJSAtMrkzx+bFSh7
7
- HWhuk4ByAdoZr6kFzpYBrxcfjSLU2oqaCyzb5vfksfrk1Yyr/i8DK6sw640OUlzP
8
- 1rtKTuRCpYm8We/wrxEvrcdzk7FVFYLCzQfGOZLPmZpoDI3pdUJ/wkY4tjPAPFLn
9
- 143L+07ZLN7Zuf0mpRAGpKve+MWzfId4xXoJ2XpGolLEkhJhfDNI9DCBjEZ3cYmJ
10
- LYSkpevQuTJNQm/89ivPk3FV0p3H4KjKKgtvtUaOZQIDAQABo1MwUTAdBgNVHQ4E
11
- FgQUjriVSWVYIJ/a21utkNLU2Cvjl1swHwYDVR0jBBgwFoAUjriVSWVYIJ/a21ut
12
- kNLU2Cvjl1swDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAkcHr
13
- TPlqzehyG1KGGb7VAGwCoqhUJfz0Pd3z2p+UOoKq/EmC8b1FRgKDZxSQHk0hhQWE
14
- M4f51tTbv2LgskfOF/S/tDoKBGnOwYS0oFmVqEstHuC2uto8ukj3FNvsZW58x4Q1
15
- DOGLkUqlPj5vAh8Zhw8GWokorF11dZlnKdPTC3NCdqyPTWNQPeSqd2qGsFgMBB2i
16
- q49QsUCNqiVc5ZfpbTBq+Jk1qHd1Pyf9Dlxm3Wi6RBguBcpHooZTAAYleSk+uhSa
17
- xH9f8v9E2aeYAk/zsZQt2f3x9sZQCAfjHPDOzXydiv568FasLxACY12/GkMNGdvy
18
- xlgkz6c0z3TS8kFxrQ==
19
- -----END CERTIFICATE-----
@@ -1,19 +0,0 @@
1
- -----BEGIN CERTIFICATE-----
2
- MIIDCTCCAfGgAwIBAgIUBpZ2v9fA8XfqVF83Nw5byUa0ylswDQYJKoZIhvcNAQEL
3
- BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDYwODA5MDA1N1oXDTI2MDYw
4
- ODA5MDA1N1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
5
- AAOCAQ8AMIIBCgKCAQEAymikxJnbPFs3H/rRzetPjUdOPVuMOTfkz5oBDcP7s/2W
6
- O7135UPbDGzClYyYJvwAZm9i1Vivzm6KrNDZR0lawqYVf6COGJSAtMrkzx+bFSh7
7
- HWhuk4ByAdoZr6kFzpYBrxcfjSLU2oqaCyzb5vfksfrk1Yyr/i8DK6sw640OUlzP
8
- 1rtKTuRCpYm8We/wrxEvrcdzk7FVFYLCzQfGOZLPmZpoDI3pdUJ/wkY4tjPAPFLn
9
- 143L+07ZLN7Zuf0mpRAGpKve+MWzfId4xXoJ2XpGolLEkhJhfDNI9DCBjEZ3cYmJ
10
- LYSkpevQuTJNQm/89ivPk3FV0p3H4KjKKgtvtUaOZQIDAQABo1MwUTAdBgNVHQ4E
11
- FgQUjriVSWVYIJ/a21utkNLU2Cvjl1swHwYDVR0jBBgwFoAUjriVSWVYIJ/a21ut
12
- kNLU2Cvjl1swDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAkcHr
13
- TPlqzehyG1KGGb7VAGwCoqhUJfz0Pd3z2p+UOoKq/EmC8b1FRgKDZxSQHk0hhQWE
14
- M4f51tTbv2LgskfOF/S/tDoKBGnOwYS0oFmVqEstHuC2uto8ukj3FNvsZW58x4Q1
15
- DOGLkUqlPj5vAh8Zhw8GWokorF11dZlnKdPTC3NCdqyPTWNQPeSqd2qGsFgMBB2i
16
- q49QsUCNqiVc5ZfpbTBq+Jk1qHd1Pyf9Dlxm3Wi6RBguBcpHooZTAAYleSk+uhSa
17
- xH9f8v9E2aeYAk/zsZQt2f3x9sZQCAfjHPDOzXydiv568FasLxACY12/GkMNGdvy
18
- xlgkz6c0z3TS8kFxrQ==
19
- -----END CERTIFICATE-----
@@ -1,28 +0,0 @@
1
- -----BEGIN PRIVATE KEY-----
2
- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDKaKTEmds8Wzcf
3
- +tHN60+NR049W4w5N+TPmgENw/uz/ZY7vXflQ9sMbMKVjJgm/ABmb2LVWK/Oboqs
4
- 0NlHSVrCphV/oI4YlIC0yuTPH5sVKHsdaG6TgHIB2hmvqQXOlgGvFx+NItTaipoL
5
- LNvm9+Sx+uTVjKv+LwMrqzDrjQ5SXM/Wu0pO5EKlibxZ7/CvES+tx3OTsVUVgsLN
6
- B8Y5ks+ZmmgMjel1Qn/CRji2M8A8UufXjcv7Ttks3tm5/SalEAakq974xbN8h3jF
7
- egnZekaiUsSSEmF8M0j0MIGMRndxiYkthKSl69C5Mk1Cb/z2K8+TcVXSncfgqMoq
8
- C2+1Ro5lAgMBAAECggEAEva0lUI6/aUoKC62Ox4++6QWIFPe8o9vjcRxMSp5qcvq
9
- Uu+LTPzLXZclBfZAXSACzkDFAusbvEdeu8FCJ4EnvTvqoLoDW38yWH3367CbiuMa
10
- NyT30zRXZMqxLw7ddJUDqbViEeA/uWKp+xO5Xfg/bNjDrtROsEe++szqY5n5dl2B
11
- aJLPEtg2OQW7GWKF7PX9YyZmbZds+L7WFhTE+42zfO6MMQm5jaTr8er74u6FnEA2
12
- ewrJmB7i9CS1DfYhCnJCwqOIxr9rPp9WMvRNfmL8wwqAr+4f1TidI7aKpA3vt2d0
13
- HOrnNws+08tlcqQYHHyasLzBzsYGAZRTvCoCxjVd8wKBgQDkWjNxp9TmNxQXxvhB
14
- J42lmo+9xBYzKUzmMMSvS7RoPFG8myf1s82qPTiwZZzGlpR6XIXfSZKQ45YhYz7S
15
- 8i8VbhjVtGyOmExBRYryI3y8pC1aVHk6bPQl8rATIkqsIGN8VHSiwCMeV+j6VWUP
16
- ZYjb0iqS2zKhhXD9CVcXIT/SfwKBgQDi6lG64nI/ECjakXfrXa+J1T5NJE+OEP3J
17
- AR9nqRiR/VcwAFMBiJwFfm07dv4WHLbfI1qdx+OU4aSsBohyLg3X9WO0Y8iGw2w4
18
- V6GGiwGK009OfzdrwTM9QjVHhk23SlFeQFPib4uRGcRnjimMNY6M1E3qvXHpyVXV
19
- rpvxyGElGwKBgGjcXwlPJ738BvcQQIoy7qHggyeCdytRSOXf+UICQrsnD+XLXiM/
20
- SS9m47RlRQQQu+ggur0ZnPt590Qnvf7ChgqSP0dLjhpBJ6tFkxO0ZiB+R/FWH0FM
21
- LSWL930h3yaBzQ2X/uOJ1damSe9C7aCPYLSJI1HC5NI1Y/hepKaTdypjAoGAEfkr
22
- ZhkfoX0fL0jMbdkq2UkJuUSCBKe14mDzYtuS9aVSbZvo9zsh2JGOB2LCd2/o0D3V
23
- pJ+7mARTbcjKr/iT4iIutpAcxwfdn4zZX3XNNnjMVFRhSGiyLUz8OWEa8MSzMzr3
24
- Kf1Z2bFnzCgHhHKNivwZ+9jrl+/5m4ZMFdegUjcCgYEAu/cKxIBc3Lb4gVZx6nHp
25
- bPr9flDbdCKQk20kpVPvNxq67FuVyKy3n0svRX+fZ9uPhuv20IWkTBue3yCGKpjD
26
- kUJ2A9Y9FZGSwhU3LSo6grdEv3pIW1YLTl95nUO20bRWCWcwsoV5/GEhFkEZWq06
27
- wZCQXFhIY78gzd4gk+qJQpg=
28
- -----END PRIVATE KEY-----
@@ -1,40 +0,0 @@
1
- export default class App_Plugin_Start {
2
- /**
3
- * @param {typeof import('node:path')} path
4
- * @param {typeof import('node:url')} url
5
- * @param {Fl32_Web_Back_Dispatcher} dispatcher
6
- * @param {Fl32_Web_Back_Handler_Pre_Log} hndlRequestLog
7
- * @param {Fl32_Web_Back_Handler_Static} hndlStatic
8
- * @param {Fl32_Web_Back_Dto_Handler_Source} dtoCfg
9
- */
10
- constructor(
11
- {
12
- 'node:path': path,
13
- 'node:url': url,
14
- Fl32_Web_Back_Dispatcher$: dispatcher,
15
- Fl32_Web_Back_Handler_Pre_Log$: hndlRequestLog,
16
- Fl32_Web_Back_Handler_Static$: hndlStatic,
17
- Fl32_Web_Back_Dto_Handler_Source$: dtoCfg,
18
- }
19
- ) {
20
- // VARS
21
- const {dirname, join} = path;
22
- const {fileURLToPath} = url;
23
-
24
- // MAIN
25
- /* Resolve a path to the root folder. */
26
- const metaUrl = new URL(import.meta.url);
27
- const script = fileURLToPath(metaUrl);
28
- const cur = dirname(script);
29
- const root = join(cur, '..', '..');
30
- const webRoot = join(root, 'web');
31
-
32
- return async function () {
33
- const srcNpm = dtoCfg.create({root: 'node_modules', prefix: '/npm/', allow: {'@teqfw/di': ['src/Container.js']}});
34
- const srcWeb = dtoCfg.create({root: webRoot, prefix: '/'});
35
- await hndlStatic.init({sources: [srcNpm, srcWeb]});
36
- dispatcher.addHandler(hndlRequestLog);
37
- dispatcher.addHandler(hndlStatic);
38
- };
39
- }
40
- }
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
- });