feat: verify content asset uploads

This commit is contained in:
Codex
2026-06-29 03:30:06 +08:00
parent 7c41bf525f
commit 21c0634020
13 changed files with 988 additions and 35 deletions

View File

@@ -1850,6 +1850,8 @@ async function testTenantContentAssetsAndImports() {
assetType: 'pdf',
storageProvider: 'local_dev',
mimeType: 'application/pdf',
fileSizeBytes: 4096,
checksumSha256: 'a'.repeat(64),
},
});
assert.equal(upload.assetDraft?.assetType, 'pdf', 'upload signer should return asset draft');
@@ -1989,11 +1991,34 @@ async function testTenantContentAssetsAndImports() {
fileName: upload.assetDraft.fileName,
mimeType: upload.assetDraft.mimeType,
fileSizeBytes: upload.assetDraft.fileSizeBytes,
checksumSha256: upload.assetDraft.checksumSha256,
visibility: 'tenant',
status: 'active',
},
});
assert.equal(localAsset.item?.objectKey, upload.assetDraft.objectKey, 'asset upsert should keep normalized object key');
assert.equal(localAsset.item?.status, 'draft', 'managed object asset should stay draft before upload confirmation');
assert.equal(localAsset.item?.uploadStatus, 'pending', 'managed object asset should be pending before upload confirmation');
const unconfirmedDownload = await request('/api/catalog/assets/download', {
query: { assetId: localAsset.item.id },
expectStatus: 404,
});
assert.equal(unconfirmedDownload.code, 'ASSET_NOT_FOUND', 'unconfirmed draft asset should not be downloadable by students');
const confirmLocal = await request('/api/tenant-content/assets/confirm-upload', {
userId: TENANT_ADMIN_USER_ID,
method: 'POST',
body: {
assetId: localAsset.item.id,
fileSizeBytes: upload.assetDraft.fileSizeBytes,
checksumSha256: upload.assetDraft.checksumSha256,
mimeType: 'application/pdf',
publish: true,
},
});
assert.equal(confirmLocal.item?.status, 'active', 'confirmed managed asset should publish when requested');
assert.equal(confirmLocal.item?.uploadStatus, 'verified', 'confirm upload should mark asset verified');
assert.equal(confirmLocal.item?.verifiedChecksumSha256, 'a'.repeat(64), 'confirm upload should persist checksum');
const localDownload = await request('/api/catalog/assets/download', {
query: { assetId: localAsset.item.id },
@@ -2002,6 +2027,50 @@ async function testTenantContentAssetsAndImports() {
assert.equal(localDownload.download?.signatureMode, 'local-placeholder', 'local object asset should use local placeholder signer');
assert.ok(localDownload.download?.url?.includes(encodeURIComponent(localAsset.item.bucket)), 'object asset download should include bucket');
const localPreview = await request('/api/catalog/assets/preview', {
query: { assetId: localAsset.item.id },
});
assert.equal(localPreview.preview?.method, 'GET', 'asset preview should sign GET');
assert.equal(localPreview.preview?.signatureMode, 'local-placeholder', 'local asset preview should use local placeholder signer');
assert.ok(localPreview.preview?.url?.includes('disposition=inline'), 'asset preview should request inline disposition');
const adminPreview = await request('/api/tenant-content/assets/sign-preview', {
userId: TENANT_ADMIN_USER_ID,
method: 'POST',
body: { assetId: localAsset.item.id },
});
assert.ok(adminPreview.preview?.url?.includes('disposition=inline'), 'admin preview should request inline disposition');
const failedAsset = await request('/api/tenant-content/assets', {
userId: TENANT_ADMIN_USER_ID,
method: 'PUT',
body: {
title: '集成测试校验失败资料',
assetType: 'pdf',
storageProvider: 'local_dev',
bucket: upload.assetDraft.bucket,
objectKey: `${MAIN_TENANT_ID}/pdf/failed-${Date.now()}.pdf`,
fileName: 'failed.pdf',
mimeType: 'application/pdf',
fileSizeBytes: 100,
checksumSha256: 'b'.repeat(64),
visibility: 'tenant',
},
});
const failedConfirm = await request('/api/tenant-content/assets/confirm-upload', {
userId: TENANT_ADMIN_USER_ID,
method: 'POST',
body: {
assetId: failedAsset.item.id,
fileSizeBytes: 101,
checksumSha256: 'b'.repeat(64),
mimeType: 'application/pdf',
publish: true,
},
expectStatus: 409,
});
assert.equal(failedConfirm.code, 'UPLOAD_VERIFICATION_FAILED', 'confirm upload should reject mismatched declared metadata');
const invalidObjectAsset = await request('/api/tenant-content/assets', {
userId: TENANT_ADMIN_USER_ID,
method: 'PUT',