Skip to content

Commit 7254917

Browse files
jlucaso1purpshell
andauthored
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 <[email protected]> * Update src/__tests__/Utils/process-message.test.ts Co-authored-by: Rajeh Taher <[email protected]> * Update src/__tests__/Utils/process-message.test.ts Co-authored-by: Rajeh Taher <[email protected]> --------- Co-authored-by: Rajeh Taher <[email protected]>
1 parent ae5260f commit 7254917

File tree

3 files changed

+254
-0
lines changed

3 files changed

+254
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { jest } from '@jest/globals'
2+
import P from 'pino'
3+
import { LIDMappingStore } from '../../Signal/lid-mapping'
4+
import type { LIDMapping, SignalDataTypeMap, SignalKeyStoreWithTransaction } from '../../Types'
5+
6+
const HOSTED_DEVICE_ID = 99
7+
8+
const mockKeys: jest.Mocked<SignalKeyStoreWithTransaction> = {
9+
get: jest.fn<SignalKeyStoreWithTransaction['get']>() as any,
10+
set: jest.fn<SignalKeyStoreWithTransaction['set']>(),
11+
transaction: jest.fn<SignalKeyStoreWithTransaction['transaction']>(async (work: () => any) => await work()) as any,
12+
isInTransaction: jest.fn<SignalKeyStoreWithTransaction['isInTransaction']>()
13+
}
14+
const logger = P({ level: 'silent' })
15+
16+
describe('LIDMappingStore', () => {
17+
const mockPnToLIDFunc = jest.fn<(jids: string[]) => Promise<LIDMapping[] | undefined>>()
18+
let lidMappingStore: LIDMappingStore
19+
20+
beforeEach(() => {
21+
jest.clearAllMocks()
22+
lidMappingStore = new LIDMappingStore(mockKeys, logger, mockPnToLIDFunc)
23+
})
24+
25+
describe('getPNForLID', () => {
26+
it('should correctly map a standard LID with a hosted device ID back to a standard PN with a hosted device', async () => {
27+
const lidWithHostedDevice = `12345:${HOSTED_DEVICE_ID}@lid`
28+
const pnUser = '54321'
29+
30+
// @ts-ignore
31+
mockKeys.get.mockResolvedValue({ [`12345_reverse`]: pnUser } as SignalDataTypeMap['lid-mapping'])
32+
33+
const result = await lidMappingStore.getPNForLID(lidWithHostedDevice)
34+
expect(result).toBe(`${pnUser}:${HOSTED_DEVICE_ID}@s.whatsapp.net`)
35+
})
36+
37+
it('should return null if no reverse mapping is found', async () => {
38+
const lid = 'nonexistent@lid'
39+
40+
// @ts-ignore
41+
mockKeys.get.mockResolvedValue({} as SignalDataTypeMap['lid-mapping']) // Simulate not found in DB
42+
43+
const result = await lidMappingStore.getPNForLID(lid)
44+
expect(result).toBeNull()
45+
})
46+
})
47+
})
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import type { WAMessage } from '../../Types'
2+
import { cleanMessage } from '../../Utils/process-message'
3+
4+
const createBaseMessage = (key: Partial<WAMessage['key']>, message?: Partial<WAMessage['message']>): WAMessage => {
5+
return {
6+
key: {
7+
remoteJid: '[email protected]',
8+
fromMe: false,
9+
id: 'ABC',
10+
...key
11+
},
12+
message: message || { conversation: 'hello' },
13+
messageTimestamp: 1675888000
14+
}
15+
}
16+
17+
describe('cleanMessage', () => {
18+
const meId = '[email protected]'
19+
const meLid = 'me@lid'
20+
const otherUserId = '[email protected]'
21+
22+
describe('JID Normalization', () => {
23+
it('should correctly normalize a standard phone number JID with a device', () => {
24+
const message = createBaseMessage({
25+
remoteJid: '1234567890:[email protected]',
26+
participant: '9876543210:[email protected]'
27+
})
28+
29+
cleanMessage(message, meId, meLid)
30+
31+
expect(message.key.remoteJid).toBe('[email protected]')
32+
expect(message.key.participant).toBe('[email protected]')
33+
})
34+
35+
it('should not modify a group JID', () => {
36+
const message = createBaseMessage({
37+
remoteJid: '[email protected]'
38+
})
39+
40+
cleanMessage(message, meId, meLid)
41+
42+
expect(message.key.remoteJid).toBe('[email protected]')
43+
})
44+
45+
it('should correctly normalize a LID with a device', () => {
46+
const message = createBaseMessage({
47+
participant: '1234567890:12@lid'
48+
})
49+
50+
cleanMessage(message, meId, meLid)
51+
52+
expect(message.key.participant).toBe('1234567890@lid')
53+
})
54+
})
55+
56+
describe('Hosted JID Handling', () => {
57+
it('should correctly normalize a hosted PN JID back to PN form', () => {
58+
const hostedJid = '1234567890:99@hosted'
59+
const message = createBaseMessage({
60+
remoteJid: hostedJid
61+
})
62+
63+
cleanMessage(message, meId, meLid)
64+
65+
expect(message.key.remoteJid).toBe('[email protected]')
66+
})
67+
68+
it('should correctly normalize a hosted LID JID back to LID form', () => {
69+
const hostedLidJid = '9876543210:[email protected]'
70+
const message = createBaseMessage({
71+
participant: hostedLidJid
72+
})
73+
74+
cleanMessage(message, meId, meLid)
75+
76+
expect(message.key.participant).toBe('9876543210@lid')
77+
})
78+
})
79+
80+
describe('Reaction Message Perspective', () => {
81+
it("should correct the perspective of a reaction to another user's message", () => {
82+
const message = createBaseMessage(
83+
{ fromMe: false, participant: otherUserId },
84+
{
85+
reactionMessage: {
86+
key: {
87+
remoteJid: '[email protected]',
88+
fromMe: false,
89+
id: 'MSG_THEY_SENT',
90+
participant: otherUserId
91+
},
92+
text: '😂'
93+
}
94+
}
95+
)
96+
97+
cleanMessage(message, meId, meLid)
98+
99+
const reactionKey = message.message!.reactionMessage!.key!
100+
expect(reactionKey.fromMe).toBe(false)
101+
})
102+
103+
it('should not modify a reaction on a message I sent from another device', () => {
104+
const message = createBaseMessage(
105+
{ fromMe: true },
106+
{
107+
reactionMessage: {
108+
key: { remoteJid: '[email protected]', fromMe: true, id: 'MSG_I_SENT' },
109+
text: '❤️'
110+
}
111+
}
112+
)
113+
114+
const originalReactionKey = { ...message.message!.reactionMessage!.key! }
115+
116+
cleanMessage(message, meId, meLid)
117+
118+
const reactionKey = message.message!.reactionMessage!.key!
119+
expect(reactionKey).toEqual(originalReactionKey)
120+
})
121+
})
122+
123+
describe('Edge Cases', () => {
124+
it('should not crash if JIDs are undefined', () => {
125+
const message = createBaseMessage({
126+
remoteJid: undefined,
127+
participant: undefined
128+
})
129+
130+
expect(() => cleanMessage(message, meId, meLid)).not.toThrow()
131+
})
132+
133+
it('should not crash on an empty message object', () => {
134+
const message = createBaseMessage({}, {})
135+
expect(() => cleanMessage(message, meId, meLid)).not.toThrow()
136+
})
137+
})
138+
})

src/__tests__/Utils/signal.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { jest } from '@jest/globals'
2+
import type { SignalRepositoryWithLIDStore } from '../../Types'
3+
import { parseAndInjectE2ESessions } from '../../Utils/signal'
4+
import type { BinaryNode } from '../../WABinary/types'
5+
6+
describe('parseAndInjectE2ESessions', () => {
7+
it('should process all user node', async () => {
8+
const mockRepository = {
9+
injectE2ESession: jest.fn<SignalRepositoryWithLIDStore['injectE2ESession']>()
10+
} as jest.Mocked<Pick<SignalRepositoryWithLIDStore, 'injectE2ESession'>>
11+
12+
mockRepository.injectE2ESession.mockResolvedValue(undefined)
13+
14+
const createUserNode = (jid: string): BinaryNode => ({
15+
tag: 'user',
16+
attrs: { jid },
17+
content: [
18+
{
19+
tag: 'skey',
20+
attrs: {},
21+
content: [
22+
{ tag: 'id', attrs: {}, content: Buffer.from([0, 0, 1]) },
23+
{ tag: 'value', attrs: {}, content: Buffer.alloc(33) },
24+
{ tag: 'signature', attrs: {}, content: Buffer.alloc(64) }
25+
]
26+
},
27+
{
28+
tag: 'key',
29+
attrs: {},
30+
content: [
31+
{ tag: 'id', attrs: {}, content: Buffer.from([0, 0, 2]) },
32+
{ tag: 'value', attrs: {}, content: Buffer.alloc(33) }
33+
]
34+
},
35+
{ tag: 'identity', attrs: {}, content: Buffer.alloc(32) },
36+
{ tag: 'registration', attrs: {}, content: Buffer.alloc(4) }
37+
]
38+
})
39+
40+
const mockNode: BinaryNode = {
41+
tag: 'iq',
42+
attrs: {},
43+
content: [
44+
{
45+
tag: 'list',
46+
attrs: {},
47+
content: [
48+
createUserNode('[email protected]'),
49+
createUserNode('[email protected]'),
50+
createUserNode('[email protected]')
51+
]
52+
}
53+
]
54+
}
55+
56+
await parseAndInjectE2ESessions(mockNode, mockRepository as any)
57+
58+
expect(mockRepository.injectE2ESession).toHaveBeenCalledTimes(3)
59+
expect(mockRepository.injectE2ESession).toHaveBeenCalledWith(
60+
expect.objectContaining({ jid: '[email protected]' })
61+
)
62+
expect(mockRepository.injectE2ESession).toHaveBeenCalledWith(
63+
expect.objectContaining({ jid: '[email protected]' })
64+
)
65+
expect(mockRepository.injectE2ESession).toHaveBeenCalledWith(
66+
expect.objectContaining({ jid: '[email protected]' })
67+
)
68+
})
69+
})

0 commit comments

Comments
 (0)