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
57 changes: 57 additions & 0 deletions apps/meteor/client/views/room/hooks/useOpenRoom.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { renderHook, waitFor } from '@testing-library/react';

import { useOpenRoom } from './useOpenRoom';
import { createFakeRoom, createFakeSubscription } from '../../../../tests/mocks/data';
import { RoomNotFoundError } from '../../../lib/errors/RoomNotFoundError';
import { Rooms, Subscriptions } from '../../../stores';

jest.mock('../../../../app/ui-utils/client', () => ({
Expand Down Expand Up @@ -76,4 +77,60 @@ describe('useOpenRoom', () => {
expect(result.current.data?.rid).toBe(dmRid);
});
});

describe('error classification', () => {
it('maps error-no-permission to RoomNotFoundError without retrying (regression for #40991)', async () => {
const getRoomByTypeAndName = jest.fn().mockImplementation(() => {
throw Object.assign(new Error('No permission'), { error: 'error-no-permission' });
});

const { result } = renderHook(() => useOpenRoom({ type: 'p', reference: 'private-channel' }), {
wrapper: mockAppRoot().withJohnDoe().withMethod('getRoomByTypeAndName', getRoomByTypeAndName).build(),
});

await waitFor(() => expect(result.current.isError).toBe(true));
expect(result.current.error).toBeInstanceOf(RoomNotFoundError);
expect(getRoomByTypeAndName).toHaveBeenCalledTimes(1);
});

it('maps error-invalid-room to RoomNotFoundError without retrying for non-DM rooms', async () => {
const getRoomByTypeAndName = jest.fn().mockImplementation(() => {
throw Object.assign(new Error('Invalid room'), { error: 'error-invalid-room' });
});

const { result } = renderHook(() => useOpenRoom({ type: 'c', reference: 'missing-channel' }), {
wrapper: mockAppRoot().withJohnDoe().withMethod('getRoomByTypeAndName', getRoomByTypeAndName).build(),
});

await waitFor(() => expect(result.current.isError).toBe(true));
expect(result.current.error).toBeInstanceOf(RoomNotFoundError);
expect(getRoomByTypeAndName).toHaveBeenCalledTimes(1);
});

it('retries unclassified transient errors and recovers on a later attempt', async () => {
const channelRid = 'channel-rid-transient';
const channelName = 'flaky-channel';
const channelRoom = createFakeRoom({ _id: channelRid, t: 'c', name: channelName });

const getRoomByTypeAndName = jest
.fn()
.mockImplementationOnce(() => {
throw new Error('network down');
})
.mockImplementation(() => channelRoom);

const { result } = renderHook(() => useOpenRoom({ type: 'c', reference: channelName }), {
wrapper: mockAppRoot()
.withJohnDoe()
.withPermission('preview-c-room')
.withMethod('getRoomByTypeAndName', getRoomByTypeAndName)
.build(),
});

// Backoff is Math.min(1000 * 2 ** attempt, 5000); first retry fires after ~1s.
await waitFor(() => expect(result.current.isSuccess).toBe(true), { timeout: 3000 });
expect(result.current.data?.rid).toBe(channelRid);
expect(getRoomByTypeAndName).toHaveBeenCalledTimes(2);
});
});
});
10 changes: 8 additions & 2 deletions apps/meteor/client/views/room/hooks/useOpenRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,15 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st
try {
roomData = await getRoomByTypeAndName(type, reference);
} catch (error) {
const isDefinitivelyNotFound = error && typeof error === 'object' && 'error' in error && error.error === 'error-invalid-room';
const errorCode = error && typeof error === 'object' && 'error' in error ? error.error : undefined;

if (!isDefinitivelyNotFound) {
// "No permission" means the room exists but the user can't see it — surface the
// not-found/no-access screen rather than retrying it as a transient failure.
if (errorCode === 'error-no-permission') {
throw new RoomNotFoundError(undefined, { type, reference });
}

if (errorCode !== 'error-invalid-room') {
throw error;
}

Expand Down
Loading