mock runtime: node-parity gaps left open by #1494 (Buffer/fs shim)
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- Mezza giornata
- Idoneità per principianti
- 68/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- node.js, typescript
Direzione di ricerca
Start with src/commands/build/internal/mockSite/assets/runtime-core.js and the existing mock runtime specs. For gap 1, check the Buffer.from(arrayBuffer) branch and add a spec that mutates the Buffer and reads the ArrayBuffer; for gap 2, trace binary _SASJS_WEBOUT_HEADERS handling and add a Buffer-written header spec. Done when both cases match the server behavior and the specs pass.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Context
#1494 (merged as 0cc25294) made a mocked streaming service produce a real download: _SASJS_WEBOUT_HEADERS is applied, a byte body is served as bytes, and a project-supplied archive, image or xlsx is seeded onto the browser drive.
Reviewing it turned into a long audit of the node shim underneath (Buffer, fs, sw.js header handling), because that shim is what makes a browser-hosted mock behave like the server. Most findings were fixed in the PR. This issue records what was deliberately left out, one finding raised at the final head, and the deviations that are not defects - so they are not re-filed and can be sequenced on their own merit.
Gaps to close
1. Buffer.from(arrayBuffer) copies instead of sharing memory
src/commands/build/internal/mockSite/assets/runtime-core.js:493 returns asBuffer(new Uint8Array(value, start, end - start)), and asBuffer (:409) does decorate(new Uint8Array(bytes)), which copies. Node returns a view over that ArrayBuffer for this overload, and copies only for a Uint8Array input - the split the shim already honours for slice() and subarray().
const ab = new Uint8Array([1, 2, 3, 4]).buffer
const b = Buffer.from(ab)
b[0] = 9
new Uint8Array(ab)[0] // node: 9 mock: 1
Impact. Silent wrong bytes. A service that builds a body from an ArrayBuffer and then patches it in place - a zip end-of-central-directory record, a length prefix fixed up after the fact, a checksum written back - serves the unpatched buffer from the mock and the patched one from the server, with HTTP 200 either way. Nothing in the mock's contract reports it, and the failure surfaces only as a corrupt download the user has to open.
Reachability. Low: most services mutate the Buffer they built, which behaves identically in both hosts. Reached only by code that keeps the ArrayBuffer and mutates it after Buffer.from.
Fix. Route the ArrayBuffer branch through decorate instead of asBuffer; keep asBuffer for Uint8Array and other inputs, which node copies. One line plus a spec that mutates through the Buffer and reads the ArrayBuffer back.
2. A binary _SASJS_WEBOUT_HEADERS drops every declared header
runtime-core.js:1004 reads the headers reply with headers: entryText(FILES.get(headersPath) || ''), which only removes the text escape. A service that writes the headers file as bytes - fs.writeFileSync(_SASJS_WEBOUT_HEADERS, Buffer.from('Content-type: text/csv\n...')), or a string with a non-UTF-8 encoding - leaves a byte entry on the drive, so headers becomes the raw \u0000base64:... string. parseHeaders (sw.js:84) then splits on newlines, finds a colon only inside the marker, matches no allowed header name, and drops everything.
Impact. Silent loss of the whole channel: the reply keeps its default text/plain and no Content-Disposition, so a download either does not happen or is saved under the wrong name. No error, no warning. This is the channel that decides a download's name and type, so when it is hit the feature the PR added does nothing at all.
Reachability. Low: writing headers as bytes is unusual, though valid on the server (where the file is read as text regardless of how it was written).
Fix. Decode the headers entry the way the body is decoded - isBinary(contents) ? utf8 decode of the entry : entryText(contents) - so the two channels cannot diverge, with a spec that writes the headers as a Buffer.
3. The seed marker format is two copies of the same constants
runtime-core.js:95 (BINARY_PREFIX = '\u0000base64:', TEXT_PREFIX = '\u0000text:') and mockSite.ts:78 (DRIVE_BINARY_PREFIX, DRIVE_TEXT_PREFIX) hold the same storage format independently. Nothing fails at the point of the edit if one side changes.
Impact. Latent, and quiet when it bites: the build writes a marker the runtime no longer recognises, so a seeded byte file reads as text (a large mojibake string, served or written back by a service) or a runtime-written entry fails to decode in the worker (500 per entry). Today the build-side output is read back through the runtime by a spec (mockSite.spec.ts, seeds a project file the runtime reads back as its bytes), so a one-sided edit that changes decoding fails CI - but only for the seeded byte path, and only because a test happens to exercise both sides.
Fix. One shared module for the format, or a version/format field in vfs-seed.json that the runtime checks on load.
Deviations that stay (no change proposed)
fs.readFileSync(p)returns a string for a text entry and a Buffer for a byte entry. Node always returns a Buffer. The drive's convention is deliberate - a service reading a config or a CSV gets text, as it does through the server's own text paths - and it is the documented contract inruntime-core.js. A service that calls.length,.toString('base64')or other Buffer methods on a text file without an encoding gets a character count (accented characters count 1, not 2) instead of a byte count. Worth stating in the mock documentation; changing it would be a breaking behaviour change for existing mocks, so it belongs to a future major if it is wanted.- Buffer methods the shim does not carry (
write,equals,compare,indexOf,includes,copy,fill, theread*/write*accessors,swap*) throwBuffer.<name> is not implemented in the browser host. Loud and immediate, by design: better a named gap thanx.write is not a function. Implementequals/indexOf/copy/read*if a service is found that needs them.
Not defects - do not re-file
Buffer.alloc(size, fill)andBuffer.from([...])mask rather than reject out-of-range values. Verified on node v18.20.8, v22.23.4 and v26.5.1:Buffer.alloc(4, 300)is2c2c2c2c,Buffer.from([256])is00,Buffer.from([1.5])is01. The shim matches; adding aRangeErrorwould create a divergence.- Header values containing 0x7F or 0x80-0x9F are refused rather than applied. The browser's
Headersimplementation strips those bytes from a value silently (new Response('x', { headers: { 'content-disposition': 'a\u0080b' } })comes back as"ab"), so applying the header would save a file under a name with characters missing. The value is dropped and the reply keeps its default delivery instead. - A repeated header line takes the last value, and a value that fails validation removes the header entirely. That is what the server does in
api/src/utils/extractHeaders.ts.
Impact summary
| # | Item | Observable effect | Failure mode | Severity |
|---|---|---|---|---|
| 1 | ArrayBuffer copy | patched bytes missing from the served body | silent | medium-low |
| 2 | Binary headers file | download typed and named by default; no declared headers | silent | medium |
| 3 | Marker constants duplicated | seeded bytes read as text, or a 500 per entry | silent (guarded by one spec) | low |
| 4 | readFileSync text-as-text |
character count instead of byte count on text | silent, documented | low |
| 5 | Missing Buffer methods | named throw at the call site | loud | low |
Suggested order
- #1 and #2 are both small, single-file changes with a spec each; neither needs a design decision.
- #3 needs a decision (shared module vs seed format version) and can follow.
- #4 and #5 are documentation until there is evidence of a service that needs them.
- Lingua principale
- TypeScript
- Stelle
- 40
- Fork
- 6
- Merge medio
- 11h 23m
- PR unite (30g)
- 15
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di sasjs/cli
-
False positive lint reportAperta
Difficoltà 3/5 1-2 giorni Idoneità per principianti 62/100
I maintainer di solito rispondono entro 1 giorno
-
Automatically ratchet up coverage thresholds instead of hand-maintaining themForse di nuovo libera @YuryShkoda l’ha presa 115 giorni fa e non c’è nessuna pull request aperta. Apertarefactoring
sasjs/cli#1453 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Mocked test suite (`npm run test:mocked`) is too slow — speed it up without losing coverageForse di nuovo libera @YuryShkoda l’ha presa 115 giorni fa e non c’è nessuna pull request aperta. Apertarefactoring
sasjs/cli#1452 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
I maintainer di solito rispondono entro 1 giorno
-
Move all HTTP requests to @sasjs/adapterForse di nuovo libera @YuryShkoda l’ha presa 1167 giorni fa e non c’è nessuna pull request aperta. Apertaadapter refactoring
sasjs/cli#1369 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
grafana/profiles-drilldown#1212 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
grafana/traces-drilldown#980 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
openremote/openremote#3487 · 1 commento ·
-
triage:needs-human
Difficoltà 2/5 1-3 ore Idoneità per principianti 67/100
garrytan/gbrain#7635 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
coollabsio/jean#867 ·
I maintainer di solito rispondono entro 1 giorno