From efeabb48a20c836ca2b9b447530f7e826dc57a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Vay=C3=A1?= Date: Wed, 11 Jun 2025 13:22:51 +0200 Subject: [PATCH] [MM-64358] Don't send custom profile info if the feature is not enabled (#8904) * don't send custom profile info if the feature is not enabled * add tests * missing changes * missing check --------- Co-authored-by: Mattermost Build --- .../edit_profile/edit_profile.test.tsx | 217 +++++++++++++++++- app/screens/edit_profile/edit_profile.tsx | 6 +- 2 files changed, 219 insertions(+), 4 deletions(-) diff --git a/app/screens/edit_profile/edit_profile.test.tsx b/app/screens/edit_profile/edit_profile.test.tsx index e967a2efa..430234a63 100644 --- a/app/screens/edit_profile/edit_profile.test.tsx +++ b/app/screens/edit_profile/edit_profile.test.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {act} from '@testing-library/react-hooks'; -import {fireEvent} from '@testing-library/react-native'; +import {fireEvent, screen, waitFor} from '@testing-library/react-native'; import React from 'react'; import AvailableScreens from '@constants/screens'; @@ -67,9 +67,19 @@ jest.mock('@context/server', () => ({ // Mock for custom profile attributes API const mockFetchCustomProfileAttributes = jest.fn(); +// Add mock for updateCustomProfileAttributes to track calls +const mockUpdateCustomProfileAttributes = jest.fn(); + +// Mock logError function +const mockLogError = jest.fn(); + jest.mock('@actions/remote/custom_profile', () => ({ fetchCustomProfileAttributes: (...args: any[]) => mockFetchCustomProfileAttributes(...args), - updateCustomProfileAttributes: jest.fn().mockResolvedValue({success: true, error: undefined}), + updateCustomProfileAttributes: (...args: any[]) => mockUpdateCustomProfileAttributes(...args), +})); + +jest.mock('@utils/log', () => ({ + logError: (...args: any[]) => mockLogError(...args), })); jest.mock('@actions/remote/user', () => ({ @@ -155,6 +165,12 @@ describe('EditProfile', () => { attributes: serverAttributesSet, error: undefined, })); + + // Reset updateCustomProfileAttributes mock + mockUpdateCustomProfileAttributes.mockResolvedValue({success: true, error: undefined}); + + // Reset logError mock + mockLogError.mockClear(); }); it('should update custom attribute value while preserving name and sort order', async () => { @@ -388,4 +404,201 @@ describe('EditProfile', () => { const newAttributeItem = await findAllByTestId('edit_profile_form.customAttributes.attr4.input'); expect(newAttributeItem[0].props.value).toBe('new db value'); }); + + describe('Submission Logic', () => { + it('should update custom attributes when enableCustomAttributes is true and customAttributes exist', async () => { + renderWithIntlAndTheme( + , + ); + + // Wait for component to load and custom attributes to be fetched + await waitFor(async() => { + const customAttributeItems = await screen.findAllByTestId(new RegExp('^edit_profile_form.customAttributes.attr[0-9]+.input$')); + expect(customAttributeItems.length).toBeGreaterThan(0); + }); + + // Modify a field to trigger the hasUpdateUserInfo flag + await act(async () => { + const customAttributeItems = await screen.findAllByTestId(new RegExp('^edit_profile_form.customAttributes.attr[0-9]+.input$')); + fireEvent.changeText(customAttributeItems[0], 'modified value'); + }); + + // Trigger form submission + const saveButton = screen.getByTestId('edit_profile.save.button'); + await act(async () => { + fireEvent.press(saveButton); + }); + + // Verify that updateCustomProfileAttributes was called + expect(mockUpdateCustomProfileAttributes).toHaveBeenCalledWith( + 'http://localhost:8065', + 'user1', + expect.objectContaining({ + attr1: expect.objectContaining({ + value: 'modified value', + }), + }), + ); + }); + + it('should not update custom attributes when enableCustomAttributes is false', async () => { + const {getByTestId} = renderWithIntlAndTheme( + , + ); + + // Wait for component to load + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + + // Modify a standard field to trigger the hasUpdateUserInfo flag + const firstNameField = getByTestId('edit_profile_form.firstName.input'); + await act(async () => { + fireEvent.changeText(firstNameField, 'Modified John'); + }); + + // Trigger form submission + const saveButton = getByTestId('edit_profile.save.button'); + await act(async () => { + fireEvent.press(saveButton); + }); + + // Verify that updateCustomProfileAttributes was NOT called + expect(mockUpdateCustomProfileAttributes).not.toHaveBeenCalled(); + }); + + it('should call updateCustomProfileAttributes with empty object when customAttributes is empty', async () => { + // Mock server fetch to return empty attributes for this test + mockFetchCustomProfileAttributes.mockResolvedValue({ + attributes: {}, + error: undefined, + }); + + const {getByTestId} = renderWithIntlAndTheme( + , + ); + + // Wait for component to load + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + + // Modify a standard field to trigger the hasUpdateUserInfo flag + const firstNameField = getByTestId('edit_profile_form.firstName.input'); + await act(async () => { + fireEvent.changeText(firstNameField, 'Modified John'); + }); + + // Trigger form submission + const saveButton = getByTestId('edit_profile.save.button'); + await act(async () => { + fireEvent.press(saveButton); + }); + + // Verify that updateCustomProfileAttributes was called with empty object + expect(mockUpdateCustomProfileAttributes).toHaveBeenCalledWith( + 'http://localhost:8065', + 'user1', + {}, + ); + }); + + it('should handle custom attributes update error gracefully', async () => { + // Mock updateCustomProfileAttributes to return an error + mockUpdateCustomProfileAttributes.mockResolvedValue({ + success: false, + error: new Error('Failed to update custom attributes'), + }); + + renderWithIntlAndTheme( + , + ); + + // Wait for component to load and custom attributes to be fetched + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + + // Modify a custom attribute to trigger the hasUpdateUserInfo flag + const customAttributeItems = await screen.findAllByTestId(new RegExp('^edit_profile_form.customAttributes.attr[0-9]+.input$')); + await act(async () => { + fireEvent.changeText(customAttributeItems[0], 'modified value that will error'); + }); + + // Trigger form submission + const saveButton = screen.getByTestId('edit_profile.save.button'); + await act(async () => { + fireEvent.press(saveButton); + }); + + // Verify that updateCustomProfileAttributes was called + expect(mockUpdateCustomProfileAttributes).toHaveBeenCalled(); + + // Verify error handling - check that error is logged + await waitFor(() => { + expect(mockLogError).toHaveBeenCalledWith( + 'Error updating custom attributes', + expect.objectContaining({ + message: 'Failed to update custom attributes', + }), + ); + }); + }); + }); }); diff --git a/app/screens/edit_profile/edit_profile.tsx b/app/screens/edit_profile/edit_profile.tsx index 16924f58a..36b79c011 100644 --- a/app/screens/edit_profile/edit_profile.tsx +++ b/app/screens/edit_profile/edit_profile.tsx @@ -19,6 +19,7 @@ import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; import useNavButtonPressed from '@hooks/navigation_button_pressed'; import SecurityManager from '@managers/security_manager'; import {dismissModal, popTopScreen, setButtons} from '@screens/navigation'; +import {logError} from '@utils/log'; import {preventDoubleTap} from '@utils/tap'; import ProfileForm, {CUSTOM_ATTRS_PREFIX} from './components/form'; @@ -186,10 +187,11 @@ const EditProfile = ({ } // Update custom attributes if changed - if (userInfo.customAttributes) { + if (userInfo.customAttributes && enableCustomAttributes) { const {error: attrError} = await updateCustomProfileAttributes(serverUrl, currentUser.id, userInfo.customAttributes); if (attrError) { - resetScreen(attrError); + logError('Error updating custom attributes', attrError); + resetScreenForProfileError(attrError); return; } }