@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.
@@ -181,53 +181,72 @@ describe('| DyNTS_Errors_Controller', () => {
181
181
  DyNTS_Errors_Controller._resetAuthConfigForTesting();
182
182
  });
183
183
 
184
- it('| default: NO preProcesses on any endpoint (backwards compatible)', (): void => {
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 ep of controller.endpoints) {
187
- const pre: any = (ep as any).preProcesses;
188
- // Ures array vagy undefined elfogadhato — fontos hogy NE legyen auth
189
- expect(pre === undefined || pre.length === 0).toBe(true);
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 }) → mind a 8 endpoint kap preProcesses-t', (): void => {
194
- const fakeAuth = async (): Promise<void> => { /* noop */ };
195
- DyNTS_Errors_Controller.configure({ authPreProcess: fakeAuth });
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
- 'markErrorDone', 'markAllErrorDone',
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 ep: any = c.endpoints.find((e: any) => e.name === name);
208
- expect(ep).withContext(`endpoint ${name} missing`).toBeDefined();
209
- const pre: any = (ep as any).preProcesses;
210
- expect(pre.length).withContext(`endpoint ${name} preProcesses`).toBe(1);
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({ authPreProcess, protectedEndpoints: subset }) → csak a megadott neveken aktiv', (): void => {
216
- const fakeAuth = async (): Promise<void> => { /* noop */ };
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: fakeAuth,
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
- const markAll: any = c.endpoints.find((e: any) => e.name === 'markAllErrorDone');
225
- const log: any = c.endpoints.find((e: any) => e.name === 'logError');
226
- const getErrors: any = c.endpoints.find((e: any) => e.name === 'getErrors');
227
-
228
- expect((markAll as any).preProcesses.length).toBe(1);
229
- expect((log as any).preProcesses.length).toBe(1);
230
- expect((getErrors as any).preProcesses?.length ?? 0).toBe(0);
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.getPreProcessesFor('logError'),
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.getPreProcessesFor('newError'),
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.getPreProcessesFor('markErrorDone'),
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.getPreProcessesFor('markAllErrorDone'),
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.getPreProcessesFor('recordFixAttempt'),
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.getPreProcessesFor('resolveError'),
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.getPreProcessesFor('getErrors'),
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.getPreProcessesFor('getErrorsPaged'),
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.getPreProcessesFor('getErrorsByCategoryPaged'),
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.getPreProcessesFor('getErrorsByStatusPaged'),
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.getPreProcessesFor('getLastErrors'),
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.getPreProcessesFor('searchErrors'),
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
- const preCount = (controller: any, name: string): number => {
176
- const ep: any = controller.endpoints.find((e: any) => e.name === name);
177
- return (ep?.preProcesses ?? []).length;
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: NO preProcesses on any endpoint (backwards compatible)', (): void => {
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
- const pre: any = (ep as any).preProcesses;
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 }) → csak getErrorStatistics gated, a status-vegpontok NEM', (): void => {
189
- const fakeAuth = async (): Promise<void> => { /* noop */ };
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
- // admin-adat → gated
195
- expect(preCount(c, 'getErrorStatistics')).toBe(1);
196
- // status-vegpontok (a kliens-indikator hivja, /health-tel adat-ekvivalens) → SOHA nem gated
197
- expect(preCount(c, 'getServerStatus')).toBe(0);
198
- expect(preCount(c, 'getServerHealth')).toBe(0);
199
- expect(preCount(c, 'getServerReadiness')).toBe(0);
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 (getServerStatus is) explicit protectedEndpoints-lista SEM gate-eli', (): void => {
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: fakeAuth,
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
- expect(preCount(c, 'getServerStatus')).toBe(0);
213
- expect(preCount(c, 'getServerHealth')).toBe(0);
214
- expect(preCount(c, 'getServerReadiness')).toBe(0);
215
- expect(preCount(c, 'getServerStatusForClient')).toBe(0);
216
- // a valodi admin-adat viszont gated
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 → csak a megadott (nem-proba) neveken aktiv', (): void => {
221
- const fakeAuth = async (): Promise<void> => { /* noop */ };
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
- expect(preCount(c, 'getErrorStatistics')).toBe(1);
230
- expect(preCount(c, 'getServerStatus')).toBe(0);
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.getPreProcessesFor('getServerStatus'),
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.getPreProcessesFor('getServerHealth'),
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.getPreProcessesFor('getServerReadiness'),
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.getPreProcessesFor('getServerStatusForClient'),
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.getPreProcessesFor('getErrorStatistics'),
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);