Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions apps/meteor/tests/end-to-end/api/file-upload-image-rotation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import path from 'path';

import type { Credentials } from '@rocket.chat/api-client';
import type { ImageAttachmentProps, IRoom, IUser, SettingValue } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import sharp from 'sharp';

import { getCredentials, request } from '../../data/api-data';
import { uploadFileToRC } from '../../data/file.helper';
import { getSettingValueById, updateSetting } from '../../data/permissions.helper';
import { createRoom, deleteRoom } from '../../data/rooms.helper';
import type { IRequestConfig, TestUser } from '../../data/users.helper';
import { createUser, deleteUser, login } from '../../data/users.helper';

const testImagePath = path.join(__dirname, '../../mocks/files/exif-orientation-6.jpg');
const testImageName = 'exif-orientation-6.jpg';

const downloadBuffer = async (url: string, auth: Credentials): Promise<Buffer> => {
const response = await request.get(url).set(auth).buffer(true).expect(200);
return response.body as Buffer;
};

describe('[File Upload - Image Rotation]', () => {
before((done) => getCredentials(done));

let user: TestUser<IUser>;
const userPassword = `pass${Date.now()}`;
let userCredentials: Credentials;
let testRoom: IRoom;
let rotateImagesSetting: SettingValue;
let stripExifSetting: SettingValue;
let thumbnailsEnabledSetting: SettingValue;

before(async () => {
user = await createUser({ joinDefaultChannels: false, password: userPassword });
userCredentials = await login(user.username, userPassword);
testRoom = (await createRoom({ type: 'p', name: `rotate-upload-${Date.now()}`, members: [user.username] })).body.group;

rotateImagesSetting = await getSettingValueById('FileUpload_RotateImages');
stripExifSetting = await getSettingValueById('Message_Attachments_Strip_Exif');
thumbnailsEnabledSetting = await getSettingValueById('Message_Attachments_Thumbnails_Enabled');

await updateSetting('FileUpload_RotateImages', true, false);
await updateSetting('Message_Attachments_Strip_Exif', true, false);
await updateSetting('Message_Attachments_Thumbnails_Enabled', true);
});

after(async () => {
await Promise.all([
updateSetting('FileUpload_RotateImages', rotateImagesSetting),
updateSetting('Message_Attachments_Strip_Exif', stripExifSetting),
updateSetting('Message_Attachments_Thumbnails_Enabled', thumbnailsEnabledSetting),
deleteRoom({ type: 'p', roomId: testRoom._id }),
deleteUser(user),
]);
});

it('should rotate pixels and strip EXIF orientation', async () => {
const fixtureMetadata = await sharp(testImagePath).metadata();
expect(fixtureMetadata.width).to.equal(719);
expect(fixtureMetadata.height).to.equal(479);
expect(fixtureMetadata.orientation).to.equal(6);

const requestConfig: IRequestConfig = { request, credentials: userCredentials };
const { message } = await uploadFileToRC(testRoom._id, testImagePath, 'rotation-exif-test', requestConfig);

expect(message).to.have.property('attachments');
const attachment = message.attachments?.find((item) => item.title === testImageName);
expect(attachment).to.be.an('object');

const fileUrl = (attachment as { title_link?: string }).title_link;
const thumbUrl = (attachment as ImageAttachmentProps).image_url;

expect(fileUrl).to.be.a('string');
expect(thumbUrl).to.be.a('string');

const originalBuffer = await downloadBuffer(fileUrl as string, userCredentials);
const originalMetadata = await sharp(originalBuffer).metadata();

expect(originalMetadata.width).to.equal(479);
expect(originalMetadata.height).to.equal(719);
expect(originalMetadata.exif).to.be.undefined;
});

it('should generate thumbnail from rotated image', async () => {
const requestConfig: IRequestConfig = { request, credentials: userCredentials };
const { message } = await uploadFileToRC(testRoom._id, testImagePath, 'rotation-thumb-test', requestConfig);

expect(message).to.have.property('attachments');
const attachment = message.attachments?.find((item) => item.title === testImageName);
expect(attachment).to.be.an('object');

const fileUrl = (attachment as { title_link?: string }).title_link;
const thumbUrl = (attachment as ImageAttachmentProps).image_url;

expect(fileUrl).to.be.a('string');
expect(thumbUrl).to.be.a('string');

const thumbBuffer = await downloadBuffer(thumbUrl as string, userCredentials);
const thumbMetadata = await sharp(thumbBuffer).metadata();

expect(thumbMetadata.width).to.be.lessThan(thumbMetadata.height as number);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe here is more accurate if we assert that thumbMetadata.width and thumbMetadata.height are a specific number instead of 'height less than width'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, yeah, it makes sense.. I kept the orientation-based assertion because the test is more focused on rotation behavior, not thumbnail sizing config
if i assert exact thumbnail dimensions, the test gets tied to Message_Attachments_Thumbnails_Width/Height, so it could fail whenever those settings change even though rotation is still working correctly, but i can change the setting to a specific width/height, what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm with you then, ignore my comment. It'd be out of the scope of this task to verify whether the thumbnail sizing or config is correct, but if we don't have any test checking it I'd suggest creating a separate task for it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But as I see here: This has to be applied to both the original image being uploaded and the thumbnail generated in the server

Copy link
Member Author

@jessicaschelly jessicaschelly Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed! it mentions the orientation should be applied, so we have the assert for the orientation (height less than width = portrait), but not the sizing.. I could add here the size no problem, i was just worried about tying this test with another setting, wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as you are validating both width and height instead of just orientation above, I thought it could be more accurate to validate in the same way the thumbnail (perhaps adding Message_Attachments_Thumbnails_Width settings on the before), not 100% sure maybe someone can help us with a 3rd pov 👀

});

describe('when FileUpload_RotateImages is disabled', () => {
before(async () => {
await updateSetting('FileUpload_RotateImages', false);
});

after(async () => {
await updateSetting('FileUpload_RotateImages', rotateImagesSetting);
});

it('should NOT rotate pixels', async () => {
const requestConfig: IRequestConfig = { request, credentials: userCredentials };
const { message } = await uploadFileToRC(testRoom._id, testImagePath, 'no-rotation-test', requestConfig);

expect(message).to.have.property('attachments');
const attachment = message.attachments?.find((item) => item.title === testImageName);
expect(attachment).to.be.an('object');

const fileUrl = (attachment as { title_link?: string }).title_link;
expect(fileUrl).to.be.a('string');

const originalBuffer = await downloadBuffer(fileUrl as string, userCredentials);
const originalMetadata = await sharp(originalBuffer).metadata();

expect(originalMetadata.width).to.equal(719);
expect(originalMetadata.height).to.equal(479);
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading