Skip to content

Commit f8276b6

Browse files
committed
refactor: update thumbnail handling to use image size for thumbnailable checks and adjust related tests
1 parent 553af55 commit f8276b6

5 files changed

Lines changed: 75 additions & 134 deletions

File tree

src/utils/thumbnail.utils.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,21 @@ const imageExtensions: FileExtensionMap = {
1717
raw: ['raw', 'cr2', 'nef', 'orf', 'sr2'],
1818
webp: ['webp'],
1919
};
20-
const pdfExtensions: FileExtensionMap = {
21-
pdf: ['pdf'],
22-
};
2320
const thumbnailableImageExtension: Set<string> = new Set([
2421
...imageExtensions['jpg'],
2522
...imageExtensions['png'],
2623
...imageExtensions['webp'],
2724
...imageExtensions['gif'],
2825
...imageExtensions['tiff'],
2926
]);
30-
const thumbnailablePdfExtension: Set<string> = new Set(pdfExtensions['pdf']);
31-
const thumbnailableExtension: Set<string> = new Set(thumbnailableImageExtension);
3227

3328
export class ThumbnailUtils {
34-
static readonly MAX_IMAGE_THUMBNAILABLE_SIZE_IN_MB = 500 * 1024 * 1024;
35-
36-
static readonly isFileThumbnailable = (fileType: string) => {
37-
return fileType.trim().length > 0 && thumbnailableExtension.has(fileType.trim().toLowerCase());
38-
};
39-
40-
static readonly isPDFThumbnailable = (fileType: string) => {
41-
return fileType.trim().length > 0 && thumbnailablePdfExtension.has(fileType.trim().toLowerCase());
42-
};
29+
static readonly MAX_IMAGE_THUMBNAILABLE_SIZE_IN_BYTES = 128 * 1024 * 1024;
4330

4431
static readonly isImageThumbnailable = (fileType: string, size: number) => {
45-
if (size > ThumbnailUtils.MAX_IMAGE_THUMBNAILABLE_SIZE_IN_MB) return false;
32+
if (size <= 0 || size > ThumbnailUtils.MAX_IMAGE_THUMBNAILABLE_SIZE_IN_BYTES) {
33+
return false;
34+
}
4635
return fileType.trim().length > 0 && thumbnailableImageExtension.has(fileType.trim().toLowerCase());
4736
};
4837
}

src/utils/upload.utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ export class UploadUtils {
2525
static readonly prepareUploadStreams = (
2626
readable: Readable,
2727
fileType: string,
28+
size: number,
2829
): {
2930
fileStream: Readable;
3031
thumbnailStream: BufferStream | undefined;
3132
isThumbnailable: boolean;
3233
} => {
33-
const isThumbnailable = ThumbnailUtils.isFileThumbnailable(fileType);
34+
const isThumbnailable = ThumbnailUtils.isImageThumbnailable(fileType, size);
3435
if (!isThumbnailable) {
3536
return { fileStream: readable, thumbnailStream: undefined, isThumbnailable };
3637
}

src/webdav/handlers/PUT.handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class PUTRequestHandler implements WebDavMethodHandler {
6363
const { user } = await AuthService.instance.getAuthDetails();
6464
const fileType = resource.path.ext.replace('.', '');
6565

66-
const { fileStream, thumbnailStream } = UploadUtils.prepareUploadStreams(req, fileType);
66+
const { fileStream, thumbnailStream } = UploadUtils.prepareUploadStreams(req, fileType, contentLength);
6767

6868
const { networkFacade, bucket } = await CLIUtils.prepareNetwork(user);
6969

test/utils/thumbnail.utils.test.ts

Lines changed: 43 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -2,139 +2,69 @@ import { describe, expect, test } from 'vitest';
22
import { ThumbnailUtils } from '../../src/utils/thumbnail.utils';
33

44
describe('Thumbnail Utils tests', () => {
5-
describe('isFileThumbnailable', () => {
6-
test('when a valid image extension is given, then true is returned', () => {
7-
expect(ThumbnailUtils.isFileThumbnailable('jpg')).toBe(true);
8-
expect(ThumbnailUtils.isFileThumbnailable('jpeg')).toBe(true);
9-
expect(ThumbnailUtils.isFileThumbnailable('png')).toBe(true);
10-
expect(ThumbnailUtils.isFileThumbnailable('webp')).toBe(true);
11-
expect(ThumbnailUtils.isFileThumbnailable('gif')).toBe(true);
12-
expect(ThumbnailUtils.isFileThumbnailable('tif')).toBe(true);
13-
expect(ThumbnailUtils.isFileThumbnailable('tiff')).toBe(true);
14-
});
15-
16-
test('when an extension has mixed case, then true is returned', () => {
17-
expect(ThumbnailUtils.isFileThumbnailable('JPG')).toBe(true);
18-
expect(ThumbnailUtils.isFileThumbnailable('PNG')).toBe(true);
19-
expect(ThumbnailUtils.isFileThumbnailable('Webp')).toBe(true);
20-
expect(ThumbnailUtils.isFileThumbnailable('GIF')).toBe(true);
21-
});
22-
23-
test('when an extension has surrounding whitespace, then true is returned', () => {
24-
expect(ThumbnailUtils.isFileThumbnailable(' jpg ')).toBe(true);
25-
expect(ThumbnailUtils.isFileThumbnailable(' png ')).toBe(true);
26-
expect(ThumbnailUtils.isFileThumbnailable('\tgif\t')).toBe(true);
27-
});
28-
29-
test('when a non-thumbnailable extension is given, then false is returned', () => {
30-
expect(ThumbnailUtils.isFileThumbnailable('pdf')).toBe(false);
31-
expect(ThumbnailUtils.isFileThumbnailable('doc')).toBe(false);
32-
expect(ThumbnailUtils.isFileThumbnailable('txt')).toBe(false);
33-
expect(ThumbnailUtils.isFileThumbnailable('mp4')).toBe(false);
34-
expect(ThumbnailUtils.isFileThumbnailable('bmp')).toBe(false);
35-
expect(ThumbnailUtils.isFileThumbnailable('raw')).toBe(false);
36-
expect(ThumbnailUtils.isFileThumbnailable('heic')).toBe(false);
37-
});
38-
39-
test('when an empty or blank string is given, then false is returned', () => {
40-
expect(ThumbnailUtils.isFileThumbnailable('')).toBe(false);
41-
expect(ThumbnailUtils.isFileThumbnailable(' ')).toBe(false);
42-
expect(ThumbnailUtils.isFileThumbnailable('\t\n')).toBe(false);
43-
});
44-
45-
test('when an invalid extension is given, then false is returned', () => {
46-
expect(ThumbnailUtils.isFileThumbnailable('unknown')).toBe(false);
47-
expect(ThumbnailUtils.isFileThumbnailable('jpgg')).toBe(false);
48-
});
49-
});
50-
51-
describe('isPDFThumbnailable', () => {
52-
test('when a pdf extension is given, then true is returned', () => {
53-
expect(ThumbnailUtils.isPDFThumbnailable('pdf')).toBe(true);
54-
});
55-
56-
test('when an extension has mixed case, then true is returned', () => {
57-
expect(ThumbnailUtils.isPDFThumbnailable('PDF')).toBe(true);
58-
expect(ThumbnailUtils.isPDFThumbnailable('Pdf')).toBe(true);
59-
expect(ThumbnailUtils.isPDFThumbnailable('pDf')).toBe(true);
60-
});
61-
62-
test('when an extension has surrounding whitespace, then true is returned', () => {
63-
expect(ThumbnailUtils.isPDFThumbnailable(' pdf ')).toBe(true);
64-
expect(ThumbnailUtils.isPDFThumbnailable(' PDF ')).toBe(true);
65-
expect(ThumbnailUtils.isPDFThumbnailable('\tpdf\n')).toBe(true);
66-
});
67-
68-
test('when a non-pdf extension is given, then false is returned', () => {
69-
expect(ThumbnailUtils.isPDFThumbnailable('jpg')).toBe(false);
70-
expect(ThumbnailUtils.isPDFThumbnailable('png')).toBe(false);
71-
expect(ThumbnailUtils.isPDFThumbnailable('doc')).toBe(false);
72-
expect(ThumbnailUtils.isPDFThumbnailable('docx')).toBe(false);
73-
expect(ThumbnailUtils.isPDFThumbnailable('txt')).toBe(false);
74-
});
75-
76-
test('when an empty or blank string is given, then false is returned', () => {
77-
expect(ThumbnailUtils.isPDFThumbnailable('')).toBe(false);
78-
expect(ThumbnailUtils.isPDFThumbnailable(' ')).toBe(false);
79-
expect(ThumbnailUtils.isPDFThumbnailable('\t\n')).toBe(false);
80-
});
81-
82-
test('when an invalid extension is given, then false is returned', () => {
83-
expect(ThumbnailUtils.isPDFThumbnailable('pdff')).toBe(false);
84-
expect(ThumbnailUtils.isPDFThumbnailable('pd')).toBe(false);
85-
});
86-
});
87-
885
describe('isImageThumbnailable', () => {
6+
const validSize = 1024;
7+
898
test('when a thumbnailable image extension is given, then true is returned', () => {
90-
expect(ThumbnailUtils.isImageThumbnailable('jpg')).toBe(true);
91-
expect(ThumbnailUtils.isImageThumbnailable('jpeg')).toBe(true);
92-
expect(ThumbnailUtils.isImageThumbnailable('png')).toBe(true);
93-
expect(ThumbnailUtils.isImageThumbnailable('webp')).toBe(true);
94-
expect(ThumbnailUtils.isImageThumbnailable('gif')).toBe(true);
95-
expect(ThumbnailUtils.isImageThumbnailable('tif')).toBe(true);
96-
expect(ThumbnailUtils.isImageThumbnailable('tiff')).toBe(true);
9+
expect(ThumbnailUtils.isImageThumbnailable('jpg', validSize)).toBe(true);
10+
expect(ThumbnailUtils.isImageThumbnailable('jpeg', validSize)).toBe(true);
11+
expect(ThumbnailUtils.isImageThumbnailable('png', validSize)).toBe(true);
12+
expect(ThumbnailUtils.isImageThumbnailable('webp', validSize)).toBe(true);
13+
expect(ThumbnailUtils.isImageThumbnailable('gif', validSize)).toBe(true);
14+
expect(ThumbnailUtils.isImageThumbnailable('tif', validSize)).toBe(true);
15+
expect(ThumbnailUtils.isImageThumbnailable('tiff', validSize)).toBe(true);
9716
});
9817

9918
test('when an extension has mixed case, then true is returned', () => {
100-
expect(ThumbnailUtils.isImageThumbnailable('JPG')).toBe(true);
101-
expect(ThumbnailUtils.isImageThumbnailable('PNG')).toBe(true);
102-
expect(ThumbnailUtils.isImageThumbnailable('GIF')).toBe(true);
103-
expect(ThumbnailUtils.isImageThumbnailable('Jpeg')).toBe(true);
19+
expect(ThumbnailUtils.isImageThumbnailable('JPG', validSize)).toBe(true);
20+
expect(ThumbnailUtils.isImageThumbnailable('PNG', validSize)).toBe(true);
21+
expect(ThumbnailUtils.isImageThumbnailable('GIF', validSize)).toBe(true);
22+
expect(ThumbnailUtils.isImageThumbnailable('Jpeg', validSize)).toBe(true);
10423
});
10524

10625
test('when an extension has surrounding whitespace, then true is returned', () => {
107-
expect(ThumbnailUtils.isImageThumbnailable(' jpg ')).toBe(true);
108-
expect(ThumbnailUtils.isImageThumbnailable(' png ')).toBe(true);
109-
expect(ThumbnailUtils.isImageThumbnailable('\twebp\n')).toBe(true);
26+
expect(ThumbnailUtils.isImageThumbnailable(' jpg ', validSize)).toBe(true);
27+
expect(ThumbnailUtils.isImageThumbnailable(' png ', validSize)).toBe(true);
28+
expect(ThumbnailUtils.isImageThumbnailable('\twebp\n', validSize)).toBe(true);
11029
});
11130

11231
test('when a non-thumbnailable image format is given, then false is returned', () => {
113-
expect(ThumbnailUtils.isImageThumbnailable('bmp')).toBe(false);
114-
expect(ThumbnailUtils.isImageThumbnailable('heic')).toBe(false);
115-
expect(ThumbnailUtils.isImageThumbnailable('raw')).toBe(false);
116-
expect(ThumbnailUtils.isImageThumbnailable('cr2')).toBe(false);
117-
expect(ThumbnailUtils.isImageThumbnailable('nef')).toBe(false);
118-
expect(ThumbnailUtils.isImageThumbnailable('eps')).toBe(false);
32+
expect(ThumbnailUtils.isImageThumbnailable('bmp', validSize)).toBe(false);
33+
expect(ThumbnailUtils.isImageThumbnailable('heic', validSize)).toBe(false);
34+
expect(ThumbnailUtils.isImageThumbnailable('raw', validSize)).toBe(false);
35+
expect(ThumbnailUtils.isImageThumbnailable('cr2', validSize)).toBe(false);
36+
expect(ThumbnailUtils.isImageThumbnailable('nef', validSize)).toBe(false);
37+
expect(ThumbnailUtils.isImageThumbnailable('eps', validSize)).toBe(false);
11938
});
12039

12140
test('when a non-image extension is given, then false is returned', () => {
122-
expect(ThumbnailUtils.isImageThumbnailable('pdf')).toBe(false);
123-
expect(ThumbnailUtils.isImageThumbnailable('doc')).toBe(false);
124-
expect(ThumbnailUtils.isImageThumbnailable('txt')).toBe(false);
125-
expect(ThumbnailUtils.isImageThumbnailable('mp4')).toBe(false);
126-
expect(ThumbnailUtils.isImageThumbnailable('mp3')).toBe(false);
41+
expect(ThumbnailUtils.isImageThumbnailable('pdf', validSize)).toBe(false);
42+
expect(ThumbnailUtils.isImageThumbnailable('doc', validSize)).toBe(false);
43+
expect(ThumbnailUtils.isImageThumbnailable('txt', validSize)).toBe(false);
44+
expect(ThumbnailUtils.isImageThumbnailable('mp4', validSize)).toBe(false);
45+
expect(ThumbnailUtils.isImageThumbnailable('mp3', validSize)).toBe(false);
12746
});
12847

12948
test('when an empty or blank string is given, then false is returned', () => {
130-
expect(ThumbnailUtils.isImageThumbnailable('')).toBe(false);
131-
expect(ThumbnailUtils.isImageThumbnailable(' ')).toBe(false);
132-
expect(ThumbnailUtils.isImageThumbnailable('\t\n')).toBe(false);
49+
expect(ThumbnailUtils.isImageThumbnailable('', validSize)).toBe(false);
50+
expect(ThumbnailUtils.isImageThumbnailable(' ', validSize)).toBe(false);
51+
expect(ThumbnailUtils.isImageThumbnailable('\t\n', validSize)).toBe(false);
13352
});
13453

13554
test('when an invalid extension is given, then false is returned', () => {
136-
expect(ThumbnailUtils.isImageThumbnailable('jpgg')).toBe(false);
137-
expect(ThumbnailUtils.isImageThumbnailable('unknown')).toBe(false);
55+
expect(ThumbnailUtils.isImageThumbnailable('jpgg', validSize)).toBe(false);
56+
expect(ThumbnailUtils.isImageThumbnailable('unknown', validSize)).toBe(false);
57+
});
58+
59+
test('when the size is zero or negative, then false is returned', () => {
60+
expect(ThumbnailUtils.isImageThumbnailable('jpg', 0)).toBe(false);
61+
expect(ThumbnailUtils.isImageThumbnailable('jpg', -1)).toBe(false);
62+
});
63+
64+
test('when the size exceeds the max thumbnailable size, then false is returned', () => {
65+
expect(ThumbnailUtils.isImageThumbnailable('jpg', ThumbnailUtils.MAX_IMAGE_THUMBNAILABLE_SIZE_IN_BYTES + 1)).toBe(
66+
false,
67+
);
13868
});
13969
});
14070
});

test/utils/upload.utils.test.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,27 +93,48 @@ describe('UploadUtils', () => {
9393

9494
describe('prepareUploadStreams', () => {
9595
test('when the file type does not support thumbnails, then the original stream is returned without a thumbnail', () => {
96-
vi.spyOn(ThumbnailUtils, 'isFileThumbnailable').mockReturnValue(false);
96+
vi.spyOn(ThumbnailUtils, 'isImageThumbnailable').mockReturnValue(false);
9797
const readable = Readable.from(['test']);
9898

99-
const result = UploadUtils.prepareUploadStreams(readable, 'pdf');
99+
const result = UploadUtils.prepareUploadStreams(readable, 'pdf', 1024);
100100

101101
expect(result.fileStream).toBe(readable);
102102
expect(result.thumbnailStream).toBeUndefined();
103103
expect(result.isThumbnailable).toBe(false);
104104
});
105105

106106
test('when the file type supports thumbnails, then a piped stream and thumbnail stream are returned', () => {
107-
vi.spyOn(ThumbnailUtils, 'isFileThumbnailable').mockReturnValue(true);
107+
vi.spyOn(ThumbnailUtils, 'isImageThumbnailable').mockReturnValue(true);
108108
const readable = Readable.from(['test-data']);
109109

110-
const result = UploadUtils.prepareUploadStreams(readable, 'jpg');
110+
const result = UploadUtils.prepareUploadStreams(readable, 'jpg', 1024);
111111

112112
expect(result.fileStream).not.toBe(readable);
113113
expect(result.fileStream).toBeInstanceOf(Readable);
114114
expect(result.thumbnailStream).toBeInstanceOf(BufferStream);
115115
expect(result.isThumbnailable).toBe(true);
116116
});
117+
118+
test('when the file exceeds the max thumbnailable size, then no thumbnail stream is created', () => {
119+
const readable = Readable.from(['test-data']);
120+
const size = ThumbnailUtils.MAX_IMAGE_THUMBNAILABLE_SIZE_IN_BYTES + 1;
121+
122+
const result = UploadUtils.prepareUploadStreams(readable, 'jpg', size);
123+
124+
expect(result.fileStream).toBe(readable);
125+
expect(result.thumbnailStream).toBeUndefined();
126+
expect(result.isThumbnailable).toBe(false);
127+
});
128+
129+
test('when the file size is zero, then no thumbnail stream is created', () => {
130+
const readable = Readable.from([]);
131+
132+
const result = UploadUtils.prepareUploadStreams(readable, 'jpg', 0);
133+
134+
expect(result.fileStream).toBe(readable);
135+
expect(result.thumbnailStream).toBeUndefined();
136+
expect(result.isThumbnailable).toBe(false);
137+
});
117138
});
118139

119140
describe('getTimings', () => {

0 commit comments

Comments
 (0)