Skip to content

Commit

Permalink
fix: Syntax errors on queries (#40)
Browse files Browse the repository at this point in the history
* fix: Created at wasn't on the select, the order by was failing

* fix: Query with any was failing, change to IN instead

* fix: Syntax error in query

* fix: Missing /content

* refactor: Use sql LOWER as much as I can

* refactor: Move const to logic/friendship

* feat: Add NATS to docker compose

* chore: Add placeholder values in the .env.default file
kevinszuchet authored Jan 24, 2025
1 parent 46b4a24 commit c7755e2
Showing 10 changed files with 149 additions and 114 deletions.
12 changes: 9 additions & 3 deletions .env.default
Original file line number Diff line number Diff line change
@@ -13,7 +13,13 @@ HTTP_SERVER_HOST=0.0.0.0
# reset metrics at 00:00UTC
WKC_METRICS_RESET_AT_NIGHT=false

NATS_URL=localhost:4222
# Placeholders values
NATS_URL=<URL>
CATALYST_CONTENT_URL_LOADBALANCER=<URL>
PROFILE_IMAGES_URL=<URL>
ARCHIPELAGO_STATS_URL=<URL>
CATALYST_URL=<URL>

PEER_SYNC_INTERVAL_MS=<MS>
PEERS_SYNC_CACHE_TTL_MS=<MS>

#CATALYST_CONTENT_URL_LOADBALANCER=https://peer.decentraland.org/
#PROFILE_IMAGES_URL=https://profile-images.decentraland.org
14 changes: 13 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -17,6 +17,12 @@ services:
ports:
- '6379:6379'

nats:
image: nats:latest
container_name: nats-local
ports:
- '4222:4222'

app:
image: social-service-ea:local
build:
@@ -25,12 +31,18 @@ services:
environment:
PG_COMPONENT_PSQL_CONNECTION_STRING: 'postgresql://postgres:postgres@postgres:5432/social_service_ea'
REDIS_HOST: 'redis'
NATS_URL: 'nats://nats:4222'
ARCHIPELAGO_STATS_URL: 'http://archipelago-ea-stats.decentraland.zone'
PROFILE_IMAGES_URL: 'https://profile-images.decentraland.zone'
CATALYST_URL: 'https://peer.decentraland.zone'
PEER_SYNC_INTERVAL_MS: '45000'
PEERS_SYNC_CACHE_TTL_MS: '90000'
ports:
- '3000:3000'
- '8085:8085'
depends_on:
- postgres
- redis

- nats
volumes:
postgres-data:
12 changes: 5 additions & 7 deletions src/adapters/catalyst-client.ts
Original file line number Diff line number Diff line change
@@ -16,12 +16,8 @@ export async function createCatalystClient({
const contractNetwork = (await config.getString('ENV')) === 'prod' ? L1_MAINNET : L1_TESTNET

function getContentClientOrDefault(contentServerUrl?: string): ContentClient {
return contentServerUrl
? createContentClient({ fetcher, url: contentServerUrl })
: createContentClient({
fetcher,
url: loadBalancer
})
contentServerUrl = contentServerUrl?.endsWith('/content') ? contentServerUrl : `${contentServerUrl}/content`
return createContentClient({ fetcher, url: contentServerUrl ?? loadBalancer })
}

function rotateContentServerClient<T>(
@@ -34,7 +30,7 @@ export async function createCatalystClient({
return (attempt: number): Promise<T> => {
if (attempt > 1 && catalystServers.length > 0) {
const [catalystServerUrl] = catalystServers.splice(attempt % catalystServers.length, 1)
contentClientToUse = getContentClientOrDefault(catalystServerUrl)
contentClientToUse = getContentClientOrDefault(`${catalystServerUrl}/content`)
}

return executeClientRequest(contentClientToUse)
@@ -45,6 +41,8 @@ export async function createCatalystClient({
pointers: string[],
options: ICatalystClientRequestOptions = {}
): Promise<Entity[]> {
if (pointers.length === 0) return []

const { retries = 3, waitTime = 300, contentServerUrl } = options
const executeClientRequest = rotateContentServerClient(
(contentClientToUse) => contentClientToUse.fetchEntitiesByPointers(pointers),
114 changes: 64 additions & 50 deletions src/adapters/db.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto'
import { PoolClient } from 'pg'
import { AppComponents, Friendship, FriendshipAction, FriendshipRequest, IDatabaseComponent, Friend } from '../types'
import { FRIENDSHIPS_PER_PAGE } from './rpc-server/constants'
import { normalizeAddress } from '../utils/address'

export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>): IDatabaseComponent {
const { pg, logs } = components
@@ -11,14 +12,16 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>

// TODO: abstract common statements in a util file
function getFriendsBaseQuery(userAddress: string) {
const normalizedUserAddress = normalizeAddress(userAddress)
return SQL`
SELECT DISTINCT
CASE
WHEN address_requester = ${userAddress} THEN address_requested
WHEN LOWER(address_requester) = ${normalizedUserAddress} THEN address_requested
ELSE address_requester
END as address
END as address,
created_at
FROM friendships
WHERE (address_requester = ${userAddress} OR address_requested = ${userAddress})`
WHERE (LOWER(address_requester) = ${normalizedUserAddress} OR LOWER(address_requested) = ${normalizedUserAddress})`
}

function getFriendshipRequestBaseQuery(userAddress: string, type: 'sent' | 'received'): SQLStatement {
@@ -27,20 +30,21 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
received: SQL` f.address_requester`
}
const filterMapping = {
sent: SQL`f.address_requester`,
received: SQL`f.address_requested`
sent: SQL`LOWER(f.address_requester)`,
received: SQL`LOWER(f.address_requested)`
}

const baseQuery = SQL`SELECT fa.id,`
baseQuery.append(columnMapping[type].append(', as address'))
baseQuery.append(columnMapping[type])
baseQuery.append(SQL` as address`)
baseQuery.append(SQL`
fa.timestamp, fa.metadata
FROM friendships f
INNER JOIN friendship_actions fa ON f.id = fa.friendship_id
WHERE
`)

baseQuery.append(filterMapping[type].append(SQL` = ${userAddress}`))
baseQuery.append(filterMapping[type].append(SQL` = ${normalizeAddress(userAddress)}`))

baseQuery.append(SQL`
AND fa.action = 'request'
@@ -56,22 +60,6 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
return baseQuery
}

function filterActiveFriendshipsFromAddresses(userAddress: string, userAddresses: string[]) {
return SQL`
SELECT DISTINCT
CASE
WHEN address_requester = ${userAddress} THEN address_requested
ELSE address_requester
END as address
FROM friendships
WHERE (
(address_requester = ${userAddress} AND address_requested = ANY(${userAddresses}))
OR
(address_requested = ${userAddress} AND address_requester = ANY(${userAddresses}))
)
AND is_active = true`
}

return {
async getFriends(userAddress, { onlyActive = true, pagination = { limit: FRIENDSHIPS_PER_PAGE, offset: 0 } } = {}) {
const { limit, offset } = pagination
@@ -88,7 +76,11 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
return result.rows
},
async getFriendsCount(userAddress, { onlyActive } = { onlyActive: true }) {
const query: SQLStatement = SQL`SELECT COUNT(*) FROM friendships WHERE (address_requester = ${userAddress} OR address_requested = ${userAddress})`
const normalizedUserAddress = normalizeAddress(userAddress)
const query: SQLStatement = SQL`
SELECT COUNT(*)
FROM friendships
WHERE (LOWER(address_requester) = ${normalizedUserAddress} OR LOWER(address_requested) = ${normalizedUserAddress})`

if (onlyActive) {
query.append(SQL` AND is_active = true`)
@@ -99,11 +91,15 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
},
async getMutualFriends(userAddress1, userAddress2, pagination = { limit: FRIENDSHIPS_PER_PAGE, offset: 0 }) {
const { limit, offset } = pagination

const normalizedUserAddress1 = normalizeAddress(userAddress1)
const normalizedUserAddress2 = normalizeAddress(userAddress2)

const result = await pg.query<Friend>(
SQL`WITH friendsA as (
SELECT
CASE
WHEN address_requester = ${userAddress1} then address_requested
WHEN LOWER(address_requester) = ${normalizedUserAddress1} then address_requested
else address_requester
end as address
FROM
@@ -112,9 +108,8 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
FROM friendships f_a
WHERE
(
f_a.address_requester = ${userAddress1}
or f_a.address_requested = ${userAddress1}
) and f_a.is_active = true
LOWER(f_a.address_requester) = ${normalizedUserAddress1} or LOWER(f_a.address_requested) = ${normalizedUserAddress1}
) AND f_a.is_active = true
) as friends_a
)
SELECT
@@ -125,7 +120,7 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
f_b.address IN (
SELECT
CASE
WHEN address_requester = ${userAddress2} then address_requested
WHEN LOWER(address_requester) = ${normalizedUserAddress2} then address_requested
else address_requester
end as address_a
FROM
@@ -136,9 +131,9 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
friendships f_b
WHERE
(
f_b.address_requester = ${userAddress2}
or f_b.address_requested = ${userAddress2}
) and f_b.is_active = true
LOWER(f_b.address_requester) = ${normalizedUserAddress2}
or LOWER(f_b.address_requested) = ${normalizedUserAddress2}
) AND f_b.is_active = true
) as friends_b
)
ORDER BY f_b.address
@@ -149,11 +144,14 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
return result.rows
},
async getMutualFriendsCount(userAddress1, userAddress2) {
const normalizedUserAddress1 = normalizeAddress(userAddress1)
const normalizedUserAddress2 = normalizeAddress(userAddress2)

const result = await pg.query<{ count: number }>(
SQL`WITH friendsA as (
SELECT
CASE
WHEN address_requester = ${userAddress1} THEN address_requested
WHEN LOWER(address_requester) = ${normalizedUserAddress1} THEN address_requested
ELSE address_requester
END as address
FROM
@@ -162,8 +160,8 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
FROM friendships f_a
WHERE
(
f_a.address_requester = ${userAddress1}
OR f_a.address_requested = ${userAddress1}
LOWER(f_a.address_requester) = ${normalizedUserAddress1}
OR LOWER(f_a.address_requested) = ${normalizedUserAddress1}
) AND f_a.is_active = true
) as friends_a
)
@@ -175,7 +173,7 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
address IN (
SELECT
CASE
WHEN address_requester = ${userAddress2} THEN address_requested
WHEN address_requester = ${normalizedUserAddress2} THEN address_requested
ELSE address_requester
END as address_a
FROM
@@ -184,8 +182,8 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
FROM friendships f_b
WHERE
(
f_b.address_requester = ${userAddress2}
OR f_b.address_requested = ${userAddress2}
f_b.address_requester = ${normalizedUserAddress2}
OR f_b.address_requested = ${normalizedUserAddress2}
) AND f_b.is_active = true
) as friends_b
)`
@@ -194,10 +192,10 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
return result.rows[0].count
},
async getFriendship(users) {
const [userAddress1, userAddress2] = users
const [userAddress1, userAddress2] = users.map(normalizeAddress)
const query = SQL`
SELECT * FROM friendships
WHERE (address_requester, address_requested) IN ((${userAddress1}, ${userAddress2}), (${userAddress2}, ${userAddress1}))
WHERE (LOWER(address_requester), LOWER(address_requested)) IN ((${userAddress1}, ${userAddress2}), (${userAddress2}, ${userAddress1}))
`

const results = await pg.query<Friendship>(query)
@@ -213,13 +211,17 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
return results.rows[0]
},
async getLastFriendshipActionByUsers(loggedUser: string, friendUser: string) {
const normalizedLoggedUser = normalizeAddress(loggedUser)
const normalizedFriendUser = normalizeAddress(friendUser)

const query = SQL`
SELECT fa.*
FROM friendships f
INNER JOIN friendship_actions fa ON f.id = fa.friendship_id
WHERE (f.address_requester, f.address_requested) IN ((${loggedUser}, ${friendUser}), (${friendUser}, ${loggedUser}))
WHERE (LOWER(f.address_requester), LOWER(f.address_requested)) IN ((${normalizedLoggedUser}, ${normalizedFriendUser}), (${normalizedFriendUser}, ${normalizedLoggedUser}))
ORDER BY fa.timestamp DESC LIMIT 1
`

const results = await pg.query<FriendshipAction>(query)

return results.rows[0]
@@ -230,7 +232,7 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>

const query = SQL`
INSERT INTO friendships (id, address_requester, address_requested, is_active)
VALUES (${uuid}, ${addressRequester}, ${addressRequested}, ${isActive})
VALUES (${uuid}, ${normalizeAddress(addressRequester)}, ${normalizeAddress(addressRequested)}, ${isActive})
RETURNING id, created_at`

const {
@@ -263,7 +265,7 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
const uuid = randomUUID()
const query = SQL`
INSERT INTO friendship_actions (id, friendship_id, action, acting_user, metadata)
VALUES (${uuid}, ${friendshipId}, ${action}, ${actingUser}, ${metadata})`
VALUES (${uuid}, ${friendshipId}, ${action}, ${normalizeAddress(actingUser)}, ${metadata})`

if (txClient) {
await txClient.query(query)
@@ -306,14 +308,26 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>

return results.rows
},
streamOnlineFriends(userAddress: string, onlinePeers: string[]) {
const query: SQLStatement = filterActiveFriendshipsFromAddresses(userAddress, onlinePeers)
return pg.streamQuery<Friend>(query)
},
async getOnlineFriends(userAddress: string, potentialFriends: string[]) {
if (potentialFriends.length === 0) return []
async getOnlineFriends(userAddress: string, onlinePotentialFriends: string[]) {
if (onlinePotentialFriends.length === 0) return []

const normalizedUserAddress = normalizeAddress(userAddress)
const normalizedOnlinePotentialFriends = onlinePotentialFriends.map(normalizeAddress)

const query: SQLStatement = SQL`
SELECT DISTINCT
CASE
WHEN LOWER(address_requester) = ${normalizedUserAddress} THEN address_requested
ELSE address_requester
END as address
FROM friendships
WHERE (
(LOWER(address_requester) = ${normalizedUserAddress} AND LOWER(address_requested) IN (${normalizedOnlinePotentialFriends}))
OR
(LOWER(address_requested) = ${normalizedUserAddress} AND LOWER(address_requester) IN (${normalizedOnlinePotentialFriends}))
)
AND is_active = true`

const query: SQLStatement = filterActiveFriendshipsFromAddresses(userAddress, potentialFriends)
const results = await pg.query<Friend>(query)
return results.rows
},
19 changes: 11 additions & 8 deletions src/logic/friendships.ts
Original file line number Diff line number Diff line change
@@ -5,19 +5,21 @@ import {
FriendConnectivityUpdate,
FriendshipRequestResponse
} from '@dcl/protocol/out-js/decentraland/social_service/v2/social_service_v2.gen'
import {
Action,
FRIENDSHIP_ACTION_TRANSITIONS,
FriendshipAction,
FriendshipRequest,
FriendshipStatus,
SubscriptionEventsEmitter
} from '../types'
import { Action, FriendshipAction, FriendshipRequest, FriendshipStatus, SubscriptionEventsEmitter } from '../types'
import { normalizeAddress } from '../utils/address'
import { parseProfileToFriend } from './friends'
import { Entity } from '@dcl/schemas'
import { getProfileAvatar } from './profiles'

// [to]: [from]
export const FRIENDSHIP_ACTION_TRANSITIONS: Record<Action, (Action | null)[]> = {
[Action.REQUEST]: [Action.CANCEL, Action.REJECT, Action.DELETE, null],
[Action.ACCEPT]: [Action.REQUEST],
[Action.CANCEL]: [Action.REQUEST],
[Action.REJECT]: [Action.REQUEST],
[Action.DELETE]: [Action.ACCEPT]
}

const FRIENDSHIP_STATUS_BY_ACTION: Record<
Action,
(actingUser: string, contextAddress: string) => FriendshipRequestStatus | undefined
@@ -80,6 +82,7 @@ export function validateNewFriendshipAction(
lastAction?: FriendshipAction
): boolean {
if (!isFriendshipActionValid(lastAction?.action || null, newAction.action)) return false
console.log('isUserActionValid', actingUser, newAction, lastAction)
return isUserActionValid(actingUser, newAction, lastAction)
}

5 changes: 0 additions & 5 deletions src/migrations/1736277138587_add-indexes.ts
Original file line number Diff line number Diff line change
@@ -11,10 +11,6 @@ export async function up(pgm: MigrationBuilder): Promise<void> {
unique: ['address_requester', 'address_requested']
})

pgm.createConstraint('friendships', 'address_requester_smaller_than_address_requested', {
check: 'address_requester < address_requested'
})

// Lowercase indexes
pgm.createIndex('friendships', 'LOWER(address_requester) text_pattern_ops', {
name: 'friendships_address_requester_lower',
@@ -33,7 +29,6 @@ export async function down(pgm: MigrationBuilder): Promise<void> {
pgm.dropIndex('friendships', 'friendships_address_requester')
pgm.dropIndex('friendships', 'friendships_address_requested')
pgm.dropConstraint('friendships', 'unique_addresses')
pgm.dropConstraint('friendships', 'address_requester_smaller_than_address_requested')
pgm.dropIndex('friendships', 'friendships_address_requester_lower')
pgm.dropIndex('friendships', 'friendships_address_requested_lower')
pgm.dropIndex('friendship_actions', 'friendship_actions_friendship_id')
10 changes: 0 additions & 10 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -99,7 +99,6 @@ export interface IDatabaseComponent {
): Promise<string>
getReceivedFriendshipRequests(userAddress: string, pagination?: Pagination): Promise<FriendshipRequest[]>
getSentFriendshipRequests(userAddress: string, pagination?: Pagination): Promise<FriendshipRequest[]>
streamOnlineFriends(userAddress: string, onlinePeers: string[]): AsyncGenerator<Friend>
getOnlineFriends(userAddress: string, potentialFriends: string[]): Promise<Friend[]>
executeTx<T>(cb: (client: PoolClient) => Promise<T>): Promise<T>
}
@@ -234,15 +233,6 @@ export enum Action {
DELETE = 'delete' // delete a friendship
}

// [to]: [from]
export const FRIENDSHIP_ACTION_TRANSITIONS: Record<Action, (Action | null)[]> = {
[Action.REQUEST]: [Action.CANCEL, Action.REJECT, Action.DELETE, null],
[Action.ACCEPT]: [Action.REQUEST],
[Action.CANCEL]: [Action.REQUEST],
[Action.REJECT]: [Action.REQUEST],
[Action.DELETE]: [Action.ACCEPT]
}

export type FriendshipAction = {
id: string
friendship_id: string
1 change: 0 additions & 1 deletion test/mocks/components/db.ts
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ export const mockDb: jest.Mocked<IDatabaseComponent> = {
updateFriendshipStatus: jest.fn(),
getFriends: jest.fn(),
getFriendsCount: jest.fn(),
streamOnlineFriends: jest.fn(),
getOnlineFriends: jest.fn(),
getMutualFriends: jest.fn(),
getMutualFriendsCount: jest.fn(),
14 changes: 7 additions & 7 deletions test/unit/adapters/catalyst-client.spec.ts
Original file line number Diff line number Diff line change
@@ -29,20 +29,20 @@ jest.mock('../../../src/utils/timer', () => ({
sleep: jest.fn()
}))

const LOAD_BALANCER_URL = 'http://catalyst-server.com'
const CATALYST_CONTENT_LOAD_BALANCER_URL = 'http://catalyst-server.com/content'

describe('Catalyst client', () => {
let catalystClient: ICatalystClientComponent
let contentClientMock: ContentClient

beforeEach(async () => {
mockConfig.requireString.mockResolvedValue(LOAD_BALANCER_URL)
mockConfig.requireString.mockResolvedValue(CATALYST_CONTENT_LOAD_BALANCER_URL)

catalystClient = await createCatalystClient({
fetcher: mockFetcher,
config: mockConfig
})
contentClientMock = createContentClient({ fetcher: mockFetcher, url: LOAD_BALANCER_URL })
contentClientMock = createContentClient({ fetcher: mockFetcher, url: CATALYST_CONTENT_LOAD_BALANCER_URL })
})

describe('getEntitiesByPointers', () => {
@@ -53,7 +53,7 @@ describe('Catalyst client', () => {
beforeEach(() => {
pointers = ['pointer1', 'pointer2']
entities = [{ id: 'entity1' }, { id: 'entity2' }]
customContentServer = 'http://custom-content-server.com'
customContentServer = 'http://custom-content-server.com/content'
})

it('should fetch entities by pointers with retries and default values', async () => {
@@ -100,9 +100,9 @@ describe('Catalyst client', () => {

await catalystClient.getEntitiesByPointers(pointers)

expectContentClientToHaveBeenCalledWithUrl(LOAD_BALANCER_URL)
expectContentClientToHaveBeenCalledWithUrl('http://catalyst-server-3.com')
expectContentClientToHaveBeenCalledWithUrl('http://catalyst-server-2.com')
expectContentClientToHaveBeenCalledWithUrl(CATALYST_CONTENT_LOAD_BALANCER_URL)
expectContentClientToHaveBeenCalledWithUrl('http://catalyst-server-3.com/content')
expectContentClientToHaveBeenCalledWithUrl('http://catalyst-server-2.com/content')

expect(contentClientMock.fetchEntitiesByPointers).toHaveBeenCalledTimes(3)
})
62 changes: 40 additions & 22 deletions test/unit/adapters/db.spec.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import { createDBComponent } from '../../../src/adapters/db'
import { Action } from '../../../src/types'
import SQL from 'sql-template-strings'
import { mockLogs, mockPg } from '../../mocks/components'
import { normalizeAddress } from '../../../src/utils/address'

jest.mock('node:crypto', () => ({
randomUUID: jest.fn().mockReturnValue('mock-uuid')
@@ -27,15 +28,15 @@ describe('db', () => {

const expectedFragmentsOfTheQuery = [
{
text: 'WHEN address_requester =',
text: 'WHEN LOWER(address_requester) =',
values: ['0x123']
},
{
text: 'WHERE (address_requester =',
text: 'WHERE (LOWER(address_requester) =',
values: ['0x123']
},
{
text: 'OR address_requested =',
text: 'OR LOWER(address_requested) =',
values: ['0x123']
},
{
@@ -92,8 +93,13 @@ describe('db', () => {

const result = await dbComponent.getFriendsCount('0x123', { onlyActive: true })

const expectedQuery = SQL`WHERE (LOWER(address_requester) = ${'0x123'} OR LOWER(address_requested) = ${'0x123'}) AND is_active = true`

expect(mockPg.query).toHaveBeenCalledWith(
SQL`SELECT COUNT(*) FROM friendships WHERE (address_requester = ${'0x123'} OR address_requested = ${'0x123'}) AND is_active = true`
expect.objectContaining({
text: expect.stringContaining(expectedQuery.text),
values: expectedQuery.values
})
)
expect(result).toBe(mockCount)
})
@@ -104,8 +110,13 @@ describe('db', () => {

const result = await dbComponent.getFriendsCount('0x123', { onlyActive: false })

const expectedQuery = SQL`WHERE (LOWER(address_requester) = ${'0x123'} OR LOWER(address_requested) = ${'0x123'})`

expect(mockPg.query).toHaveBeenCalledWith(
SQL`SELECT COUNT(*) FROM friendships WHERE (address_requester = ${'0x123'} OR address_requested = ${'0x123'})`
expect.objectContaining({
text: expect.stringContaining(expectedQuery.text),
values: expectedQuery.values
})
)
expect(result).toBe(mockCount)
})
@@ -154,7 +165,7 @@ describe('db', () => {
expect(result).toEqual(mockAction)
expect(mockPg.query).toHaveBeenCalledWith(
expect.objectContaining({
text: expect.stringContaining('WHERE (f.address_requester, f.address_requested) IN'),
text: expect.stringContaining('WHERE (LOWER(f.address_requester), LOWER(f.address_requested)) IN'),
values: expect.arrayContaining(['0x123', '0x456', '0x456', '0x123'])
})
)
@@ -257,7 +268,7 @@ describe('db', () => {
expect(result).toEqual(mockRequests)
expect(mockPg.query).toHaveBeenCalledWith(
expect.objectContaining({
text: expect.stringContaining('f.address_requested ='),
text: expect.stringContaining('LOWER(f.address_requested) ='),
values: expect.arrayContaining(['0x456'])
})
)
@@ -283,7 +294,7 @@ describe('db', () => {
expect(result).toEqual(mockRequests)
expect(mockPg.query).toHaveBeenCalledWith(
expect.objectContaining({
text: expect.stringContaining('f.address_requester ='),
text: expect.stringContaining('LOWER(f.address_requester) ='),
values: expect.arrayContaining(['0x123'])
})
)
@@ -334,32 +345,39 @@ describe('db', () => {
rows: [{ address: '0x456' }, { address: '0x789' }],
rowCount: 2
}
const userAddress = '0x123'
mockPg.query.mockResolvedValueOnce(mockResult)

const potentialFriends = ['0x456', '0x789', '0x999']
const result = await dbComponent.getOnlineFriends('0x123', potentialFriends)
const potentialFriends = ['0x456ABC', '0x789DEF', '0x999GHI']
const normalizedPotentialFriends = potentialFriends.map((address) => normalizeAddress(address))
await dbComponent.getOnlineFriends('0x123', normalizedPotentialFriends)

const queryExpectations = [
{ text: 'address_requester =', values: ['0x123'] },
{ text: 'AND address_requested = ANY(' },
{ text: 'address_requested =', values: ['0x123'] },
{ text: 'address_requester = ANY(' }
{ text: 'LOWER(address_requester) =' },
{ text: 'AND LOWER(address_requested) IN' },
{ text: 'LOWER(address_requested) =' },
{ text: 'LOWER(address_requester) IN' }
]

queryExpectations.forEach(({ text, values }) => {
queryExpectations.forEach(({ text }) => {
expect(mockPg.query).toHaveBeenCalledWith(
expect.objectContaining({
text: expect.stringContaining(text)
})
)
if (values) {
expect(mockPg.query).toHaveBeenCalledWith(
expect.objectContaining({
values: expect.arrayContaining(values)
})
)
}
})

expect(mockPg.query).toHaveBeenCalledWith(
expect.objectContaining({
values: expect.arrayContaining([
userAddress,
userAddress,
normalizedPotentialFriends,
userAddress,
normalizedPotentialFriends
])
})
)
})
})

0 comments on commit c7755e2

Please sign in to comment.