diff --git a/src/logic/friendships.ts b/src/logic/friendships.ts index 6e0320c..517649c 100644 --- a/src/logic/friendships.ts +++ b/src/logic/friendships.ts @@ -68,7 +68,7 @@ type CommonParsedRequest = { user: string } -type ParsedUpsertFriendshipRequest = +export type ParsedUpsertFriendshipRequest = | (CommonParsedRequest & { metadata: { message: string } | null }) | CommonParsedRequest | CommonParsedRequest diff --git a/test/mocks/components/index.ts b/test/mocks/components/index.ts index 616a847..55a351e 100644 --- a/test/mocks/components/index.ts +++ b/test/mocks/components/index.ts @@ -1,2 +1,3 @@ export * from './logs' export * from './db' +export * from './pubsub' diff --git a/test/mocks/components/logs.ts b/test/mocks/components/logs.ts index ad53e69..0235740 100644 --- a/test/mocks/components/logs.ts +++ b/test/mocks/components/logs.ts @@ -1,4 +1,6 @@ +import { IMetricsComponent } from '@well-known-components/interfaces' import { ILoggerComponent } from '@well-known-components/interfaces/dist/components/logger' +import { createLogComponent } from '@well-known-components/logger' export const mockLogs: jest.Mocked = { getLogger: jest.fn().mockReturnValue({ diff --git a/test/mocks/components/pubsub.ts b/test/mocks/components/pubsub.ts new file mode 100644 index 0000000..95f6485 --- /dev/null +++ b/test/mocks/components/pubsub.ts @@ -0,0 +1,8 @@ +import { IPubSubComponent } from '../../../src/adapters/pubsub' + +export const mockPubSub: jest.Mocked = { + start: jest.fn(), + stop: jest.fn(), + subscribeToFriendshipUpdates: jest.fn(), + publishFriendshipUpdate: jest.fn() +} diff --git a/test/unit/logic/adapters/rpc-server/services/upsert-friendship.spec.ts b/test/unit/logic/adapters/rpc-server/services/upsert-friendship.spec.ts new file mode 100644 index 0000000..278dbcb --- /dev/null +++ b/test/unit/logic/adapters/rpc-server/services/upsert-friendship.spec.ts @@ -0,0 +1,154 @@ +import { mockDb, mockLogs, mockPubSub } from '../../../../../mocks/components' +import { upsertFriendshipService } from '../../../../../../src/adapters/rpc-server/services/upsert-friendship' +import { Action, FriendshipStatus, RpcServerContext, AppComponents } from '../../../../../../src/types' +import * as FriendshipsLogic from '../../../../../../src/logic/friendships' +import { + UpsertFriendshipPayload, + UpsertFriendshipResponse +} from '@dcl/protocol/out-ts/decentraland/social_service_v2/social_service.gen' +import { ParsedUpsertFriendshipRequest } from '../../../../../../src/logic/friendships' + +jest.mock('../../../../../../src/logic/friendships') + +describe('upsertFriendshipService', () => { + let components: jest.Mocked> + let upsertFriendship: ReturnType + + const rpcContext: RpcServerContext = { address: '0x123', subscribers: undefined } + const userAddress = '0x456' + const message = 'Hello' + + const mockRequest: UpsertFriendshipPayload = { + action: { + $case: 'request', + request: { user: { address: userAddress }, message } + } + } + + const mockParsedRequest: ParsedUpsertFriendshipRequest = { + action: Action.REQUEST, + user: userAddress, + metadata: { message } + } + + const existingFriendship = { + id: 'friendship-id', + status: FriendshipStatus.Requested, + address_requester: rpcContext.address, + address_requested: userAddress, + is_active: true, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() + } + + const lastFriendshipAction = { + id: 'action-id', + friendship_id: 'friendship-id', + acting_user: rpcContext.address, + action: Action.REQUEST, + timestamp: Date.now().toString() + } + + beforeEach(() => { + components = { db: mockDb, logs: mockLogs, pubsub: mockPubSub } + upsertFriendship = upsertFriendshipService({ components }) + + jest.clearAllMocks() + mockDb.executeTx.mockImplementation(async (cb) => await cb({} as any)) + }) + + it('should return an internalServerError for an invalid request', async () => { + jest.spyOn(FriendshipsLogic, 'parseUpsertFriendshipRequest').mockReturnValueOnce(undefined) + + const result: UpsertFriendshipResponse = await upsertFriendship(mockRequest, rpcContext) + + expect(result).toEqual({ + response: { + $case: 'internalServerError', + internalServerError: {} + } + }) + }) + + it('should return invalidFriendshipAction for an invalid action', async () => { + jest.spyOn(FriendshipsLogic, 'parseUpsertFriendshipRequest').mockReturnValueOnce(mockParsedRequest) + jest.spyOn(FriendshipsLogic, 'validateNewFriendshipAction').mockReturnValueOnce(false) + + const result: UpsertFriendshipResponse = await upsertFriendship(mockRequest, rpcContext) + + expect(result).toEqual({ + response: { + $case: 'invalidFriendshipAction', + invalidFriendshipAction: {} + } + }) + }) + + it('should update an existing friendship', async () => { + jest.spyOn(FriendshipsLogic, 'parseUpsertFriendshipRequest').mockReturnValueOnce(mockParsedRequest) + jest.spyOn(FriendshipsLogic, 'validateNewFriendshipAction').mockReturnValueOnce(true) + jest.spyOn(FriendshipsLogic, 'getNewFriendshipStatus').mockReturnValueOnce(FriendshipStatus.Friends) + + mockDb.getFriendship.mockResolvedValueOnce(existingFriendship) + mockDb.getLastFriendshipAction.mockResolvedValueOnce(lastFriendshipAction) + + const result: UpsertFriendshipResponse = await upsertFriendship(mockRequest, rpcContext) + + expect(mockDb.updateFriendshipStatus).toHaveBeenCalledWith(existingFriendship.id, true, expect.anything()) + expect(mockDb.recordFriendshipAction).toHaveBeenCalledWith( + existingFriendship.id, + rpcContext.address, + mockParsedRequest.action, + mockParsedRequest.metadata, + expect.anything() + ) + expect(result).toEqual({ + response: { + $case: 'accepted', + accepted: {} + } + }) + }) + + it('should create a new friendship', async () => { + jest.spyOn(FriendshipsLogic, 'parseUpsertFriendshipRequest').mockReturnValueOnce(mockParsedRequest) + jest.spyOn(FriendshipsLogic, 'validateNewFriendshipAction').mockReturnValueOnce(true) + jest.spyOn(FriendshipsLogic, 'getNewFriendshipStatus').mockReturnValueOnce(FriendshipStatus.Requested) + + mockDb.getFriendship.mockResolvedValueOnce(null) + mockDb.createFriendship.mockResolvedValueOnce('new-friendship-id') + + const result: UpsertFriendshipResponse = await upsertFriendship(mockRequest, rpcContext) + + expect(mockDb.createFriendship).toHaveBeenCalledWith([rpcContext.address, userAddress], false, expect.anything()) + expect(mockDb.recordFriendshipAction).toHaveBeenCalledWith( + 'new-friendship-id', + rpcContext.address, + mockParsedRequest.action, + mockParsedRequest.metadata, + expect.anything() + ) + expect(result).toEqual({ + response: { + $case: 'accepted', + accepted: {} + } + }) + }) + + it('should handle errors gracefully', async () => { + jest.spyOn(FriendshipsLogic, 'parseUpsertFriendshipRequest').mockReturnValueOnce(mockParsedRequest) + mockDb.getFriendship.mockImplementationOnce(() => { + throw new Error('Database error') + }) + + const result: UpsertFriendshipResponse = await upsertFriendship(mockRequest, rpcContext) + + expect(result).toEqual({ + response: { + $case: 'internalServerError', + internalServerError: {} + } + }) + }) +})