import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import { DyNTS_global_settings } from '../../_collections/global-settings.const'; import { DyNTS_FileLog_Service } from './file-log.service'; describe('| DyNTS_FileLog_Service', (): void => { let tempDir: string; let originalFileLogConfig: typeof DyNTS_global_settings.log_settings.file_log; beforeEach((): void => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nts-filelog-spec-')); originalFileLogConfig = DyNTS_global_settings.log_settings.file_log; }); afterEach((): void => { // Mindig allitsuk vissza az eredeti console-t es config-ot, hogy // ne szivarogtassuk at a teszt envt. DyNTS_FileLog_Service.getInstance()._teardownForTesting(); DyNTS_global_settings.log_settings.file_log = originalFileLogConfig; try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch { /* swallow */ } }); describe('| install() — gating', (): void => { it('| no-op ha file_log config hianyzik', (): void => { DyNTS_global_settings.log_settings.file_log = undefined; DyNTS_FileLog_Service.getInstance().install(); expect(DyNTS_FileLog_Service.getInstance().isInstalled()).toBe(false); }); it('| no-op ha enabled === false', (): void => { DyNTS_global_settings.log_settings.file_log = { enabled: false, logDir: tempDir }; DyNTS_FileLog_Service.getInstance().install(); expect(DyNTS_FileLog_Service.getInstance().isInstalled()).toBe(false); }); it('| install() idempotens — masodszorra is no-op', (): void => { DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir }; const svc: DyNTS_FileLog_Service = DyNTS_FileLog_Service.getInstance(); svc.install(); const firstPath: string = svc.getCurrentLogPath(); svc.install(); // ismet expect(svc.getCurrentLogPath()).toBe(firstPath); }); }); describe('| install() — file creation + intercept', (): void => { it('| stdout iras a fajlba is bekerul', (): void => { DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir, filenamePrefix: 'spec-', }; const svc: DyNTS_FileLog_Service = DyNTS_FileLog_Service.getInstance(); svc.install(); const marker: string = `hello-from-spec-${Date.now()}\n`; process.stdout.write(marker); const logPath: string = svc.getCurrentLogPath(); expect(fs.existsSync(logPath)).toBe(true); const contents: string = fs.readFileSync(logPath, 'utf-8'); expect(contents).toContain(marker.trim()); }); it('| stderr iras a fajlba is bekerul ha includeStderr=true', (): void => { DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir, filenamePrefix: 'spec-', }; const svc: DyNTS_FileLog_Service = DyNTS_FileLog_Service.getInstance(); svc.install(); const marker: string = `error-marker-${Date.now()}\n`; process.stderr.write(marker); const contents: string = fs.readFileSync(svc.getCurrentLogPath(), 'utf-8'); expect(contents).toContain(marker.trim()); }); it('| ANSI escape kodok strippelve a fajlban ha stripAnsi=true (default)', (): void => { DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir, filenamePrefix: 'spec-', }; const svc: DyNTS_FileLog_Service = DyNTS_FileLog_Service.getInstance(); svc.install(); process.stdout.write('\x1b[31mredtext\x1b[0m-payload\n'); const contents: string = fs.readFileSync(svc.getCurrentLogPath(), 'utf-8'); expect(contents).toContain('redtext-payload'); expect(contents).not.toContain('\x1b[31m'); expect(contents).not.toContain('\x1b[0m'); }); it('| ANSI megmarad ha stripAnsi=false', (): void => { DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir, filenamePrefix: 'spec-', stripAnsi: false, }; const svc: DyNTS_FileLog_Service = DyNTS_FileLog_Service.getInstance(); svc.install(); process.stdout.write('\x1b[31mraw\x1b[0m\n'); const contents: string = fs.readFileSync(svc.getCurrentLogPath(), 'utf-8'); expect(contents).toContain('\x1b[31mraw\x1b[0m'); }); }); describe('| rotation', (): void => { it('| uj fajl jon letre ha az iras meghaladja a maxFileSizeMb-t', (): void => { DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir, filenamePrefix: 'rotspec-', // 1 byte limit → minden iras azonnal triggereli a rotaciot maxFileSizeMb: 1 / (1024 * 1024), }; const svc: DyNTS_FileLog_Service = DyNTS_FileLog_Service.getInstance(); svc.install(); const firstPath: string = svc.getCurrentLogPath(); process.stdout.write('payload-1\n'); process.stdout.write('payload-2\n'); const secondPath: string = svc.getCurrentLogPath(); expect(secondPath).not.toBe(firstPath); // Mindket fajl letezik a tempDir-ben const files: string[] = fs.readdirSync(tempDir).filter((f: string) => f.startsWith('rotspec-')); expect(files.length).toBeGreaterThanOrEqual(2); }); }); describe('| retention', (): void => { it('| install() torli a `retentionDays`-nel regebbi fajlokat (mtime backdated)', (): void => { const oldFile: string = path.join(tempDir, 'spec-2020-01-01_00-00-00_abc.log'); fs.writeFileSync(oldFile, 'old'); const oldMs: number = Date.now() - 365 * 24 * 60 * 60 * 1000; fs.utimesSync(oldFile, oldMs / 1000, oldMs / 1000); DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir, filenamePrefix: 'spec-', retentionDays: 30, maxFiles: 100, // count szabaly NE triggereljen }; DyNTS_FileLog_Service.getInstance().install(); expect(fs.existsSync(oldFile)).toBe(false); }); it('| install() retention by count — maxFiles-nel tobb fajl van, a legregebbi torlodik', (): void => { // 3 fix mtime-u "regi" fajl letrehozasa (azonnali age szempontbol mind ok) const now: number = Date.now(); const f1: string = path.join(tempDir, 'spec-2026-01-01_00-00-01_aaa.log'); const f2: string = path.join(tempDir, 'spec-2026-01-01_00-00-02_bbb.log'); const f3: string = path.join(tempDir, 'spec-2026-01-01_00-00-03_ccc.log'); fs.writeFileSync(f1, '1'); fs.utimesSync(f1, (now - 30000) / 1000, (now - 30000) / 1000); fs.writeFileSync(f2, '2'); fs.utimesSync(f2, (now - 20000) / 1000, (now - 20000) / 1000); fs.writeFileSync(f3, '3'); fs.utimesSync(f3, (now - 10000) / 1000, (now - 10000) / 1000); DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir, filenamePrefix: 'spec-', maxFiles: 2, // a legregebbi (f1) torlodik retentionDays: 36500, // age szabaly NE triggereljen }; DyNTS_FileLog_Service.getInstance().install(); expect(fs.existsSync(f1)).toBe(false); expect(fs.existsSync(f2)).toBe(true); expect(fs.existsSync(f3)).toBe(true); }); }); describe('| silent failure', (): void => { it('| install() NEM crashel ha a logDir letrehozasa bukik', (): void => { // Olyan path amit nem lehet letrehozni: egy meglévő fajl ALATTI subdir. const blocker: string = path.join(tempDir, 'iam-a-file'); fs.writeFileSync(blocker, ''); const impossible: string = path.join(blocker, 'cant-create-subdir'); DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: impossible, }; expect(() => DyNTS_FileLog_Service.getInstance().install()).not.toThrow(); expect(DyNTS_FileLog_Service.getInstance().isInstalled()).toBe(false); }); }); describe('| listLogFiles()', (): void => { it('| ures lista ha nincs installalva', (): void => { const svc = DyNTS_FileLog_Service.getInstance(); // NEM hivunk install-t expect(svc.listLogFiles()).toEqual([]); }); it('| listazza az osszes prefix-elt log fajlt + jelzi az isCurrent flag-et', (): void => { // 3 fajl letrehozasa kezzel kulonbozo prefix-szel fs.writeFileSync(path.join(tempDir, 'spec-2026-01-01_aaa.log'), 'a'); fs.writeFileSync(path.join(tempDir, 'spec-2026-01-02_bbb.log'), 'bb'); fs.writeFileSync(path.join(tempDir, 'spec-2026-01-03_ccc.log'), 'ccc'); fs.writeFileSync(path.join(tempDir, 'other-2026-01-04_zzz.log'), 'should-be-ignored'); DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir, filenamePrefix: 'spec-', }; const svc = DyNTS_FileLog_Service.getInstance(); svc.install(); const list = svc.listLogFiles(); // 3 spec- fajl + 1 aktualis session fajl = 4 expect(list.length).toBe(4); const names: string[] = list.map((e) => e.name); expect(names.find((n) => n.startsWith('other-'))).toBeUndefined(); expect(list.filter((e) => e.isCurrent).length).toBe(1); }); }); describe('| readLogFile() — happy paths', (): void => { let svc: DyNTS_FileLog_Service; let testFile: string; beforeEach((): void => { DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir, filenamePrefix: 'spec-', }; svc = DyNTS_FileLog_Service.getInstance(); svc.install(); // Kulon teszt-fajl testFile = `spec-test-${Date.now()}.log`; const lines: string[] = []; for (let i: number = 1; i <= 50; i++) { lines.push(`line-${i}`); } fs.writeFileSync(path.join(tempDir, testFile), lines.join('\n') + '\n'); }); it('| tail mod: utolso N sor', (): void => { const result = svc.readLogFile(testFile, { tail: 5 }); expect(result.mode).toBe('tail'); expect(result.lines).toEqual(['line-46', 'line-47', 'line-48', 'line-49', 'line-50']); expect(result.totalLines).toBe(50); expect(result.rangeStart).toBe(46); expect(result.rangeEnd).toBe(50); }); it('| head mod: elso N sor', (): void => { const result = svc.readLogFile(testFile, { head: 3 }); expect(result.mode).toBe('head'); expect(result.lines).toEqual(['line-1', 'line-2', 'line-3']); expect(result.rangeStart).toBe(1); expect(result.rangeEnd).toBe(3); }); it('| range mod: explicit 10-15 tartomany', (): void => { const result = svc.readLogFile(testFile, { rangeStart: 10, rangeEnd: 15 }); expect(result.mode).toBe('range'); expect(result.lines).toEqual(['line-10', 'line-11', 'line-12', 'line-13', 'line-14', 'line-15']); }); it('| default tail 200 ha semmi mod nincs megadva', (): void => { const result = svc.readLogFile(testFile, {}); expect(result.mode).toBe('tail'); // A teszt fajl 50 sor → mind visszajon expect(result.lines.length).toBe(50); }); }); describe('| readLogFile() — error paths', (): void => { let svc: DyNTS_FileLog_Service; beforeEach((): void => { DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir, filenamePrefix: 'spec-', }; svc = DyNTS_FileLog_Service.getInstance(); svc.install(); }); it('| throw ha invalid filename (prefix nem matchel)', (): void => { expect(() => svc.readLogFile('other-2026.log')).toThrowError('invalid filename'); }); it('| throw ha path traversal kiserlet (`../`)', (): void => { expect(() => svc.readLogFile('../etc/passwd')).toThrowError('invalid filename'); }); it('| throw ha path separator (`/`) van a nevben', (): void => { expect(() => svc.readLogFile('spec-foo/bar.log')).toThrowError('invalid filename'); }); it('| throw ha file nem letezik', (): void => { expect(() => svc.readLogFile('spec-nonexistent.log')).toThrowError('file not found'); }); it('| NEM installalt service → throw `not installed`', (): void => { svc._teardownForTesting(); expect(() => svc.readLogFile('spec-foo.log')).toThrowError(/not installed/); }); }); describe('| getCurrentLogFilename()', (): void => { it('| ures string ha nincs installalva', (): void => { expect(DyNTS_FileLog_Service.getInstance().getCurrentLogFilename()).toBe(''); }); it('| basename-t ad vissza ha installalva van', (): void => { DyNTS_global_settings.log_settings.file_log = { enabled: true, logDir: tempDir, filenamePrefix: 'spec-', }; const svc = DyNTS_FileLog_Service.getInstance(); svc.install(); const basename: string = svc.getCurrentLogFilename(); expect(basename.startsWith('spec-')).toBe(true); expect(basename.endsWith('.log')).toBe(true); expect(basename.includes('/')).toBe(false); expect(basename.includes('\\')).toBe(false); }); }); });