import { DyNTS_ServerStatus_Controller } from './server-status.controller'; import { DyFM_ServerStatus, DyFM_Error, DyFM_Errors, DyFM_HttpCallType, DyFM_RelativeDate } from '@futdevpro/fsm-dynamo'; import { DyNTS_Errors_ControlService } from '../errors/errors.control-service'; import { DyNTS_ServerStatus_ControlService } from './server-status.control-service'; import { DyNTS_ServerStatusSnapshot_ControlService } from './server-status-snapshot.control-service'; import { DyNTS_Endpoint_Params } from '../../../_models/control-models/endpoint-params.control-model'; class TestServerStatus extends DyFM_ServerStatus { testProperty?: string; } class TestError extends DyFM_Error { testProperty?: string; } class TestErrors extends DyFM_Errors { testProperty?: string; } class TestErrorsControlService extends DyNTS_Errors_ControlService { async recordError(data: TestErrors, issuer: string, alwaysRecord?: boolean): Promise {} async handleInternalError(error: any, issuer: string, alwaysRecord?: boolean): Promise {} checkErrorIsStringifyableOrResolvable(error: TestError, issuer: string): TestError | 'UNRESOLVABLE' { return error; } getPriorityMultiplierByLevel(level: any, issuer: string): number { return 0; } async getErrorsFromDate(date: Date, issuer: string): Promise { return { results: [] }; } async deleteError(errorId: string, issuer: string, alwaysDelete?: boolean): Promise {} async deleteAllErrors(issuer: string, alwaysDelete?: boolean): Promise {} async recordFixAttempt(errorId: string, version: string, hypothesis: string, issuer: string): Promise { return { at: new Date(), by: issuer, version, hypothesis }; } async getErrorsByCategoryPaged(category: string, range: any, pageSize: number, pageIndex: number, issuer: string): Promise { return { items: [], total: 0, pageIndex, pageSize }; } async resolveError(errorId: string, issuer: string, notes?: string): Promise { return; } async getErrorsByStatusPaged(status: string, range: any, pageSize: number, pageIndex: number, issuer: string): Promise { return { items: [], total: 0, pageIndex, pageSize }; } async getErrorsInRange(range: DyFM_RelativeDate, issuer: string): Promise { return { results: [] }; } async getErrorsPaged(range: DyFM_RelativeDate, pageSize: number, pageIndex: number, issuer: string): Promise { return { results: [] }; } async getLastErrors(range: DyFM_RelativeDate, pageSize: number, pageIndex: number, issuer: string): Promise { return { results: [] }; } async searchErrors(searchQuery: any, issuer: string): Promise { return { results: [] }; } } class TestServerStatusSnapshotControlService extends DyNTS_ServerStatusSnapshot_ControlService { async saveSnapshot(snapshot: TestServerStatus, issuer: string): Promise { return snapshot; } } class TestServerStatusControlService extends DyNTS_ServerStatus_ControlService< TestServerStatus, TestError, TestErrors, TestErrorsControlService, TestServerStatus, TestServerStatusSnapshotControlService > { protected getErrorControlService?(set?: { data?: TestErrors, issuer?: string }): TestErrorsControlService { return new TestErrorsControlService(); } protected getServerStatusSnapshotControlService?(set?: { data?: TestServerStatus, issuer?: string }): TestServerStatusSnapshotControlService { return new TestServerStatusSnapshotControlService(); } protected requiredServerConnections: any[] = []; } class TestServerStatusController extends DyNTS_ServerStatus_Controller< TestServerStatus, TestError, TestErrors, TestErrorsControlService, TestServerStatus, TestServerStatusSnapshotControlService, TestServerStatusControlService > { protected readonly server_CS: TestServerStatusControlService = (TestServerStatusControlService as any).getInstance ? (TestServerStatusControlService as any).getInstance() : new (TestServerStatusControlService as any)(); protected override readonly additionalEndpoints: DyNTS_Endpoint_Params[] = []; } describe('| DyNTS_ServerStatus_Controller', () => { let controller: TestServerStatusController; beforeEach(() => { controller = new (TestServerStatusController as any)(); }); describe('| setupEndpoints', () => { it('| should setup all server status endpoints', () => { controller.setupEndpoints(); expect(controller.endpoints).toBeDefined(); expect(controller.endpoints.length).toBeGreaterThanOrEqual(4); }); it('| should setup getServerStatus endpoint', () => { controller.setupEndpoints(); const getServerStatusEndpoint = controller.endpoints.find(ep => ep.name === 'getServerStatus'); expect(getServerStatusEndpoint).toBeDefined(); expect(getServerStatusEndpoint?.type).toBe(DyFM_HttpCallType.get); expect(getServerStatusEndpoint?.endpoint).toBe('/status'); expect((getServerStatusEndpoint as any)?.tasks).toBeDefined(); expect((getServerStatusEndpoint as any)?.tasks?.length).toBe(1); }); it('| should setup getServerHealth endpoint', () => { controller.setupEndpoints(); const getServerHealthEndpoint = controller.endpoints.find(ep => ep.name === 'getServerHealth'); expect(getServerHealthEndpoint).toBeDefined(); expect(getServerHealthEndpoint?.type).toBe(DyFM_HttpCallType.get); expect(getServerHealthEndpoint?.endpoint).toBe('/health'); expect((getServerHealthEndpoint as any)?.tasks).toBeDefined(); expect((getServerHealthEndpoint as any)?.tasks?.length).toBe(1); }); it('| should setup getServerStatusForClient endpoint', () => { controller.setupEndpoints(); const getServerStatusForClientEndpoint = controller.endpoints.find(ep => ep.name === 'getServerStatusForClient'); expect(getServerStatusForClientEndpoint).toBeDefined(); expect(getServerStatusForClientEndpoint?.type).toBe(DyFM_HttpCallType.get); expect(getServerStatusForClientEndpoint?.endpoint).toBe('/status/:version'); expect((getServerStatusForClientEndpoint as any)?.tasks).toBeDefined(); expect((getServerStatusForClientEndpoint as any)?.tasks?.length).toBe(1); }); it('| should setup getErrorStatistics endpoint', () => { controller.setupEndpoints(); const getErrorStatisticsEndpoint = controller.endpoints.find(ep => ep.name === 'getErrorStatistics'); expect(getErrorStatisticsEndpoint).toBeDefined(); expect(getErrorStatisticsEndpoint?.type).toBe(DyFM_HttpCallType.get); expect(getErrorStatisticsEndpoint?.endpoint).toBe('/statistics/error/:range'); expect((getErrorStatisticsEndpoint as any)?.tasks).toBeDefined(); expect((getErrorStatisticsEndpoint as any)?.tasks?.length).toBe(1); }); it('| should include additionalEndpoints', () => { const additionalEndpoint = new DyNTS_Endpoint_Params({ name: 'customEndpoint', type: DyFM_HttpCallType.get, endpoint: '/status/custom', tasks: [], }); (controller as any).additionalEndpoints.push(additionalEndpoint); controller.setupEndpoints(); const customEndpoint = controller.endpoints.find(ep => ep.name === 'customEndpoint'); expect(customEndpoint).toBeDefined(); }); it('| should throw error when additionalEndpoints is not defined', () => { const controllerWithoutEndpoints = new (class extends TestServerStatusController { protected override readonly additionalEndpoints: DyNTS_Endpoint_Params[] = undefined as any; } as any)(); expect(() => { controllerWithoutEndpoints.setupEndpoints(); }).toThrow(); }); }); describe('| opt-in admin auth retrofit', (): void => { afterEach((): void => { DyNTS_ServerStatus_Controller._resetAuthConfigForTesting(); }); // FR-263 — a guard request-idoben fut (lazy wrapper). "Gated-e egy endpoint" = a wrapper // meghivasa lefuttatja-e a configured authPreProcess-t. A counter-t a teszt-spy lepteti. let guardCalls: number = 0; const spyAuth = async (): Promise => { guardCalls++; }; const runGuard = async (c: any, name: string): Promise => { const ep: any = c.endpoints.find((e: any) => e.name === name); await ep?.preProcesses?.[0]?.({} as any, { headersSent: false } as any); }; beforeEach((): void => { guardCalls = 0; }); it('| default (no config): a wrapper jelen van, de egy keres NEM hiv guardot (nyitva marad)', async (): Promise => { controller.setupEndpoints(); for (const ep of controller.endpoints) { await (ep as any).preProcesses?.[0]?.({} as any, { headersSent: false } as any); // no-op, nem dob } expect(guardCalls).toBe(0); }); it('| configure({ authPreProcess }) → CSAK getErrorStatistics-en fut a guard, a status-vegpontokon NEM', async (): Promise => { DyNTS_ServerStatus_Controller.configure({ authPreProcess: spyAuth }); const c: any = new (TestServerStatusController as any)(); c.setupEndpoints(); await runGuard(c, 'getErrorStatistics'); expect(guardCalls).withContext('admin-adat → gated').toBe(1); guardCalls = 0; for (const name of ['getServerStatus', 'getServerHealth', 'getServerReadiness', 'getServerStatusForClient']) { await runGuard(c, name); } expect(guardCalls).withContext('status-probak SOHA nem gated').toBe(0); }); it('| a status-vegpontokat explicit protectedEndpoints-lista SEM gate-eli (PUBLIC_PROBE kizart)', async (): Promise => { DyNTS_ServerStatus_Controller.configure({ authPreProcess: spyAuth, protectedEndpoints: [ 'getServerHealth', 'getServerReadiness', 'getServerStatusForClient', 'getServerStatus', 'getErrorStatistics' ], }); const c: any = new (TestServerStatusController as any)(); c.setupEndpoints(); for (const name of ['getServerStatus', 'getServerHealth', 'getServerReadiness', 'getServerStatusForClient']) { await runGuard(c, name); } expect(guardCalls).withContext('status-probak kizartak meg explicit listan is').toBe(0); await runGuard(c, 'getErrorStatistics'); expect(guardCalls).withContext('getErrorStatistics gated').toBe(1); }); it('| protectedEndpoints subset → getErrorStatistics gated, getServerStatus NEM', async (): Promise => { DyNTS_ServerStatus_Controller.configure({ authPreProcess: spyAuth, protectedEndpoints: [ 'getErrorStatistics' ] }); const c: any = new (TestServerStatusController as any)(); c.setupEndpoints(); await runGuard(c, 'getErrorStatistics'); expect(guardCalls).toBe(1); guardCalls = 0; await runGuard(c, 'getServerStatus'); expect(guardCalls).toBe(0); }); it('| FR-263 ORDER-INDEPENDENCE: setupEndpoints() ELOTT epitett endpoint is enforce-olja a guardot', async (): Promise => { const c: any = new (TestServerStatusController as any)(); c.setupEndpoints(); // config NELKUL (korai framework-konstrukcio szimulacioja) DyNTS_ServerStatus_Controller.configure({ authPreProcess: spyAuth }); // configure UTANA await runGuard(c, 'getErrorStatistics'); expect(guardCalls).withContext('guard runs despite configure() AFTER setupEndpoints').toBe(1); }); // 2026-07-22 — a `/diag` EGYIK listaban sem szerepelt (sem publikus-proba, sem default-vedett), // ezert MINDEN FDP-szolgaltatason NYITOTT volt: elesben 200 + event-loop lag-history, aktiv // handle-ok, memoria-nyomas, uptime. Nem szandek volt, hanem kimaradas. Ez a teszt rogziti, hogy // explicit lista NELKUL a diag VEDETT — es hogy a publikus probak ettol valtozatlanul nyitva vannak. it('| getServerDiag is protected BY DEFAULT, while the public probes stay open', async (): Promise => { const c: any = new (TestServerStatusController as any)(); // ccap-review-disable-line no-any-type c.setupEndpoints(); DyNTS_ServerStatus_Controller.configure({ authPreProcess: spyAuth }); // NINCS explicit lista → default await runGuard(c, 'getServerDiag'); expect(guardCalls).withContext('a /diag guard-olt a default listaval').toBe(1); guardCalls = 0; await runGuard(c, 'getServerHealth'); expect(guardCalls).withContext('a publikus proba NEM gate-elt').toBe(0); guardCalls = 0; await runGuard(c, 'getServerReadiness'); expect(guardCalls).withContext('a readiness-proba NEM gate-elt').toBe(0); }); it('| getAuthConfig() returns the active config', (): void => { const fakeAuth = async (): Promise => { /* noop */ }; DyNTS_ServerStatus_Controller.configure({ authPreProcess: fakeAuth, protectedEndpoints: [ 'getErrorStatistics' ] }); const cfg = DyNTS_ServerStatus_Controller.getAuthConfig(); expect(cfg.authPreProcess).toBe(fakeAuth); expect(cfg.protectedEndpoints).toEqual([ 'getErrorStatistics' ]); }); it('| _resetAuthConfigForTesting() clears the config', (): void => { DyNTS_ServerStatus_Controller.configure({ authPreProcess: async (): Promise => { /* noop */ } }); DyNTS_ServerStatus_Controller._resetAuthConfigForTesting(); expect(DyNTS_ServerStatus_Controller.getAuthConfig().authPreProcess).toBeUndefined(); }); }); });