This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import sharp from 'sharp';
|
||||
import { createSharpImageInspector } from '../../src/infrastructure/images/sharp-image-inspector.js';
|
||||
import { createInventoryImageService } from '../../src/modules/inventory/services/inventory-image-service.js';
|
||||
|
||||
function createService() {
|
||||
const writes = [];
|
||||
return { writes, service: createInventoryImageService({
|
||||
imageInspector: createSharpImageInspector(),
|
||||
imageStorage: { async put(input) { writes.push(input); }, async delete() {} },
|
||||
inventoryImageRepository: { async createForSeller(input) { return { id: 'image-1', ...input }; } },
|
||||
}) };
|
||||
}
|
||||
const metadata = { inventoryItemId: 'item-1', sellerId: 'seller-1', storageKey: 'test', originalFilename: 'test.png', width: 9999, height: 9999, fileSize: 1 };
|
||||
|
||||
test('inspects JPEG, PNG and WebP bytes and stores their true dimensions and size', async () => {
|
||||
for (const [format, mimeType] of [['jpeg', 'image/jpeg'], ['png', 'image/png'], ['webp', 'image/webp']]) {
|
||||
const data = await sharp({ create: { width: 12, height: 8, channels: 3, background: '#123456' } }).toFormat(format).toBuffer();
|
||||
const { service, writes } = createService();
|
||||
const image = await service.upload({ ...metadata, data, mimeType });
|
||||
assert.equal(image.width, 12);
|
||||
assert.equal(image.height, 8);
|
||||
assert.equal(image.fileSize, data.length);
|
||||
assert.equal(writes.length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects actual oversized dimensions, signature-only and truncated images before storage', async () => {
|
||||
const oversized = await sharp({ create: { width: 10001, height: 1, channels: 3, background: 'white' } }).png().toBuffer();
|
||||
const valid = await sharp({ create: { width: 12, height: 8, channels: 3, background: 'white' } }).png().toBuffer();
|
||||
const incompletePixels = valid.subarray(0, valid.length - 15);
|
||||
assert.equal((await createSharpImageInspector().readMetadata(incompletePixels)).width, 12);
|
||||
for (const [data, code] of [[incompletePixels, 'IMAGE_DECODE_INVALID'], [oversized, 'IMAGE_DIMENSIONS_INVALID'], [Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), 'IMAGE_DECODE_INVALID'], [valid.subarray(0, 40), 'IMAGE_DECODE_INVALID']]) {
|
||||
const { service, writes } = createService();
|
||||
await assert.rejects(() => service.upload({ ...metadata, width: 1, height: 1, data, mimeType: 'image/png' }), { code });
|
||||
assert.deepEqual(writes, []);
|
||||
}
|
||||
});
|
||||
|
||||
test('checks true byte length before image inspection', async () => {
|
||||
const data = Buffer.alloc(10 * 1024 * 1024 + 1);
|
||||
Buffer.from([0xff, 0xd8, 0xff]).copy(data);
|
||||
const service = createInventoryImageService({ imageInspector: { async readMetadata() { assert.fail('oversized input decoded'); } } });
|
||||
await assert.rejects(() => service.upload({ ...metadata, data, mimeType: 'image/jpeg' }), { code: 'IMAGE_FILE_TOO_LARGE' });
|
||||
});
|
||||
@@ -19,6 +19,7 @@ test('stores validated upload bytes before persisting image metadata', async ()
|
||||
const stored = [];
|
||||
const jobs = [];
|
||||
const service = createInventoryImageService({
|
||||
imageInspector: { async readMetadata() { return { width: 2, height: 3, mimeType: 'image/jpeg' }; }, async validateDecoding() {} },
|
||||
imageStorage: {
|
||||
async put(value) { stored.push(value); },
|
||||
async delete() {},
|
||||
@@ -38,6 +39,9 @@ test('stores validated upload bytes before persisting image metadata', async ()
|
||||
assert.equal(stored.length, 1);
|
||||
assert.deepEqual(stored[0], { key: input.storageKey, data: input.data });
|
||||
assert.equal(image.id, 'image-1');
|
||||
assert.equal(image.width, 2);
|
||||
assert.equal(image.height, 3);
|
||||
assert.equal(image.fileSize, input.data.length);
|
||||
assert.equal(image.processingStatus, 'PENDING');
|
||||
assert.equal('data' in image, false);
|
||||
assert.deepEqual(jobs, [{
|
||||
@@ -50,6 +54,7 @@ test('stores validated upload bytes before persisting image metadata', async ()
|
||||
test('removes stored bytes when metadata persistence fails', async () => {
|
||||
const deleted = [];
|
||||
const service = createInventoryImageService({
|
||||
imageInspector: { async readMetadata() { return { width: 2, height: 3, mimeType: 'image/jpeg' }; }, async validateDecoding() {} },
|
||||
imageStorage: {
|
||||
async put() {},
|
||||
async delete(key) { deleted.push(key); },
|
||||
|
||||
Reference in New Issue
Block a user