Files
iptvnator/apps/electron-backend/src/app/server/resolve-static-file-path.spec.ts
T
4grayandClaude Opus 5 518964c57f refactor(electron-backend): split the last four files over the max-lines cap (#1288)
Follow-up to #1278, which split four of the files the electron-backend lint
target had been silently skipping. Four were left over; this splits them, so
no file in the project sits above the 400-line cap outside the baseline.

None are added to tools/eslint/max-lines-baseline.mjs — the baseline only
shrinks. Shared setup moves to *.test-helpers.ts, the suffix
tsconfig.app.json already excludes, so the production build never sees jest
globals.

- remote-control.events.spec.ts 588 -> 188, plus remote-control-http.spec.ts.
  The mock registry moves to remote-control.test-helpers.ts; each spec keeps
  its own jest.mock() factories, which resolve the mock-prefixed exports.
- downloads.events.spec.ts 530 -> 182, plus downloads-actions.spec.ts. The
  jest.doMock setup is not hoisted, so the whole harness moves to
  downloads.test-helpers.ts behind setupDownloadsEventsHarness().
- http-server.spec.ts 448 -> 377, plus resolve-static-file-path.spec.ts. The
  resolveStaticFilePath cases were already self-contained.
- worker-performance-capture.spec.ts 408 -> 149, plus
  worker-performance-capture.resilience.spec.ts, matching the .concurrency
  and .histogram siblings.

Test bodies are unchanged; the helpers keep the same identifiers in scope so
the splits are a move, not a rewrite.

Verified with the glob quoted locally (that quoting is #1176's, not part of
this change): 245 files, 0 max-lines errors, and the only remaining lint
errors are the five #1176 fixes. Both tsconfig.app.json and
tsconfig.spec.json typecheck clean.

Note: worker-performance-capture.concurrency.spec.ts is flaky on master
independently of this change — it asserts a real 20ms event-loop block and
failed 4/4 clean-master runs here.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:21:24 +02:00

75 lines
2.1 KiB
TypeScript

import { posix, win32 } from 'node:path';
import { resolveStaticFilePath } from './http-server';
describe('resolveStaticFilePath', () => {
const POSIX_STATIC_ROOT = '/opt/iptvnator/remote-control';
const WINDOWS_STATIC_ROOT = 'C:\\iptvnator\\remote-control';
it('rejects a Windows double-leading-slash traversal', () => {
expect(
resolveStaticFilePath(
WINDOWS_STATIC_ROOT,
'//../../outside-secret.txt',
win32
)
).toBeNull();
});
it('rejects encoded traversal segments after one decode', () => {
expect(
resolveStaticFilePath(
POSIX_STATIC_ROOT,
'/%2e%2e/outside-secret.txt',
posix
)
).toBeNull();
});
it('fails closed for malformed percent encoding without throwing', () => {
expect(
resolveStaticFilePath(POSIX_STATIC_ROOT, '/%E0%A4%A', posix)
).toBeNull();
});
it('rejects decoded NUL bytes', () => {
expect(
resolveStaticFilePath(
POSIX_STATIC_ROOT,
'/assets/%00secret.js',
posix
)
).toBeNull();
});
it('resolves a valid Windows-style asset inside the static root', () => {
expect(
resolveStaticFilePath(
WINDOWS_STATIC_ROOT,
'/assets\\app.js?version=1',
win32
)
).toBe('C:\\iptvnator\\remote-control\\assets\\app.js');
});
it('ignores query and fragment data for filesystem resolution', () => {
expect(
resolveStaticFilePath(
POSIX_STATIC_ROOT,
'/data.json?cache=1#ignored',
posix
)
).toBe('/opt/iptvnator/remote-control/data.json');
});
it('decodes the URL pathname exactly once', () => {
expect(
resolveStaticFilePath(
POSIX_STATIC_ROOT,
'/%252e%252e/asset.js',
posix
)
).toBe('/opt/iptvnator/remote-control/%2e%2e/asset.js');
});
});