From 0b8ef02e6f101ab92d3a604025cf9fdcf17be77a Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Thu, 2 Jul 2026 16:51:44 +0200 Subject: [PATCH] fix(auth): surface requestJoin failures in the org-setup alert (#4389) --- .../organizationSetup.component.vue | 3 +- ....organizationSetup.component.unit.tests.js | 98 ++++++++++++++++++- 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/modules/auth/components/organizationSetup.component.vue b/src/modules/auth/components/organizationSetup.component.vue index c7132265f..68160b175 100644 --- a/src/modules/auth/components/organizationSetup.component.vue +++ b/src/modules/auth/components/organizationSetup.component.vue @@ -174,13 +174,14 @@ export default { if (!organizationId) return; this.requestLoading = true; + this.error = null; const organizationsStore = useOrganizationsStore(); try { await organizationsStore.createJoinRequest(organizationId); this.requestSent = true; this.$emit('requestSent', this.suggestedOrganization); } catch (err) { - console.error(err); + this.error = err?.response?.data?.message || err?.message || 'Could not send join request. Please try again.'; } finally { this.requestLoading = false; } diff --git a/src/modules/auth/tests/auth.organizationSetup.component.unit.tests.js b/src/modules/auth/tests/auth.organizationSetup.component.unit.tests.js index 49ecbee8e..adf761885 100644 --- a/src/modules/auth/tests/auth.organizationSetup.component.unit.tests.js +++ b/src/modules/auth/tests/auth.organizationSetup.component.unit.tests.js @@ -4,8 +4,12 @@ import { createPinia, setActivePinia } from 'pinia'; import { createVuetify } from 'vuetify'; const createOrganizationMock = vi.hoisted(() => vi.fn()); +const createJoinRequestMock = vi.hoisted(() => vi.fn()); vi.mock('../../organizations/stores/organizations.store', () => ({ - useOrganizationsStore: () => ({ createOrganization: createOrganizationMock }), + useOrganizationsStore: () => ({ + createOrganization: createOrganizationMock, + createJoinRequest: createJoinRequestMock, + }), })); import AuthOrganizationSetupComponent from '../components/organizationSetup.component.vue'; @@ -30,10 +34,12 @@ const makeFormStub = (valid = true) => ({ /** * Mount the organization setup component with Vuetify and stubbed form. * @param {object} formStub - VForm component definition controlling validation outcome. + * @param {object} props - Component props (e.g. suggestedOrganization). * @returns {import('@vue/test-utils').VueWrapper} mounted wrapper */ -const mountComponent = (formStub = makeFormStub()) => +const mountComponent = (formStub = makeFormStub(), props = {}) => mount(AuthOrganizationSetupComponent, { + props, global: { plugins: [createVuetify()], mocks: { config: mockConfig }, @@ -45,6 +51,7 @@ describe('auth.organizationSetup.component', () => { beforeEach(() => { setActivePinia(createPinia()); createOrganizationMock.mockReset(); + createJoinRequestMock.mockReset(); }); it('renders the organization name field and heading', async () => { @@ -192,4 +199,91 @@ describe('auth.organizationSetup.component', () => { await wrapper.vm.submit(); expect(wrapper.vm.error).toBeNull(); }); + + // --- request to join an existing organization --- + + const suggestedOrg = { name: 'Acme Inc.', _id: 'org-1' }; + + it('calls createJoinRequest with the organization id and leaves no error on success', async () => { + createJoinRequestMock.mockResolvedValueOnce(undefined); + + const wrapper = mountComponent(makeFormStub(), { suggestedOrganization: suggestedOrg }); + await flushPromises(); + + await wrapper.vm.requestJoin(); + + expect(createJoinRequestMock).toHaveBeenCalledTimes(1); + expect(createJoinRequestMock).toHaveBeenCalledWith('org-1'); + expect(wrapper.vm.error).toBeNull(); + }); + + it('sets requestSent and emits requestSent on a successful join request', async () => { + createJoinRequestMock.mockResolvedValueOnce(undefined); + + const wrapper = mountComponent(makeFormStub(), { suggestedOrganization: suggestedOrg }); + await flushPromises(); + + await wrapper.vm.requestJoin(); + + expect(wrapper.vm.requestSent).toBe(true); + expect(wrapper.emitted('requestSent')).toBeTruthy(); + expect(wrapper.emitted('requestSent')[0]).toEqual([suggestedOrg]); + }); + + it('sets error from response data message on createJoinRequest failure', async () => { + const apiError = { response: { data: { message: 'You are already a member' } } }; + createJoinRequestMock.mockRejectedValueOnce(apiError); + + const wrapper = mountComponent(makeFormStub(), { suggestedOrganization: suggestedOrg }); + await flushPromises(); + + await wrapper.vm.requestJoin(); + + expect(wrapper.vm.error).toBe('You are already a member'); + expect(wrapper.vm.requestSent).toBe(false); + }); + + it('falls back to err.message when a join request has no response data message', async () => { + createJoinRequestMock.mockRejectedValueOnce(new Error('Network error')); + + const wrapper = mountComponent(makeFormStub(), { suggestedOrganization: suggestedOrg }); + await flushPromises(); + + await wrapper.vm.requestJoin(); + + expect(wrapper.vm.error).toBe('Network error'); + }); + + it('falls back to a generic join message when the error has no message', async () => { + createJoinRequestMock.mockRejectedValueOnce({}); + + const wrapper = mountComponent(makeFormStub(), { suggestedOrganization: suggestedOrg }); + await flushPromises(); + + await wrapper.vm.requestJoin(); + + expect(wrapper.vm.error).toBe('Could not send join request. Please try again.'); + }); + + it('clears a stale error at the start of a join request', async () => { + const wrapper = mountComponent(makeFormStub(), { suggestedOrganization: suggestedOrg }); + await flushPromises(); + + // A prior create-org failure left an error on the component + wrapper.vm.error = 'Could not create organization. Please try again.'; + + createJoinRequestMock.mockResolvedValueOnce(undefined); + await wrapper.vm.requestJoin(); + + expect(wrapper.vm.error).toBeNull(); + }); + + it('does nothing when there is no suggested organization', async () => { + const wrapper = mountComponent(); + await flushPromises(); + + await wrapper.vm.requestJoin(); + + expect(createJoinRequestMock).not.toHaveBeenCalled(); + }); });