From 72ac0a63d54f1f5b18982cf0ac47b24827dc79e1 Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Sat, 25 Jul 2026 19:03:08 +0200 Subject: [PATCH] Filter out banned users and disabled profiles in `searchUsers` when `excludeUnavailable` is enabled. Add corresponding unit tests. --- backend/api/src/search-users.ts | 7 +++ .../api/tests/unit/search-users.unit.test.ts | 51 +++++++++++++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/backend/api/src/search-users.ts b/backend/api/src/search-users.ts index 00e3bc78..b5d7fe64 100644 --- a/backend/api/src/search-users.ts +++ b/backend/api/src/search-users.ts @@ -44,6 +44,13 @@ function getSearchUserSQL(props: { // ] // : [select('*'), from('users')], + where( + `not users.is_banned_from_posting + and not exists (select 1 + from profiles + where profiles.user_id = users.id + and profiles.disabled)`, + ), term ? [ where( diff --git a/backend/api/tests/unit/search-users.unit.test.ts b/backend/api/tests/unit/search-users.unit.test.ts index d65ff5da..de543e5c 100644 --- a/backend/api/tests/unit/search-users.unit.test.ts +++ b/backend/api/tests/unit/search-users.unit.test.ts @@ -61,6 +61,7 @@ describe('searchUsers', () => { expect(sqlBuilderModules.renderSql).toBeCalledTimes(1) expect(sqlBuilderModules.renderSql).toBeCalledWith( ['Select', 'From'], + 'Where', ['Where', 'OrderBy'], 'Limit', ) @@ -69,8 +70,9 @@ describe('searchUsers', () => { expect(sqlBuilderModules.select).toBeCalledWith('*') expect(sqlBuilderModules.from).toBeCalledTimes(1) expect(sqlBuilderModules.from).toBeCalledWith('users') - expect(sqlBuilderModules.where).toBeCalledTimes(1) - expect(sqlBuilderModules.where).toBeCalledWith( + expect(sqlBuilderModules.where).toBeCalledTimes(2) + expect(sqlBuilderModules.where).toHaveBeenNthCalledWith( + 2, sqlMatch("name_username_vector @@ websearch_to_tsquery('english', $1)"), [mockProps.term, 'ConstructPrefix'], ) @@ -117,7 +119,12 @@ describe('searchUsers', () => { expect(result[2]).toContain(mockAllUser[2].id) expect(sqlBuilderModules.renderSql).toBeCalledTimes(1) - expect(sqlBuilderModules.renderSql).toBeCalledWith(['Select', 'From'], 'OrderBy', 'Limit') + expect(sqlBuilderModules.renderSql).toBeCalledWith( + ['Select', 'From'], + undefined, + 'OrderBy', + 'Limit', + ) expect(sqlBuilderModules.select).toBeCalledTimes(1) expect(sqlBuilderModules.select).toBeCalledWith('*') @@ -135,5 +142,43 @@ describe('searchUsers', () => { expect(mockPg.map).toBeCalledTimes(1) expect(mockPg.map).toBeCalledWith(mockSearchAllSql, null, expect.any(Function)) }) + + it('should filter out banned users and disabled profiles when excludeUnavailable is set', async () => { + const mockProps = { + term: 'mockTerm', + limit: 10, + page: 1, + excludeUnavailable: true, + } + const mockAuth = {uid: '321'} as AuthedUser + const mockReq = {} as any + + ;(sqlBuilderModules.renderSql as jest.Mock).mockReturnValue('mockSQL') + ;(sqlBuilderModules.select as jest.Mock).mockReturnValue('Select') + ;(sqlBuilderModules.from as jest.Mock).mockReturnValue('From') + ;(sqlBuilderModules.where as jest.Mock) + .mockReturnValueOnce('AvailableWhere') + .mockReturnValueOnce('TermWhere') + ;(searchHelpers.constructPrefixTsQuery as jest.Mock).mockReturnValue('ConstructPrefix') + ;(sqlBuilderModules.orderBy as jest.Mock).mockReturnValue('OrderBy') + ;(sqlBuilderModules.limit as jest.Mock).mockReturnValue('Limit') + ;(supabaseUsers.convertUser as jest.Mock).mockResolvedValue(null) + ;(mockPg.map as jest.Mock).mockResolvedValue([]) + + await searchUsers(mockProps, mockAuth, mockReq) + + expect(sqlBuilderModules.where).toBeCalledTimes(2) + expect(sqlBuilderModules.where).toHaveBeenNthCalledWith( + 1, + sqlMatch('not users.is_banned_from_posting'), + ) + expect(sqlBuilderModules.where).toHaveBeenNthCalledWith(1, sqlMatch('and profiles.disabled')) + expect(sqlBuilderModules.renderSql).toBeCalledWith( + ['Select', 'From'], + 'AvailableWhere', + ['TermWhere', 'OrderBy'], + 'Limit', + ) + }) }) })