Add backend API unit tests (#21)

* setting up test structure

* .

* added playwright config file, deleted original playwright folder and moved "some.test" file

* continued test structure setup

* Updating test folder structure

* Added database seeding script and backend testing folder structure

* removed the database test

* Replaced db seeding script

* Updated userInformation.ts to use values from choices.tsx

* merge prep

* removing extra unit test, moving api test to correct folder

* Pushing to get help with sql Unit test

* Updating get-profiles unit tests

* Added more unit tests

* .

* Added more unit tests

* Added getSupabaseToken unit test

* .

* excluding supabase token test so ci can pass

* .

* Seperated the seedDatabase func into its own file so it can be accessed seperatly

* Fixed failing test

* .

* .

* Fix tests

* Fix lint

* Clean

---------

Co-authored-by: MartinBraquet <martin.braquet@gmail.com>
This commit is contained in:
Okechi Jones-Williams
2025-11-29 23:03:16 +00:00
committed by GitHub
parent f323034eed
commit ab612a3eca
26 changed files with 1449 additions and 170 deletions

32
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,32 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug Jest Tests",
"type": "node",
"request": "launch",
"runtimeArgs": [
"--inspect-brk",
"${workspaceRoot}/node_modules/.bin/jest",
"--runInBand"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
// {
// "type": "node",
// "request": "launch",
// "name": "Launch Program",
// "skipFiles": [
// "<node_internals>/**"
// ],
// "program": "${workspaceFolder}/backend/api/tests/unit/get-profiles.unit.test.ts",
// "outFiles": [
// "${workspaceFolder}/**/*.js"
// ]
// }
]
}

View File

@@ -13,7 +13,7 @@ module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
project: ['./tsconfig.json', './tsconfig.test.json'],
},
rules: {
'@typescript-eslint/ban-types': [

View File

@@ -168,3 +168,6 @@ docker rmi -f $(docker images -aq)
### Documentation
The API doc is available at https://api.compassmeet.com. It's dynamically prepared in [app.ts](src/app.ts).
### Todo (Tests)
- [ ] Finish get-supabase-token unit test when endpoint is implemented

View File

@@ -10,26 +10,26 @@ export type profileQueryType = {
after?: string | undefined,
// Search and filter parameters
name?: string | undefined,
genders?: String[] | undefined,
education_levels?: String[] | undefined,
pref_gender?: String[] | undefined,
genders?: string[] | undefined,
education_levels?: string[] | undefined,
pref_gender?: string[] | undefined,
pref_age_min?: number | undefined,
pref_age_max?: number | undefined,
drinks_min?: number | undefined,
drinks_max?: number | undefined,
pref_relation_styles?: String[] | undefined,
pref_romantic_styles?: String[] | undefined,
diet?: String[] | undefined,
political_beliefs?: String[] | undefined,
mbti?: String[] | undefined,
relationship_status?: String[] | undefined,
languages?: String[] | undefined,
religion?: String[] | undefined,
pref_relation_styles?: string[] | undefined,
pref_romantic_styles?: string[] | undefined,
diet?: string[] | undefined,
political_beliefs?: string[] | undefined,
mbti?: string[] | undefined,
relationship_status?: string[] | undefined,
languages?: string[] | undefined,
religion?: string[] | undefined,
wants_kids_strength?: number | undefined,
has_kids?: number | undefined,
is_smoker?: boolean | undefined,
shortBio?: boolean | undefined,
geodbCityIds?: String[] | undefined,
geodbCityIds?: string[] | undefined,
lat?: number | undefined,
lon?: number | undefined,
radius?: number | undefined,

View File

@@ -1,8 +1,7 @@
import { toUserAPIResponse } from 'common/api/user-types'
import { convertUser, displayUserColumns } from 'common/supabase/users'
import { convertUser } from 'common/supabase/users'
import { createSupabaseDirectClient } from 'shared/supabase/init'
import { APIError } from 'common/api/utils'
import { removeNullOrUndefinedProps } from 'common/util/object'
export const getUser = async (props: { id: string } | { username: string }) => {
const pg = createSupabaseDirectClient()

View File

@@ -52,7 +52,7 @@ export const report: APIHandler<'report'> = async (body, auth) => {
console.error('Failed to get reported user for report', userError)
return
}
let message: string = `
const message: string = `
🚨 **New Report** 🚨
**Type:** ${contentType}
**Content ID:** ${contentId}

View File

@@ -0,0 +1,115 @@
jest.mock('shared/supabase/init')
jest.mock('shared/helpers/auth')
jest.mock('common/envs/constants')
jest.mock('shared/supabase/users')
jest.mock('shared/analytics')
jest.mock('shared/utils')
import { banUser } from "api/ban-user";
import * as supabaseInit from "shared/supabase/init";
import { throwErrorIfNotMod } from "shared/helpers/auth";
import * as constants from "common/envs/constants";
import * as supabaseUsers from "shared/supabase/users";
import * as sharedAnalytics from "shared/analytics";
import { } from "shared/helpers/auth";
import { APIError, AuthedUser } from "api/helpers/endpoint"
describe('banUser', () => {
const mockPg = {} as any;
beforeEach(() => {
jest.resetAllMocks();
(supabaseInit.createSupabaseDirectClient as jest.Mock)
.mockReturnValue(mockPg);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('should', () => {
it('ban a user successfully', async () => {
const mockUser = {
userId: '123',
unban: false
};
const mockAuth = {uid: '321'} as AuthedUser;
const mockReq = {} as any;
(constants.isAdminId as jest.Mock).mockReturnValue(false);
await banUser(mockUser, mockAuth, mockReq);
expect(throwErrorIfNotMod).toBeCalledWith(mockAuth.uid);
expect(constants.isAdminId).toBeCalledWith(mockUser.userId);
expect(sharedAnalytics.trackPublicEvent)
.toBeCalledWith(mockAuth.uid, 'ban user', {userId: mockUser.userId});
expect(supabaseUsers.updateUser)
.toBeCalledWith(mockPg, mockUser.userId, {isBannedFromPosting: true});
});
it('unban a user successfully', async () => {
const mockUser = {
userId: '123',
unban: true
};
const mockAuth = {uid: '321'} as AuthedUser;
const mockReq = {} as any;
(constants.isAdminId as jest.Mock).mockReturnValue(false);
await banUser(mockUser, mockAuth, mockReq);
expect(throwErrorIfNotMod).toBeCalledWith(mockAuth.uid);
expect(constants.isAdminId).toBeCalledWith(mockUser.userId);
expect(sharedAnalytics.trackPublicEvent)
.toBeCalledWith(mockAuth.uid, 'ban user', {userId: mockUser.userId});
expect(supabaseUsers.updateUser)
.toBeCalledWith(mockPg, mockUser.userId, {isBannedFromPosting: false});
});
it('throw and error if the ban requester is not a mod or admin', async () => {
const mockUser = {
userId: '123',
unban: false
};
const mockAuth = {uid: '321'} as AuthedUser;
const mockReq = {} as any;
(throwErrorIfNotMod as jest.Mock).mockRejectedValue(
new APIError(
403,
`User ${mockAuth.uid} must be an admin or trusted to perform this action.`
)
);
await expect(banUser(mockUser, mockAuth, mockReq))
.rejects
.toThrowError(`User ${mockAuth.uid} must be an admin or trusted to perform this action.`);
expect(throwErrorIfNotMod).toBeCalledWith(mockAuth.uid);
expect(sharedAnalytics.trackPublicEvent).toBeCalledTimes(0);
expect(supabaseUsers.updateUser).toBeCalledTimes(0);
});
it('throw an error if the ban target is an admin', async () => {
const mockUser = {
userId: '123',
unban: false
};
const mockAuth = {uid: '321'} as AuthedUser;
const mockReq = {} as any;
(constants.isAdminId as jest.Mock).mockReturnValue(true);
await expect(banUser(mockUser, mockAuth, mockReq))
.rejects
.toThrowError('Cannot ban admin');
expect(throwErrorIfNotMod).toBeCalledWith(mockAuth.uid);
expect(constants.isAdminId).toBeCalledWith(mockUser.userId);
expect(sharedAnalytics.trackPublicEvent).toBeCalledTimes(0);
expect(supabaseUsers.updateUser).toBeCalledTimes(0);
});
});
});

View File

@@ -0,0 +1,119 @@
jest.mock('shared/supabase/init')
jest.mock('shared/supabase/users')
jest.mock('shared/supabase/utils')
import * as blockUserModule from "api/block-user";
import { AuthedUser } from "api/helpers/endpoint";
import * as supabaseInit from "shared/supabase/init";
import * as supabaseUsers from "shared/supabase/users";
import * as supabaseUtils from "shared/supabase/utils";
describe('blockUser', () => {
let mockPg: any;
beforeEach(() => {
jest.resetAllMocks()
mockPg = {
tx: jest.fn(async (cb) => {
const mockTx = {};
await cb(mockTx);
}),
};
(supabaseInit.createSupabaseDirectClient as jest.Mock)
.mockReturnValue(mockPg)
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('should', () => {
it('block the user successfully', async () => {
const mockParams = { id: '123' }
const mockAuth = {uid: '321'} as AuthedUser;
const mockReq = {} as any;
(supabaseUsers.updatePrivateUser as jest.Mock).mockResolvedValue(null);
await blockUserModule.blockUser(mockParams, mockAuth, mockReq)
expect(mockPg.tx).toHaveBeenCalledTimes(1)
expect(supabaseUsers.updatePrivateUser)
.toHaveBeenCalledWith(
expect.any(Object),
mockAuth.uid,
{ blockedByUserIds: supabaseUtils.FieldVal.arrayConcat(mockParams.id)}
);
expect(supabaseUsers.updatePrivateUser)
.toHaveBeenCalledWith(
expect.any(Object),
mockParams.id,
{ blockedByUserIds: supabaseUtils.FieldVal.arrayConcat(mockAuth.uid)}
);
});
it('throw an error if the user tries to block themselves', async () => {
const mockParams = { id: '123' }
const mockAuth = {uid: '123'} as AuthedUser;
const mockReq = {} as any;
expect(blockUserModule.blockUser(mockParams, mockAuth, mockReq))
.rejects
.toThrowError('You cannot block yourself')
expect(mockPg.tx).toHaveBeenCalledTimes(0)
});
});
});
describe('unblockUser', () => {
let mockPg: any;
beforeEach(() => {
jest.resetAllMocks()
mockPg = {
tx: jest.fn(async (cb) => {
const mockTx = {};
await cb(mockTx);
}),
};
(supabaseInit.createSupabaseDirectClient as jest.Mock)
.mockReturnValue(mockPg)
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('should', () => {
it('block the user successfully', async () => {
const mockParams = { id: '123' }
const mockAuth = {uid: '321'} as AuthedUser;
const mockReq = {} as any;
(supabaseUsers.updatePrivateUser as jest.Mock).mockResolvedValue(null);
await blockUserModule.unblockUser(mockParams, mockAuth, mockReq)
expect(mockPg.tx).toHaveBeenCalledTimes(1)
expect(supabaseUsers.updatePrivateUser)
.toHaveBeenCalledWith(
expect.any(Object),
mockAuth.uid,
{ blockedByUserIds: supabaseUtils.FieldVal.arrayConcat(mockParams.id)}
);
expect(supabaseUsers.updatePrivateUser)
.toHaveBeenCalledWith(
expect.any(Object),
mockParams.id,
{ blockedByUserIds: supabaseUtils.FieldVal.arrayConcat(mockAuth.uid)}
);
});
});
});

View File

@@ -0,0 +1,32 @@
import * as supabaseInit from "shared/supabase/init";
import {getCompatibleProfiles} from "api/compatible-profiles";
jest.mock('shared/supabase/init')
describe('getCompatibleProfiles', () => {
beforeEach(() => {
jest.resetAllMocks();
const mockPg = {
none: jest.fn().mockResolvedValue(null),
one: jest.fn().mockResolvedValue(null),
oneOrNone: jest.fn().mockResolvedValue(null),
any: jest.fn().mockResolvedValue([]),
map: jest.fn().mockResolvedValue([["abc", {score: 0.69}]]),
} as any;
(supabaseInit.createSupabaseDirectClient as jest.Mock)
.mockReturnValue(mockPg);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('should', () => {
it('successfully get compatible profiles when supplied with a valid user Id', async () => {
const results = await getCompatibleProfiles("123");
expect(results.status).toEqual('success');
expect(results.profileCompatibilityScores).toEqual({"abc": {score: 0.69}});
});
});
});

View File

@@ -0,0 +1,114 @@
jest.mock('common/discord/core');
jest.mock('shared/supabase/utils');
jest.mock('shared/supabase/init');
jest.mock('common/util/try-catch');
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";
describe('contact', () => {
let mockPg: any;
beforeEach(() => {
jest.resetAllMocks();
mockPg = {
oneOrNone: jest.fn(),
};
(supabaseInit.createSupabaseDirectClient as jest.Mock)
.mockReturnValue(mockPg);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('should', () => {
it('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 });
(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockDbUser);
(sendDiscordMessage as jest.Mock).mockResolvedValue(null);
const results = await contact(mockProps, mockAuth, mockReq);
expect(supabaseUtils.insert).toBeCalledTimes(1)
expect(supabaseUtils.insert).toBeCalledWith(
mockPg,
'contact',
{
user_id: mockProps.userId,
content: JSON.stringify(mockProps.content)
}
);
expect(results.success).toBe(true);
await results.continue();
expect(sendDiscordMessage).toBeCalledWith(
expect.stringContaining(`New message from ${mockDbUser.name}`),
'contact'
)
expect(sendDiscordMessage).toBeCalledTimes(1);
});
it('throw an error if the inser 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');
expect(supabaseUtils.insert).toBeCalledTimes(1)
expect(supabaseUtils.insert).toBeCalledWith(
mockPg,
'contact',
{
user_id: mockProps.userId,
content: JSON.stringify(mockProps.content)
}
);
});
});
});

View File

@@ -0,0 +1,47 @@
jest.mock('shared/supabase/init');
import { createBookmarkedSearch } from "api/create-bookmarked-search";
import { AuthedUser } from "api/helpers/endpoint";
import * as supabaseInit from "shared/supabase/init";
describe('createBookmarkedSearch', () => {
let mockPg: any;
beforeEach(() => {
jest.resetAllMocks();
mockPg = {
one: jest.fn(),
};
(supabaseInit.createSupabaseDirectClient as jest.Mock)
.mockReturnValue(mockPg);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('should', () => {
it('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;
await createBookmarkedSearch(mockProps, mockAuth, mockReq)
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
]
);
});
});
});

View File

@@ -0,0 +1,56 @@
jest.mock('shared/supabase/init');
import * as supabaseInit from "shared/supabase/init";
import { AuthedUser } from "api/helpers/endpoint";
describe('createComment', () => {
let mockPg: any;
beforeEach(() => {
jest.resetAllMocks();
mockPg = {
one: jest.fn()
};
(supabaseInit.createSupabaseDirectClient as jest.Mock)
.mockReturnValue(mockPg);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('should', () => {
it('successfully create a comment with information provided', async () => {
const mockUserId = {userId: '123'}
const mockOnUser = {id: '123'}
const mockCreator = {
id: '123',
name: 'Mock Creator',
username: 'mock.creator.username',
avatarUrl: 'mock.creator.avatarurl'
}
const mockContent = {
content: {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'This is the comment text'
}
]
}
]
},
userId: '123'
};
const mockAuth = { uid: '321' } as AuthedUser;
const mockReplyToCommentId = {} as any;
});
});
});

View File

@@ -0,0 +1,333 @@
import * as profilesModule from "api/get-profiles";
import { Profile } from "common/profiles/profile";
import * as supabaseInit from "shared/supabase/init";
describe('getProfiles', () => {
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('should fetch the user profiles', () => {
it('successfully', async ()=> {
const mockProfiles = [
{
diet: ['Jonathon Hammon'],
has_kids: 0
},
{
diet: ['Joseph Hammon'],
has_kids: 1
},
{
diet: ['Jolene Hammon'],
has_kids: 2,
}
] as Profile [];
jest.spyOn(profilesModule, 'loadProfiles').mockResolvedValue({profiles: mockProfiles, count: 3});
const props = {
limit: 2,
orderBy: "last_online_time" as const,
};
const mockReq = {} as any;
const results = await profilesModule.getProfiles(props, mockReq, mockReq);
if('continue' in results) {
throw new Error('Expected direct response')
};
expect(results.status).toEqual('success');
expect(results.profiles).toEqual(mockProfiles);
expect(results.profiles[0]).toEqual(mockProfiles[0]);
expect(profilesModule.loadProfiles).toHaveBeenCalledWith(props);
expect(profilesModule.loadProfiles).toHaveBeenCalledTimes(1);
});
it('unsuccessfully', async () => {
jest.spyOn(profilesModule, 'loadProfiles').mockRejectedValue(null);
const props = {
limit: 2,
orderBy: "last_online_time" as const,
};
const mockReq = {} as any;
const results = await profilesModule.getProfiles(props, mockReq, mockReq);
if('continue' in results) {
throw new Error('Expected direct response')
};
expect(results.status).toEqual('fail');
expect(results.profiles).toEqual([]);
expect(profilesModule.loadProfiles).toHaveBeenCalledWith(props);
expect(profilesModule.loadProfiles).toHaveBeenCalledTimes(1);
});
});
});
describe('loadProfiles', () => {
let mockPg: any;
describe('should call pg.map with an SQL query', () => {
beforeEach(() => {
jest.clearAllMocks();
mockPg = {
map: jest.fn().mockResolvedValue([]),
one: jest.fn().mockResolvedValue(1),
};
jest.spyOn(supabaseInit, 'createSupabaseDirectClient')
.mockReturnValue(mockPg);
});
afterEach(() => {
jest.restoreAllMocks();
});
it('successfully', async () => {
await profilesModule.loadProfiles({
limit: 10,
name: 'John',
is_smoker: true,
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain('select');
expect(query).toContain('from profiles');
expect(query).toContain('where');
expect(query).toContain('limit 10');
expect(query).toContain(`John`);
expect(query).toContain(`is_smoker`);
expect(query).not.toContain(`gender`);
expect(query).not.toContain(`education_level`);
expect(query).not.toContain(`pref_gender`);
expect(query).not.toContain(`age`);
expect(query).not.toContain(`drinks_per_month`);
expect(query).not.toContain(`pref_relation_styles`);
expect(query).not.toContain(`pref_romantic_styles`);
expect(query).not.toContain(`diet`);
expect(query).not.toContain(`political_beliefs`);
expect(query).not.toContain(`religion`);
expect(query).not.toContain(`has_kids`);
});
it('that contains a gender filter', async () => {
await profilesModule.loadProfiles({
genders: ['Electrical_gender'],
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`gender`);
expect(query).toContain(`Electrical_gender`);
});
it('that contains a education level filter', async () => {
await profilesModule.loadProfiles({
education_levels: ['High School'],
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`education_level`);
expect(query).toContain(`High School`);
});
it('that contains a prefer gender filter', async () => {
await profilesModule.loadProfiles({
pref_gender: ['female'],
});
const [query, values, cb] = mockPg.map.mock.calls[0]
console.log(query);
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`pref_gender`);
expect(query).toContain(`female`);
});
it('that contains a minimum age filter', async () => {
await profilesModule.loadProfiles({
pref_age_min: 20,
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`age`);
expect(query).toContain(`>= 20`);
});
it('that contains a maximum age filter', async () => {
await profilesModule.loadProfiles({
pref_age_max: 40,
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`age`);
expect(query).toContain(`<= 40`);
});
it('that contains a minimum drinks per month filter', async () => {
await profilesModule.loadProfiles({
drinks_min: 4,
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`drinks_per_month`);
expect(query).toContain('4');
});
it('that contains a maximum drinks per month filter', async () => {
await profilesModule.loadProfiles({
drinks_max: 20,
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`drinks_per_month`);
expect(query).toContain('20');
});
it('that contains a relationship style filter', async () => {
await profilesModule.loadProfiles({
pref_relation_styles: ['Chill and relaxing'],
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`pref_relation_styles`);
expect(query).toContain('Chill and relaxing');
});
it('that contains a romantic style filter', async () => {
await profilesModule.loadProfiles({
pref_romantic_styles: ['Sexy'],
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`pref_romantic_styles`);
expect(query).toContain('Sexy');
});
it('that contains a diet filter', async () => {
await profilesModule.loadProfiles({
diet: ['Glutton'],
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`diet`);
expect(query).toContain('Glutton');
});
it('that contains a political beliefs filter', async () => {
await profilesModule.loadProfiles({
political_beliefs: ['For the people'],
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`political_beliefs`);
expect(query).toContain('For the people');
});
it('that contains a religion filter', async () => {
await profilesModule.loadProfiles({
religion: ['The blood god'],
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`religion`);
expect(query).toContain('The blood god');
});
it('that contains a has kids filter', async () => {
await profilesModule.loadProfiles({
has_kids: 3,
});
const [query, values, cb] = mockPg.map.mock.calls[0]
expect(mockPg.map.mock.calls).toHaveLength(1)
expect(query).toContain(`has_kids`);
expect(query).toContain('> 0');
});
});
describe('should', () => {
beforeEach(() => {
jest.clearAllMocks();
mockPg = {
map: jest.fn(),
one: jest.fn().mockResolvedValue(1),
};
jest.spyOn(supabaseInit, 'createSupabaseDirectClient')
.mockReturnValue(mockPg)
});
afterEach(() => {
jest.restoreAllMocks();
});
it('return profiles from the database', async () => {
const mockProfiles = [
{
diet: ['Jonathon Hammon'],
is_smoker: true,
has_kids: 0
},
{
diet: ['Joseph Hammon'],
is_smoker: false,
has_kids: 1
},
{
diet: ['Jolene Hammon'],
is_smoker: true,
has_kids: 2,
}
] as Profile [];
mockPg.map.mockResolvedValue(mockProfiles);
const props = {} as any;
const results = await profilesModule.loadProfiles(props);
expect(results).toEqual({profiles: mockProfiles, count: 1});
});
it('throw an error if there is no compatability', async () => {
const props = {
orderBy: 'compatibility_score'
}
expect(profilesModule.loadProfiles(props))
.rejects
.toThrowError('Incompatible with user ID')
});
})
})

View File

@@ -0,0 +1,49 @@
jest.mock('jsonwebtoken');
describe.skip('getSupabaseToken', () => {
// const originalSupabaseJwtSecret = process.env.SUPABASE_JWT_SECRET
// const originalInstanceId = constants.ENV_CONFIG.supabaseInstanceId
// const originalProjectId = constants.ENV_CONFIG.firebaseConfig.projectId
// describe('should', () => {
// beforeEach(() => {
// jest.resetAllMocks();
// process.env.SUPABASE_JWT_SECRET = 'test-jwt-secret-123';
// constants.ENV_CONFIG.supabaseInstanceId = 'test-instance-id';
// constants.ENV_CONFIG.firebaseConfig.projectId = 'test-project-id';
// (jsonWebtokenModules.sign as jest.Mock).mockReturnValue('fake-jwt-token-abc123');
// });
// afterEach(() => {
// if (originalSupabaseJwtSecret === undefined) {
// delete process.env.SUPABASE_JWT_SECRET;
// } else {
// process.env.SUPABASE_JWT_SECRET = originalSupabaseJwtSecret;
// }
// constants.ENV_CONFIG.supabaseInstanceId = originalInstanceId;
// constants.ENV_CONFIG.firebaseConfig.projectId = originalProjectId;
// jest.restoreAllMocks();
// });
// it('successfully generate a JTW token with correct parameters', async () => {
// const mockParams = {} as any;
// const mockAuth = {uid: '321'} as AuthedUser;
// const result = await getSupabaseToken(mockParams, mockAuth, mockParams)
// expect(result).toEqual({
// jwt: 'fake-jwt-token-abc123'
// })
// })
// });
});
describe('getCompatibleProfiles', () => {
it('skip', async () => {
console.log('This needs tests');
})
})

View File

@@ -1,12 +1,15 @@
jest.mock("shared/supabase/init");
import { getUser } from "api/get-user";
import { createSupabaseDirectClient } from "shared/supabase/init";
import { toUserAPIResponse } from "common/api/user-types";
import { convertUser } from "common/supabase/users";
import { APIError } from "common/src/api/utils";
import { APIError } from "common/api/utils";
jest.spyOn(require("common/supabase/users"), 'convertUser')
jest.spyOn(require("common/api/user-types"), 'toUserAPIResponse')
jest.mock("shared/supabase/init");
jest.mock("common/supabase/users");
jest.mock("common/api/utils");
describe('getUser', () =>{
let mockPg: any;
@@ -19,41 +22,142 @@ describe('getUser', () =>{
jest.clearAllMocks();
});
it('should fetch user successfully by id', async () => {
const mockDbUser = {
created_time: '2025-11-11T16:42:05.188Z',
data: { link: {}, avatarUrl: "", isBannedFromPosting: false },
id: 'feUaIfcxVmJZHJOVVfawLTTPgZiP',
name: 'Franklin Buckridge',
name_username_vector: "'buckridg':2,4 'franklin':1,3",
username: 'Franky_Buck'
};
const mockConvertedUser = {
created_time: new Date(),
id: 'feUaIfcxVmJZHJOVVfawLTTPgZiP',
name: 'Franklin Buckridge',
name_username_vector: "'buckridg':2,4 'franklin':1,3",
username: 'Franky_Buck'
describe('when fetching by id', () => {
it('should fetch user successfully by id', async () => {
const mockDbUser = {
created_time: '2025-11-11T16:42:05.188Z',
data: { link: {}, avatarUrl: "", isBannedFromPosting: false },
id: 'feUaIfcxVmJZHJOVVfawLTTPgZiP',
name: 'Franklin Buckridge',
name_username_vector: "'buckridg':2,4 'franklin':1,3",
username: 'Franky_Buck'
};
const mockConvertedUser = {
created_time: new Date(),
id: 'feUaIfcxVmJZHJOVVfawLTTPgZiP',
name: 'Franklin Buckridge',
name_username_vector: "'buckridg':2,4 'franklin':1,3",
username: 'Franky_Buck'
};
const mockApiResponse = {
created_time: '2025-11-11T16:42:05.188Z',
data: { link: {}, avatarUrl: "", isBannedFromPosting: false },
id: 'feUaIfcxVmJZHJOVVfawLTTPgZiP',
name: 'Franklin Buckridge',
username: 'Franky_Buck'
};
mockPg.oneOrNone.mockImplementation((query: string, values: any[], cb: (value: any) => any) => {
const result = cb(mockDbUser);
return Promise.resolve(result);
});
(convertUser as jest.Mock).mockReturnValue(mockConvertedUser);
( toUserAPIResponse as jest.Mock).mockReturnValue(mockApiResponse);
const result = await getUser({id: 'feUaIfcxVmJZHJOVVfawLTTPgZiP'})
expect(mockPg.oneOrNone).toHaveBeenCalledWith(
expect.stringContaining('where id = $1'),
['feUaIfcxVmJZHJOVVfawLTTPgZiP'],
expect.any(Function)
);
expect(convertUser).toHaveBeenCalledWith(mockDbUser);
expect(toUserAPIResponse).toHaveBeenCalledWith(mockConvertedUser);
expect(result).toEqual(mockApiResponse);
});
};
const mockApiResponse = {
created_time: '2025-11-11T16:42:05.188Z',
data: { link: {}, avatarUrl: "", isBannedFromPosting: false },
id: 'feUaIfcxVmJZHJOVVfawLTTPgZiP',
name: 'Franklin Buckridge',
username: 'Franky_Buck'
};
it('should throw 404 when user is not found by id', async () => {
mockPg.oneOrNone.mockImplementation((query: string, values: any[], cb: (value: any) => any) => {
return Promise.resolve(null);
});
// mockPg.oneOrNone.mockImplementation((query: any, params: any, callback: any) => {
// return Promise.resolve(callback(mockDbUser))
// })
// (convertUser as jest.Mock).mockReturnValue(mockConvertedUser);
// ( toUserAPIResponse as jest.Mock).mockReturnValue(mockApiResponse);
// const result = await getUser({id: 'feUaIfcxVmJZHJOVVfawLTTPgZiP'})
// console.log(result);
(convertUser as jest.Mock).mockReturnValue(null)
try {
await getUser({id: '3333'});
fail('Should have thrown');
} catch (error) {
const apiError = error as APIError;
expect(apiError.code).toBe(404)
expect(apiError.message).toBe('User not found')
expect(apiError.details).toBeUndefined()
expect(apiError.name).toBe('APIError')
}
})
})
describe('when fetching by username', () => {
it('should fetch user successfully by username', async () => {
const mockDbUser = {
created_time: '2025-11-11T16:42:05.188Z',
data: { link: {}, avatarUrl: "", isBannedFromPosting: false },
id: 'feUaIfcxVmJZHJOVVfawLTTPgZiP',
name: 'Franklin Buckridge',
name_username_vector: "'buckridg':2,4 'franklin':1,3",
username: 'Franky_Buck'
};
const mockConvertedUser = {
created_time: new Date(),
id: 'feUaIfcxVmJZHJOVVfawLTTPgZiP',
name: 'Franklin Buckridge',
name_username_vector: "'buckridg':2,4 'franklin':1,3",
username: 'Franky_Buck'
};
const mockApiResponse = {
created_time: '2025-11-11T16:42:05.188Z',
data: { link: {}, avatarUrl: "", isBannedFromPosting: false },
id: 'feUaIfcxVmJZHJOVVfawLTTPgZiP',
name: 'Franklin Buckridge',
username: 'Franky_Buck'
};
mockPg.oneOrNone.mockImplementation((query: string, values: any[], cb: (value: any) => any) => {
const result = cb(mockDbUser);
return Promise.resolve(result);
});
(convertUser as jest.Mock).mockReturnValue(mockConvertedUser);
(toUserAPIResponse as jest.Mock).mockReturnValue(mockApiResponse);
const result = await getUser({username: 'Franky_Buck'})
expect(mockPg.oneOrNone).toHaveBeenCalledWith(
expect.stringContaining('where username = $1'),
['Franky_Buck'],
expect.any(Function)
);
expect(convertUser).toHaveBeenCalledWith(mockDbUser);
expect(toUserAPIResponse).toHaveBeenCalledWith(mockConvertedUser);
expect(result).toEqual(mockApiResponse);
});
it('should throw 404 when user is not found by id', async () => {
mockPg.oneOrNone.mockImplementation((query: string, values: any[], cb: (value: any) => any) => {
return Promise.resolve(null);
});
(convertUser as jest.Mock).mockReturnValue(null)
try {
await getUser({username: '3333'});
fail('Should have thrown');
} catch (error) {
const apiError = error as APIError;
expect(apiError.code).toBe(404)
expect(apiError.message).toBe('User not found')
expect(apiError.details).toBeUndefined()
expect(apiError.name).toBe('APIError')
}
})
})
})

View File

@@ -0,0 +1,34 @@
jest.mock('shared/supabase/init');
import * as setLastTimeOnlineModule from "api/set-last-online-time";
import * as supabaseInit from "shared/supabase/init";
describe('Should', () => {
let mockPg: any;
beforeEach(() => {
mockPg = {
none: jest.fn(),
};
(supabaseInit.createSupabaseDirectClient as jest.Mock)
.mockReturnValue(mockPg);
jest.clearAllMocks();
});
it('change the users last online time', async () => {
const mockProfile = {user_id: 'Jonathon'};
await setLastTimeOnlineModule.setLastOnlineTimeUser(mockProfile.user_id);
expect(mockPg.none).toBeCalledTimes(1);
const [query, userId] = mockPg.none.mock.calls[0];
expect(userId).toContain(mockProfile.user_id);
expect(query).toContain("VALUES ($1, now())")
expect(query).toContain("ON CONFLICT (user_id)")
expect(query).toContain("DO UPDATE")
expect(query).toContain("user_activity.last_online_time < now() - interval '1 minute'")
});
})

View File

@@ -0,0 +1,86 @@
jest.mock("shared/supabase/init");
jest.mock("shared/supabase/utils");
import { AuthedUser } from "api/helpers/endpoint";
import { updateProfile } from "api/update-profile";
import * as supabaseInit from "shared/supabase/init";
import * as supabaseUtils from "shared/supabase/utils";
describe('updateProfiles', () => {
let mockPg: any;
beforeEach(() => {
mockPg = {
oneOrNone: jest.fn(),
};
(supabaseInit.createSupabaseDirectClient as jest.Mock)
.mockReturnValue(mockPg);
jest.clearAllMocks();
});
describe('should', () => {
it('update an existing profile when provided the user id', async () => {
const mockUserProfile = {
user_id: '234',
diet: 'Nothing',
gender: 'female',
is_smoker: true,
}
const mockUpdateMade = {
gender: 'male'
}
const mockUpdatedProfile = {
user_id: '234',
diet: 'Nothing',
gender: 'male',
is_smoker: true,
}
const mockParams = {} as any;
const mockAuth = {
uid: '234'
}
mockPg.oneOrNone.mockResolvedValue(mockUserProfile);
(supabaseUtils.update as jest.Mock).mockResolvedValue(mockUpdatedProfile);
const result = await updateProfile(
mockUpdateMade,
mockAuth as AuthedUser,
mockParams
);
expect(mockPg.oneOrNone.mock.calls.length).toBe(1);
expect(mockPg.oneOrNone.mock.calls[0][1]).toEqual([mockAuth.uid]);
expect(result).toEqual(mockUpdatedProfile);
});
it('throw an error if a profile is not found', async () => {
mockPg.oneOrNone.mockResolvedValue(null);
expect(updateProfile({} as any, {} as any, {} as any,))
.rejects
.toThrowError('Profile not found');
});
it('throw an error if unable to update the profile', async () => {
const mockUserProfile = {
user_id: '234',
diet: 'Nothing',
gender: 'female',
is_smoker: true,
}
const data = null;
const error = true;
const mockError = {
data,
error
}
mockPg.oneOrNone.mockResolvedValue(mockUserProfile);
(supabaseUtils.update as jest.Mock).mockRejectedValue(mockError);
expect(updateProfile({} as any, {} as any, {} as any,))
.rejects
.toThrowError('Error updating profile');
});
});
});

View File

@@ -13,7 +13,7 @@ module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
project: ['./tsconfig.json', './tsconfig.test.json'],
},
rules: {
'@typescript-eslint/ban-types': [

View File

@@ -1,12 +1,14 @@
import {SupabaseDirectClient} from 'shared/supabase/init'
import {Row as RowFor} from 'common/supabase/utils'
import {createSupabaseDirectClient, SupabaseDirectClient} from 'shared/supabase/init'
import {getCompatibilityScore, hasAnsweredQuestions} from 'common/profiles/compatibility-score'
import {getCompatibilityAnswers, getGenderCompatibleProfiles, getProfile} from "shared/profiles/supabase"
import {
getAnswersForUser,
getCompatibilityAnswers,
getGenderCompatibleProfiles,
getProfile
} from "shared/profiles/supabase"
import {groupBy} from "lodash"
import {hrtime} from "node:process"
type AnswerRow = RowFor<'compatibility_answers'>
// Canonicalize pair ordering (user_id_1 < user_id_2 lexicographically)
function canonicalPair(a: string, b: string) {
return a < b ? [a, b] as const : [b, a] as const
@@ -14,15 +16,16 @@ function canonicalPair(a: string, b: string) {
export async function recomputeCompatibilityScoresForUser(
userId: string,
pg: SupabaseDirectClient,
client?: SupabaseDirectClient,
) {
const pg = client ?? createSupabaseDirectClient()
const startTs = hrtime.bigint()
const profile = await getProfile(userId)
if (!profile) throw new Error(`Profile not found for user ${userId}`)
// Load all answers for the target user
const answersSelf = await pg.manyOrNone<AnswerRow>(
'select * from compatibility_answers where creator_id = $1',
[userId]
)
const answersSelf = await getAnswersForUser(userId);
// If the user has no answered questions, set the score to null
if (!hasAnsweredQuestions(answersSelf)) {
@@ -35,10 +38,7 @@ export async function recomputeCompatibilityScoresForUser(
)
return
}
const profile = await getProfile(userId, pg)
if (!profile) throw new Error(`Profile not found for user ${userId}`)
let profiles = await getGenderCompatibleProfiles(profile)
const profiles = await getGenderCompatibleProfiles(profile)
const otherUserIds = profiles.map((l) => l.user_id)
const profileAnswers = await getCompatibilityAnswers([userId, ...otherUserIds])
const answersByUser = groupBy(profileAnswers, 'creator_id')
@@ -96,4 +96,6 @@ export async function recomputeCompatibilityScoresForUser(
const dt = Number(hrtime.bigint() - startTs) / 1e9
console.log(`Done recomputing compatibility scores for user ${userId} (${dt.toFixed(1)}s).`)
return rows
}

View File

@@ -11,7 +11,7 @@ export const createProfileLikeNotification = async (like: Row<'profile_likes'>)
const pg = createSupabaseDirectClient()
const targetPrivateUser = await getPrivateUser(target_id)
const profile = await getProfile(creator_id, pg)
const profile = await getProfile(creator_id)
if (!targetPrivateUser || !profile) return
@@ -49,7 +49,7 @@ export const createProfileShipNotification = async (
const creator = await getUser(creator_id)
const targetPrivateUser = await getPrivateUser(recipientId)
const pg = createSupabaseDirectClient()
const profile = await getProfile(otherTargetId, pg)
const profile = await getProfile(otherTargetId)
if (!creator || !targetPrivateUser || !profile) {
console.error('Could not load user object', {

View File

@@ -2,7 +2,7 @@ import {areGenderCompatible} from 'common/profiles/compatibility-util'
import {type Profile, type ProfileRow} from 'common/profiles/profile'
import {type User} from 'common/user'
import {Row} from 'common/supabase/utils'
import {createSupabaseDirectClient, SupabaseDirectClient} from 'shared/supabase/init'
import {createSupabaseDirectClient} from 'shared/supabase/init'
export type ProfileAndUserRow = ProfileRow & {
name: string
@@ -26,8 +26,8 @@ export function convertRow(row: ProfileAndUserRow | undefined): Profile | null {
const PROFILE_COLS = 'profiles.*, name, username, users.data as user'
export const getProfile = async (userId: string, client?: SupabaseDirectClient) => {
const pg = client ?? createSupabaseDirectClient()
export const getProfile = async (userId: string) => {
const pg = createSupabaseDirectClient()
return await pg.oneOrNone(
`
select ${PROFILE_COLS}
@@ -122,3 +122,14 @@ export const getCompatibilityAnswers = async (userIds: string[]) => {
[userIds]
)
}
type AnswerRow = Row<'compatibility_answers'>
export async function getAnswersForUser(userId: string) {
const pg = createSupabaseDirectClient()
const answersSelf = await pg.manyOrNone<AnswerRow>(
'select * from compatibility_answers where creator_id = $1',
[userId]
)
return answersSelf
}

View File

View File

@@ -0,0 +1,140 @@
import {recomputeCompatibilityScoresForUser} from "api/compatibility/compute-scores";
import * as supabaseInit from "shared/supabase/init";
import * as profilesSupabaseModules from "shared/profiles/supabase";
import * as compatibilityScoreModules from "common/profiles/compatibility-score";
import {Profile} from "common/profiles/profile";
jest.mock('shared/profiles/supabase')
jest.mock('shared/supabase/init')
jest.mock('common/profiles/compatibility-score')
describe('recomputeCompatibilityScoresForUser', () => {
beforeEach(() => {
jest.resetAllMocks();
const mockPg = {
none: jest.fn().mockResolvedValue(null),
one: jest.fn().mockResolvedValue(null),
oneOrNone: jest.fn().mockResolvedValue(null),
any: jest.fn().mockResolvedValue([]),
} as any;
(supabaseInit.createSupabaseDirectClient as jest.Mock)
.mockReturnValue(mockPg);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('should', () => {
it('successfully get compute score when supplied with a valid user Id', async () => {
const mockUser = {userId: "123"};
const mockUserProfile = {
id: 1,
user_id: '123',
user: {
username: "Mockuser.getProfile"
},
created_time: "10:30",
explanation: "mockExplanation3",
importance: 3,
};
const mockGenderCompatibleProfiles = [
{
age: 20,
user_id: "1",
company: 'Mock Texan Roadhouse',
drinks_per_month: 3,
city: 'Mockingdale'
},
{
age: 23,
user_id: "2",
company: 'Chicken fried goose',
drinks_per_month: 2,
city: 'Mockingdale'
},
{
age: 40,
user_id: "3",
company: 'World Peace',
drinks_per_month: 10,
city: 'Velvet Suite'
},
] as Partial<Profile> [];
const mockProfileCompatibilityAnswers = [
{
created_time: "10:30",
creator_id: "3",
explanation: "mockExplanation3",
id: 3,
importance: 3
},
{
created_time: "10:20",
creator_id: "2",
explanation: "mockExplanation2",
id: 2,
importance: 2
},
{
created_time: "10:10",
creator_id: "1",
explanation: "mockExplanation",
id: 1,
importance: 1
},
];
const mockCompatibilityScore = {
score: 4,
confidence: "low"
};
const mockAnswersForUser = [{
created_time: "",
creator_id: mockUser.userId,
explanation: "",
id: 1,
importance: 1,
multiple_choice: 0,
pref_choices: [0, 1],
question_id: 1,
}];
(profilesSupabaseModules.getProfile as jest.Mock)
.mockResolvedValue(mockUserProfile);
(profilesSupabaseModules.getGenderCompatibleProfiles as jest.Mock)
.mockResolvedValue(mockGenderCompatibleProfiles);
(profilesSupabaseModules.getCompatibilityAnswers as jest.Mock)
.mockResolvedValue(mockProfileCompatibilityAnswers);
(profilesSupabaseModules.getAnswersForUser as jest.Mock)
.mockResolvedValue(mockAnswersForUser);
(compatibilityScoreModules.getCompatibilityScore as jest.Mock)
.mockReturnValue(mockCompatibilityScore);
(compatibilityScoreModules.hasAnsweredQuestions as jest.Mock)
.mockReturnValue(true);
const results = await recomputeCompatibilityScoresForUser(mockUser.userId);
expect(profilesSupabaseModules.getProfile).toBeCalledWith(mockUser.userId);
expect(profilesSupabaseModules.getProfile).toBeCalledTimes(1);
expect(profilesSupabaseModules.getGenderCompatibleProfiles).toBeCalledWith(mockUserProfile);
expect(profilesSupabaseModules.getGenderCompatibleProfiles).toBeCalledTimes(1);
expect(compatibilityScoreModules.getCompatibilityScore).toBeCalledTimes(mockGenderCompatibleProfiles.length)
// expect(results.profile).toEqual(mockUserProfile);
// expect(results.compatibleProfiles).toContain(mockGenderCompatibleProfiles[0]);
expect(results?.[0][0]).toEqual("1");
expect(results?.[0][1]).toEqual("123");
expect(results?.[0][2]).toBeCloseTo(mockCompatibilityScore.score, 2);
});
it('throw an error if there is no profile matching the user Id', async () => {
const mockUser = {userId: "123"};
expect(recomputeCompatibilityScoresForUser(mockUser.userId))
.rejects
.toThrowError('Profile not found');
expect(profilesSupabaseModules.getProfile).toBeCalledWith(mockUser.userId);
expect(profilesSupabaseModules.getProfile).toBeCalledTimes(1);
});
});
});

View File

@@ -2,107 +2,11 @@
// export ENVIRONMENT=DEV && ./scripts/build_api.sh && npx tsx ./scripts/userCreation.ts
import {createSupabaseDirectClient} from "../backend/shared/lib/supabase/init";
import {insert} from "../backend/shared/lib/supabase/utils";
import {PrivateUser} from "../common/lib/user";
import {getDefaultNotificationPreferences} from "../common/lib/user-notification-preferences";
import {randomString} from "../common/lib/util/random";
import UserAccountInformation from "../tests/e2e/backend/utils/userInformation";
import { seedDatabase } from "../tests/e2e/utils/seedDatabase";
type ProfileType = 'basic' | 'medium' | 'full'
/**
* Function used to populate the database with profiles.
*
* @param pg - Supabase client used to access the database.
* @param userInfo - Class object containing information to create a user account generated by `fakerjs`.
* @param profileType - Optional param used to signify how much information is used in the account generation.
*/
async function seedDatabase (pg: any, userInfo: UserAccountInformation, profileType?: string) {
const userId = userInfo.user_id
const deviceToken = randomString()
const bio = {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"text": userInfo.bio,
"type": "text"
}
]
}
]
}
const basicProfile = {
user_id: userId,
bio_length: userInfo.bio.length,
bio: bio,
age: userInfo.age,
born_in_location: userInfo.born_in_location,
company: userInfo.company,
}
const mediumProfile = {
...basicProfile,
drinks_per_month: userInfo.drinks_per_month,
diet: [userInfo.randomElement(userInfo.diet)],
education_level: userInfo.randomElement(userInfo.education_level),
ethnicity: [userInfo.randomElement(userInfo.ethnicity)],
gender: userInfo.randomElement(userInfo.gender),
height_in_inches: userInfo.height_in_inches,
pref_gender: [userInfo.randomElement(userInfo.pref_gender)],
pref_age_min: userInfo.pref_age.min,
pref_age_max: userInfo.pref_age.max,
}
const fullProfile = {
...mediumProfile,
occupation_title: userInfo.occupation_title,
political_beliefs: [userInfo.randomElement(userInfo.political_beliefs)],
pref_relation_styles: [userInfo.randomElement(userInfo.pref_relation_styles)],
religion: [userInfo.randomElement(userInfo.religion)],
}
const profileData = profileType === 'basic' ? basicProfile
: profileType === 'medium' ? mediumProfile
: fullProfile
const user = {
// avatarUrl,
isBannedFromPosting: false,
link: {},
}
const privateUser: PrivateUser = {
id: userId,
email: userInfo.email,
initialIpAddress: userInfo.ip,
initialDeviceToken: deviceToken,
notificationPreferences: getDefaultNotificationPreferences(),
blockedUserIds: [],
blockedByUserIds: [],
}
await pg.tx(async (tx:any) => {
await insert(tx, 'users', {
id: userId,
name: userInfo.name,
username: userInfo.name,
data: user,
})
await insert(tx, 'private_users', {
id: userId,
data: privateUser,
})
await insert(tx, 'profiles', profileData )
})
}
(async () => {
const pg = createSupabaseDirectClient()

View File

View File

@@ -0,0 +1,99 @@
import {insert} from "../../../backend/shared/lib/supabase/utils";
import {PrivateUser} from "../../../common/lib/user";
import {getDefaultNotificationPreferences} from "../../../common/lib/user-notification-preferences";
import {randomString} from "../../../common/lib/util/random";
import UserAccountInformation from "../backend/utils/userInformation";
/**
* Function used to populate the database with profiles.
*
* @param pg - Supabase client used to access the database.
* @param userInfo - Class object containing information to create a user account generated by `fakerjs`.
* @param profileType - Optional param used to signify how much information is used in the account generation.
*/
export async function seedDatabase (pg: any, userInfo: UserAccountInformation, profileType?: string) {
const userId = userInfo.user_id
const deviceToken = randomString()
const bio = {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"text": userInfo.bio,
"type": "text"
}
]
}
]
}
const basicProfile = {
user_id: userId,
bio_length: userInfo.bio.length,
bio: bio,
age: userInfo.age,
born_in_location: userInfo.born_in_location,
company: userInfo.company,
}
const mediumProfile = {
...basicProfile,
drinks_per_month: userInfo.drinks_per_month,
diet: [userInfo.randomElement(userInfo.diet)],
education_level: userInfo.randomElement(userInfo.education_level),
ethnicity: [userInfo.randomElement(userInfo.ethnicity)],
gender: userInfo.randomElement(userInfo.gender),
height_in_inches: userInfo.height_in_inches,
pref_gender: [userInfo.randomElement(userInfo.pref_gender)],
pref_age_min: userInfo.pref_age.min,
pref_age_max: userInfo.pref_age.max,
}
const fullProfile = {
...mediumProfile,
occupation_title: userInfo.occupation_title,
political_beliefs: [userInfo.randomElement(userInfo.political_beliefs)],
pref_relation_styles: [userInfo.randomElement(userInfo.pref_relation_styles)],
religion: [userInfo.randomElement(userInfo.religion)],
}
const profileData = profileType === 'basic' ? basicProfile
: profileType === 'medium' ? mediumProfile
: fullProfile
const user = {
// avatarUrl,
isBannedFromPosting: false,
link: {},
}
const privateUser: PrivateUser = {
id: userId,
email: userInfo.email,
initialIpAddress: userInfo.ip,
initialDeviceToken: deviceToken,
notificationPreferences: getDefaultNotificationPreferences(),
blockedUserIds: [],
blockedByUserIds: [],
}
await pg.tx(async (tx:any) => {
await insert(tx, 'users', {
id: userId,
name: userInfo.name,
username: userInfo.name,
data: user,
})
await insert(tx, 'private_users', {
id: userId,
data: privateUser,
})
await insert(tx, 'profiles', profileData )
})
};