Files
dicom-proxy/tests/frameExtractor.test.ts
padmanto 3ac04f9147 fix(wado-rs): extract only the requested frame and return typed errors
/frames/{frame} now validates the frame as a positive 1-based integer and
returns 400 for invalid values. The frame number is threaded through
doWadoRs into addFileToBuffer, where it is sliced out of uncompressed Pixel
Data using NumberOfFrames, Rows, Columns, SamplesPerPixel and BitsAllocated.

- add src/dimse/frameExtractor.ts (native frame slicing)
- add src/dimse/errors.ts (status-bearing HttpError types)
- reject compressed/encapsulated frames with 415 instead of full pixel data
- keep JPEG conversion on /rendered and /thumbnail only
- log frame requests with jpegRendererUsed=false
- add frame extraction and validation tests
2026-09-18 14:16:16 +07:00

108 lines
3.5 KiB
TypeScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { extractFrameFromBuffer, parseFrameParam } from '../src/dimse/frameExtractor';
import { BadRequestError, HttpError, statusCodeForError, UnsupportedMediaTypeError } from '../src/dimse/errors';
import { buildMonochromeImage } from './helpers/dicomWriter';
function twoFrameImage() {
return buildMonochromeImage({
rows: 2,
columns: 2,
bitsAllocated: 8,
samplesPerPixel: 1,
numberOfFrames: 2,
pixelData: [1, 2, 3, 4, 5, 6, 7, 8],
});
}
test('/frames/1 returns only the first frame byte range', () => {
const extracted = extractFrameFromBuffer(twoFrameImage(), 1);
assert.deepEqual([...extracted.buffer], [1, 2, 3, 4]);
assert.equal(extracted.frameSize, 4);
assert.equal(extracted.numberOfFrames, 2);
});
test('/frames/2 returns only the second frame byte range', () => {
const extracted = extractFrameFromBuffer(twoFrameImage(), 2);
assert.deepEqual([...extracted.buffer], [5, 6, 7, 8]);
});
test('/frames/2 for a single-frame instance is rejected with 400', () => {
const singleFrame = buildMonochromeImage({
rows: 2,
columns: 2,
bitsAllocated: 8,
pixelData: [1, 2, 3, 4],
});
assert.throws(
() => extractFrameFromBuffer(singleFrame, 2),
(error: unknown) => error instanceof BadRequestError && statusCodeForError(error) === 400,
);
});
test('extracts 16-bit frames with the correct byte offsets', () => {
const image = buildMonochromeImage({
rows: 2,
columns: 2,
bitsAllocated: 16,
samplesPerPixel: 1,
numberOfFrames: 2,
pixelData: [256, 257, 258, 259, 260, 261, 262, 263],
});
const second = extractFrameFromBuffer(image, 2);
const values = [0, 2, 4, 6].map((offset) => second.buffer.readUInt16LE(offset));
assert.deepEqual(values, [260, 261, 262, 263]);
});
test('rejects out-of-range and zero frame numbers with 400', () => {
const image = twoFrameImage();
assert.throws(
() => extractFrameFromBuffer(image, 0),
(error: unknown) => error instanceof BadRequestError,
);
assert.throws(
() => extractFrameFromBuffer(image, 3),
(error: unknown) => error instanceof BadRequestError,
);
});
test('rejects compressed transfer syntax with an explicit unsupported response', () => {
const image = buildMonochromeImage({
rows: 2,
columns: 2,
bitsAllocated: 8,
numberOfFrames: 2,
pixelData: [1, 2, 3, 4, 5, 6, 7, 8],
transferSyntaxUID: '1.2.840.10008.1.2.4.80',
});
assert.throws(
() => extractFrameFromBuffer(image, 2),
(error: unknown) => error instanceof UnsupportedMediaTypeError && statusCodeForError(error) === 415,
);
});
test('treats a too-short Pixel Data element as a 500 data error', () => {
const image = buildMonochromeImage({
rows: 4,
columns: 4,
bitsAllocated: 8,
pixelData: [1, 2, 3, 4],
});
assert.throws(
() => extractFrameFromBuffer(image, 1),
(error: unknown) => error instanceof HttpError && statusCodeForError(error) === 500,
);
});
test('parseFrameParam accepts only positive base-10 integers', () => {
assert.equal(parseFrameParam('1'), 1);
assert.equal(parseFrameParam('42'), 42);
assert.equal(parseFrameParam('0'), undefined);
assert.equal(parseFrameParam('-1'), undefined);
assert.equal(parseFrameParam('abc'), undefined);
assert.equal(parseFrameParam('1.5'), undefined);
assert.equal(parseFrameParam(' 1'), undefined);
assert.equal(parseFrameParam(''), undefined);
assert.equal(parseFrameParam(undefined), undefined);
});