- File model + migration: file metadata, content lives in object storage - IFileStorage port with local disk and S3-compatible (RustFS/MinIO) adapters - POST /files, GET /files/:id/download, DELETE /files/:id - MIME sniffed from content; only safe images served inline, nosniff header - FileOwnerRegistry: owner modules register to grant visibility and keep their files from GC; FileGcService removes orphaned files hourly - TransformResponseInterceptor passes StreamableFile through Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { StoredFile } from './stored-file.entity.js';
|
|
import { FileForbiddenException } from '../exceptions/file-forbidden.exception.js';
|
|
|
|
const build = (mimeType = 'image/png', uploaderId: string | null = 'user-1') =>
|
|
StoredFile.reconstitute({
|
|
id: 'file-1',
|
|
storageKey: 'files/key',
|
|
fileName: 'photo.png',
|
|
mimeType,
|
|
size: 10,
|
|
uploaderId,
|
|
createdAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
});
|
|
|
|
describe('StoredFile', () => {
|
|
it('creates a new file with sanitized name and no id', () => {
|
|
const file = StoredFile.create({
|
|
storageKey: 'files/key',
|
|
fileName: '../secret/report.pdf',
|
|
mimeType: 'application/pdf',
|
|
size: 100,
|
|
uploaderId: 'user-1',
|
|
});
|
|
|
|
expect(file.id).toBeNull();
|
|
expect(file.fileName).toBe('report.pdf');
|
|
expect(file.uploaderId).toBe('user-1');
|
|
});
|
|
|
|
it('allows inline view only for safe image types', () => {
|
|
expect(build('image/png').isInlineViewable).toBe(true);
|
|
expect(build('image/svg+xml').isInlineViewable).toBe(false);
|
|
expect(build('text/html').isInlineViewable).toBe(false);
|
|
expect(build('application/octet-stream').isInlineViewable).toBe(false);
|
|
});
|
|
|
|
it('treats only the uploader as the owner', () => {
|
|
expect(() => build().assertOwnedBy('user-1')).not.toThrow();
|
|
expect(() => build().assertOwnedBy('user-2')).toThrow(FileForbiddenException);
|
|
expect(() => build('image/png', null).assertOwnedBy('user-1')).toThrow(FileForbiddenException);
|
|
});
|
|
});
|