Add smart ACK provider for WeChat MP replies
Replace fixed ackText with a rule-based AckProvider that picks response templates by message type and intent (translate, summary, rewrite, poster, ppt, mindmap, code, search, schedule). Pure sync, zero I/O, auto-falls back to config.ackText on any error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { createAssetService, validateUploadRequest } from './mindspace-assets.mjs';
|
||||
import { DEFAULT_MAX_FILE_BYTES } from './mindspace.mjs';
|
||||
|
||||
function createMockPool(state) {
|
||||
return {
|
||||
@@ -87,7 +88,7 @@ function createMockPool(state) {
|
||||
state.versions.push({
|
||||
id: params[0],
|
||||
asset_id: params[1],
|
||||
storage_key: params[3],
|
||||
storage_key: params[2],
|
||||
scan_status: params[8],
|
||||
});
|
||||
return [[]];
|
||||
@@ -166,6 +167,23 @@ test('validateUploadRequest rejects traversal filenames', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('validateUploadRequest allows 5MB uploads by default', () => {
|
||||
assert.doesNotThrow(() =>
|
||||
validateUploadRequest({
|
||||
filename: 'handbook.pdf',
|
||||
sizeBytes: DEFAULT_MAX_FILE_BYTES,
|
||||
}),
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
validateUploadRequest({
|
||||
filename: 'handbook.pdf',
|
||||
sizeBytes: DEFAULT_MAX_FILE_BYTES + 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 = {
|
||||
@@ -206,6 +224,57 @@ test('completeUpload marks safe files ready and blocks script payloads', async (
|
||||
assert.equal(asset.scanStatus, 'blocked');
|
||||
});
|
||||
|
||||
test('completeUpload publishes public images to the public temp image library', 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: 5 * 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 png = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=',
|
||||
'base64',
|
||||
);
|
||||
|
||||
const upload = await service.createUpload('user-1', {
|
||||
categoryId: 'cat-public',
|
||||
filename: 'look.png',
|
||||
sizeBytes: png.length,
|
||||
});
|
||||
await service.writeUploadContent('user-1', upload.id, png);
|
||||
const asset = await service.completeUpload('user-1', upload.id);
|
||||
|
||||
assert.equal(asset.status, 'ready');
|
||||
assert.match(asset.publicUrl, /^https:\/\/g2\.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');
|
||||
await assert.doesNotReject(() =>
|
||||
fs.stat(path.join(storageRoot, 'users/user-1/public/.tmp-images/id-2.png')),
|
||||
);
|
||||
await assert.doesNotReject(() =>
|
||||
fs.stat(path.join(h5Root, 'MindSpace/user-1/public/.tmp-images/id-2.png')),
|
||||
);
|
||||
});
|
||||
|
||||
test('createUpload rejects another users category id', async () => {
|
||||
const state = {
|
||||
categories: [
|
||||
|
||||
Reference in New Issue
Block a user