Record uploaded assets in conversation packages

This commit is contained in:
john
2026-07-02 21:28:51 +08:00
parent c2308a6ead
commit 0f48792133
7 changed files with 193 additions and 4 deletions
+100 -1
View File
@@ -4,7 +4,7 @@ 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 { assetInternals, createAssetService, validateUploadRequest } from './mindspace-assets.mjs';
import { DEFAULT_MAX_FILE_BYTES } from './mindspace.mjs';
function createMockPool(state) {
@@ -40,6 +40,8 @@ function createMockPool(state) {
temporary_storage_key: params[8],
status: 'reserved',
expires_at: params[9],
source_session_id: params[11] ?? null,
source_message_id: params[12] ?? null,
});
return [[]];
}
@@ -242,6 +244,103 @@ test('completeUpload marks safe files ready and blocks script payloads', async (
assert.equal(asset.scanStatus, 'blocked');
});
test('createUpload stores optional source session metadata', async () => {
const storageRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-assets-'));
const state = {
categories: [{ id: 'cat-1', user_id: 'user-1', space_id: 'space-1', category_code: 'oa' }],
spaces: [
{
id: 'space-1',
user_id: 'user-1',
quota_bytes: 5 * 1024 * 1024,
used_bytes: 0,
reserved_bytes: 0,
status: 'active',
},
],
uploads: [],
assets: [],
versions: [],
};
const service = createAssetService(createMockPool(state), {
storageRoot,
idFactory: () => 'upload-1',
});
await service.createUpload('user-1', {
categoryId: 'cat-1',
filename: 'note.txt',
sizeBytes: 12,
sourceSessionId: 'session-1',
sourceMessageId: 'message-1',
});
assert.equal(state.uploads[0].source_session_id, 'session-1');
assert.equal(state.uploads[0].source_message_id, 'message-1');
});
test('registerUploadArtifactForConversation records uploaded input artifacts', async () => {
const calls = [];
const registry = {
async ensurePackage(input) {
calls.push(['ensurePackage', input]);
return { id: 'cp_session-1' };
},
async recordArtifact(input) {
calls.push(['recordArtifact', input]);
return input;
},
async writeManifestForSession(input) {
calls.push(['writeManifestForSession', input]);
return { storageKey: 'users/user-1/conversations/session-1/manifest.json' };
},
};
const result = await assetInternals.registerUploadArtifactForConversation({
registry,
userId: 'user-1',
upload: {
filename: 'cover.png',
source_session_id: 'session-1',
source_message_id: 'message-1',
},
asset: {
displayName: 'cover.png',
filename: 'cover.png',
mimeType: 'image/png',
sizeBytes: 123,
publicUrl: 'https://m.tkmind.cn/MindSpace/user-1/images/cover.png',
},
assetId: 'asset-1',
storageKey: 'users/user-1/images/2026-07-02/cover.png',
canonicalUrl: 'https://m.tkmind.cn/MindSpace/user-1/images/cover.png',
now: 1000,
});
assert.equal(result.artifactKind, 'input_image');
assert.equal(result.role, 'user');
assert.equal(result.messageId, 'message-1');
assert.equal(result.canonicalUrl, 'https://m.tkmind.cn/MindSpace/user-1/images/cover.png');
assert.deepEqual(calls[2], [
'writeManifestForSession',
{ userId: 'user-1', sessionId: 'session-1' },
]);
});
test('registerUploadArtifactForConversation skips uploads without source session', async () => {
assert.equal(
await assetInternals.registerUploadArtifactForConversation({
registry: { ensurePackage: async () => assert.fail('should not be called') },
userId: 'user-1',
upload: { filename: 'note.txt' },
asset: { mimeType: 'text/plain' },
assetId: 'asset-1',
now: 1000,
}),
null,
);
});
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-'));