@futdevpro/nts-dynamo 1.15.81 → 1.15.83
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/.dynamo/logs/cicd-pipeline/output.log +1680 -1674
- package/.dynamo/logs/cicd-pipeline/status.json +40 -40
- package/build/_modules/server/errors/errors.controller.d.ts +15 -0
- package/build/_modules/server/errors/errors.controller.d.ts.map +1 -1
- package/build/_modules/server/errors/errors.controller.js +39 -12
- package/build/_modules/server/errors/errors.controller.js.map +1 -1
- package/build/_modules/server/server-status/server-status.controller.d.ts +8 -0
- package/build/_modules/server/server-status/server-status.controller.d.ts.map +1 -1
- package/build/_modules/server/server-status/server-status.controller.js +25 -5
- package/build/_modules/server/server-status/server-status.controller.js.map +1 -1
- package/build/_services/base/archive-data.service.d.ts.map +1 -1
- package/build/_services/base/archive-data.service.js +8 -0
- package/build/_services/base/archive-data.service.js.map +1 -1
- package/package.json +1 -1
- package/src/_modules/server/errors/errors.controller.spec.ts +48 -29
- package/src/_modules/server/errors/errors.controller.ts +38 -12
- package/src/_modules/server/server-status/server-status.controller.spec.ts +42 -35
- package/src/_modules/server/server-status/server-status.controller.ts +24 -5
- package/src/_services/base/archive-data.service.spec.ts +13 -0
- package/src/_services/base/archive-data.service.ts +11 -3
|
@@ -181,53 +181,72 @@ describe('| DyNTS_Errors_Controller', () => {
|
|
|
181
181
|
DyNTS_Errors_Controller._resetAuthConfigForTesting();
|
|
182
182
|
});
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
// FR-263 — a guard mostantol REQUEST-IDOBEN fut (lazy wrapper); igy MINDEN endpoint-on pontosan
|
|
185
|
+
// 1 preProcess (a wrapper) van, a tenyleges guard-logikat a wrapper MEGHIVASAVAL teszteljuk.
|
|
186
|
+
const lazyWrapperOf = (c: any, name: string): ((req: any, res: any) => Promise<void>) | undefined => {
|
|
187
|
+
const ep: any = c.endpoints.find((e: any) => e.name === name);
|
|
188
|
+
return ep?.preProcesses?.[0];
|
|
189
|
+
};
|
|
190
|
+
const mockReqRes = (): { req: any; res: any } => ({ req: {} as any, res: { headersSent: false } as any });
|
|
191
|
+
|
|
192
|
+
it('| default (no config): a lazy wrapper jelen van, de egy keres NEM hiv guardot (nyitva marad)', async (): Promise<void> => {
|
|
185
193
|
controller.setupEndpoints();
|
|
186
|
-
for (const
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
194
|
+
for (const name of ['logError', 'getErrors', 'searchErrors']) {
|
|
195
|
+
const w = lazyWrapperOf(controller, name);
|
|
196
|
+
expect(w).withContext(`${name} wrapper`).toBeDefined();
|
|
197
|
+
const { req, res } = mockReqRes();
|
|
198
|
+
await w!(req, res); // nincs config → no-op, nem dob, nem blokkol
|
|
190
199
|
}
|
|
191
200
|
});
|
|
192
201
|
|
|
193
|
-
it('| configure({ authPreProcess }) →
|
|
194
|
-
|
|
195
|
-
DyNTS_Errors_Controller.configure({ authPreProcess:
|
|
196
|
-
// Uj controller instance kell — a setupEndpoints olvassa a static config-ot
|
|
202
|
+
it('| configure({ authPreProcess }) → egy keres MINDEN built-in endpoint-on meghivja a guardot', async (): Promise<void> => {
|
|
203
|
+
let calls: number = 0;
|
|
204
|
+
DyNTS_Errors_Controller.configure({ authPreProcess: async (): Promise<void> => { calls++; } });
|
|
197
205
|
const c = new (TestErrorsController as any)();
|
|
198
206
|
c.setupEndpoints();
|
|
199
|
-
|
|
200
207
|
const builtIn: string[] = [
|
|
201
|
-
'logError', 'newError',
|
|
202
|
-
'
|
|
203
|
-
'getErrors', 'getErrorsPaged', 'getLastErrors',
|
|
204
|
-
'searchErrors',
|
|
208
|
+
'logError', 'newError', 'markErrorDone', 'markAllErrorDone',
|
|
209
|
+
'getErrors', 'getErrorsPaged', 'getLastErrors', 'searchErrors',
|
|
205
210
|
];
|
|
206
211
|
for (const name of builtIn) {
|
|
207
|
-
const
|
|
208
|
-
expect(
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
expect(pre[0]).toBe(fakeAuth);
|
|
212
|
+
const w = lazyWrapperOf(c, name);
|
|
213
|
+
expect(w).withContext(`endpoint ${name} missing`).toBeDefined();
|
|
214
|
+
const { req, res } = mockReqRes();
|
|
215
|
+
await w!(req, res);
|
|
212
216
|
}
|
|
217
|
+
expect(calls).toBe(builtIn.length);
|
|
213
218
|
});
|
|
214
219
|
|
|
215
|
-
it('| configure({
|
|
216
|
-
|
|
220
|
+
it('| configure({ protectedEndpoints: subset }) → a guard CSAK a listazott endpoint-okon fut', async (): Promise<void> => {
|
|
221
|
+
let calls: number = 0;
|
|
217
222
|
DyNTS_Errors_Controller.configure({
|
|
218
|
-
authPreProcess:
|
|
223
|
+
authPreProcess: async (): Promise<void> => { calls++; },
|
|
219
224
|
protectedEndpoints: ['markAllErrorDone', 'logError'],
|
|
220
225
|
});
|
|
221
226
|
const c = new (TestErrorsController as any)();
|
|
222
227
|
c.setupEndpoints();
|
|
228
|
+
for (const name of ['markAllErrorDone', 'logError']) {
|
|
229
|
+
const { req, res } = mockReqRes();
|
|
230
|
+
await lazyWrapperOf(c, name)!(req, res);
|
|
231
|
+
}
|
|
232
|
+
expect(calls).withContext('protected → guard runs').toBe(2);
|
|
233
|
+
calls = 0;
|
|
234
|
+
const { req, res } = mockReqRes();
|
|
235
|
+
await lazyWrapperOf(c, 'getErrors')!(req, res);
|
|
236
|
+
expect(calls).withContext('non-protected getErrors → guard does NOT run').toBe(0);
|
|
237
|
+
});
|
|
223
238
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
239
|
+
it('| FR-263 ORDER-INDEPENDENCE: setupEndpoints() ELOTT epitett endpoint is enforce-olja a guardot (a fix lenyege)', async (): Promise<void> => {
|
|
240
|
+
// A framework korai singleton-konstrukcioja: az endpoints config NELKUL epul fel...
|
|
241
|
+
const c = new (TestErrorsController as any)();
|
|
242
|
+
c.setupEndpoints();
|
|
243
|
+
// ...es a configure() KESOBB fut (ez volt a fals-200 info-disclosure bug). A lazy wrapper
|
|
244
|
+
// request-idoben olvassa a configot → a guard MEGIS lefut. Ez az FR-263 regresszio-guard.
|
|
245
|
+
let calls: number = 0;
|
|
246
|
+
DyNTS_Errors_Controller.configure({ authPreProcess: async (): Promise<void> => { calls++; } });
|
|
247
|
+
const { req, res } = mockReqRes();
|
|
248
|
+
await lazyWrapperOf(c, 'getErrors')!(req, res);
|
|
249
|
+
expect(calls).withContext('guard must run despite configure() AFTER setupEndpoints').toBe(1);
|
|
231
250
|
});
|
|
232
251
|
|
|
233
252
|
it('| getAuthConfig() returns the active config', (): void => {
|
|
@@ -145,6 +145,32 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
145
145
|
return protectedNames.includes(endpointName) ? [cfg.authPreProcess] : [];
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/**
|
|
149
|
+
* FR-263 (2026-06-26) — LAZY, REQUEST-IDŐBEN kiértékelt guard-wrapper.
|
|
150
|
+
*
|
|
151
|
+
* GYÖKÉR-OK: a `setupEndpoints()` a controller ELSŐ konstrukciójakor fut, ami a built-in
|
|
152
|
+
* error-controllernél a host-app `configure()`-ja ELŐTT történhet (a framework korán példányosítja
|
|
153
|
+
* a singletont). Ilyenkor a `getPreProcessesFor()` még az ÜRES configot olvasta → a guard SOHA nem
|
|
154
|
+
* kötődött → az admin error-read endpoint-ok (`get-range`, `get-paged`, …) token nélkül **200**-at
|
|
155
|
+
* adtak vissza, kiszivárogtatva a belső error-logot (info-disclosure). A 39 másik (controller-szinten
|
|
156
|
+
* HARDCODE-olt) guard azért működött, mert nem függ a `configure()` időzítésétől.
|
|
157
|
+
*
|
|
158
|
+
* FIX: a `preProcesses` egy wrapper, ami a configot KÉRÉS-IDŐBEN olvassa (`getPreProcessesFor`), így
|
|
159
|
+
* a `configure()` SORREND-FÜGGETLEN — bármikor hívható az első kérés előtt. A 401-et a VALÓDI guard
|
|
160
|
+
* küldi; `res.headersSent`-re leállunk (nincs dupla-send), és nem-védett endpointnál no-op (átenged).
|
|
161
|
+
*/
|
|
162
|
+
protected lazyPreProcessesFor(endpointName: string): ((req: Request, res: Response) => Promise<void>)[] {
|
|
163
|
+
return [
|
|
164
|
+
async (req: Request, res: Response): Promise<void> => {
|
|
165
|
+
const guards: ((req: Request, res: Response) => Promise<void>)[] = this.getPreProcessesFor(endpointName);
|
|
166
|
+
for (const guard of guards) {
|
|
167
|
+
if (res.headersSent) { return; }
|
|
168
|
+
await guard(req, res);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
|
|
148
174
|
setupEndpoints(): void {
|
|
149
175
|
/* if (!this.getErrorDataService) {
|
|
150
176
|
throw new DyFM_Error({
|
|
@@ -165,7 +191,7 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
165
191
|
name: 'logError',
|
|
166
192
|
type: DyFM_HttpCallType.post,
|
|
167
193
|
endpoint: '/error/log',
|
|
168
|
-
preProcesses: this.
|
|
194
|
+
preProcesses: this.lazyPreProcessesFor('logError'),
|
|
169
195
|
tasks: [
|
|
170
196
|
async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
|
|
171
197
|
const error_CS: T_Error_ControlService = this.getError_ControlService({
|
|
@@ -184,7 +210,7 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
184
210
|
name: 'newError',
|
|
185
211
|
type: DyFM_HttpCallType.post,
|
|
186
212
|
endpoint: '/error/new',
|
|
187
|
-
preProcesses: this.
|
|
213
|
+
preProcesses: this.lazyPreProcessesFor('newError'),
|
|
188
214
|
tasks: [
|
|
189
215
|
async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
|
|
190
216
|
const error_CS: T_Error_ControlService = this.getError_ControlService({
|
|
@@ -204,7 +230,7 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
204
230
|
name: 'markErrorDone',
|
|
205
231
|
type: DyFM_HttpCallType.get,
|
|
206
232
|
endpoint: '/error/mark-done/:errorId',
|
|
207
|
-
preProcesses: this.
|
|
233
|
+
preProcesses: this.lazyPreProcessesFor('markErrorDone'),
|
|
208
234
|
tasks: [
|
|
209
235
|
async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
|
|
210
236
|
const error_CS: T_Error_ControlService = this.getError_ControlService({
|
|
@@ -224,7 +250,7 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
224
250
|
name: 'markAllErrorDone',
|
|
225
251
|
type: DyFM_HttpCallType.get,
|
|
226
252
|
endpoint: '/error/mark-all-done',
|
|
227
|
-
preProcesses: this.
|
|
253
|
+
preProcesses: this.lazyPreProcessesFor('markAllErrorDone'),
|
|
228
254
|
tasks: [
|
|
229
255
|
async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
|
|
230
256
|
const error_CS: T_Error_ControlService = this.getError_ControlService({
|
|
@@ -244,7 +270,7 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
244
270
|
name: 'recordFixAttempt',
|
|
245
271
|
type: DyFM_HttpCallType.post,
|
|
246
272
|
endpoint: '/error/:errorId/fix-attempt',
|
|
247
|
-
preProcesses: this.
|
|
273
|
+
preProcesses: this.lazyPreProcessesFor('recordFixAttempt'),
|
|
248
274
|
tasks: [
|
|
249
275
|
async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
|
|
250
276
|
const errorId: string = req.params.errorId;
|
|
@@ -274,7 +300,7 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
274
300
|
name: 'resolveError',
|
|
275
301
|
type: DyFM_HttpCallType.post,
|
|
276
302
|
endpoint: '/error/:errorId/resolve',
|
|
277
|
-
preProcesses: this.
|
|
303
|
+
preProcesses: this.lazyPreProcessesFor('resolveError'),
|
|
278
304
|
tasks: [
|
|
279
305
|
async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
|
|
280
306
|
const errorId: string = req.params.errorId;
|
|
@@ -321,7 +347,7 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
321
347
|
name: 'getErrors',
|
|
322
348
|
type: DyFM_HttpCallType.get,
|
|
323
349
|
endpoint: '/error/get-range/:range',
|
|
324
|
-
preProcesses: this.
|
|
350
|
+
preProcesses: this.lazyPreProcessesFor('getErrors'),
|
|
325
351
|
tasks: [
|
|
326
352
|
async (req: Request, res: Response, issuer: string = 'unknown-issuer') : Promise<void> => {
|
|
327
353
|
const error_CS: T_Error_ControlService = this.getError_ControlService({
|
|
@@ -342,7 +368,7 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
342
368
|
name: 'getErrorsPaged',
|
|
343
369
|
type: DyFM_HttpCallType.get,
|
|
344
370
|
endpoint: '/error/get-paged/:range/:pageSize/:pageIndex',
|
|
345
|
-
preProcesses: this.
|
|
371
|
+
preProcesses: this.lazyPreProcessesFor('getErrorsPaged'),
|
|
346
372
|
tasks: [
|
|
347
373
|
async (req: Request, res: Response, issuer: string = 'unknown-issuer') : Promise<void> => {
|
|
348
374
|
const error_CS: T_Error_ControlService = this.getError_ControlService({
|
|
@@ -374,7 +400,7 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
374
400
|
name: 'getErrorsByCategoryPaged',
|
|
375
401
|
type: DyFM_HttpCallType.get,
|
|
376
402
|
endpoint: '/error/get-by-category/:category/:range/:pageSize/:pageIndex',
|
|
377
|
-
preProcesses: this.
|
|
403
|
+
preProcesses: this.lazyPreProcessesFor('getErrorsByCategoryPaged'),
|
|
378
404
|
tasks: [
|
|
379
405
|
async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
|
|
380
406
|
const error_CS: T_Error_ControlService = this.getError_ControlService({ issuer });
|
|
@@ -403,7 +429,7 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
403
429
|
name: 'getErrorsByStatusPaged',
|
|
404
430
|
type: DyFM_HttpCallType.get,
|
|
405
431
|
endpoint: '/error/get-by-status/:status/:range/:pageSize/:pageIndex',
|
|
406
|
-
preProcesses: this.
|
|
432
|
+
preProcesses: this.lazyPreProcessesFor('getErrorsByStatusPaged'),
|
|
407
433
|
tasks: [
|
|
408
434
|
async (req: Request, res: Response, issuer: string = 'unknown-issuer'): Promise<void> => {
|
|
409
435
|
const error_CS: T_Error_ControlService = this.getError_ControlService({ issuer });
|
|
@@ -432,7 +458,7 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
432
458
|
name: 'getLastErrors',
|
|
433
459
|
type: DyFM_HttpCallType.get,
|
|
434
460
|
endpoint: '/error/get-last-paged/:range/:pageSize/:pageIndex',
|
|
435
|
-
preProcesses: this.
|
|
461
|
+
preProcesses: this.lazyPreProcessesFor('getLastErrors'),
|
|
436
462
|
tasks: [
|
|
437
463
|
async (req: Request, res: Response, issuer: string = 'unknown-issuer') : Promise<void> => {
|
|
438
464
|
const error_CS: T_Error_ControlService = this.getError_ControlService({
|
|
@@ -464,7 +490,7 @@ export abstract class DyNTS_Errors_Controller<
|
|
|
464
490
|
name: 'searchErrors',
|
|
465
491
|
type: DyFM_HttpCallType.get,
|
|
466
492
|
endpoint: '/error/search',
|
|
467
|
-
preProcesses: this.
|
|
493
|
+
preProcesses: this.lazyPreProcessesFor('searchErrors'),
|
|
468
494
|
tasks: [
|
|
469
495
|
async (req: Request, res: Response, issuer: string = 'unknown-issuer') : Promise<void> => {
|
|
470
496
|
const error_CS: T_Error_ControlService = this.getError_ControlService({
|
|
@@ -172,62 +172,69 @@ describe('| DyNTS_ServerStatus_Controller', () => {
|
|
|
172
172
|
DyNTS_ServerStatus_Controller._resetAuthConfigForTesting();
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
// FR-263 — a guard request-idoben fut (lazy wrapper). "Gated-e egy endpoint" = a wrapper
|
|
176
|
+
// meghivasa lefuttatja-e a configured authPreProcess-t. A counter-t a teszt-spy lepteti.
|
|
177
|
+
let guardCalls: number = 0;
|
|
178
|
+
const spyAuth = async (): Promise<void> => { guardCalls++; };
|
|
179
|
+
const runGuard = async (c: any, name: string): Promise<void> => {
|
|
180
|
+
const ep: any = c.endpoints.find((e: any) => e.name === name);
|
|
181
|
+
await ep?.preProcesses?.[0]?.({} as any, { headersSent: false } as any);
|
|
178
182
|
};
|
|
183
|
+
beforeEach((): void => { guardCalls = 0; });
|
|
179
184
|
|
|
180
|
-
it('| default:
|
|
185
|
+
it('| default (no config): a wrapper jelen van, de egy keres NEM hiv guardot (nyitva marad)', async (): Promise<void> => {
|
|
181
186
|
controller.setupEndpoints();
|
|
182
187
|
for (const ep of controller.endpoints) {
|
|
183
|
-
|
|
184
|
-
expect(pre === undefined || pre.length === 0).toBe(true);
|
|
188
|
+
await (ep as any).preProcesses?.[0]?.({} as any, { headersSent: false } as any); // no-op, nem dob
|
|
185
189
|
}
|
|
190
|
+
expect(guardCalls).toBe(0);
|
|
186
191
|
});
|
|
187
192
|
|
|
188
|
-
it('| configure({ authPreProcess }) →
|
|
189
|
-
|
|
190
|
-
DyNTS_ServerStatus_Controller.configure({ authPreProcess: fakeAuth });
|
|
193
|
+
it('| configure({ authPreProcess }) → CSAK getErrorStatistics-en fut a guard, a status-vegpontokon NEM', async (): Promise<void> => {
|
|
194
|
+
DyNTS_ServerStatus_Controller.configure({ authPreProcess: spyAuth });
|
|
191
195
|
const c: any = new (TestServerStatusController as any)();
|
|
192
196
|
c.setupEndpoints();
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
expect(
|
|
200
|
-
expect(preCount(c, 'getServerStatusForClient')).toBe(0);
|
|
197
|
+
await runGuard(c, 'getErrorStatistics');
|
|
198
|
+
expect(guardCalls).withContext('admin-adat → gated').toBe(1);
|
|
199
|
+
guardCalls = 0;
|
|
200
|
+
for (const name of ['getServerStatus', 'getServerHealth', 'getServerReadiness', 'getServerStatusForClient']) {
|
|
201
|
+
await runGuard(c, name);
|
|
202
|
+
}
|
|
203
|
+
expect(guardCalls).withContext('status-probak SOHA nem gated').toBe(0);
|
|
201
204
|
});
|
|
202
205
|
|
|
203
|
-
it('| a status-vegpontokat
|
|
204
|
-
const fakeAuth = async (): Promise<void> => { /* noop */ };
|
|
206
|
+
it('| a status-vegpontokat explicit protectedEndpoints-lista SEM gate-eli (PUBLIC_PROBE kizart)', async (): Promise<void> => {
|
|
205
207
|
DyNTS_ServerStatus_Controller.configure({
|
|
206
|
-
authPreProcess:
|
|
208
|
+
authPreProcess: spyAuth,
|
|
207
209
|
protectedEndpoints: [ 'getServerHealth', 'getServerReadiness', 'getServerStatusForClient', 'getServerStatus', 'getErrorStatistics' ],
|
|
208
210
|
});
|
|
209
211
|
const c: any = new (TestServerStatusController as any)();
|
|
210
212
|
c.setupEndpoints();
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
expect(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
expect(preCount(c, 'getErrorStatistics')).toBe(1);
|
|
213
|
+
for (const name of ['getServerStatus', 'getServerHealth', 'getServerReadiness', 'getServerStatusForClient']) {
|
|
214
|
+
await runGuard(c, name);
|
|
215
|
+
}
|
|
216
|
+
expect(guardCalls).withContext('status-probak kizartak meg explicit listan is').toBe(0);
|
|
217
|
+
await runGuard(c, 'getErrorStatistics');
|
|
218
|
+
expect(guardCalls).withContext('getErrorStatistics gated').toBe(1);
|
|
218
219
|
});
|
|
219
220
|
|
|
220
|
-
it('| protectedEndpoints subset →
|
|
221
|
-
|
|
222
|
-
DyNTS_ServerStatus_Controller.configure({
|
|
223
|
-
authPreProcess: fakeAuth,
|
|
224
|
-
protectedEndpoints: [ 'getErrorStatistics' ],
|
|
225
|
-
});
|
|
221
|
+
it('| protectedEndpoints subset → getErrorStatistics gated, getServerStatus NEM', async (): Promise<void> => {
|
|
222
|
+
DyNTS_ServerStatus_Controller.configure({ authPreProcess: spyAuth, protectedEndpoints: [ 'getErrorStatistics' ] });
|
|
226
223
|
const c: any = new (TestServerStatusController as any)();
|
|
227
224
|
c.setupEndpoints();
|
|
225
|
+
await runGuard(c, 'getErrorStatistics');
|
|
226
|
+
expect(guardCalls).toBe(1);
|
|
227
|
+
guardCalls = 0;
|
|
228
|
+
await runGuard(c, 'getServerStatus');
|
|
229
|
+
expect(guardCalls).toBe(0);
|
|
230
|
+
});
|
|
228
231
|
|
|
229
|
-
|
|
230
|
-
|
|
232
|
+
it('| FR-263 ORDER-INDEPENDENCE: setupEndpoints() ELOTT epitett endpoint is enforce-olja a guardot', async (): Promise<void> => {
|
|
233
|
+
const c: any = new (TestServerStatusController as any)();
|
|
234
|
+
c.setupEndpoints(); // config NELKUL (korai framework-konstrukcio szimulacioja)
|
|
235
|
+
DyNTS_ServerStatus_Controller.configure({ authPreProcess: spyAuth }); // configure UTANA
|
|
236
|
+
await runGuard(c, 'getErrorStatistics');
|
|
237
|
+
expect(guardCalls).withContext('guard runs despite configure() AFTER setupEndpoints').toBe(1);
|
|
231
238
|
});
|
|
232
239
|
|
|
233
240
|
it('| getAuthConfig() returns the active config', (): void => {
|
|
@@ -151,6 +151,25 @@ export abstract class DyNTS_ServerStatus_Controller<
|
|
|
151
151
|
return protectedNames.includes(endpointName) ? [cfg.authPreProcess] : [];
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* FR-263 (2026-06-26) — LAZY, REQUEST-IDŐBEN kiértékelt guard-wrapper (lásd a DyNTS_Errors_Controller
|
|
156
|
+
* azonos fixét). A `setupEndpoints()` a host-app `configure()`-ja ELŐTT futhat (korai singleton-
|
|
157
|
+
* konstrukció) → a guard ürès configgal kötődött → a `getServerStatus`/`getErrorStatistics` admin-adat
|
|
158
|
+
* token nélkül 200-at adott (info-disclosure). A wrapper a configot KÉRÉS-IDŐBEN olvassa → sorrend-
|
|
159
|
+
* független. A 401-et a valódi guard küldi; `headersSent`-re leállunk; nem-védett endpointnál no-op.
|
|
160
|
+
*/
|
|
161
|
+
protected lazyPreProcessesFor(endpointName: string): ((req: Request, res: Response) => Promise<void>)[] {
|
|
162
|
+
return [
|
|
163
|
+
async (req: Request, res: Response): Promise<void> => {
|
|
164
|
+
const guards: ((req: Request, res: Response) => Promise<void>)[] = this.getPreProcessesFor(endpointName);
|
|
165
|
+
for (const guard of guards) {
|
|
166
|
+
if (res.headersSent) { return; }
|
|
167
|
+
await guard(req, res);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
|
|
154
173
|
setupEndpoints(): void {
|
|
155
174
|
/* if (!this.getServerService) {
|
|
156
175
|
throw new DyFM_Error({
|
|
@@ -173,7 +192,7 @@ export abstract class DyNTS_ServerStatus_Controller<
|
|
|
173
192
|
name: 'getServerStatus',
|
|
174
193
|
type: DyFM_HttpCallType.get,
|
|
175
194
|
endpoint: '/status',
|
|
176
|
-
preProcesses: this.
|
|
195
|
+
preProcesses: this.lazyPreProcessesFor('getServerStatus'),
|
|
177
196
|
tasks: [
|
|
178
197
|
async (req: Request, res: Response, issuer: string): Promise<void> => {
|
|
179
198
|
res.send(
|
|
@@ -187,7 +206,7 @@ export abstract class DyNTS_ServerStatus_Controller<
|
|
|
187
206
|
name: 'getServerHealth',
|
|
188
207
|
type: DyFM_HttpCallType.get,
|
|
189
208
|
endpoint: '/health',
|
|
190
|
-
preProcesses: this.
|
|
209
|
+
preProcesses: this.lazyPreProcessesFor('getServerHealth'),
|
|
191
210
|
tasks: [
|
|
192
211
|
async (req: Request, res: Response, issuer: string): Promise<void> => {
|
|
193
212
|
res.send(
|
|
@@ -203,7 +222,7 @@ export abstract class DyNTS_ServerStatus_Controller<
|
|
|
203
222
|
name: 'getServerReadiness',
|
|
204
223
|
type: DyFM_HttpCallType.get,
|
|
205
224
|
endpoint: '/readiness',
|
|
206
|
-
preProcesses: this.
|
|
225
|
+
preProcesses: this.lazyPreProcessesFor('getServerReadiness'),
|
|
207
226
|
tasks: [
|
|
208
227
|
async (req: Request, res: Response, issuer: string): Promise<void> => {
|
|
209
228
|
const readiness: DyNTS_DbReadiness = await this.server_CS.checkDbReadiness();
|
|
@@ -217,7 +236,7 @@ export abstract class DyNTS_ServerStatus_Controller<
|
|
|
217
236
|
name: 'getServerStatusForClient',
|
|
218
237
|
type: DyFM_HttpCallType.get,
|
|
219
238
|
endpoint: '/status/:version',
|
|
220
|
-
preProcesses: this.
|
|
239
|
+
preProcesses: this.lazyPreProcessesFor('getServerStatusForClient'),
|
|
221
240
|
tasks: [
|
|
222
241
|
async (req: Request, res: Response, issuer: string): Promise<void> => {
|
|
223
242
|
|
|
@@ -232,7 +251,7 @@ export abstract class DyNTS_ServerStatus_Controller<
|
|
|
232
251
|
name: 'getErrorStatistics',
|
|
233
252
|
type: DyFM_HttpCallType.get,
|
|
234
253
|
endpoint: '/statistics/error/:range',
|
|
235
|
-
preProcesses: this.
|
|
254
|
+
preProcesses: this.lazyPreProcessesFor('getErrorStatistics'),
|
|
236
255
|
tasks: [
|
|
237
256
|
async (req: Request, res: Response, issuer: string) : Promise<void> => {
|
|
238
257
|
|
|
@@ -58,6 +58,19 @@ describe('| DyNTS_ArchiveDataService', () => {
|
|
|
58
58
|
expect(service.dataParams.dataName).toBe('test_data_archived');
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
it('| should NOT inherit haveArchiveDataService (an archive has no archive-of-itself)', () => {
|
|
62
|
+
// Regression guard: the archive service must NOT think it has its own archive, otherwise base
|
|
63
|
+
// deleteData()/loadData() call the unimplemented getArchiveDataService() and throw
|
|
64
|
+
// "getArchiveDataService is not implemented!" — which spun the overseer archive-rotation in a
|
|
65
|
+
// tight retry loop ("Failed to delete old archive"). The constructor forces addArchive:false.
|
|
66
|
+
const data = new TestMetadata();
|
|
67
|
+
|
|
68
|
+
const service = new DyNTS_ArchiveDataService(data, mockDataParams, 'issuer-123');
|
|
69
|
+
|
|
70
|
+
expect(service.haveArchiveDataService).toBe(false);
|
|
71
|
+
expect(service.dataParams.addArchive).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
61
74
|
describe('| archiveDataById', () => {
|
|
62
75
|
it('| should throw error when id is missing', async () => {
|
|
63
76
|
const data = new TestMetadata();
|
|
@@ -42,12 +42,20 @@ export class DyNTS_ArchiveDataService<T extends DyFM_Metadata> extends DyNTS_Dat
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const originalDataParams = { ...dataParams };
|
|
45
|
-
|
|
46
|
-
dataParams = {
|
|
45
|
+
|
|
46
|
+
dataParams = {
|
|
47
47
|
...dataParams,
|
|
48
48
|
dataName: DyNTS_getArchivedDBName(dataParams.dataName),
|
|
49
|
+
// An archive collection has NO archive-of-itself. Without this the archive service would inherit
|
|
50
|
+
// the parent's `addArchive: true` → `haveArchiveDataService = true` → base deleteData()/loadData()
|
|
51
|
+
// would call the (unimplemented) getArchiveDataService() and THROW
|
|
52
|
+
// "getArchiveDataService is not implemented!". That broke archive-rotation hard-deletes (e.g. the
|
|
53
|
+
// overseer build-report archive trim, which spun in a tight retry loop logging
|
|
54
|
+
// "Failed to delete old archive"). Forcing it false makes deleteData(id, true) hard-delete the
|
|
55
|
+
// archive record (the `absolute` branch), as intended.
|
|
56
|
+
addArchive: false,
|
|
49
57
|
};
|
|
50
|
-
|
|
58
|
+
|
|
51
59
|
super(data, dataParams, issuer);
|
|
52
60
|
|
|
53
61
|
this.originalDBService = DyNTS_GlobalService.getDBService<T>(originalDataParams);
|