From fc6628be089e4ce3fbf8543cb95ece255188cc29 Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Tue, 17 Feb 2026 22:37:32 +0100 Subject: [PATCH] Refactor unit tests to use sqlMatch for SQL query assertions --- backend/api/src/update-me.ts | 25 +- backend/api/tests/unit/contact.unit.test.ts | 292 +++++++++--------- .../create-bookmarked-search.unit.test.ts | 89 +++--- .../tests/unit/create-comment.unit.test.ts | 25 +- ...-private-user-message-channel.unit.test.ts | 23 +- .../tests/unit/create-profile.unit.test.ts | 15 +- .../delete-bookmarked-search.unit.test.ts | 11 +- backend/api/tests/unit/delete-me.unit.test.ts | 17 +- .../get-current-private-users.unit.test.ts | 13 +- .../unit/get-messages-count.unit.test.ts | 11 +- .../tests/unit/get-notifications.unit.test.ts | 7 +- .../unit/get-private-messages.unit.test.ts | 27 +- .../unit/get-profile-answers.unit.test.ts | 11 +- backend/api/tests/unit/get-users.unit.test.ts | 13 +- .../api/tests/unit/hide-comment.unit.test.ts | 17 +- ...-private-user-message-channel.unit.test.ts | 9 +- .../api/tests/unit/like-profile.unit.test.ts | 21 +- .../mark-all-notifications-read.unit.test.ts | 7 +- .../tests/unit/react-to-message.unit.test.ts | 23 +- .../unit/remove-pinned-photo.unit.test.ts | 9 +- backend/api/tests/unit/report.unit.test.ts | 19 +- .../save-subscription-mobile.unit.test.ts | 11 +- .../tests/unit/save-subscription.unit.test.ts | 13 +- .../api/tests/unit/search-users.unit.test.ts | 21 +- .../api/tests/unit/ship-profiles.unit.test.ts | 15 +- .../api/tests/unit/star-profile.unit.test.ts | 17 +- backend/api/tests/unit/update-me.unit.test.ts | 9 +- .../tests/unit/update-options.unit.test.ts | 20 +- ...-private-user-message-channel.unit.test.ts | 15 +- .../tests/unit/update-profile.unit.test.ts | 3 +- backend/api/tests/unit/vote-unit.test.ts | 7 +- common/src/test-utils.ts | 6 + 32 files changed, 431 insertions(+), 390 deletions(-) create mode 100644 common/src/test-utils.ts diff --git a/backend/api/src/update-me.ts b/backend/api/src/update-me.ts index 31583ce..498a88d 100644 --- a/backend/api/src/update-me.ts +++ b/backend/api/src/update-me.ts @@ -32,7 +32,7 @@ export const updateMe: APIHandler<'me/update'> = async (props, auth) => { const pg = createSupabaseDirectClient() - const { name, username, avatarUrl, link = {}, ...rest } = update + const {name, username, avatarUrl, link = {}, ...rest} = update await updateUser(pg, auth.uid, removeUndefinedProps(rest)) const stripped = mapValues( @@ -53,14 +53,14 @@ export const updateMe: APIHandler<'me/update'> = async (props, auth) => { let newLinks: any = null if (Object.keys(adds).length > 0 || removes.length > 0) { const data = await pg.oneOrNone( - `update users - set data = jsonb_set( - data, '{link}', - (data->'link' || $(adds)) - $(removes) - ) - where id = $(id) - returning data->'link' as link`, - { adds, removes, id: auth.uid } + `update users + set data = jsonb_set( + data, '{link}', + (data -> 'link' || $(adds)) - $(removes) + ) + where id = $(id) + returning data -> 'link' as link`, + {adds, removes, id: auth.uid} ) newLinks = data?.link } @@ -73,10 +73,7 @@ export const updateMe: APIHandler<'me/update'> = async (props, auth) => { if (username) { await pg.none(`update users set username = $1 - where id = $2`, [ - username, - auth.uid, - ]) + where id = $2`, [username, auth.uid]) } if (avatarUrl) { await updateUser(pg, auth.uid, {avatarUrl}) @@ -96,5 +93,5 @@ export const updateMe: APIHandler<'me/update'> = async (props, auth) => { ) } - return toUserAPIResponse({ ...user, ...update, link: newLinks }) + return toUserAPIResponse({...user, ...update, link: newLinks}) } diff --git a/backend/api/tests/unit/contact.unit.test.ts b/backend/api/tests/unit/contact.unit.test.ts index 1719697..49d3ed8 100644 --- a/backend/api/tests/unit/contact.unit.test.ts +++ b/backend/api/tests/unit/contact.unit.test.ts @@ -3,167 +3,169 @@ jest.mock('shared/supabase/utils'); jest.mock('shared/supabase/init'); jest.mock('common/util/try-catch'); -import { contact } from "api/contact"; +import {contact} from "api/contact"; import * as supabaseInit from "shared/supabase/init"; import * as supabaseUtils from "shared/supabase/utils"; -import { tryCatch } from "common/util/try-catch"; -import { sendDiscordMessage } from "common/discord/core"; -import { AuthedUser } from "api/helpers/endpoint"; +import {tryCatch} from "common/util/try-catch"; +import {sendDiscordMessage} from "common/discord/core"; +import {AuthedUser} from "api/helpers/endpoint"; +import {sqlMatch} from 'common/test-utils' describe('contact', () => { - let mockPg: any; - beforeEach(() => { - jest.resetAllMocks(); - mockPg = { - oneOrNone: jest.fn(), - }; + let mockPg: any; + beforeEach(() => { + jest.resetAllMocks(); + mockPg = { + oneOrNone: jest.fn(), + }; - (supabaseInit.createSupabaseDirectClient as jest.Mock) - .mockReturnValue(mockPg); - }); - afterEach(() => { - jest.restoreAllMocks(); - }); + (supabaseInit.createSupabaseDirectClient as jest.Mock) + .mockReturnValue(mockPg); + }); + afterEach(() => { + jest.restoreAllMocks(); + }); - describe('when given valid input', () => { - it('should send a discord message to the user', async () => { - const mockProps = { - content: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Error test message' - } - ] - } - ] - }, - userId: '123' - }; - const mockAuth = { uid: '321' } as AuthedUser; - const mockReq = {} as any; - const mockDbUser = { name: 'Humphrey Mocker' }; - const mockReturnData = {} as any; - - (tryCatch as jest.Mock).mockResolvedValue({ data: mockReturnData, error: null }); - - - const results = await contact(mockProps, mockAuth, mockReq); - - expect(results.success).toBe(true); - expect(results.result).toStrictEqual({}); - expect(tryCatch).toBeCalledTimes(1); - expect(supabaseUtils.insert).toBeCalledTimes(1) - expect(supabaseUtils.insert).toBeCalledWith( - expect.any(Object), - 'contact', + describe('when given valid input', () => { + it('should send a discord message to the user', async () => { + const mockProps = { + content: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ { - user_id: mockProps.userId, - content: JSON.stringify(mockProps.content) + type: 'text', + text: 'Error test message' } - ); + ] + } + ] + }, + userId: '123' + }; + const mockAuth = {uid: '321'} as AuthedUser; + const mockReq = {} as any; + const mockDbUser = {name: 'Humphrey Mocker'}; + const mockReturnData = {} as any; - (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockDbUser); + (tryCatch as jest.Mock).mockResolvedValue({data: mockReturnData, error: null}); - await results.continue(); - expect(mockPg.oneOrNone).toBeCalledTimes(1); - expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select name from users where id = $1'), - [mockProps.userId] - ); - expect(sendDiscordMessage).toBeCalledTimes(1); - expect(sendDiscordMessage).toBeCalledWith( - expect.stringContaining(`New message from ${mockDbUser.name}`), - 'contact' - ); - }); + const results = await contact(mockProps, mockAuth, mockReq); + + expect(results.success).toBe(true); + expect(results.result).toStrictEqual({}); + expect(tryCatch).toBeCalledTimes(1); + expect(supabaseUtils.insert).toBeCalledTimes(1) + expect(supabaseUtils.insert).toBeCalledWith( + expect.any(Object), + 'contact', + { + user_id: mockProps.userId, + content: JSON.stringify(mockProps.content) + } + ); + + (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockDbUser); + + await results.continue(); + + expect(mockPg.oneOrNone).toBeCalledTimes(1); + expect(mockPg.oneOrNone).toBeCalledWith( + sqlMatch('select name from users where id = $1'), + [mockProps.userId] + ); + expect(sendDiscordMessage).toBeCalledTimes(1); + expect(sendDiscordMessage).toBeCalledWith( + expect.stringContaining(`New message from ${mockDbUser.name}`), + 'contact' + ); }); - - describe('when an error occurs', () => { - it('should throw if the insert function fails', async () => { - const mockProps = { - content: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Error test message' - } - ] - } - ] - }, - userId: '123' - }; - const mockAuth = { uid: '321' } as AuthedUser; - const mockReq = {} as any; + }); - (tryCatch as jest.Mock).mockResolvedValue({ data: null, error: Error }); - - expect(contact(mockProps, mockAuth, mockReq)) - .rejects - .toThrowError('Failed to submit contact message'); - }); - - it('should throw if unable to send discord message', async () => { - const mockProps = { - content: { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Error test message' - } - ] - } - ] - }, - userId: '123' - }; - const mockAuth = { uid: '321' } as AuthedUser; - const mockReq = {} as any; - const mockDbUser = { name: 'Humphrey Mocker' }; - const mockReturnData = {} as any; - - (tryCatch as jest.Mock).mockResolvedValue({ data: mockReturnData, error: null }); - - const results = await contact(mockProps, mockAuth, mockReq); - - expect(results.success).toBe(true); - expect(results.result).toStrictEqual({}); - expect(tryCatch).toBeCalledTimes(1); - expect(supabaseUtils.insert).toBeCalledTimes(1) - expect(supabaseUtils.insert).toBeCalledWith( - expect.any(Object), - 'contact', + describe('when an error occurs', () => { + it('should throw if the insert function fails', async () => { + const mockProps = { + content: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ { - user_id: mockProps.userId, - content: JSON.stringify(mockProps.content) + type: 'text', + text: 'Error test message' } - ); + ] + } + ] + }, + userId: '123' + }; + const mockAuth = {uid: '321'} as AuthedUser; + const mockReq = {} as any; - (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockDbUser); - (sendDiscordMessage as jest.Mock).mockRejectedValue(new Error('Unable to send message')); - const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + (tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error}); - await results.continue(); - - expect(errorSpy).toBeCalledTimes(1); - expect(errorSpy).toBeCalledWith( - expect.stringContaining('Failed to send discord contact'), - expect.objectContaining({name: 'Error'}) - ); - }); + expect(contact(mockProps, mockAuth, mockReq)) + .rejects + .toThrowError('Failed to submit contact message'); }); + + it('should throw if unable to send discord message', async () => { + const mockProps = { + content: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Error test message' + } + ] + } + ] + }, + userId: '123' + }; + const mockAuth = {uid: '321'} as AuthedUser; + const mockReq = {} as any; + const mockDbUser = {name: 'Humphrey Mocker'}; + const mockReturnData = {} as any; + + (tryCatch as jest.Mock).mockResolvedValue({data: mockReturnData, error: null}); + + const results = await contact(mockProps, mockAuth, mockReq); + + expect(results.success).toBe(true); + expect(results.result).toStrictEqual({}); + expect(tryCatch).toBeCalledTimes(1); + expect(supabaseUtils.insert).toBeCalledTimes(1) + expect(supabaseUtils.insert).toBeCalledWith( + expect.any(Object), + 'contact', + { + user_id: mockProps.userId, + content: JSON.stringify(mockProps.content) + } + ); + + (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockDbUser); + (sendDiscordMessage as jest.Mock).mockRejectedValue(new Error('Unable to send message')); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => { + }); + + await results.continue(); + + expect(errorSpy).toBeCalledTimes(1); + expect(errorSpy).toBeCalledWith( + expect.stringContaining('Failed to send discord contact'), + expect.objectContaining({name: 'Error'}) + ); + }); + }); }); \ No newline at end of file diff --git a/backend/api/tests/unit/create-bookmarked-search.unit.test.ts b/backend/api/tests/unit/create-bookmarked-search.unit.test.ts index 32bf332..3305041 100644 --- a/backend/api/tests/unit/create-bookmarked-search.unit.test.ts +++ b/backend/api/tests/unit/create-bookmarked-search.unit.test.ts @@ -1,50 +1,51 @@ -jest.mock('shared/supabase/init'); - -import { createBookmarkedSearch } from "api/create-bookmarked-search"; -import { AuthedUser } from "api/helpers/endpoint"; +import {sqlMatch} from "common/test-utils"; +import {createBookmarkedSearch} from "api/create-bookmarked-search"; +import {AuthedUser} from "api/helpers/endpoint"; import * as supabaseInit from "shared/supabase/init"; +jest.mock('shared/supabase/init'); + describe('createBookmarkedSearch', () => { - let mockPg: any; - beforeEach(() => { - jest.resetAllMocks(); - mockPg = { - one: jest.fn(), - }; + let mockPg: any; + beforeEach(() => { + jest.resetAllMocks(); + mockPg = { + one: jest.fn(), + }; - (supabaseInit.createSupabaseDirectClient as jest.Mock) - .mockReturnValue(mockPg); - }); - afterEach(() => { - jest.restoreAllMocks(); - }); - - describe('when given valid input', () => { - it('should insert a bookmarked search into the database', async () => { - const mockProps = { - search_filters: 'mock_search_filters', - location: 'mock_location', - search_name: 'mock_search_name' - }; - const mockAuth = { uid: '321' } as AuthedUser; - const mockReq = {} as any; - const mockInserted = "mockInsertedReturn"; - - (mockPg.one as jest.Mock).mockResolvedValue(mockInserted); - - const result = await createBookmarkedSearch(mockProps, mockAuth, mockReq); - - expect(result).toBe(mockInserted); - expect(mockPg.one).toBeCalledTimes(1); - expect(mockPg.one).toHaveBeenCalledWith( - expect.stringContaining('INSERT INTO bookmarked_searches'), - [ - mockAuth.uid, - mockProps.search_filters, - mockProps.location, - mockProps.search_name - ] - ); - }); + (supabaseInit.createSupabaseDirectClient as jest.Mock) + .mockReturnValue(mockPg); + }); + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('when given valid input', () => { + it('should insert a bookmarked search into the database', async () => { + const mockProps = { + search_filters: 'mock_search_filters', + location: 'mock_location', + search_name: 'mock_search_name' + }; + const mockAuth = {uid: '321'} as AuthedUser; + const mockReq = {} as any; + const mockInserted = "mockInsertedReturn"; + + (mockPg.one as jest.Mock).mockResolvedValue(mockInserted); + + const result = await createBookmarkedSearch(mockProps, mockAuth, mockReq); + + expect(result).toBe(mockInserted); + expect(mockPg.one).toBeCalledTimes(1); + expect(mockPg.one).toHaveBeenCalledWith( + sqlMatch('INSERT INTO bookmarked_searches'), + [ + mockAuth.uid, + mockProps.search_filters, + mockProps.location, + mockProps.search_name + ] + ); }); + }); }); \ No newline at end of file diff --git a/backend/api/tests/unit/create-comment.unit.test.ts b/backend/api/tests/unit/create-comment.unit.test.ts index 51ec44c..e1154eb 100644 --- a/backend/api/tests/unit/create-comment.unit.test.ts +++ b/backend/api/tests/unit/create-comment.unit.test.ts @@ -1,3 +1,15 @@ +import {sqlMatch} from "common/test-utils"; +import * as supabaseInit from "shared/supabase/init"; +import {AuthedUser} from "api/helpers/endpoint"; +import * as sharedUtils from "shared/utils"; +import {createComment} from "api/create-comment"; +import * as notificationPrefs from "common/user-notification-preferences"; +import * as supabaseNotifications from "shared/supabase/notifications"; +import * as emailHelpers from "email/functions/helpers"; +import * as websocketHelpers from "shared/websockets/helpers"; +import {convertComment} from "common/supabase/comment"; +import {richTextToString} from "common/util/parse"; + jest.mock('shared/supabase/init'); jest.mock('shared/supabase/notifications'); jest.mock('email/functions/helpers'); @@ -6,17 +18,6 @@ jest.mock('shared/utils'); jest.mock('common/user-notification-preferences'); jest.mock('shared/websockets/helpers'); -import * as supabaseInit from "shared/supabase/init"; -import { AuthedUser } from "api/helpers/endpoint"; -import * as sharedUtils from "shared/utils"; -import { createComment } from "api/create-comment"; -import * as notificationPrefs from "common/user-notification-preferences"; -import * as supabaseNotifications from "shared/supabase/notifications"; -import * as emailHelpers from "email/functions/helpers"; -import * as websocketHelpers from "shared/websockets/helpers"; -import { convertComment } from "common/supabase/comment"; -import { richTextToString } from "common/util/parse"; - describe('createComment', () => { let mockPg: any; beforeEach(() => { @@ -102,7 +103,7 @@ describe('createComment', () => { expect(sharedUtils.getPrivateUser).toHaveBeenNthCalledWith(2, mockOnUser.id); expect(mockPg.one).toBeCalledTimes(1); expect(mockPg.one).toBeCalledWith( - expect.stringContaining('insert into profile_comments'), + sqlMatch('insert into profile_comments'), [ mockCreator.id, mockCreator.name, diff --git a/backend/api/tests/unit/create-private-user-message-channel.unit.test.ts b/backend/api/tests/unit/create-private-user-message-channel.unit.test.ts index acb1ec3..32e63ca 100644 --- a/backend/api/tests/unit/create-private-user-message-channel.unit.test.ts +++ b/backend/api/tests/unit/create-private-user-message-channel.unit.test.ts @@ -1,11 +1,4 @@ -jest.mock('shared/supabase/init'); -jest.mock('common/util/array'); -jest.mock('api/helpers/private-messages'); -jest.mock('shared/utils'); -jest.mock('firebase-admin', () => ({ - auth: jest.fn() -})); - +import {sqlMatch} from "common/test-utils"; import {createPrivateUserMessageChannel} from "api/create-private-user-message-channel"; import * as supabaseInit from "shared/supabase/init"; import * as sharedUtils from "shared/utils"; @@ -14,6 +7,14 @@ import * as privateMessageModules from "api/helpers/private-messages"; import * as admin from 'firebase-admin'; import {AuthedUser} from "api/helpers/endpoint"; +jest.mock('shared/supabase/init'); +jest.mock('common/util/array'); +jest.mock('api/helpers/private-messages'); +jest.mock('shared/utils'); +jest.mock('firebase-admin', () => ({ + auth: jest.fn() +})); + describe('createPrivateUserMessageChannel', () => { let mockPg = {} as any; beforeEach(() => { @@ -77,7 +78,7 @@ describe('createPrivateUserMessageChannel', () => { expect(sharedUtils.getPrivateUser).toBeCalledWith(mockUserIds[1]); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select channel_id\n from private_user_message_channel_members'), + sqlMatch('select channel_id\n from private_user_message_channel_members'), [mockUserIds] ); }); @@ -124,11 +125,11 @@ describe('createPrivateUserMessageChannel', () => { expect(sharedUtils.getPrivateUser).toBeCalledWith(mockUserIds[1]); expect(mockPg.one).toBeCalledTimes(1); expect(mockPg.one).toBeCalledWith( - expect.stringContaining('insert into private_user_message_channels default\n values\n returning id') + sqlMatch('insert into private_user_message_channels default\n values\n returning id') ); expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('insert into private_user_message_channel_members (channel_id, user_id, role, status)'), + sqlMatch('insert into private_user_message_channel_members (channel_id, user_id, role, status)'), [mockChannel.id, mockAuth.uid] ); expect(privateMessageModules.addUsersToPrivateMessageChannel).toBeCalledTimes(1); diff --git a/backend/api/tests/unit/create-profile.unit.test.ts b/backend/api/tests/unit/create-profile.unit.test.ts index 580b073..d717e84 100644 --- a/backend/api/tests/unit/create-profile.unit.test.ts +++ b/backend/api/tests/unit/create-profile.unit.test.ts @@ -8,16 +8,17 @@ jest.mock('shared/analytics'); jest.mock('common/discord/core'); jest.mock('common/util/time'); -import { createProfile } from "api/create-profile"; +import {createProfile} from "api/create-profile"; import * as supabaseInit from "shared/supabase/init"; import * as sharedUtils from "shared/utils"; import * as supabaseUsers from "shared/supabase/users"; import * as supabaseUtils from "shared/supabase/utils"; -import { tryCatch } from "common/util/try-catch"; -import { removePinnedUrlFromPhotoUrls } from "shared/profiles/parse-photos"; +import {tryCatch} from "common/util/try-catch"; +import {removePinnedUrlFromPhotoUrls} from "shared/profiles/parse-photos"; import * as sharedAnalytics from "shared/analytics"; -import { sendDiscordMessage } from "common/discord/core"; -import { AuthedUser } from "api/helpers/endpoint"; +import {sendDiscordMessage} from "common/discord/core"; +import {AuthedUser} from "api/helpers/endpoint"; +import {sqlMatch} from 'common/test-utils' describe('createProfile', () => { let mockPg = {} as any; @@ -71,7 +72,7 @@ describe('createProfile', () => { expect(tryCatch).toBeCalledTimes(2); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select id from profiles where user_id = $1'), + sqlMatch('select id from profiles where user_id = $1'), [mockAuth.uid] ); expect(removePinnedUrlFromPhotoUrls).toBeCalledTimes(1); @@ -147,7 +148,7 @@ describe('createProfile', () => { expect(mockPg.one).toBeCalledTimes(1); expect(mockPg.one).toBeCalledWith( - expect.stringContaining('SELECT count(*) FROM profiles'), + sqlMatch('SELECT count(*) FROM profiles'), [], expect.any(Function) ); diff --git a/backend/api/tests/unit/delete-bookmarked-search.unit.test.ts b/backend/api/tests/unit/delete-bookmarked-search.unit.test.ts index 0d6ba27..09fee09 100644 --- a/backend/api/tests/unit/delete-bookmarked-search.unit.test.ts +++ b/backend/api/tests/unit/delete-bookmarked-search.unit.test.ts @@ -1,9 +1,10 @@ -jest.mock('shared/supabase/init'); - -import { deleteBookmarkedSearch } from "api/delete-bookmarked-search"; -import { AuthedUser } from "api/helpers/endpoint"; +import {sqlMatch} from "common/test-utils"; +import {deleteBookmarkedSearch} from "api/delete-bookmarked-search"; +import {AuthedUser} from "api/helpers/endpoint"; import * as supabaseInit from "shared/supabase/init"; +jest.mock('shared/supabase/init'); + describe('deleteBookmarkedSearch', () => { let mockPg = {} as any; @@ -32,7 +33,7 @@ describe('deleteBookmarkedSearch', () => { expect(result).toStrictEqual({}); expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('DELETE FROM bookmarked_searches'), + sqlMatch('DELETE FROM bookmarked_searches'), [ mockProps.id, mockAuth.uid diff --git a/backend/api/tests/unit/delete-me.unit.test.ts b/backend/api/tests/unit/delete-me.unit.test.ts index b1d6808..b7847fb 100644 --- a/backend/api/tests/unit/delete-me.unit.test.ts +++ b/backend/api/tests/unit/delete-me.unit.test.ts @@ -1,3 +1,11 @@ +import {sqlMatch} from "common/test-utils"; +import {deleteMe} from "api/delete-me"; +import * as supabaseInit from "shared/supabase/init"; +import * as sharedUtils from "shared/utils"; +import * as firebaseAdmin from "firebase-admin"; +import * as firebaseUtils from "shared/firebase-utils"; +import {AuthedUser} from "api/helpers/endpoint"; + jest.mock('shared/supabase/init'); jest.mock('shared/utils'); jest.mock('firebase-admin', () => ({ @@ -5,13 +13,6 @@ jest.mock('firebase-admin', () => ({ })); jest.mock('shared/firebase-utils'); -import { deleteMe } from "api/delete-me"; -import * as supabaseInit from "shared/supabase/init"; -import * as sharedUtils from "shared/utils"; -import * as firebaseAdmin from "firebase-admin"; -import * as firebaseUtils from "shared/firebase-utils"; -import { AuthedUser } from "api/helpers/endpoint"; - describe('deleteMe', () => { let mockPg = {} as any; beforeEach(() => { @@ -50,7 +51,7 @@ describe('deleteMe', () => { expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid); expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('DELETE FROM users WHERE id = $1'), + sqlMatch('DELETE FROM users WHERE id = $1'), [mockUser.id] ); expect(firebaseUtils.deleteUserFiles).toBeCalledTimes(1); diff --git a/backend/api/tests/unit/get-current-private-users.unit.test.ts b/backend/api/tests/unit/get-current-private-users.unit.test.ts index 285294d..8d83bdd 100644 --- a/backend/api/tests/unit/get-current-private-users.unit.test.ts +++ b/backend/api/tests/unit/get-current-private-users.unit.test.ts @@ -1,11 +1,12 @@ +import {sqlMatch} from "common/test-utils"; +import {getCurrentPrivateUser} from "api/get-current-private-user"; +import * as supabaseInit from "shared/supabase/init"; +import {tryCatch} from "common/util/try-catch"; +import {AuthedUser} from "api/helpers/endpoint"; + jest.mock('shared/supabase/init'); jest.mock('common/util/try-catch'); -import { getCurrentPrivateUser } from "api/get-current-private-user"; -import * as supabaseInit from "shared/supabase/init"; -import { tryCatch } from "common/util/try-catch"; -import { AuthedUser } from "api/helpers/endpoint"; - describe('getCurrentPrivateUser', () => { let mockPg = {} as any; beforeEach(() => { @@ -36,7 +37,7 @@ describe('getCurrentPrivateUser', () => { expect(result).toBe(mockData.data); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select * from private_users where id = $1'), + sqlMatch('select * from private_users where id = $1'), [mockAuth.uid] ); }); diff --git a/backend/api/tests/unit/get-messages-count.unit.test.ts b/backend/api/tests/unit/get-messages-count.unit.test.ts index 9df56b8..63f14ee 100644 --- a/backend/api/tests/unit/get-messages-count.unit.test.ts +++ b/backend/api/tests/unit/get-messages-count.unit.test.ts @@ -1,9 +1,10 @@ -jest.mock('shared/supabase/init'); - -import { getMessagesCount } from "api/get-messages-count"; -import { AuthedUser } from "api/helpers/endpoint"; +import {sqlMatch} from "common/test-utils"; +import {getMessagesCount} from "api/get-messages-count"; +import {AuthedUser} from "api/helpers/endpoint"; import * as supabaseInit from "shared/supabase/init"; +jest.mock('shared/supabase/init'); + describe('getMessagesCount', () => { let mockPg = {} as any; beforeEach(() => { @@ -32,7 +33,7 @@ describe('getMessagesCount', () => { expect(result.count).toBe(Number(mockResults.count)); expect(mockPg.one).toBeCalledTimes(1); expect(mockPg.one).toBeCalledWith( - expect.stringContaining('SELECT COUNT(*) AS count'), + sqlMatch('SELECT COUNT(*) AS count'), expect.any(Object) ); }); diff --git a/backend/api/tests/unit/get-notifications.unit.test.ts b/backend/api/tests/unit/get-notifications.unit.test.ts index 5db8287..a164fd3 100644 --- a/backend/api/tests/unit/get-notifications.unit.test.ts +++ b/backend/api/tests/unit/get-notifications.unit.test.ts @@ -1,8 +1,9 @@ jest.mock('shared/supabase/init'); -import { getNotifications } from "api/get-notifications"; -import { AuthedUser } from "api/helpers/endpoint"; +import {getNotifications} from "api/get-notifications"; +import {AuthedUser} from "api/helpers/endpoint"; import * as supabaseInit from "shared/supabase/init"; +import {sqlMatch} from 'common/test-utils' describe('getNotifications', () => { let mockPg = {} as any; @@ -35,7 +36,7 @@ describe('getNotifications', () => { expect(result).toBe(mockNotifications); expect(mockPg.map).toBeCalledTimes(1); expect(mockPg.map).toBeCalledWith( - expect.stringContaining('select data from user_notifications'), + sqlMatch('select data from user_notifications'), [mockAuth.uid, mockProps.limit, mockProps.after], expect.any(Function) ); diff --git a/backend/api/tests/unit/get-private-messages.unit.test.ts b/backend/api/tests/unit/get-private-messages.unit.test.ts index 82f1cbc..13fec05 100644 --- a/backend/api/tests/unit/get-private-messages.unit.test.ts +++ b/backend/api/tests/unit/get-private-messages.unit.test.ts @@ -1,12 +1,13 @@ +import {sqlMatch} from "common/test-utils"; +import * as getPrivateMessages from "api/get-private-messages"; +import * as supabaseInit from "shared/supabase/init"; +import {tryCatch} from "common/util/try-catch"; +import {AuthedUser} from "api/helpers/endpoint"; + jest.mock('shared/supabase/init'); jest.mock('common/util/try-catch'); jest.mock('shared/supabase/messages'); -import * as getPrivateMessages from "api/get-private-messages"; -import * as supabaseInit from "shared/supabase/init"; -import { tryCatch } from "common/util/try-catch"; -import { AuthedUser } from "api/helpers/endpoint"; - describe('getChannelMemberships', () => { let mockPg = {} as any; beforeEach(() => { @@ -58,13 +59,13 @@ describe('getChannelMemberships', () => { expect(mockPg.map).toBeCalledTimes(2); expect(mockPg.map).toHaveBeenNthCalledWith( 1, - expect.stringContaining('select channel_id, notify_after_time, pumcm.created_time, last_updated_time'), + sqlMatch('select channel_id, notify_after_time, pumcm.created_time, last_updated_time'), [mockAuth.uid, mockProps.channelId, mockProps.limit], expect.any(Function) ); expect(mockPg.map).toHaveBeenNthCalledWith( 2, - expect.stringContaining('select channel_id, user_id'), + sqlMatch('select channel_id, user_id'), [mockAuth.uid, [mockChannels[0].channel_id]], expect.any(Function) ); @@ -105,13 +106,13 @@ describe('getChannelMemberships', () => { expect(mockPg.map).toBeCalledTimes(2); expect(mockPg.map).toHaveBeenNthCalledWith( 1, - expect.stringContaining('with latest_channels as (select distinct on (pumc.id) pumc.id as channel_id'), + sqlMatch('with latest_channels as (select distinct on (pumc.id) pumc.id as channel_id'), [mockAuth.uid, mockProps.createdTime, mockProps.limit, mockProps.lastUpdatedTime], expect.any(Function) ); expect(mockPg.map).toHaveBeenNthCalledWith( 2, - expect.stringContaining('select channel_id, user_id'), + sqlMatch('select channel_id, user_id'), [mockAuth.uid, [mockChannels[0].channel_id]], expect.any(Function) ); @@ -138,7 +139,7 @@ describe('getChannelMemberships', () => { expect(mockPg.map).toBeCalledTimes(1); expect(mockPg.map).toHaveBeenNthCalledWith( 1, - expect.stringContaining('select channel_id, notify_after_time, pumcm.created_time, last_updated_time'), + sqlMatch('select channel_id, notify_after_time, pumcm.created_time, last_updated_time'), [mockAuth.uid, mockProps.channelId, mockProps.limit], expect.any(Function) ); @@ -179,7 +180,7 @@ describe('getChannelMessagesEndpoint', () => { expect(result).toBe(mockData); expect(mockPg.map).toBeCalledTimes(1); expect(mockPg.map).toBeCalledWith( - expect.stringContaining('select *, created_time as created_time_ts'), + sqlMatch('select *, created_time as created_time_ts'), [mockProps.channelId, mockAuth.uid, mockProps.limit, mockProps.id], expect.any(Function) ); @@ -244,7 +245,7 @@ describe('getLastSeenChannelTime', () => { expect(result).toBe(mockUnseens); expect(mockPg.map).toBeCalledTimes(1); expect(mockPg.map).toBeCalledWith( - expect.stringContaining('select distinct on (channel_id) channel_id, created_time'), + sqlMatch('select distinct on (channel_id) channel_id, created_time'), [mockProps.channelIds, mockAuth.uid], expect.any(Function) ); @@ -281,7 +282,7 @@ describe('setChannelLastSeenTime', () => { expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('insert into private_user_seen_message_channels (user_id, channel_id)'), + sqlMatch('insert into private_user_seen_message_channels (user_id, channel_id)'), [mockAuth.uid, mockProps.channelId] ); }); diff --git a/backend/api/tests/unit/get-profile-answers.unit.test.ts b/backend/api/tests/unit/get-profile-answers.unit.test.ts index 9937acb..bed2f09 100644 --- a/backend/api/tests/unit/get-profile-answers.unit.test.ts +++ b/backend/api/tests/unit/get-profile-answers.unit.test.ts @@ -1,9 +1,10 @@ -jest.mock('shared/supabase/init'); - -import { getProfileAnswers } from "api/get-profile-answers"; -import { AuthedUser } from "api/helpers/endpoint"; +import {sqlMatch} from "common/test-utils"; +import {getProfileAnswers} from "api/get-profile-answers"; +import {AuthedUser} from "api/helpers/endpoint"; import * as supabaseInit from "shared/supabase/init"; +jest.mock('shared/supabase/init'); + describe('getProfileAnswers', () => { let mockPg = {} as any; beforeEach(() => { @@ -45,7 +46,7 @@ describe('getProfileAnswers', () => { expect(result.answers).toBe(mockAnswers); expect(mockPg.manyOrNone).toBeCalledTimes(1); expect(mockPg.manyOrNone).toBeCalledWith( - expect.stringContaining('select * from compatibility_answers'), + sqlMatch('select * from compatibility_answers'), [mockProps.userId] ); }); diff --git a/backend/api/tests/unit/get-users.unit.test.ts b/backend/api/tests/unit/get-users.unit.test.ts index 929ad6e..112095e 100644 --- a/backend/api/tests/unit/get-users.unit.test.ts +++ b/backend/api/tests/unit/get-users.unit.test.ts @@ -1,11 +1,12 @@ +import {sqlMatch} from "common/test-utils"; +import {getUser} from "api/get-user"; +import * as supabaseInit from "shared/supabase/init"; +import {toUserAPIResponse} from "common/api/user-types"; + jest.mock("shared/supabase/init"); jest.mock("common/supabase/users"); jest.mock("common/api/user-types"); -import { getUser } from "api/get-user"; -import * as supabaseInit from "shared/supabase/init"; -import { toUserAPIResponse } from "common/api/user-types"; - describe('getUser', () =>{ let mockPg: any; @@ -36,7 +37,7 @@ describe('getUser', () =>{ expect(result).toBe('mockApiResponse'); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select * from users'), + sqlMatch('select * from users'), [mockProps.id], expect.any(Function) ); @@ -55,7 +56,7 @@ describe('getUser', () =>{ await getUser(mockProps) expect(mockPg.oneOrNone).toHaveBeenCalledWith( - expect.stringContaining('where username = $1'), + sqlMatch('where username = $1'), [mockProps.username], expect.any(Function) ); diff --git a/backend/api/tests/unit/hide-comment.unit.test.ts b/backend/api/tests/unit/hide-comment.unit.test.ts index 83ae3e7..7615748 100644 --- a/backend/api/tests/unit/hide-comment.unit.test.ts +++ b/backend/api/tests/unit/hide-comment.unit.test.ts @@ -1,14 +1,15 @@ +import {sqlMatch} from "common/test-utils"; +import {hideComment} from "api/hide-comment"; +import * as supabaseInit from "shared/supabase/init"; +import * as envConsts from "common/envs/constants"; +import {convertComment} from "common/supabase/comment"; +import * as websocketHelpers from "shared/websockets/helpers"; +import {AuthedUser} from "api/helpers/endpoint"; + jest.mock('shared/supabase/init'); jest.mock('common/supabase/comment'); jest.mock('shared/websockets/helpers'); -import { hideComment } from "api/hide-comment"; -import * as supabaseInit from "shared/supabase/init"; -import * as envConsts from "common/envs/constants"; -import { convertComment } from "common/supabase/comment"; -import * as websocketHelpers from "shared/websockets/helpers"; -import { AuthedUser } from "api/helpers/endpoint"; - describe('hideComment', () => { let mockPg = {} as any; beforeEach(() => { @@ -55,7 +56,7 @@ describe('hideComment', () => { expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select * from profile_comments where id = $1'), + sqlMatch('select * from profile_comments where id = $1'), [mockProps.commentId] ); expect(envConsts.isAdminId).toBeCalledTimes(1); diff --git a/backend/api/tests/unit/leave-private-user-message-channel.unit.test.ts b/backend/api/tests/unit/leave-private-user-message-channel.unit.test.ts index 82f85f4..2df647f 100644 --- a/backend/api/tests/unit/leave-private-user-message-channel.unit.test.ts +++ b/backend/api/tests/unit/leave-private-user-message-channel.unit.test.ts @@ -2,11 +2,12 @@ jest.mock('shared/supabase/init'); jest.mock('shared/utils'); jest.mock('api/helpers/private-messages'); -import { leavePrivateUserMessageChannel } from "api/leave-private-user-message-channel"; +import {leavePrivateUserMessageChannel} from "api/leave-private-user-message-channel"; import * as supabaseInit from "shared/supabase/init"; import * as sharedUtils from "shared/utils"; import * as messageHelpers from "api/helpers/private-messages"; -import { AuthedUser } from "api/helpers/endpoint"; +import {AuthedUser} from "api/helpers/endpoint"; +import {sqlMatch} from 'common/test-utils' describe('leavePrivateUserMessageChannel', () => { let mockPg = {} as any; @@ -43,12 +44,12 @@ describe('leavePrivateUserMessageChannel', () => { expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select status from private_user_message_channel_members'), + sqlMatch('select status from private_user_message_channel_members'), [mockProps.channelId, mockAuth.uid] ); expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('update private_user_message_channel_members'), + sqlMatch('update private_user_message_channel_members'), [mockProps.channelId, mockAuth.uid] ); expect(messageHelpers.leaveChatContent).toBeCalledTimes(1); diff --git a/backend/api/tests/unit/like-profile.unit.test.ts b/backend/api/tests/unit/like-profile.unit.test.ts index 80e5738..ac1fe4c 100644 --- a/backend/api/tests/unit/like-profile.unit.test.ts +++ b/backend/api/tests/unit/like-profile.unit.test.ts @@ -1,15 +1,16 @@ +import {sqlMatch} from "common/test-utils"; +import {likeProfile} from "api/like-profile"; +import * as supabaseInit from "shared/supabase/init"; +import * as profileNotifiction from "shared/create-profile-notification"; +import * as likeModules from "api/has-free-like"; +import {tryCatch} from "common/util/try-catch"; +import {AuthedUser} from "api/helpers/endpoint"; + jest.mock('shared/supabase/init'); jest.mock('shared/create-profile-notification'); jest.mock('api/has-free-like'); jest.mock('common/util/try-catch'); -import { likeProfile } from "api/like-profile"; -import * as supabaseInit from "shared/supabase/init"; -import * as profileNotifiction from "shared/create-profile-notification"; -import * as likeModules from "api/has-free-like"; -import { tryCatch } from "common/util/try-catch"; -import { AuthedUser } from "api/helpers/endpoint"; - describe('likeProfile', () => { let mockPg = {} as any; beforeEach(() => { @@ -51,13 +52,13 @@ describe('likeProfile', () => { expect(result.result.status).toBe('success'); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select * from profile_likes where creator_id = $1 and target_id = $2'), + sqlMatch('select * from profile_likes where creator_id = $1 and target_id = $2'), [mockAuth.uid, mockProps.targetUserId] ); expect(tryCatch).toBeCalledTimes(2); expect(mockPg.one).toBeCalledTimes(1); expect(mockPg.one).toBeCalledWith( - expect.stringContaining('insert into profile_likes (creator_id, target_id) values ($1, $2) returning *'), + sqlMatch('insert into profile_likes (creator_id, target_id) values ($1, $2) returning *'), [mockAuth.uid, mockProps.targetUserId] ); @@ -105,7 +106,7 @@ describe('likeProfile', () => { expect(result.status).toBe('success'); expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('delete from profile_likes where creator_id = $1 and target_id = $2'), + sqlMatch('delete from profile_likes where creator_id = $1 and target_id = $2'), [mockAuth.uid, mockProps.targetUserId] ); }); diff --git a/backend/api/tests/unit/mark-all-notifications-read.unit.test.ts b/backend/api/tests/unit/mark-all-notifications-read.unit.test.ts index 8d20039..62d34ff 100644 --- a/backend/api/tests/unit/mark-all-notifications-read.unit.test.ts +++ b/backend/api/tests/unit/mark-all-notifications-read.unit.test.ts @@ -1,8 +1,9 @@ jest.mock('shared/supabase/init'); -import { markAllNotifsRead } from "api/mark-all-notifications-read"; -import { AuthedUser } from "api/helpers/endpoint"; +import {markAllNotifsRead} from "api/mark-all-notifications-read"; +import {AuthedUser} from "api/helpers/endpoint"; import * as supabaseInit from "shared/supabase/init"; +import {sqlMatch} from 'common/test-utils' describe('markAllNotifsRead', () => { let mockPg = {} as any; @@ -29,7 +30,7 @@ describe('markAllNotifsRead', () => { expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('update user_notifications'), + sqlMatch('update user_notifications'), [mockAuth.uid] ); }); diff --git a/backend/api/tests/unit/react-to-message.unit.test.ts b/backend/api/tests/unit/react-to-message.unit.test.ts index b4d213b..7dd2dcf 100644 --- a/backend/api/tests/unit/react-to-message.unit.test.ts +++ b/backend/api/tests/unit/react-to-message.unit.test.ts @@ -1,10 +1,11 @@ -jest.mock('shared/supabase/init'); -jest.mock('api/helpers/private-messages'); - -import { reactToMessage } from "api/react-to-message"; +import {sqlMatch} from "common/test-utils"; +import {reactToMessage} from "api/react-to-message"; import * as supabaseInit from "shared/supabase/init"; import * as messageHelpers from "api/helpers/private-messages"; -import { AuthedUser } from "api/helpers/endpoint"; +import {AuthedUser} from "api/helpers/endpoint"; + +jest.mock('shared/supabase/init'); +jest.mock('api/helpers/private-messages'); describe('reactToMessage', () => { let mockPg = {} as any; @@ -45,18 +46,18 @@ describe('reactToMessage', () => { expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(params).toEqual([mockAuth.uid, mockProps.messageId]) expect(sql).toEqual( - expect.stringContaining('SELECT *') + sqlMatch('SELECT *') ); expect(sql).toEqual( - expect.stringContaining('FROM private_user_message_channel_members m') + sqlMatch('FROM private_user_message_channel_members m') ); expect(mockPg.none).toBeCalledTimes(1); expect(params1).toEqual([mockProps.reaction, mockAuth.uid, mockProps.messageId]) expect(sql1).toEqual( - expect.stringContaining('UPDATE private_user_messages') + sqlMatch('UPDATE private_user_messages') ); expect(sql1).toEqual( - expect.stringContaining('SET reactions =') + sqlMatch('SET reactions =') ); expect(messageHelpers.broadcastPrivateMessages).toBeCalledTimes(1); expect(messageHelpers.broadcastPrivateMessages).toBeCalledWith( @@ -89,10 +90,10 @@ describe('reactToMessage', () => { expect(mockPg.none).toBeCalledTimes(1); expect(params1).toEqual([mockProps.reaction, mockProps.messageId, mockAuth.uid]) expect(sql1).toEqual( - expect.stringContaining('UPDATE private_user_messages') + sqlMatch('UPDATE private_user_messages') ); expect(sql1).toEqual( - expect.stringContaining('SET reactions = reactions - $1') + sqlMatch('SET reactions = reactions - $1') ); }); }); diff --git a/backend/api/tests/unit/remove-pinned-photo.unit.test.ts b/backend/api/tests/unit/remove-pinned-photo.unit.test.ts index d77c73b..2485d6c 100644 --- a/backend/api/tests/unit/remove-pinned-photo.unit.test.ts +++ b/backend/api/tests/unit/remove-pinned-photo.unit.test.ts @@ -2,11 +2,12 @@ jest.mock('shared/supabase/init'); jest.mock('common/envs/constants'); jest.mock('common/util/try-catch'); -import { removePinnedPhoto } from "api/remove-pinned-photo"; +import {removePinnedPhoto} from "api/remove-pinned-photo"; import * as supabaseInit from "shared/supabase/init"; import * as envConstants from "common/envs/constants"; -import { tryCatch } from "common/util/try-catch"; -import { AuthedUser } from "api/helpers/endpoint"; +import {tryCatch} from "common/util/try-catch"; +import {AuthedUser} from "api/helpers/endpoint"; +import {sqlMatch} from 'common/test-utils' describe('removePinnedPhoto', () => { let mockPg = {} as any; @@ -40,7 +41,7 @@ describe('removePinnedPhoto', () => { expect(envConstants.isAdminId).toBeCalledWith(mockAuth.uid); expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('update profiles set pinned_url = null where user_id = $1'), + sqlMatch('update profiles set pinned_url = null where user_id = $1'), [mockBody.userId] ); }); diff --git a/backend/api/tests/unit/report.unit.test.ts b/backend/api/tests/unit/report.unit.test.ts index ece72c9..8d3af70 100644 --- a/backend/api/tests/unit/report.unit.test.ts +++ b/backend/api/tests/unit/report.unit.test.ts @@ -1,15 +1,16 @@ +import {sqlMatch} from "common/test-utils"; +import {report} from "api/report"; +import * as supabaseInit from "shared/supabase/init"; +import {tryCatch} from "common/util/try-catch"; +import * as supabaseUtils from "shared/supabase/utils"; +import {sendDiscordMessage} from "common/discord/core"; +import {AuthedUser} from "api/helpers/endpoint"; + jest.mock('shared/supabase/init'); jest.mock('common/util/try-catch'); jest.mock('shared/supabase/utils'); jest.mock('common/discord/core'); -import { report } from "api/report"; -import * as supabaseInit from "shared/supabase/init"; -import { tryCatch } from "common/util/try-catch"; -import * as supabaseUtils from "shared/supabase/utils"; -import { sendDiscordMessage } from "common/discord/core"; -import { AuthedUser } from "api/helpers/endpoint"; - describe('report', () => { let mockPg = {} as any; beforeEach(() => { @@ -72,12 +73,12 @@ describe('report', () => { expect(mockPg.oneOrNone).toBeCalledTimes(2); expect(mockPg.oneOrNone).toHaveBeenNthCalledWith( 1, - expect.stringContaining('select * from users where id = $1'), + sqlMatch('select * from users where id = $1'), [mockAuth.uid] ); expect(mockPg.oneOrNone).toHaveBeenNthCalledWith( 2, - expect.stringContaining('select * from users where id = $1'), + sqlMatch('select * from users where id = $1'), [mockBody.contentOwnerId] ); expect(sendDiscordMessage).toBeCalledTimes(1); diff --git a/backend/api/tests/unit/save-subscription-mobile.unit.test.ts b/backend/api/tests/unit/save-subscription-mobile.unit.test.ts index 8fdb6f1..9691577 100644 --- a/backend/api/tests/unit/save-subscription-mobile.unit.test.ts +++ b/backend/api/tests/unit/save-subscription-mobile.unit.test.ts @@ -1,9 +1,10 @@ -jest.mock('shared/supabase/init'); - -import { AuthedUser } from "api/helpers/endpoint"; -import { saveSubscriptionMobile } from "api/save-subscription-mobile"; +import {sqlMatch} from "common/test-utils"; +import {AuthedUser} from "api/helpers/endpoint"; +import {saveSubscriptionMobile} from "api/save-subscription-mobile"; import * as supabaseInit from "shared/supabase/init"; +jest.mock('shared/supabase/init'); + describe('saveSubscriptionMobile', () => { let mockPg = {} as any; beforeEach(() => { @@ -32,7 +33,7 @@ describe('saveSubscriptionMobile', () => { expect(result.success).toBeTruthy(); expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('insert into push_subscriptions_mobile(token, platform, user_id)'), + sqlMatch('insert into push_subscriptions_mobile(token, platform, user_id)'), [mockBody.token, 'android', mockAuth.uid] ); }); diff --git a/backend/api/tests/unit/save-subscription.unit.test.ts b/backend/api/tests/unit/save-subscription.unit.test.ts index 2d3b31b..7ce43b6 100644 --- a/backend/api/tests/unit/save-subscription.unit.test.ts +++ b/backend/api/tests/unit/save-subscription.unit.test.ts @@ -1,8 +1,9 @@ jest.mock('shared/supabase/init'); -import { AuthedUser } from "api/helpers/endpoint"; -import { saveSubscription } from "api/save-subscription"; +import {AuthedUser} from "api/helpers/endpoint"; +import {saveSubscription} from "api/save-subscription"; import * as supabaseInit from "shared/supabase/init"; +import {sqlMatch} from 'common/test-utils' describe('saveSubscription', () => { let mockPg = {} as any; @@ -40,12 +41,12 @@ describe('saveSubscription', () => { expect(result.success).toBeTruthy(); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select id from push_subscriptions where endpoint = $1'), + sqlMatch('select id from push_subscriptions where endpoint = $1'), [mockBody.subscription.endpoint] ); expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('update push_subscriptions set keys = $1, user_id = $2 where id = $3'), + sqlMatch('update push_subscriptions set keys = $1, user_id = $2 where id = $3'), [mockBody.subscription.keys, mockAuth.uid, mockExists.id] ); }); @@ -68,12 +69,12 @@ describe('saveSubscription', () => { expect(result.success).toBeTruthy(); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select id from push_subscriptions where endpoint = $1'), + sqlMatch('select id from push_subscriptions where endpoint = $1'), [mockBody.subscription.endpoint] ); expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('insert into push_subscriptions(endpoint, keys, user_id) values($1, $2, $3)'), + sqlMatch('insert into push_subscriptions(endpoint, keys, user_id) values($1, $2, $3)'), [mockBody.subscription.endpoint, mockBody.subscription.keys, mockAuth.uid] ); }); diff --git a/backend/api/tests/unit/search-users.unit.test.ts b/backend/api/tests/unit/search-users.unit.test.ts index 68c7127..d905b72 100644 --- a/backend/api/tests/unit/search-users.unit.test.ts +++ b/backend/api/tests/unit/search-users.unit.test.ts @@ -1,17 +1,18 @@ +import {sqlMatch} from "common/test-utils"; +import {searchUsers} from "api/search-users"; +import * as supabaseInit from "shared/supabase/init"; +import * as searchHelpers from "shared/helpers/search"; +import * as sqlBuilderModules from "shared/supabase/sql-builder"; +import * as supabaseUsers from "common/supabase/users"; +import {toUserAPIResponse} from "common/api/user-types"; +import {AuthedUser} from "api/helpers/endpoint"; + jest.mock('shared/supabase/init'); jest.mock('shared/helpers/search'); jest.mock('shared/supabase/sql-builder'); jest.mock('common/supabase/users'); jest.mock('common/api/user-types'); -import { searchUsers } from "api/search-users"; -import * as supabaseInit from "shared/supabase/init"; -import * as searchHelpers from "shared/helpers/search"; -import * as sqlBuilderModules from "shared/supabase/sql-builder"; -import * as supabaseUsers from "common/supabase/users"; -import { toUserAPIResponse } from "common/api/user-types"; -import { AuthedUser } from "api/helpers/endpoint"; - describe('searchUsers', () => { let mockPg = {} as any; beforeEach(() => { @@ -76,12 +77,12 @@ describe('searchUsers', () => { expect(sqlBuilderModules.from).toBeCalledWith('users'); expect(sqlBuilderModules.where).toBeCalledTimes(1); expect(sqlBuilderModules.where).toBeCalledWith( - expect.stringContaining("name_username_vector @@ websearch_to_tsquery('english', $1)"), + sqlMatch("name_username_vector @@ websearch_to_tsquery('english', $1)"), [mockProps.term, 'ConstructPrefix'] ); expect(sqlBuilderModules.orderBy).toBeCalledTimes(1); expect(sqlBuilderModules.orderBy).toBeCalledWith( - expect.stringContaining("ts_rank(name_username_vector, websearch_to_tsquery($1)) desc,"), + sqlMatch("ts_rank(name_username_vector, websearch_to_tsquery($1)) desc,"), [mockProps.term] ); expect(sqlBuilderModules.limit).toBeCalledTimes(1); diff --git a/backend/api/tests/unit/ship-profiles.unit.test.ts b/backend/api/tests/unit/ship-profiles.unit.test.ts index 397280b..186ed40 100644 --- a/backend/api/tests/unit/ship-profiles.unit.test.ts +++ b/backend/api/tests/unit/ship-profiles.unit.test.ts @@ -3,12 +3,13 @@ jest.mock('common/util/try-catch'); jest.mock('shared/supabase/utils'); jest.mock('shared/create-profile-notification'); -import { shipProfiles } from "api/ship-profiles"; +import {shipProfiles} from "api/ship-profiles"; import * as supabaseInit from "shared/supabase/init"; -import { tryCatch } from "common/util/try-catch"; +import {tryCatch} from "common/util/try-catch"; import * as supabaseUtils from "shared/supabase/utils"; import * as profileNotificationModules from "shared/create-profile-notification"; -import { AuthedUser } from "api/helpers/endpoint"; +import {AuthedUser} from "api/helpers/endpoint"; +import {sqlMatch} from 'common/test-utils' describe('shipProfiles', () => { let mockPg = {} as any; @@ -49,7 +50,7 @@ describe('shipProfiles', () => { expect(tryCatch).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select ship_id from profile_ships'), + sqlMatch('select ship_id from profile_ships'), [mockAuth.uid, mockProps.targetUserId1, mockProps.targetUserId2] ); }); @@ -79,12 +80,12 @@ describe('shipProfiles', () => { expect(tryCatch).toBeCalledTimes(2); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select ship_id from profile_ships'), + sqlMatch('select ship_id from profile_ships'), [mockAuth.uid, mockProps.targetUserId1, mockProps.targetUserId2] ); expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('delete from profile_ships where ship_id = $1'), + sqlMatch('delete from profile_ships where ship_id = $1'), [mockExisting.data.ship_id] ); }); @@ -121,7 +122,7 @@ describe('shipProfiles', () => { expect(tryCatch).toBeCalledTimes(2); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select ship_id from profile_ships'), + sqlMatch('select ship_id from profile_ships'), [mockAuth.uid, mockProps.targetUserId1, mockProps.targetUserId2] ); expect(supabaseUtils.insert).toBeCalledTimes(1); diff --git a/backend/api/tests/unit/star-profile.unit.test.ts b/backend/api/tests/unit/star-profile.unit.test.ts index 3708918..911c36d 100644 --- a/backend/api/tests/unit/star-profile.unit.test.ts +++ b/backend/api/tests/unit/star-profile.unit.test.ts @@ -1,13 +1,14 @@ +import {sqlMatch} from "common/test-utils"; +import {AuthedUser} from "api/helpers/endpoint"; +import {starProfile} from "api/star-profile"; +import {tryCatch} from "common/util/try-catch"; +import * as supabaseInit from "shared/supabase/init"; +import * as supabaseUtils from "shared/supabase/utils"; + jest.mock('common/util/try-catch'); jest.mock('shared/supabase/init'); jest.mock('shared/supabase/utils'); -import { AuthedUser } from "api/helpers/endpoint"; -import { starProfile } from "api/star-profile"; -import { tryCatch } from "common/util/try-catch"; -import * as supabaseInit from "shared/supabase/init"; -import * as supabaseUtils from "shared/supabase/utils"; - describe('startProfile', () => { let mockPg = {} as any; beforeEach(() => { @@ -45,7 +46,7 @@ describe('startProfile', () => { expect(tryCatch).toBeCalledTimes(2); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select * from profile_stars where creator_id = $1 and target_id = $2'), + sqlMatch('select * from profile_stars where creator_id = $1 and target_id = $2'), [mockAuth.uid, mockProps.targetUserId] ); expect(supabaseUtils.insert).toBeCalledTimes(1); @@ -101,7 +102,7 @@ describe('startProfile', () => { expect(tryCatch).toBeCalledTimes(1); expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('delete from profile_stars where creator_id = $1 and target_id = $2'), + sqlMatch('delete from profile_stars where creator_id = $1 and target_id = $2'), [mockAuth.uid, mockProps.targetUserId] ); }); diff --git a/backend/api/tests/unit/update-me.unit.test.ts b/backend/api/tests/unit/update-me.unit.test.ts index ef4cede..7c5cc9b 100644 --- a/backend/api/tests/unit/update-me.unit.test.ts +++ b/backend/api/tests/unit/update-me.unit.test.ts @@ -18,6 +18,7 @@ import * as sharedUtils from "shared/utils"; import * as supabaseUsers from "shared/supabase/users"; import * as websocketHelperModules from "shared/websockets/helpers"; import {AuthedUser} from "api/helpers/endpoint"; +import {sqlMatch} from 'common/test-utils' describe('updateMe', () => { let mockPg = {} as any; @@ -114,12 +115,16 @@ describe('updateMe', () => { expect(mockPg.none).toBeCalledTimes(2); expect(mockPg.none).toHaveBeenNthCalledWith( 1, - expect.stringContaining(`update users set name = $1 where id = $2`), + sqlMatch(`update users + set name = $1 + where id = $2`), [mockUpdate.name, mockAuth.uid] ); expect(mockPg.none).toHaveBeenNthCalledWith( 2, - expect.stringContaining(`update users set username = $1 where id = $2`), + sqlMatch(`update users + set username = $1 + where id = $2`), [mockUpdate.username, mockAuth.uid] ); expect(objectUtils.removeUndefinedProps).toBeCalledTimes(2); diff --git a/backend/api/tests/unit/update-options.unit.test.ts b/backend/api/tests/unit/update-options.unit.test.ts index 2b32fc3..97626af 100644 --- a/backend/api/tests/unit/update-options.unit.test.ts +++ b/backend/api/tests/unit/update-options.unit.test.ts @@ -1,11 +1,12 @@ -jest.mock('common/util/try-catch'); -jest.mock('shared/supabase/init'); - +import {sqlMatch} from "common/test-utils"; import {AuthedUser} from "api/helpers/endpoint"; import {updateOptions} from "api/update-options"; import {tryCatch} from "common/util/try-catch"; import * as supabaseInit from "shared/supabase/init"; +jest.mock('common/util/try-catch'); +jest.mock('shared/supabase/init'); + describe('updateOptions', () => { let mockPg = {} as any; let mockTx = {} as any; @@ -64,30 +65,33 @@ describe('updateOptions', () => { expect(result.updatedIds).toStrictEqual([mockRow1.id, mockRow2.id]); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('SELECT id FROM profiles WHERE user_id = $1'), + sqlMatch('SELECT id FROM profiles WHERE user_id = $1'), [mockAuth.uid] ); expect(tryCatch).toBeCalledTimes(1); expect(mockTx.one).toBeCalledTimes(2); expect(mockTx.one).toHaveBeenNthCalledWith( 1, - expect.stringContaining(`INSERT INTO ${mockProps.table} (name, creator_id)`), + sqlMatch(`INSERT INTO ${mockProps.table} (name, creator_id)`), [mockProps.values[0], mockAuth.uid] ); expect(mockTx.one).toHaveBeenNthCalledWith( 2, - expect.stringContaining(`INSERT INTO ${mockProps.table} (name, creator_id)`), + sqlMatch(`INSERT INTO ${mockProps.table} (name, creator_id)`), [mockProps.values[1], mockAuth.uid] ); expect(mockTx.none).toBeCalledTimes(2); expect(mockTx.none).toHaveBeenNthCalledWith( 1, - expect.stringContaining(`DELETE FROM profile_${mockProps.table} WHERE profile_id = $1`), + sqlMatch(`DELETE + FROM profile_${mockProps.table} + WHERE profile_id = $1`), [mockProfileIdResult.id] ); expect(mockTx.none).toHaveBeenNthCalledWith( 2, - expect.stringContaining(`INSERT INTO profile_${mockProps.table} (profile_id, option_id) VALUES`), + sqlMatch(`INSERT INTO profile_${mockProps.table} (profile_id, option_id) + VALUES`), [mockProfileIdResult.id, mockRow1.id, mockRow2.id] ); }); diff --git a/backend/api/tests/unit/update-private-user-message-channel.unit.test.ts b/backend/api/tests/unit/update-private-user-message-channel.unit.test.ts index 01dfd7f..1b74216 100644 --- a/backend/api/tests/unit/update-private-user-message-channel.unit.test.ts +++ b/backend/api/tests/unit/update-private-user-message-channel.unit.test.ts @@ -1,12 +1,13 @@ -jest.mock('shared/supabase/init'); -jest.mock('shared/utils'); -jest.mock('common/supabase/utils'); - +import {sqlMatch} from "common/test-utils"; import {updatePrivateUserMessageChannel} from "api/update-private-user-message-channel"; import * as supabaseInit from "shared/supabase/init"; import * as sharedUtils from "shared/utils"; import * as supabaseUtils from "common/supabase/utils"; -import { AuthedUser } from "api/helpers/endpoint"; +import {AuthedUser} from "api/helpers/endpoint"; + +jest.mock('shared/supabase/init'); +jest.mock('shared/utils'); +jest.mock('common/supabase/utils'); describe('updatePrivateUserMessageChannel', () => { let mockPg = {} as any; @@ -45,12 +46,12 @@ describe('updatePrivateUserMessageChannel', () => { expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select status from private_user_message_channel_members'), + sqlMatch('select status from private_user_message_channel_members'), [mockBody.channelId, mockAuth.uid] ); expect(mockPg.none).toBeCalledTimes(1); expect(mockPg.none).toBeCalledWith( - expect.stringContaining('update private_user_message_channel_members'), + sqlMatch('update private_user_message_channel_members'), [mockBody.channelId, mockAuth.uid, 'mockMillisToTs'] ); }); diff --git a/backend/api/tests/unit/update-profile.unit.test.ts b/backend/api/tests/unit/update-profile.unit.test.ts index 988262f..3880c3f 100644 --- a/backend/api/tests/unit/update-profile.unit.test.ts +++ b/backend/api/tests/unit/update-profile.unit.test.ts @@ -11,6 +11,7 @@ import * as supabaseUtils from "shared/supabase/utils"; import * as supabaseUsers from "shared/supabase/users"; import {tryCatch} from "common/util/try-catch"; import {removePinnedUrlFromPhotoUrls} from "shared/profiles/parse-photos"; +import {sqlMatch} from 'common/test-utils' describe('updateProfiles', () => { let mockPg: any; @@ -45,7 +46,7 @@ describe('updateProfiles', () => { expect(result).toBe(mockData); expect(mockPg.oneOrNone).toBeCalledTimes(1); expect(mockPg.oneOrNone).toBeCalledWith( - expect.stringContaining('select * from profiles where user_id = $1'), + sqlMatch('select * from profiles where user_id = $1'), [mockAuth.uid] ); expect(removePinnedUrlFromPhotoUrls).toBeCalledTimes(1); diff --git a/backend/api/tests/unit/vote-unit.test.ts b/backend/api/tests/unit/vote-unit.test.ts index 25f2358..94e960f 100644 --- a/backend/api/tests/unit/vote-unit.test.ts +++ b/backend/api/tests/unit/vote-unit.test.ts @@ -1,10 +1,11 @@ jest.mock('shared/supabase/init'); jest.mock('shared/utils'); -import { AuthedUser } from "api/helpers/endpoint"; -import { vote } from "api/vote"; +import {AuthedUser} from "api/helpers/endpoint"; +import {vote} from "api/vote"; import * as supabaseInit from "shared/supabase/init"; import * as sharedUtils from "shared/utils"; +import {sqlMatch} from 'common/test-utils' describe('vote', () => { let mockPg = {} as any; @@ -42,7 +43,7 @@ describe('vote', () => { expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid); expect(mockPg.one).toBeCalledTimes(1); expect(mockPg.one).toBeCalledWith( - expect.stringContaining('insert into vote_results (user_id, vote_id, choice, priority)'), + sqlMatch('insert into vote_results (user_id, vote_id, choice, priority)'), [mockUser.id, mockProps.voteId, 1, mockProps.priority] ); }); diff --git a/common/src/test-utils.ts b/common/src/test-utils.ts new file mode 100644 index 0000000..0b80901 --- /dev/null +++ b/common/src/test-utils.ts @@ -0,0 +1,6 @@ +export function sqlMatch(sql: string) { + // Normalize: collapse all whitespace to single spaces, trim + // Then build a regex that tolerates any whitespace or line break in the real SQL. + const escaped = sql.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').trim() + return expect.stringMatching(new RegExp(escaped.replace(/\s+/g, '\\s+'))) +}