144 lines
5.3 KiB
JavaScript
144 lines
5.3 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const crypto = require('crypto');
|
|
const sharp = require('sharp');
|
|
const {
|
|
normalizeMediaPath,
|
|
createImageVariants,
|
|
removeImageVariants,
|
|
optimizeUploadedImage,
|
|
registerMediaAssets,
|
|
syncMediaAssetsFromDirectory,
|
|
countMediaAssetReferences
|
|
} = require('../src/web/lib/media/library');
|
|
|
|
test('media library normalizes only media URLs', () => {
|
|
assert.equal(normalizeMediaPath('/media/uploads/photo.png'), '/media/uploads/photo.png');
|
|
assert.equal(normalizeMediaPath(' /media/video.mp4 '), '/media/video.mp4');
|
|
assert.equal(normalizeMediaPath('/assets/photo.png'), null);
|
|
});
|
|
|
|
test('media library optimizes large raster uploads in place', async () => {
|
|
const uploadDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'pulse-media-optimize-'));
|
|
const filePath = path.join(uploadDir, 'large.jpg');
|
|
const sourceBuffer = await sharp(crypto.randomBytes(5000 * 100 * 3), {
|
|
raw: { width: 5000, height: 100, channels: 3 }
|
|
}).jpeg({ quality: 100 }).toBuffer();
|
|
await fs.promises.writeFile(filePath, sourceBuffer);
|
|
|
|
try {
|
|
const file = { path: filePath, size: sourceBuffer.length };
|
|
await optimizeUploadedImage(file, { maxDimension: 8192 });
|
|
const optimizedStats = await fs.promises.stat(filePath);
|
|
|
|
assert.equal(file.path, filePath);
|
|
assert.equal(file.size, optimizedStats.size);
|
|
assert.ok(optimizedStats.size < sourceBuffer.length);
|
|
const metadata = await sharp(filePath).metadata();
|
|
assert.equal(metadata.format, 'jpeg');
|
|
assert.equal(metadata.width, 5000);
|
|
} finally {
|
|
await fs.promises.rm(uploadDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('media library creates only smaller image variants and preserves the original', async () => {
|
|
const uploadDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'pulse-media-variants-'));
|
|
const filePath = path.join(uploadDir, 'large.jpg');
|
|
const sourceBuffer = await sharp(crypto.randomBytes(5000 * 100 * 3), {
|
|
raw: { width: 5000, height: 100, channels: 3 }
|
|
}).jpeg({ quality: 100 }).toBuffer();
|
|
await fs.promises.writeFile(filePath, sourceBuffer);
|
|
|
|
try {
|
|
const variants = await createImageVariants({ path: filePath });
|
|
assert.deepEqual(variants.map(function (variant) { return variant.width; }), [2048, 4096]);
|
|
assert.equal((await fs.promises.stat(filePath)).size, sourceBuffer.length);
|
|
for (const variant of variants) {
|
|
const variantPath = path.join(uploadDir, path.basename(variant.path));
|
|
assert.ok((await fs.promises.stat(variantPath)).size < sourceBuffer.length);
|
|
}
|
|
await removeImageVariants(filePath);
|
|
assert.equal((await fs.promises.readdir(uploadDir)).filter(function (name) { return name.includes('.variant-'); }).length, 0);
|
|
} finally {
|
|
await fs.promises.rm(uploadDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('media library registers uploaded files with their metadata', async () => {
|
|
const queries = [];
|
|
const pool = {
|
|
async query(sql, params) {
|
|
queries.push({ sql, params });
|
|
return [{ affectedRows: 1 }];
|
|
}
|
|
};
|
|
const registered = await registerMediaAssets(pool, [{
|
|
filename: 'upload.png',
|
|
originalname: 'Photo.png',
|
|
mimetype: 'image/png',
|
|
size: 42
|
|
}], file => '/media/uploads/' + file.filename, () => 'image', 7);
|
|
|
|
assert.deepEqual(registered, ['/media/uploads/upload.png']);
|
|
assert.match(queries[0].sql, /INSERT INTO c_media_assets/);
|
|
assert.deepEqual(queries[0].params, [
|
|
'/media/uploads/upload.png',
|
|
'Photo.png',
|
|
'image',
|
|
'image/png',
|
|
42,
|
|
1,
|
|
7,
|
|
7
|
|
]);
|
|
});
|
|
|
|
test('media library backfills existing image and video uploads', async () => {
|
|
const uploadDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'pulse-media-library-'));
|
|
await fs.promises.writeFile(path.join(uploadDir, 'legacy.png'), Buffer.from('image'));
|
|
await fs.promises.writeFile(path.join(uploadDir, 'legacy.mp4'), Buffer.from('video'));
|
|
await fs.promises.writeFile(path.join(uploadDir, 'ignore.txt'), Buffer.from('other'));
|
|
const queries = [];
|
|
const pool = {
|
|
async query(sql, params) {
|
|
queries.push({ sql, params });
|
|
return [{ affectedRows: 1 }];
|
|
}
|
|
};
|
|
|
|
try {
|
|
assert.equal(await syncMediaAssetsFromDirectory(pool, uploadDir), 2);
|
|
assert.deepEqual(queries.map(query => query.params[0]).sort(), [
|
|
'/media/uploads/legacy.mp4',
|
|
'/media/uploads/legacy.png'
|
|
]);
|
|
assert.match(queries[0].sql, /original_name = original_name/);
|
|
assert.match(queries[0].sql, /is_published = is_published/);
|
|
} finally {
|
|
await fs.promises.rm(uploadDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('media library counts slide and template references', async () => {
|
|
const queries = [];
|
|
const pool = {
|
|
async query(sql, params) {
|
|
queries.push({ sql, params });
|
|
if (sql.includes('FROM c_slides')) {
|
|
return [[{ ref_count: 2 }]];
|
|
}
|
|
return [[{ ref_count: 1 }]];
|
|
}
|
|
};
|
|
|
|
assert.equal(await countMediaAssetReferences(pool, '/media/uploads/shared.png'), 3);
|
|
assert.equal(queries.length, 2);
|
|
assert.equal(queries[0].params[0], '/media/uploads/shared.png');
|
|
assert.equal(queries[1].params[0], '/media/uploads/shared.png');
|
|
assert.match(queries[0].sql, /LOCATE/);
|
|
});
|