[MM-63548] Address potential risks (#8816)

* Address potential risks
This commit is contained in:
Guillermo Vayá 2025-05-07 17:20:33 +02:00 committed by GitHub
parent ec1c0e4f58
commit fcdef7b488
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 331 additions and 7 deletions

View file

@ -0,0 +1,324 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {render} from '@testing-library/react-native';
import React from 'react';
import Preferences from '@constants/preferences';
// Mock the child components to focus on testing Boolean checks
const mockAttachmentThumbnail = jest.fn().mockReturnValue(null);
jest.mock('./attachment_thumbnail', () => {
return function MockAttachmentThumbnail(props: {uri: string}) {
mockAttachmentThumbnail(props);
return null;
};
});
const mockAttachmentFields = jest.fn().mockReturnValue(null);
jest.mock('./attachment_fields', () => {
return function MockAttachmentFields(props: {fields: MessageAttachmentField[]}) {
mockAttachmentFields(props);
return null;
};
});
const mockAttachmentFooter = jest.fn().mockReturnValue(null);
jest.mock('./attachment_footer', () => {
return function MockAttachmentFooter(props: {text: string; icon?: string; theme: Theme}) {
mockAttachmentFooter(props);
return null;
};
});
const mockAttachmentActions = jest.fn().mockReturnValue(null);
jest.mock('./attachment_actions', () => {
return function MockAttachmentActions(props: {actions: PostAction[]; postId: string; theme: Theme}) {
mockAttachmentActions(props);
return null;
};
});
jest.mock('./attachment_author', () => {
return function MockAttachmentAuthor() {
return null;
};
});
jest.mock('./attachment_image', () => {
return function MockAttachmentImage() {
return null;
};
});
jest.mock('./attachment_pretext', () => {
return function MockAttachmentPretext() {
return null;
};
});
jest.mock('./attachment_text', () => {
return function MockAttachmentText() {
return null;
};
});
jest.mock('./attachment_title', () => {
return function MockAttachmentTitle() {
return null;
};
});
// Mock the URL validation function
jest.mock('@utils/url', () => ({
isValidUrl: jest.fn((url) => url === 'https://example.com/image.png'),
}));
import MessageAttachment from './message_attachment';
describe('MessageAttachment', () => {
beforeEach(() => {
jest.clearAllMocks();
});
const baseProps = {
attachment: {
id: 1,
text: 'This is the text of an attachment',
thumb_url: 'https://example.com/image.png',
author_name: 'Author Name',
author_icon: 'https://example.com/author.png',
title: 'Attachment Title',
title_link: 'https://example.com',
fields: [
{
title: 'Field Title',
value: 'Field Value',
short: true,
},
],
actions: [
{
id: 'action1',
name: 'Action 1',
type: 'button',
integration: {
url: 'https://example.com/action',
},
},
],
footer: 'Attachment Footer',
footer_icon: 'https://example.com/footer.png',
image_url: 'https://example.com/image.jpg',
},
channelId: 'channel-id',
postId: 'post-id',
location: 'CENTER',
theme: Preferences.THEMES.denim,
metadata: {
images: {
'https://example.com/image.jpg': {
width: 200,
height: 200,
format: 'jpg',
frame_count: 0,
},
},
},
};
test('it renders without crashing with full attachment data', () => {
const wrapper = render(<MessageAttachment {...baseProps}/>);
expect(wrapper.toJSON()).toBeTruthy();
});
test('it handles undefined thumb_url without crashing', () => {
const props = {
...baseProps,
attachment: {
...baseProps.attachment,
thumb_url: undefined,
},
};
const wrapper = render(<MessageAttachment {...props}/>);
expect(wrapper.toJSON()).toBeTruthy();
expect(mockAttachmentThumbnail).not.toHaveBeenCalled();
});
test('it handles falsy thumb_url without crashing', () => {
const props = {
...baseProps,
attachment: {
...baseProps.attachment,
thumb_url: '',
},
};
const wrapper = render(<MessageAttachment {...props}/>);
expect(wrapper.toJSON()).toBeTruthy();
expect(mockAttachmentThumbnail).not.toHaveBeenCalled();
});
test('it correctly passes non-null thumb_url to AttachmentThumbnail', () => {
render(<MessageAttachment {...baseProps}/>);
expect(mockAttachmentThumbnail).toHaveBeenCalledWith(
expect.objectContaining({
uri: 'https://example.com/image.png',
}),
);
});
test('it handles undefined fields without crashing', () => {
const props = {
...baseProps,
attachment: {
...baseProps.attachment,
fields: undefined,
},
};
const wrapper = render(<MessageAttachment {...props}/>);
expect(wrapper.toJSON()).toBeTruthy();
expect(mockAttachmentFields).not.toHaveBeenCalled();
});
test('it handles empty fields array without crashing', () => {
const props = {
...baseProps,
attachment: {
...baseProps.attachment,
fields: [],
},
};
const wrapper = render(<MessageAttachment {...props}/>);
expect(wrapper.toJSON()).toBeTruthy();
expect(mockAttachmentFields).not.toHaveBeenCalled();
});
test('it correctly passes non-null fields to AttachmentFields', () => {
render(<MessageAttachment {...baseProps}/>);
expect(mockAttachmentFields).toHaveBeenCalledWith(
expect.objectContaining({
fields: baseProps.attachment.fields,
}),
);
});
test('it handles undefined footer without crashing', () => {
const props = {
...baseProps,
attachment: {
...baseProps.attachment,
footer: undefined,
},
};
const wrapper = render(<MessageAttachment {...props}/>);
expect(wrapper.toJSON()).toBeTruthy();
expect(mockAttachmentFooter).not.toHaveBeenCalled();
});
test('it handles falsy footer without crashing', () => {
const props = {
...baseProps,
attachment: {
...baseProps.attachment,
footer: '',
},
};
const wrapper = render(<MessageAttachment {...props}/>);
expect(wrapper.toJSON()).toBeTruthy();
expect(mockAttachmentFooter).not.toHaveBeenCalled();
});
test('it correctly passes non-null footer to AttachmentFooter', () => {
render(<MessageAttachment {...baseProps}/>);
expect(mockAttachmentFooter).toHaveBeenCalledWith(
expect.objectContaining({
text: 'Attachment Footer',
}),
);
});
test('it handles undefined actions without crashing', () => {
const props = {
...baseProps,
attachment: {
...baseProps.attachment,
actions: undefined,
},
};
const wrapper = render(<MessageAttachment {...props}/>);
expect(wrapper.toJSON()).toBeTruthy();
expect(mockAttachmentActions).not.toHaveBeenCalled();
});
test('it handles empty actions array without crashing', () => {
const props = {
...baseProps,
attachment: {
...baseProps.attachment,
actions: [],
},
};
const wrapper = render(<MessageAttachment {...props}/>);
expect(wrapper.toJSON()).toBeTruthy();
expect(mockAttachmentActions).not.toHaveBeenCalled();
});
test('it correctly passes non-null actions to AttachmentActions', () => {
render(<MessageAttachment {...baseProps}/>);
expect(mockAttachmentActions).toHaveBeenCalledWith(
expect.objectContaining({
actions: baseProps.attachment.actions,
}),
);
});
test('it handles multiple undefined properties without crashing', () => {
const props = {
...baseProps,
attachment: {
id: 1,
text: 'This is the text of an attachment with multiple undefined properties',
thumb_url: undefined,
fields: undefined,
actions: undefined,
footer: undefined,
},
};
const wrapper = render(<MessageAttachment {...props}/>);
expect(wrapper.toJSON()).toBeTruthy();
expect(mockAttachmentThumbnail).not.toHaveBeenCalled();
expect(mockAttachmentFields).not.toHaveBeenCalled();
expect(mockAttachmentFooter).not.toHaveBeenCalled();
expect(mockAttachmentActions).not.toHaveBeenCalled();
});
test('it handles mixed falsy properties without crashing', () => {
const props = {
...baseProps,
attachment: {
id: 1,
text: 'This is the text of an attachment with mixed falsy properties',
thumb_url: '',
fields: undefined,
actions: [],
footer: '',
},
};
const wrapper = render(<MessageAttachment {...props}/>);
expect(wrapper.toJSON()).toBeTruthy();
expect(mockAttachmentThumbnail).not.toHaveBeenCalled();
expect(mockAttachmentFields).not.toHaveBeenCalled();
expect(mockAttachmentFooter).not.toHaveBeenCalled();
expect(mockAttachmentActions).not.toHaveBeenCalled();
});
});

View file

@ -98,8 +98,8 @@ export default function MessageAttachment({attachment, channelId, layoutWidth, l
value={attachment.title}
/>
}
{attachment.thumb_url && isValidUrl(attachment.thumb_url) &&
<AttachmentThumbnail uri={attachment.thumb_url}/>
{Boolean(attachment.thumb_url) && isValidUrl(attachment.thumb_url) &&
<AttachmentThumbnail uri={attachment.thumb_url!}/>
}
{Boolean(attachment.text) &&
<AttachmentText
@ -114,26 +114,26 @@ export default function MessageAttachment({attachment, channelId, layoutWidth, l
theme={theme}
/>
}
{attachment.fields && attachment.fields?.length &&
{Boolean(attachment.fields?.length) &&
<AttachmentFields
baseTextStyle={style.message}
blockStyles={blockStyles}
channelId={channelId}
location={location}
fields={attachment.fields}
fields={attachment.fields!}
metadata={metadata}
textStyles={textStyles}
theme={theme}
/>
}
{attachment.footer &&
{Boolean(attachment.footer) &&
<AttachmentFooter
icon={attachment.footer_icon}
text={attachment.footer}
text={attachment.footer!}
theme={theme}
/>
}
{attachment.actions && attachment.actions.length &&
{Boolean(attachment.actions && attachment.actions.length) &&
<AttachmentActions
actions={attachment.actions!}
postId={postId}