Files
hamlab/web/scripts/test-resolve-path.mjs
Mitchell-JohnsonandClaude Fable 5.1 1038c010fa Files: a sweep of the edges
Found by walking every handler and the UI with odd inputs, then a battery of requests
against the live hub. Fixed:
- A save over the 8 MiB cap was cut short in place: the file was truncated, then the
  first 8 MiB written, and the caller told 204. Now the body is read whole first and a
  body over the cap is a 413 with the file untouched.
- A link to a file showed the link's own time; now the file's, so the row reads right
  and the preview key follows the file.
- Reading a link to nothing answered 200 with an empty body (the open failed after the
  headers in root mode); now a 404 that names the target.
- A play request with an unknown codec or size was answered by inspecting the file
  first, so an audio-only file said "no video stream" instead of what was wrong; the
  choice is checked before any work, and is a 400.
- A stream that ended in failure was handed back to the next identical request for ten
  minutes; a failed one is now dropped and a fresh one made.
- A player that left while its stream was still finding a first segment made the loop
  try the next recipe; it stops. A stream closed mid-start is not started again.
- Ending a root-mode ffmpeg could wait forever: the daemon cannot signal root's
  processes, so the wait is bounded and the helper's own grace does the rest.
- A page going away (a phone closing the tab) releases its stream by a beacon, rather
  than ten minutes later by the sweeper.
- The path box resolves like a shell — relative to the folder shown, . and .. applied,
  trailing slashes dropped — in the tab and in the Move picker, with a test. A typed path
  that is a file opens it; a remembered folder that is gone falls back to / once.
- A dropped folder arrived as a nameless empty file; it is refused with a word. The
  upload toast counts what was actually uploaded.
- The viewer names a device, pipe or socket, and a dangling link, instead of calling
  them binary files.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 20:31:10 -07:00

17 lines
1.2 KiB
JavaScript

// node scripts/test-resolve-path.mjs — the path box resolves like a shell: relative to here,
// . and .. applied, trailing slashes dropped, never anything but an absolute path.
import { readFileSync } from 'node:fs'
const src = readFileSync(new URL('../src/Files.tsx', import.meta.url), 'utf8')
const m = src.match(/export function resolvePath[\s\S]*?\n}\n/)
if (!m) { console.error('resolvePath not found'); process.exit(1) }
const resolvePath = new Function(m[0].replace('export function', 'return function').replace(/: string\[\]/g, '').replace(/: string/g, '') + ' ')()
const cases = [
['/tank/media', '/x', '/tank/media'], ['/tank/media/', '/x', '/tank/media'], ['', '/here', '/here'], [' ', '/here', '/here'],
['sub', '/here', '/here/sub'], ['./sub/', '/here', '/here/sub'], ['..', '/a/b', '/a'], ['../..', '/a/b', '/'], ['../../..', '/a', '/'],
['/', '/a', '/'], ['a//b', '/', '/a/b'], ['/tank/./x/../y', '/', '/tank/y'], ['sp ace #1', '/d', '/d/sp ace #1'],
]
let bad = 0
for (const [input, here, want] of cases) { const got = resolvePath(input, here); if (got !== want) { bad++; console.error(`resolvePath(${JSON.stringify(input)}, ${here}) = ${got}, want ${want}`) } }
if (bad) process.exit(1)
console.log(`resolve path: ok (${cases.length} cases)`)