release: prepare 0629001 portal updates
This commit is contained in:
+146
-8
@@ -3,6 +3,7 @@ import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import sharp from 'sharp';
|
||||
import { createAssetService, validateUploadRequest } from './mindspace-assets.mjs';
|
||||
import { DEFAULT_MAX_FILE_BYTES } from './mindspace.mjs';
|
||||
|
||||
@@ -184,6 +185,23 @@ test('validateUploadRequest allows 5MB uploads by default', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('validateUploadRequest keeps image uploads below the image-specific limit', () => {
|
||||
assert.doesNotThrow(() =>
|
||||
validateUploadRequest({
|
||||
filename: 'cover.jpg',
|
||||
sizeBytes: 4 * 1024 * 1024,
|
||||
}),
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
validateUploadRequest({
|
||||
filename: 'cover.jpg',
|
||||
sizeBytes: 4 * 1024 * 1024 + 1,
|
||||
}),
|
||||
(error) => error.code === 'file_too_large',
|
||||
);
|
||||
});
|
||||
|
||||
test('completeUpload marks safe files ready and blocks script payloads', async () => {
|
||||
const storageRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-assets-'));
|
||||
const state = {
|
||||
@@ -251,10 +269,16 @@ test('completeUpload publishes public images to the public temp image library',
|
||||
h5Root,
|
||||
idFactory: () => `id-${++nextId}`,
|
||||
});
|
||||
const png = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=',
|
||||
'base64',
|
||||
);
|
||||
const png = await sharp({
|
||||
create: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
channels: 4,
|
||||
background: { r: 10, g: 160, b: 220, alpha: 0.8 },
|
||||
},
|
||||
})
|
||||
.png()
|
||||
.toBuffer();
|
||||
|
||||
const upload = await service.createUpload('user-1', {
|
||||
categoryId: 'cat-public',
|
||||
@@ -265,16 +289,130 @@ test('completeUpload publishes public images to the public temp image library',
|
||||
const asset = await service.completeUpload('user-1', upload.id);
|
||||
|
||||
assert.equal(asset.status, 'ready');
|
||||
assert.match(asset.publicUrl, /^https:\/\/m\.tkmind\.cn\/MindSpace\/user-1\/public\/\.tmp-images\/id-2\.png$/);
|
||||
assert.equal(state.versions[0].storage_key, 'users/user-1/public/.tmp-images/id-2.png');
|
||||
assert.match(asset.publicUrl, /^http:\/\/localhost:20081\/unsafe\//);
|
||||
assert.match(state.versions[0].storage_key, /^users\/user-1\/images\/\d{4}-\d{2}-\d{2}\/look-id2\.png$/);
|
||||
await assert.doesNotReject(() =>
|
||||
fs.stat(path.join(storageRoot, 'users/user-1/public/.tmp-images/id-2.png')),
|
||||
fs.stat(path.join(storageRoot, state.versions[0].storage_key)),
|
||||
);
|
||||
await assert.doesNotReject(() =>
|
||||
fs.stat(path.join(h5Root, 'MindSpace/user-1/public/.tmp-images/id-2.png')),
|
||||
fs.stat(path.join(h5Root, 'MindSpace/user-1', state.assets[0].original_filename)),
|
||||
);
|
||||
});
|
||||
|
||||
test('completeUpload normalizes large images into a bounded master asset', async () => {
|
||||
const storageRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-assets-'));
|
||||
const state = {
|
||||
categories: [
|
||||
{ id: 'cat-public', user_id: 'user-1', space_id: 'space-1', category_code: 'public' },
|
||||
],
|
||||
spaces: [
|
||||
{
|
||||
id: 'space-1',
|
||||
user_id: 'user-1',
|
||||
quota_bytes: 60 * 1024 * 1024,
|
||||
used_bytes: 0,
|
||||
reserved_bytes: 0,
|
||||
status: 'active',
|
||||
},
|
||||
],
|
||||
uploads: [],
|
||||
assets: [],
|
||||
versions: [],
|
||||
};
|
||||
let nextId = 0;
|
||||
const service = createAssetService(createMockPool(state), {
|
||||
storageRoot,
|
||||
idFactory: () => `id-${++nextId}`,
|
||||
});
|
||||
const original = await sharp({
|
||||
create: {
|
||||
width: 4200,
|
||||
height: 2800,
|
||||
channels: 3,
|
||||
background: { r: 220, g: 120, b: 60 },
|
||||
},
|
||||
})
|
||||
.jpeg({ quality: 98 })
|
||||
.toBuffer();
|
||||
|
||||
const upload = await service.createUpload('user-1', {
|
||||
categoryId: 'cat-public',
|
||||
filename: 'camera-roll.jpg',
|
||||
sizeBytes: original.length,
|
||||
});
|
||||
await service.writeUploadContent('user-1', upload.id, original);
|
||||
const asset = await service.completeUpload('user-1', upload.id);
|
||||
const storedPath = path.join(storageRoot, state.versions[0].storage_key);
|
||||
const storedBuffer = await fs.readFile(storedPath);
|
||||
const metadata = await sharp(storedBuffer).metadata();
|
||||
|
||||
assert.equal(asset.status, 'ready');
|
||||
assert.equal(asset.mimeType, 'image/jpeg');
|
||||
assert.ok(asset.sizeBytes <= original.length);
|
||||
assert.ok((metadata.width ?? 0) <= 2560);
|
||||
assert.ok((metadata.height ?? 0) <= 2560);
|
||||
});
|
||||
|
||||
test('completeUpload gives same-name public images unique storage keys', async () => {
|
||||
const storageRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-assets-'));
|
||||
const h5Root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-h5-'));
|
||||
const state = {
|
||||
categories: [
|
||||
{ id: 'cat-public', user_id: 'user-1', space_id: 'space-1', category_code: 'public' },
|
||||
],
|
||||
spaces: [
|
||||
{
|
||||
id: 'space-1',
|
||||
user_id: 'user-1',
|
||||
quota_bytes: 20 * 1024 * 1024,
|
||||
used_bytes: 0,
|
||||
reserved_bytes: 0,
|
||||
status: 'active',
|
||||
},
|
||||
],
|
||||
uploads: [],
|
||||
assets: [],
|
||||
versions: [],
|
||||
};
|
||||
let nextId = 0;
|
||||
const service = createAssetService(createMockPool(state), {
|
||||
storageRoot,
|
||||
h5Root,
|
||||
idFactory: () => `id-${++nextId}`,
|
||||
});
|
||||
const image = await sharp({
|
||||
create: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
channels: 3,
|
||||
background: { r: 120, g: 120, b: 240 },
|
||||
},
|
||||
})
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
|
||||
const first = await service.createUpload('user-1', {
|
||||
categoryId: 'cat-public',
|
||||
filename: 'e20.jpg',
|
||||
sizeBytes: image.length,
|
||||
});
|
||||
await service.writeUploadContent('user-1', first.id, image);
|
||||
await service.completeUpload('user-1', first.id);
|
||||
|
||||
const second = await service.createUpload('user-1', {
|
||||
categoryId: 'cat-public',
|
||||
filename: 'e20.jpg',
|
||||
sizeBytes: image.length,
|
||||
});
|
||||
await service.writeUploadContent('user-1', second.id, image);
|
||||
await service.completeUpload('user-1', second.id);
|
||||
|
||||
assert.equal(state.versions.length, 2);
|
||||
assert.notEqual(state.versions[0].storage_key, state.versions[1].storage_key);
|
||||
assert.match(state.versions[0].storage_key, /\/e20-id2\.jpg$/);
|
||||
assert.match(state.versions[1].storage_key, /\/e20-id5\.jpg$/);
|
||||
});
|
||||
|
||||
test('createUpload rejects another users category id', async () => {
|
||||
const state = {
|
||||
categories: [
|
||||
|
||||
Reference in New Issue
Block a user