@futdevpro/nts-dynamo 1.15.8 → 1.15.10

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 (89) hide show
  1. package/.cursor/rules/_ag_frontend-structure.mdc +5 -5
  2. package/__documentations/nts-integration-tests-2026-03-17.md +26 -0
  3. package/build/_collections/default-fallback-cache-max-age.const.d.ts +3 -0
  4. package/build/_collections/default-fallback-cache-max-age.const.d.ts.map +1 -0
  5. package/build/_collections/default-fallback-cache-max-age.const.js +6 -0
  6. package/build/_collections/default-fallback-cache-max-age.const.js.map +1 -0
  7. package/build/_collections/default-not-found-page.const.d.ts +6 -0
  8. package/build/_collections/default-not-found-page.const.d.ts.map +1 -0
  9. package/build/_collections/default-not-found-page.const.js +25 -0
  10. package/build/_collections/default-not-found-page.const.js.map +1 -0
  11. package/build/_collections/default-socket-path.const.d.ts +3 -0
  12. package/build/_collections/default-socket-path.const.d.ts.map +1 -0
  13. package/build/_collections/default-socket-path.const.js +6 -0
  14. package/build/_collections/default-socket-path.const.js.map +1 -0
  15. package/build/_models/control-models/http-settings.control-model.d.ts +7 -0
  16. package/build/_models/control-models/http-settings.control-model.d.ts.map +1 -1
  17. package/build/_models/control-models/http-settings.control-model.js +7 -0
  18. package/build/_models/control-models/http-settings.control-model.js.map +1 -1
  19. package/build/_models/interfaces/static-client-settings.interface.d.ts +29 -0
  20. package/build/_models/interfaces/static-client-settings.interface.d.ts.map +1 -0
  21. package/build/_models/interfaces/static-client-settings.interface.js +3 -0
  22. package/build/_models/interfaces/static-client-settings.interface.js.map +1 -0
  23. package/build/_modules/mock/app-extended-server.mock.js +2 -2
  24. package/build/_modules/mock/app-extended-server.mock.js.map +1 -1
  25. package/build/_modules/mock/app-integration-test.mock.d.ts +20 -0
  26. package/build/_modules/mock/app-integration-test.mock.d.ts.map +1 -0
  27. package/build/_modules/mock/app-integration-test.mock.js +46 -0
  28. package/build/_modules/mock/app-integration-test.mock.js.map +1 -0
  29. package/build/_modules/mock/app-server.mock.js +2 -2
  30. package/build/_modules/mock/app-server.mock.js.map +1 -1
  31. package/build/_modules/mock/socket-client.mock.js +1 -1
  32. package/build/_modules/mock/socket-client.mock.js.map +1 -1
  33. package/build/_modules/mock/socket-server.mock.d.ts +1 -1
  34. package/build/_modules/mock/socket-server.mock.d.ts.map +1 -1
  35. package/build/_modules/mock/socket-server.mock.js +2 -8
  36. package/build/_modules/mock/socket-server.mock.js.map +1 -1
  37. package/build/_modules/server/errors/errors.data-service.d.ts.map +1 -1
  38. package/build/_modules/server/errors/errors.data-service.js +20 -9
  39. package/build/_modules/server/errors/errors.data-service.js.map +1 -1
  40. package/build/_modules/socket/_services/socket-server.service.d.ts.map +1 -1
  41. package/build/_modules/socket/_services/socket-server.service.js +3 -1
  42. package/build/_modules/socket/_services/socket-server.service.js.map +1 -1
  43. package/build/_modules/socket/app-extended.server.d.ts +1 -0
  44. package/build/_modules/socket/app-extended.server.d.ts.map +1 -1
  45. package/build/_modules/socket/app-extended.server.js +6 -2
  46. package/build/_modules/socket/app-extended.server.js.map +1 -1
  47. package/build/_services/base/data.service.d.ts.map +1 -1
  48. package/build/_services/base/data.service.js +4 -5
  49. package/build/_services/base/data.service.js.map +1 -1
  50. package/build/_services/base/db.service.d.ts.map +1 -1
  51. package/build/_services/base/db.service.js +8 -0
  52. package/build/_services/base/db.service.js.map +1 -1
  53. package/build/_services/server/app.server.d.ts +18 -0
  54. package/build/_services/server/app.server.d.ts.map +1 -1
  55. package/build/_services/server/app.server.js +74 -2
  56. package/build/_services/server/app.server.js.map +1 -1
  57. package/build/index.d.ts +4 -0
  58. package/build/index.d.ts.map +1 -1
  59. package/build/index.js +4 -0
  60. package/build/index.js.map +1 -1
  61. package/nodemon.json +2 -1
  62. package/package.json +4 -4
  63. package/src/_collections/default-fallback-cache-max-age.const.spec.ts +11 -0
  64. package/src/_collections/default-fallback-cache-max-age.const.ts +2 -0
  65. package/src/_collections/default-not-found-page.const.spec.ts +19 -0
  66. package/src/_collections/default-not-found-page.const.ts +22 -0
  67. package/src/_collections/default-socket-path.const.spec.ts +12 -0
  68. package/src/_collections/default-socket-path.const.ts +2 -0
  69. package/src/_models/control-models/http-settings.control-model.spec.ts +11 -0
  70. package/src/_models/control-models/http-settings.control-model.ts +8 -0
  71. package/src/_models/interfaces/static-client-settings.interface.spec.ts +29 -0
  72. package/src/_models/interfaces/static-client-settings.interface.ts +28 -0
  73. package/src/_modules/mock/app-extended-server.mock.ts +2 -2
  74. package/src/_modules/mock/app-integration-test.mock.ts +51 -0
  75. package/src/_modules/mock/app-server.mock.ts +2 -2
  76. package/src/_modules/mock/socket-client.mock.spec.ts +1 -1
  77. package/src/_modules/mock/socket-client.mock.ts +1 -1
  78. package/src/_modules/mock/socket-server.mock.spec.ts +1 -1
  79. package/src/_modules/mock/socket-server.mock.ts +5 -13
  80. package/src/_modules/server/errors/errors.data-service.spec.ts +6 -6
  81. package/src/_modules/server/errors/errors.data-service.ts +24 -9
  82. package/src/_modules/socket/_services/socket-server.service.ts +6 -2
  83. package/src/_modules/socket/app-extended.integration.spec.ts +85 -0
  84. package/src/_modules/socket/app-extended.server.ts +6 -2
  85. package/src/_services/base/data.service.ts +4 -7
  86. package/src/_services/base/db.service.ts +11 -3
  87. package/src/_services/route/routing-module.service.spec.ts +15 -0
  88. package/src/_services/server/app.server.ts +107 -2
  89. package/src/index.ts +4 -0
@@ -0,0 +1,11 @@
1
+ import { DyNTS_defaultFallbackCacheMaxAge } from './default-fallback-cache-max-age.const';
2
+
3
+ describe('| DyNTS_defaultFallbackCacheMaxAge', () => {
4
+ it('| should export default fallback cache max-age as 0', () => {
5
+ expect(DyNTS_defaultFallbackCacheMaxAge).toBe(0);
6
+ });
7
+
8
+ it('| should be a number', () => {
9
+ expect(typeof DyNTS_defaultFallbackCacheMaxAge).toBe('number');
10
+ });
11
+ });
@@ -0,0 +1,2 @@
1
+ /** SPA fallback (pl. index.html) alapértelmezett cache max-age másodpercben (0 = ne cache-eljük). */
2
+ export const DyNTS_defaultFallbackCacheMaxAge: number = 0;
@@ -0,0 +1,19 @@
1
+ import { DyNTS_defaultNotFoundPageHtml } from './default-not-found-page.const';
2
+
3
+ describe('| DyNTS_defaultNotFoundPageHtml', () => {
4
+ it('| should export a non-empty HTML string', () => {
5
+ expect(typeof DyNTS_defaultNotFoundPageHtml).toBe('string');
6
+ expect(DyNTS_defaultNotFoundPageHtml.length).toBeGreaterThan(0);
7
+ });
8
+
9
+ it('| should contain DOCTYPE and Page not found', () => {
10
+ expect(DyNTS_defaultNotFoundPageHtml).toContain('<!DOCTYPE html>');
11
+ expect(DyNTS_defaultNotFoundPageHtml).toContain('Page not found');
12
+ expect(DyNTS_defaultNotFoundPageHtml).toContain('</html>');
13
+ });
14
+
15
+ it('| should be valid for 404 response as text/html', () => {
16
+ expect(DyNTS_defaultNotFoundPageHtml).toContain('lang="en"');
17
+ expect(DyNTS_defaultNotFoundPageHtml).toContain('<title>');
18
+ });
19
+ });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Alapértelmezett "Page not found" (404) HTML, amit a static client default fallbackjaként
3
+ * szolgálunk ki, ha nincs fallbackPath (nem-SPA mód) és a kérés nem egy létező static fájlra mutat.
4
+ */
5
+ export const DyNTS_defaultNotFoundPageHtml: string =
6
+ '<!DOCTYPE html>\n' +
7
+ '<html lang="en">\n' +
8
+ '<head>\n' +
9
+ ' <meta charset="utf-8">\n' +
10
+ ' <meta name="viewport" content="width=device-width, initial-scale=1">\n' +
11
+ ' <title>Page not found</title>\n' +
12
+ ' <style>\n' +
13
+ ' body { font-family: system-ui, sans-serif; margin: 2rem; text-align: center; }\n' +
14
+ ' h1 { font-size: 1.5rem; color: #333; }\n' +
15
+ ' p { color: #666; }\n' +
16
+ ' </style>\n' +
17
+ '</head>\n' +
18
+ '<body>\n' +
19
+ ' <h1>Page not found</h1>\n' +
20
+ ' <p>The requested resource could not be found.</p>\n' +
21
+ '</body>\n' +
22
+ '</html>\n';
@@ -0,0 +1,12 @@
1
+ import { DyNTS_defaultSocketPath } from './default-socket-path.const';
2
+
3
+ describe('| DyNTS_defaultSocketPath', () => {
4
+ it('| should export default socket path as \'/socket\'', () => {
5
+ expect(DyNTS_defaultSocketPath).toBe('/socket');
6
+ });
7
+
8
+ it('| should be a non-empty string', () => {
9
+ expect(typeof DyNTS_defaultSocketPath).toBe('string');
10
+ expect(DyNTS_defaultSocketPath.length).toBeGreaterThan(0);
11
+ });
12
+ });
@@ -0,0 +1,2 @@
1
+ /** Default Socket.IO path, ha a portSettings.socketPath nincs megadva. */
2
+ export const DyNTS_defaultSocketPath: string = '/socket';
@@ -8,6 +8,7 @@ describe('| DyNTS_Http_Settings', () => {
8
8
 
9
9
  expect(settings.httpPort).toBeUndefined();
10
10
  expect(settings.httpsPort).toBeUndefined();
11
+ expect(settings.socketPath).toBeUndefined();
11
12
  expect(settings.httpUrlencoded).toEqual({
12
13
  limit: '50mb',
13
14
  extended: true,
@@ -63,4 +64,14 @@ describe('| DyNTS_Http_Settings', () => {
63
64
  limit: '100mb',
64
65
  } as BodyParser.OptionsJson);
65
66
  });
67
+
68
+ it('| should have socketPath undefined when not provided', () => {
69
+ const settings = new DyNTS_Http_Settings();
70
+ expect(settings.socketPath).toBeUndefined();
71
+ });
72
+
73
+ it('| should set socketPath when provided in constructor', () => {
74
+ const settings = new DyNTS_Http_Settings({ socketPath: '/ws' });
75
+ expect(settings.socketPath).toBe('/ws');
76
+ });
66
77
  });
@@ -1,8 +1,16 @@
1
1
 
2
2
  import * as BodyParser from 'body-parser';
3
+
4
+ /**
5
+ * HTTP/HTTPS és Socket.IO port és body parser beállítások.
6
+ * socketPath: ha nincs megadva, DyNTS_defaultSocketPath marad;
7
+ * a kliensnek ugyanazt a path-ot kell használnia.
8
+ */
3
9
  export class DyNTS_Http_Settings {
4
10
  httpPort?: number;
5
11
  httpsPort?: number;
12
+ /** Socket.IO path (pl. '/ws'); ha üres, default: DyNTS_defaultSocketPath. */
13
+ socketPath?: string;
6
14
 
7
15
  httpUrlencoded?: BodyParser.OptionsUrlencoded = {
8
16
  limit: '50mb',
@@ -0,0 +1,29 @@
1
+ import { DyNTS_StaticClient_Settings } from './static-client-settings.interface';
2
+
3
+ describe('| DyNTS_StaticClient_Settings', () => {
4
+ it('| should allow minimal settings with only root', () => {
5
+ const s: DyNTS_StaticClient_Settings = { root: '/dist' };
6
+ expect(s.root).toBe('/dist');
7
+ expect(s.fallbackPath).toBeUndefined();
8
+ });
9
+
10
+ it('| should allow optional fallbackPath', () => {
11
+ const s: DyNTS_StaticClient_Settings = {
12
+ root: '/dist',
13
+ fallbackPath: 'index.html',
14
+ };
15
+ expect(s.fallbackPath).toBe('index.html');
16
+ });
17
+
18
+ it('| should allow SPA cache options', () => {
19
+ const s: DyNTS_StaticClient_Settings = {
20
+ root: '/dist',
21
+ assetCacheMaxAge: 31536000,
22
+ assetCacheImmutable: true,
23
+ fallbackCacheMaxAge: 0,
24
+ };
25
+ expect(s.assetCacheMaxAge).toBe(31536000);
26
+ expect(s.assetCacheImmutable).toBe(true);
27
+ expect(s.fallbackCacheMaxAge).toBe(0);
28
+ });
29
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Beállítások a client static fájlok kiszolgálásához (pl. SPA) a '/' path alatt.
3
+ * Ha megadva, a getStaticClientSettings() által visszaadott értéket a base app
4
+ * a mountStaticClient() során használja (API route-ok után).
5
+ * A cache mezők SPA (pl. Angular) optimalizáláshoz opcionálisak; ha nincs kitöltve,
6
+ * viselkedés változatlan (alkalmas nem-SPA kiszolgálásra is).
7
+ */
8
+ export interface DyNTS_StaticClient_Settings {
9
+ /** A static fájlok könyvtárának path-ja (abszolút vagy process.cwd()-hez képest). */
10
+ root: string;
11
+ /** SPA fallback: ha a kérés nem egy fájlra mutat, ezt a fájlt szolgáljuk ki (pl. 'index.html'). */
12
+ fallbackPath?: string;
13
+ /**
14
+ * Static asset-ek (pl. JS, CSS) Cache-Control max-age másodpercben.
15
+ * SPA (Angular) hashed fájloknál pl. 1 év (31536000).
16
+ */
17
+ assetCacheMaxAge?: number;
18
+ /**
19
+ * Ha true és assetCacheMaxAge is megadva, Cache-Control-hoz hozzáadódik az immutable direktíva
20
+ * (Angular hashed fájlokra ideális).
21
+ */
22
+ assetCacheImmutable?: boolean;
23
+ /**
24
+ * Fallback (pl. index.html) Cache-Control max-age másodpercben.
25
+ * SPA esetén ajánlott 0, hogy a böngésző ne cache-elje az index.html-t (deploy után friss).
26
+ */
27
+ fallbackCacheMaxAge?: number;
28
+ }
@@ -96,7 +96,7 @@ export class DyNTS_AppExtendedFull_Mock extends DyNTS_AppExtended {
96
96
 
97
97
  getPortSettings(): DyNTS_Http_Settings {
98
98
  return {
99
- httpPort: 54534,
99
+ httpPort: 0,
100
100
  };
101
101
  }
102
102
 
@@ -167,7 +167,7 @@ export class DyNTS_AppWbMock_Mock extends DyNTS_AppExtended {
167
167
 
168
168
  getPortSettings(): DyNTS_Http_Settings {
169
169
  return {
170
- httpPort: 54535,
170
+ httpPort: 0,
171
171
  };
172
172
  }
173
173
 
@@ -0,0 +1,51 @@
1
+ import { DyNTS_App_Params } from '../../_models/control-models/app-params.control-model';
2
+ import { DyNTS_Http_Settings } from '../../_models/control-models/http-settings.control-model';
3
+ import { DyNTS_StaticClient_Settings } from '../../_models/interfaces/static-client-settings.interface';
4
+ import { DyNTS_AppWbMock_Mock } from './app-extended-server.mock';
5
+
6
+ /**
7
+ * Integrációs tesztekhez használt mock app: DB nélkül indul, unified host beállításokkal
8
+ * (static client root, API base path, socket path). A static root-ot a spec állítja
9
+ * beforeAll-ban az integrationStaticRoot statikus property-n keresztül.
10
+ */
11
+ export class DyNTS_AppIntegrationTest_Mock extends DyNTS_AppWbMock_Mock {
12
+
13
+ /** A spec beforeAll-ban beállítja; getStaticClientSettings() ezt használja root-ként. */
14
+ static integrationStaticRoot: string = '';
15
+
16
+ override getAppParams(): DyNTS_App_Params {
17
+ return new DyNTS_App_Params({
18
+ name: 'nts-integration-test',
19
+ version: '1.0.0',
20
+ systemShortCodeName: 'NIT',
21
+ });
22
+ }
23
+
24
+ override getPortSettings(): DyNTS_Http_Settings {
25
+ return {
26
+ httpPort: 0,
27
+ socketPath: '/socket',
28
+ };
29
+ }
30
+
31
+ /** Az OS által kiosztott tényleges port; ready() után elérhető. */
32
+ get resolvedPort(): number {
33
+ const addr = this.httpServer?.address?.();
34
+
35
+ return (typeof addr === 'object' && addr) ? addr.port : 0;
36
+ }
37
+
38
+ override getApiBasePath(): string {
39
+ return '/api';
40
+ }
41
+
42
+ override getStaticClientSettings(): DyNTS_StaticClient_Settings | undefined {
43
+ if (!DyNTS_AppIntegrationTest_Mock.integrationStaticRoot) {
44
+ return undefined;
45
+ }
46
+ return {
47
+ root: DyNTS_AppIntegrationTest_Mock.integrationStaticRoot,
48
+ fallbackPath: undefined,
49
+ };
50
+ }
51
+ }
@@ -85,7 +85,7 @@ export class DyNTS_AppFull_Mock extends DyNTS_App {
85
85
 
86
86
  getPortSettings(): DyNTS_Http_Settings {
87
87
  return {
88
- httpPort: 20101,
88
+ httpPort: 0,
89
89
  };
90
90
  }
91
91
 
@@ -172,7 +172,7 @@ export class DyNTS_AppTEST_Mock extends DyNTS_App {
172
172
 
173
173
  getPortSettings(): DyNTS_Http_Settings {
174
174
  return {
175
- httpPort: 10203,
175
+ httpPort: 0,
176
176
  };
177
177
  }
178
178
 
@@ -34,7 +34,7 @@ describe('| SocketClient_Mock', () => {
34
34
  expect(params).toEqual(jasmine.any(DyNTS_SocketClientService_Params));
35
35
  expect(params.service).toBe('-test-socket-target-');
36
36
  expect(params.name).toBe('socket-client-mock');
37
- expect(params.port).toBe(30303);
37
+ expect(params.port).toBe(0);
38
38
  expect(params.socketOptions.reconnection).toBeTrue();
39
39
  });
40
40
  });
@@ -34,7 +34,7 @@ export class SocketClient_Mock extends DyNTS_SocketClient_ServiceBase {
34
34
  return new DyNTS_SocketClientService_Params({
35
35
  service: '-test-socket-target-',
36
36
  name: 'socket-client-mock',
37
- port: 30303,
37
+ port: 0,
38
38
  socketOptions: {
39
39
  reconnection: true,
40
40
  },
@@ -20,7 +20,7 @@ describe('| DyNTS_SocketServer_Mock', () => {
20
20
 
21
21
  expect(params).toBeInstanceOf(DyNTS_SocketServerService_Params);
22
22
  expect(params.name).toBe('test');
23
- expect(params.port).toBe(19393); // Updated from 9393 to 19393
23
+ expect(params.port).toBe(0);
24
24
  });
25
25
 
26
26
  it('| should return an empty array for incoming events', () => {
@@ -1,6 +1,5 @@
1
1
  import { DyFM_SocketEvent } from '@futdevpro/fsm-dynamo/socket';
2
2
  import { Socket } from 'socket.io';
3
- import * as net from 'net';
4
3
 
5
4
  import {
6
5
  DyNTS_SocketPresence
@@ -10,28 +9,21 @@ import {
10
9
  } from '../socket/_models/socket-server-service-params.control-model';
11
10
  import { DyNTS_SocketServerService } from '../socket/_services/socket-server.service';
12
11
 
13
- /**
14
- * Utility function to find an available port starting from a base port
15
- */
16
- const findAvailablePort = (basePort: number): number => {
17
- return basePort; // For now, return the base port, but this could be enhanced
18
- };
19
-
20
12
  /**
21
13
  * Mock Socket Server Service for testing purposes
22
- * Uses a non-privileged port to avoid EACCES errors on Windows
14
+ * Uses port 0 so the OS assigns a random available port
23
15
  */
24
- export class DyNTS_SocketServer_Mock extends
16
+ export class DyNTS_SocketServer_Mock extends
25
17
  DyNTS_SocketServerService<DyNTS_SocketPresence> {
26
-
18
+
27
19
  static getInstance(): DyNTS_SocketServer_Mock {
28
20
  return DyNTS_SocketServer_Mock.getSingletonInstance();
29
21
  }
30
-
22
+
31
23
  getServiceParams(): DyNTS_SocketServerService_Params<any> {
32
24
  return new DyNTS_SocketServerService_Params({
33
25
  name: 'test',
34
- port: findAvailablePort(19393), // Changed from 9393 to 19393 (non-privileged port)
26
+ port: 0,
35
27
  });
36
28
  }
37
29
 
@@ -77,7 +77,7 @@ describe('| DyNTS_Errors_DataService', () => {
77
77
  const errorRecord = new TestErrors({
78
78
  level: DyFM_ErrorLevel.error,
79
79
  message: 'New error message',
80
- version: '1.0.0',
80
+ versions: [ '1.0.0' ],
81
81
  });
82
82
  service.data = errorRecord; // saveData() uses service.data
83
83
  mockDBService.findOne.and.returnValue(Promise.resolve(null));
@@ -93,13 +93,13 @@ describe('| DyNTS_Errors_DataService', () => {
93
93
  const errorRecord = new TestErrors({
94
94
  level: DyFM_ErrorLevel.error,
95
95
  message: 'Duplicate error message',
96
- version: '1.0.0',
96
+ versions: [ '1.0.0' ],
97
97
  });
98
98
  const duplicateError = new TestErrors({
99
99
  _id: 'duplicate-id',
100
100
  level: DyFM_ErrorLevel.error,
101
101
  message: 'Duplicate error message',
102
- version: '1.0.0',
102
+ versions: [ '1.0.0' ],
103
103
  count: 5,
104
104
  });
105
105
  mockDBService.findOne.and.returnValue(Promise.resolve(duplicateError));
@@ -115,7 +115,7 @@ describe('| DyNTS_Errors_DataService', () => {
115
115
  const errorRecord = new TestErrors({
116
116
  level: DyFM_ErrorLevel.error,
117
117
  message: 'Dev error',
118
- version: '1.0.0-dev',
118
+ versions: [ '1.0.0-dev' ],
119
119
  });
120
120
  service.data = errorRecord;
121
121
 
@@ -128,7 +128,7 @@ describe('| DyNTS_Errors_DataService', () => {
128
128
  const errorRecord = new TestErrors({
129
129
  level: DyFM_ErrorLevel.error,
130
130
  message: 'Dev error',
131
- version: '1.0.0-dev',
131
+ versions: [ '1.0.0-dev' ],
132
132
  });
133
133
  service.data = errorRecord; // saveData() uses service.data
134
134
  mockDBService.findOne.and.returnValue(Promise.resolve(null));
@@ -143,7 +143,7 @@ describe('| DyNTS_Errors_DataService', () => {
143
143
  const errorRecord = new TestErrors({
144
144
  level: DyFM_ErrorLevel.user,
145
145
  message: 'User error',
146
- version: '1.0.0',
146
+ versions: [ '1.0.0' ],
147
147
  });
148
148
 
149
149
  await service.recordError(errorRecord, 'test-issuer');
@@ -53,11 +53,12 @@ export class DyNTS_Errors_DataService<
53
53
  this.issuer = issuer;
54
54
  }
55
55
 
56
+ const errorVersion = this.data?.versions.length ? this.data?.versions[0] : ('SERVER-' + this.version);
56
57
  if (
57
58
  !alwaysRecord &&
58
- this.data?.version?.includes('dev') ||
59
- this.data?.version?.includes('alpha') ||
60
- this.data?.version?.includes('beta')
59
+ errorVersion.includes('dev') ||
60
+ errorVersion.includes('alpha') ||
61
+ errorVersion.includes('beta')
61
62
  ) {
62
63
  DyFM_Log.warn('error not saved (dev version)');
63
64
 
@@ -90,7 +91,6 @@ export class DyNTS_Errors_DataService<
90
91
  }
91
92
 
92
93
  const duplicateError: T_ErrorsRecord = await this.findData({
93
- version: errorsRecord.version,
94
94
  message: errorsRecord.message,
95
95
  } as DyFM_DBFilter<T_ErrorsRecord>, true);
96
96
 
@@ -128,11 +128,22 @@ export class DyNTS_Errors_DataService<
128
128
 
129
129
  this.duplicationCounter = duplicateError.count + 1;
130
130
 
131
+ const duplicateRecordUpdate: DyNTS_DBUpdate<T_ErrorsRecord> = {
132
+ $inc: {
133
+ count: 1,
134
+ priority: this.getPriorityMultiplierByLevel(errorsRecord?.level),
135
+ },
136
+ } as DyNTS_DBUpdate<T_ErrorsRecord>;
137
+
138
+ if (!errorsRecord.versions.includes(errorVersion)) {
139
+ duplicateRecordUpdate.$addToSet = {
140
+ versions: { $push: errorVersion },
141
+ };
142
+ }
143
+
131
144
  await this.updateData({
132
145
  filterBy: { _id: duplicateError._id } as DyFM_DBFilter<T_ErrorsRecord>,
133
- update: {
134
- $inc: { count: 1, priority: this.getPriorityMultiplierByLevel(errorsRecord?.level) },
135
- } as DyNTS_DBUpdate<T_ErrorsRecord>,
146
+ update: duplicateRecordUpdate,
136
147
  });
137
148
 
138
149
  // ezt majd ha leülepedett, hogy biztos nem kellenek a deep duplication-ök
@@ -166,9 +177,13 @@ export class DyNTS_Errors_DataService<
166
177
  if (this.debugLog) DyFM_Log.error('Error:', errorsRecord);
167
178
 
168
179
  errorsRecord.priority = this.getPriorityMultiplierByLevel(errorsRecord?.level);
180
+
169
181
  errorsRecord.duplications ??= [];
170
182
  errorsRecord.duplications.push(DyFM_Object.clone(errorsRecord));
171
183
 
184
+ errorsRecord.versions ??= [];
185
+ errorsRecord.versions.push(errorVersion);
186
+
172
187
  this.duplicationCounter = 1;
173
188
 
174
189
  await this.saveData();
@@ -200,7 +215,7 @@ export class DyNTS_Errors_DataService<
200
215
  if ((error as DyFM_Error)?.flag?.includes(DyFM_errorFlag)) {
201
216
  return new DyFM_Errors({
202
217
  issuer: this.issuer,
203
- version: 'SERVER-' + this.version,
218
+ versions: [ 'SERVER-' + this.version ],
204
219
 
205
220
  source: 'INTERNAL-SERVER-ERROR: ' + (error as DyFM_Error)?.___issuerService,
206
221
 
@@ -219,7 +234,7 @@ export class DyNTS_Errors_DataService<
219
234
  } else {
220
235
  return new DyFM_Errors({
221
236
  issuer: this.issuer,
222
- version: 'SERVER-' + this.version,
237
+ versions: [ 'SERVER-' + this.version ],
223
238
 
224
239
  source: 'UNEXPECTED-SERVER-ERROR',
225
240
 
@@ -508,8 +508,12 @@ export abstract class DyNTS_SocketServerService<
508
508
  await this.closeSocket(socket, `${this.params.name} on connection error`, error);
509
509
  }
510
510
  });
511
-
512
- if (options?.attachedToExistingServer) {
511
+
512
+ const isAttach: boolean =
513
+ options?.attachedToExistingServer === true ||
514
+ (this.params.port === undefined && options?.effectivePort !== undefined);
515
+
516
+ if (isAttach) {
513
517
  DyFM_Log.success(
514
518
  `\nsocket server setup finished: "${this.params.name}"` +
515
519
  (effectivePort !== undefined ? ` (attached to server on port: ${effectivePort})` : ' (attached to existing server)')
@@ -0,0 +1,85 @@
1
+ import * as Fs from 'fs';
2
+ import * as Os from 'os';
3
+ import * as Path from 'path';
4
+
5
+ import Axios from 'axios';
6
+
7
+ import { DyNTS_defaultNotFoundPageHtml } from '../../_collections/default-not-found-page.const';
8
+ import { DyNTS_AppIntegrationTest_Mock } from '../mock/app-integration-test.mock';
9
+
10
+ let INTEGRATION_BASE_URL: string = '';
11
+ const INTEGRATION_READY_TIMEOUT_MS: number = 10000;
12
+
13
+ describe('| DyNTS_AppExtended (integration)', (): void => {
14
+ beforeAll((): void => {
15
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = INTEGRATION_READY_TIMEOUT_MS + 5000;
16
+ });
17
+
18
+ let app: DyNTS_AppIntegrationTest_Mock;
19
+ let tempDir: string = '';
20
+
21
+ beforeAll(async (): Promise<void> => {
22
+ tempDir = Fs.mkdtempSync(Path.join(Os.tmpdir(), 'dynts-int-'));
23
+ const indexContent: string = '<!DOCTYPE html><html><body>Integration test index</body></html>';
24
+ Fs.writeFileSync(Path.join(tempDir, 'index.html'), indexContent, { encoding: 'utf-8' });
25
+
26
+ DyNTS_AppIntegrationTest_Mock.integrationStaticRoot = tempDir;
27
+ app = new DyNTS_AppIntegrationTest_Mock();
28
+ await app.ready(INTEGRATION_READY_TIMEOUT_MS);
29
+
30
+ INTEGRATION_BASE_URL = `http://localhost:${app.resolvedPort}`;
31
+ });
32
+
33
+ afterAll(async (): Promise<void> => {
34
+ try {
35
+ if (app?.started) {
36
+ await app.stop();
37
+ }
38
+ } catch {
39
+ // stop() hiba (pl. socket server már leállt) ne szakítsa meg a suite-ot
40
+ }
41
+ if (tempDir && Fs.existsSync(tempDir)) {
42
+ Fs.rmSync(tempDir, { recursive: true });
43
+ }
44
+ });
45
+
46
+ it('| app elindul és started true', (): void => {
47
+ expect(app.started).toBe(true);
48
+ });
49
+
50
+ it('| GET nem létező path -> 404 és default "Page not found" HTML', async (): Promise<void> => {
51
+ const response = await Axios.get(`${INTEGRATION_BASE_URL}/nonexistent-path-xyz`, {
52
+ validateStatus: (): boolean => true,
53
+ responseType: 'text',
54
+ });
55
+ expect(response.status).toBe(404);
56
+ expect(response.headers['content-type']).toContain('html');
57
+ expect(typeof response.data).toBe('string');
58
+ expect((response.data as string).trim()).toContain('Page not found');
59
+ expect(response.data).toBe(DyNTS_defaultNotFoundPageHtml);
60
+ });
61
+
62
+ it('| GET /index.html -> 200 és static tartalom', async (): Promise<void> => {
63
+ const response = await Axios.get(`${INTEGRATION_BASE_URL}/index.html`, {
64
+ responseType: 'text',
65
+ });
66
+ expect(response.status).toBe(200);
67
+ expect(response.data).toContain('Integration test index');
68
+ });
69
+
70
+ it('| GET API base path alatt (mock route) -> 200', async (): Promise<void> => {
71
+ const response = await Axios.get(`${INTEGRATION_BASE_URL}/api/test-0/test-base`, {
72
+ responseType: 'text',
73
+ });
74
+ expect(response.status).toBe(200);
75
+ expect(response.data).toBe('test-base');
76
+ });
77
+
78
+ it('| GET API másik endpoint -> 200', async (): Promise<void> => {
79
+ const response = await Axios.get(`${INTEGRATION_BASE_URL}/api/test-0/test-simple`, {
80
+ responseType: 'text',
81
+ });
82
+ expect(response.status).toBe(200);
83
+ expect(response.data).toBe('test-simple');
84
+ });
85
+ });
@@ -12,6 +12,7 @@ import {
12
12
  DyFM_Log,
13
13
  } from '@futdevpro/fsm-dynamo';
14
14
 
15
+ import { DyNTS_defaultSocketPath } from '../../_collections/default-socket-path.const';
15
16
  import { DyNTS_global_settings } from '../../_collections/global-settings.const';
16
17
  import { DyNTS_RouteSecurity } from '../../_enums/route-security.enum';
17
18
  import {
@@ -438,6 +439,7 @@ export abstract class DyNTS_AppExtended extends DyNTS_App {
438
439
  * ha a service.port nincs megadva, a getPortSettings() httpPort/httpsPort értékét használjuk.
439
440
  * Ha a base app már listenel (httpServer/httpsServer), arra attacholjuk a Socket.IO-t,
440
441
  * különben külön httpSocketServer-t hozunk létre és listenelünk.
442
+ * A Socket.IO path a portSettings.socketPath-ből jön (default: DyNTS_defaultSocketPath); a kliensnek ugyanazt kell használnia.
441
443
  */
442
444
  private async setupSocketServerServices(): Promise<void> {
443
445
  try {
@@ -519,9 +521,10 @@ export abstract class DyNTS_AppExtended extends DyNTS_App {
519
521
  this.httpSocketSettingUpCount++;
520
522
  this.systemControlsExt.httpSocketServer.started = false;
521
523
 
524
+ const socketPath: string = this.portSettings.socketPath ?? DyNTS_defaultSocketPath;
522
525
  this.allSocketServers.push(
523
526
  await service.setupSocketServer(
524
- new SocketIO.Server(serverForOpen),
527
+ new SocketIO.Server(serverForOpen, { path: socketPath }),
525
528
  DyNTS_SocketSecurity.open,
526
529
  (): void => {
527
530
  this.httpSocketSettingUpCount--;
@@ -546,9 +549,10 @@ export abstract class DyNTS_AppExtended extends DyNTS_App {
546
549
  this.httpsSocketSettingUpCount++;
547
550
  this.systemControlsExt.httpsSocketServer.started = false;
548
551
 
552
+ const socketPathSecure: string = this.portSettings.socketPath ?? DyNTS_defaultSocketPath;
549
553
  this.allSocketServers.push(
550
554
  await service.setupSocketServer(
551
- new SocketIO.Server(this.httpsServer),
555
+ new SocketIO.Server(this.httpsServer, { path: socketPathSecure }),
552
556
  DyNTS_SocketSecurity.secure,
553
557
  (): void => {
554
558
  this.httpsSocketSettingUpCount--;
@@ -1327,7 +1327,7 @@ export class DyNTS_DataService<T extends DyFM_Metadata> {
1327
1327
  ...this._getDefaultErrorSettings(
1328
1328
  'validateByPropertyParams',
1329
1329
  new Error(
1330
- 'Validation failed! (multiple validation errors)' +
1330
+ `Validation failed! (multiple validation errors for ${this.dataParams.dataName})` +
1331
1331
  validationErrors.map((error: DyFM_Error) => `\n ${DyFM_Error.getAnyMessage(error)}`).join('')
1332
1332
  )
1333
1333
  ),
@@ -1367,10 +1367,7 @@ export class DyNTS_DataService<T extends DyFM_Metadata> {
1367
1367
  ): void {
1368
1368
  // basic required validations
1369
1369
  if (
1370
- (
1371
- propertyParams.required ||
1372
- propertyParams.index
1373
- ) && (
1370
+ propertyParams.required && (
1374
1371
  propertyValue === null ||
1375
1372
  propertyValue === undefined
1376
1373
  )
@@ -1380,7 +1377,7 @@ export class DyNTS_DataService<T extends DyFM_Metadata> {
1380
1377
  'validateProperty',
1381
1378
  new Error(
1382
1379
  `validateProperty failed, "${this.getPropertyKey(propertyParams, isSubObjectOf)}" is missing! ` +
1383
- `(index or required in "${this.dataParams.dataName}" dataParams) ` +
1380
+ `(required in "${this.dataParams.dataName}" dataParams) ` +
1384
1381
  ((data as any)?.name ? `\n The failed data's name: ${(data as any).name}` : '')
1385
1382
  )
1386
1383
  ),
@@ -1391,7 +1388,7 @@ export class DyNTS_DataService<T extends DyFM_Metadata> {
1391
1388
  validationErrors: {
1392
1389
  [propertyParams.key]: {
1393
1390
  message: `validateProperty failed, "${this.getPropertyKey(propertyParams, isSubObjectOf)}" is missing! ` +
1394
- `(index or required in "${this.dataParams.dataName}" dataParams) ` +
1391
+ `(required in "${this.dataParams.dataName}" dataParams) ` +
1395
1392
  ((data as any)?.name ? `\n The failed data's name: ${(data as any).name}` : ''),
1396
1393
  code: `${this.ecBase}DyNTS-DS0-VP1`,
1397
1394
  userMessage: 'The property is required and must be a valid value!',