test: add unit tests for LIDMappingStore and message processing utilities (#1949)
* test: add unit tests for LIDMappingStore and message processing utilities * Update src/__tests__/Signal/lid-mapping.test.ts Co-authored-by: Rajeh Taher <rajeh@reforward.dev> * Update src/__tests__/Utils/process-message.test.ts Co-authored-by: Rajeh Taher <rajeh@reforward.dev> * Update src/__tests__/Utils/process-message.test.ts Co-authored-by: Rajeh Taher <rajeh@reforward.dev> --------- Co-authored-by: Rajeh Taher <rajeh@reforward.dev>
This commit is contained in:
committed by
GitHub
parent
ae5260fc96
commit
7254917c53
@@ -0,0 +1,47 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import P from 'pino'
|
||||
import { LIDMappingStore } from '../../Signal/lid-mapping'
|
||||
import type { LIDMapping, SignalDataTypeMap, SignalKeyStoreWithTransaction } from '../../Types'
|
||||
|
||||
const HOSTED_DEVICE_ID = 99
|
||||
|
||||
const mockKeys: jest.Mocked<SignalKeyStoreWithTransaction> = {
|
||||
get: jest.fn<SignalKeyStoreWithTransaction['get']>() as any,
|
||||
set: jest.fn<SignalKeyStoreWithTransaction['set']>(),
|
||||
transaction: jest.fn<SignalKeyStoreWithTransaction['transaction']>(async (work: () => any) => await work()) as any,
|
||||
isInTransaction: jest.fn<SignalKeyStoreWithTransaction['isInTransaction']>()
|
||||
}
|
||||
const logger = P({ level: 'silent' })
|
||||
|
||||
describe('LIDMappingStore', () => {
|
||||
const mockPnToLIDFunc = jest.fn<(jids: string[]) => Promise<LIDMapping[] | undefined>>()
|
||||
let lidMappingStore: LIDMappingStore
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
lidMappingStore = new LIDMappingStore(mockKeys, logger, mockPnToLIDFunc)
|
||||
})
|
||||
|
||||
describe('getPNForLID', () => {
|
||||
it('should correctly map a standard LID with a hosted device ID back to a standard PN with a hosted device', async () => {
|
||||
const lidWithHostedDevice = `12345:${HOSTED_DEVICE_ID}@lid`
|
||||
const pnUser = '54321'
|
||||
|
||||
// @ts-ignore
|
||||
mockKeys.get.mockResolvedValue({ [`12345_reverse`]: pnUser } as SignalDataTypeMap['lid-mapping'])
|
||||
|
||||
const result = await lidMappingStore.getPNForLID(lidWithHostedDevice)
|
||||
expect(result).toBe(`${pnUser}:${HOSTED_DEVICE_ID}@s.whatsapp.net`)
|
||||
})
|
||||
|
||||
it('should return null if no reverse mapping is found', async () => {
|
||||
const lid = 'nonexistent@lid'
|
||||
|
||||
// @ts-ignore
|
||||
mockKeys.get.mockResolvedValue({} as SignalDataTypeMap['lid-mapping']) // Simulate not found in DB
|
||||
|
||||
const result = await lidMappingStore.getPNForLID(lid)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,138 @@
|
||||
import type { WAMessage } from '../../Types'
|
||||
import { cleanMessage } from '../../Utils/process-message'
|
||||
|
||||
const createBaseMessage = (key: Partial<WAMessage['key']>, message?: Partial<WAMessage['message']>): WAMessage => {
|
||||
return {
|
||||
key: {
|
||||
remoteJid: 'chat@s.whatsapp.net',
|
||||
fromMe: false,
|
||||
id: 'ABC',
|
||||
...key
|
||||
},
|
||||
message: message || { conversation: 'hello' },
|
||||
messageTimestamp: 1675888000
|
||||
}
|
||||
}
|
||||
|
||||
describe('cleanMessage', () => {
|
||||
const meId = 'me@s.whatsapp.net'
|
||||
const meLid = 'me@lid'
|
||||
const otherUserId = 'other@s.whatsapp.net'
|
||||
|
||||
describe('JID Normalization', () => {
|
||||
it('should correctly normalize a standard phone number JID with a device', () => {
|
||||
const message = createBaseMessage({
|
||||
remoteJid: '1234567890:15@s.whatsapp.net',
|
||||
participant: '9876543210:5@s.whatsapp.net'
|
||||
})
|
||||
|
||||
cleanMessage(message, meId, meLid)
|
||||
|
||||
expect(message.key.remoteJid).toBe('1234567890@s.whatsapp.net')
|
||||
expect(message.key.participant).toBe('9876543210@s.whatsapp.net')
|
||||
})
|
||||
|
||||
it('should not modify a group JID', () => {
|
||||
const message = createBaseMessage({
|
||||
remoteJid: '123456-7890@g.us'
|
||||
})
|
||||
|
||||
cleanMessage(message, meId, meLid)
|
||||
|
||||
expect(message.key.remoteJid).toBe('123456-7890@g.us')
|
||||
})
|
||||
|
||||
it('should correctly normalize a LID with a device', () => {
|
||||
const message = createBaseMessage({
|
||||
participant: '1234567890:12@lid'
|
||||
})
|
||||
|
||||
cleanMessage(message, meId, meLid)
|
||||
|
||||
expect(message.key.participant).toBe('1234567890@lid')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Hosted JID Handling', () => {
|
||||
it('should correctly normalize a hosted PN JID back to PN form', () => {
|
||||
const hostedJid = '1234567890:99@hosted'
|
||||
const message = createBaseMessage({
|
||||
remoteJid: hostedJid
|
||||
})
|
||||
|
||||
cleanMessage(message, meId, meLid)
|
||||
|
||||
expect(message.key.remoteJid).toBe('1234567890@s.whatsapp.net')
|
||||
})
|
||||
|
||||
it('should correctly normalize a hosted LID JID back to LID form', () => {
|
||||
const hostedLidJid = '9876543210:99@hosted.lid'
|
||||
const message = createBaseMessage({
|
||||
participant: hostedLidJid
|
||||
})
|
||||
|
||||
cleanMessage(message, meId, meLid)
|
||||
|
||||
expect(message.key.participant).toBe('9876543210@lid')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Reaction Message Perspective', () => {
|
||||
it("should correct the perspective of a reaction to another user's message", () => {
|
||||
const message = createBaseMessage(
|
||||
{ fromMe: false, participant: otherUserId },
|
||||
{
|
||||
reactionMessage: {
|
||||
key: {
|
||||
remoteJid: 'chat@s.whatsapp.net',
|
||||
fromMe: false,
|
||||
id: 'MSG_THEY_SENT',
|
||||
participant: otherUserId
|
||||
},
|
||||
text: '😂'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
cleanMessage(message, meId, meLid)
|
||||
|
||||
const reactionKey = message.message!.reactionMessage!.key!
|
||||
expect(reactionKey.fromMe).toBe(false)
|
||||
})
|
||||
|
||||
it('should not modify a reaction on a message I sent from another device', () => {
|
||||
const message = createBaseMessage(
|
||||
{ fromMe: true },
|
||||
{
|
||||
reactionMessage: {
|
||||
key: { remoteJid: 'chat@s.whatsapp.net', fromMe: true, id: 'MSG_I_SENT' },
|
||||
text: '❤️'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const originalReactionKey = { ...message.message!.reactionMessage!.key! }
|
||||
|
||||
cleanMessage(message, meId, meLid)
|
||||
|
||||
const reactionKey = message.message!.reactionMessage!.key!
|
||||
expect(reactionKey).toEqual(originalReactionKey)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should not crash if JIDs are undefined', () => {
|
||||
const message = createBaseMessage({
|
||||
remoteJid: undefined,
|
||||
participant: undefined
|
||||
})
|
||||
|
||||
expect(() => cleanMessage(message, meId, meLid)).not.toThrow()
|
||||
})
|
||||
|
||||
it('should not crash on an empty message object', () => {
|
||||
const message = createBaseMessage({}, {})
|
||||
expect(() => cleanMessage(message, meId, meLid)).not.toThrow()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,69 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import type { SignalRepositoryWithLIDStore } from '../../Types'
|
||||
import { parseAndInjectE2ESessions } from '../../Utils/signal'
|
||||
import type { BinaryNode } from '../../WABinary/types'
|
||||
|
||||
describe('parseAndInjectE2ESessions', () => {
|
||||
it('should process all user node', async () => {
|
||||
const mockRepository = {
|
||||
injectE2ESession: jest.fn<SignalRepositoryWithLIDStore['injectE2ESession']>()
|
||||
} as jest.Mocked<Pick<SignalRepositoryWithLIDStore, 'injectE2ESession'>>
|
||||
|
||||
mockRepository.injectE2ESession.mockResolvedValue(undefined)
|
||||
|
||||
const createUserNode = (jid: string): BinaryNode => ({
|
||||
tag: 'user',
|
||||
attrs: { jid },
|
||||
content: [
|
||||
{
|
||||
tag: 'skey',
|
||||
attrs: {},
|
||||
content: [
|
||||
{ tag: 'id', attrs: {}, content: Buffer.from([0, 0, 1]) },
|
||||
{ tag: 'value', attrs: {}, content: Buffer.alloc(33) },
|
||||
{ tag: 'signature', attrs: {}, content: Buffer.alloc(64) }
|
||||
]
|
||||
},
|
||||
{
|
||||
tag: 'key',
|
||||
attrs: {},
|
||||
content: [
|
||||
{ tag: 'id', attrs: {}, content: Buffer.from([0, 0, 2]) },
|
||||
{ tag: 'value', attrs: {}, content: Buffer.alloc(33) }
|
||||
]
|
||||
},
|
||||
{ tag: 'identity', attrs: {}, content: Buffer.alloc(32) },
|
||||
{ tag: 'registration', attrs: {}, content: Buffer.alloc(4) }
|
||||
]
|
||||
})
|
||||
|
||||
const mockNode: BinaryNode = {
|
||||
tag: 'iq',
|
||||
attrs: {},
|
||||
content: [
|
||||
{
|
||||
tag: 'list',
|
||||
attrs: {},
|
||||
content: [
|
||||
createUserNode('user1@s.whatsapp.net'),
|
||||
createUserNode('user2@s.whatsapp.net'),
|
||||
createUserNode('user3@s.whatsapp.net')
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
await parseAndInjectE2ESessions(mockNode, mockRepository as any)
|
||||
|
||||
expect(mockRepository.injectE2ESession).toHaveBeenCalledTimes(3)
|
||||
expect(mockRepository.injectE2ESession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ jid: 'user1@s.whatsapp.net' })
|
||||
)
|
||||
expect(mockRepository.injectE2ESession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ jid: 'user2@s.whatsapp.net' })
|
||||
)
|
||||
expect(mockRepository.injectE2ESession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ jid: 'user3@s.whatsapp.net' })
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user