@massalabs/gossip-sdk 0.0.2-dev.20260128094509 → 0.0.2-dev.20260128160824
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/messageProtocol/index.d.ts +19 -0
- package/dist/api/messageProtocol/index.js +26 -0
- package/dist/api/messageProtocol/mock.d.ts +12 -0
- package/{src/api/messageProtocol/mock.ts → dist/api/messageProtocol/mock.js} +2 -3
- package/dist/api/messageProtocol/rest.d.ts +22 -0
- package/dist/api/messageProtocol/rest.js +161 -0
- package/dist/api/messageProtocol/types.d.ts +61 -0
- package/dist/api/messageProtocol/types.js +6 -0
- package/dist/assets/generated/wasm/README.md +281 -0
- package/dist/assets/generated/wasm/gossip_wasm.d.ts +638 -0
- package/dist/assets/generated/wasm/gossip_wasm.js +1557 -0
- package/dist/assets/generated/wasm/gossip_wasm_bg.wasm +0 -0
- package/dist/assets/generated/wasm/gossip_wasm_bg.wasm.d.ts +164 -0
- package/dist/assets/generated/wasm/package.json +15 -0
- package/dist/assets/generated/wasm-node/README.md +281 -0
- package/dist/assets/generated/wasm-node/gossip_wasm.d.ts +443 -0
- package/dist/assets/generated/wasm-node/gossip_wasm.js +1488 -0
- package/dist/assets/generated/wasm-node/gossip_wasm_bg.wasm +0 -0
- package/dist/assets/generated/wasm-node/gossip_wasm_bg.wasm.d.ts +164 -0
- package/dist/assets/generated/wasm-node/package.json +11 -0
- package/dist/config/protocol.d.ts +36 -0
- package/dist/config/protocol.js +77 -0
- package/dist/config/sdk.d.ts +82 -0
- package/dist/config/sdk.js +55 -0
- package/{src/contacts.ts → dist/contacts.d.ts} +11 -95
- package/dist/contacts.js +166 -0
- package/dist/core/SdkEventEmitter.d.ts +36 -0
- package/dist/core/SdkEventEmitter.js +59 -0
- package/dist/core/SdkPolling.d.ts +35 -0
- package/dist/core/SdkPolling.js +100 -0
- package/{src/core/index.ts → dist/core/index.d.ts} +0 -2
- package/dist/core/index.js +5 -0
- package/dist/crypto/bip39.d.ts +34 -0
- package/dist/crypto/bip39.js +62 -0
- package/dist/crypto/encryption.d.ts +37 -0
- package/dist/crypto/encryption.js +46 -0
- package/dist/db.d.ts +190 -0
- package/dist/db.js +311 -0
- package/dist/gossipSdk.d.ts +274 -0
- package/dist/gossipSdk.js +690 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +61 -0
- package/dist/services/announcement.d.ts +43 -0
- package/dist/services/announcement.js +491 -0
- package/dist/services/auth.d.ts +37 -0
- package/dist/services/auth.js +76 -0
- package/dist/services/discussion.d.ts +63 -0
- package/dist/services/discussion.js +297 -0
- package/dist/services/message.d.ts +74 -0
- package/dist/services/message.js +826 -0
- package/dist/services/refresh.d.ts +41 -0
- package/dist/services/refresh.js +205 -0
- package/{src/sw.ts → dist/sw.d.ts} +1 -8
- package/dist/sw.js +10 -0
- package/dist/types/events.d.ts +80 -0
- package/dist/types/events.js +7 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +7 -0
- package/dist/utils/base64.d.ts +10 -0
- package/dist/utils/base64.js +30 -0
- package/dist/utils/contacts.d.ts +42 -0
- package/dist/utils/contacts.js +113 -0
- package/dist/utils/discussions.d.ts +24 -0
- package/dist/utils/discussions.js +38 -0
- package/dist/utils/logs.d.ts +19 -0
- package/dist/utils/logs.js +89 -0
- package/dist/utils/messageSerialization.d.ts +64 -0
- package/dist/utils/messageSerialization.js +184 -0
- package/dist/utils/queue.d.ts +50 -0
- package/dist/utils/queue.js +110 -0
- package/dist/utils/type.d.ts +10 -0
- package/dist/utils/type.js +4 -0
- package/dist/utils/userId.d.ts +40 -0
- package/dist/utils/userId.js +90 -0
- package/dist/utils/validation.d.ts +50 -0
- package/dist/utils/validation.js +112 -0
- package/dist/utils.d.ts +30 -0
- package/{src/utils.ts → dist/utils.js} +9 -19
- package/dist/wasm/encryption.d.ts +56 -0
- package/{src/wasm/encryption.ts → dist/wasm/encryption.js} +22 -51
- package/dist/wasm/index.d.ts +10 -0
- package/{src/wasm/index.ts → dist/wasm/index.js} +1 -8
- package/dist/wasm/loader.d.ts +22 -0
- package/dist/wasm/loader.js +78 -0
- package/dist/wasm/session.d.ts +85 -0
- package/dist/wasm/session.js +226 -0
- package/dist/wasm/userKeys.d.ts +17 -0
- package/{src/wasm/userKeys.ts → dist/wasm/userKeys.js} +6 -13
- package/package.json +15 -2
- package/src/api/messageProtocol/index.ts +0 -53
- package/src/api/messageProtocol/rest.ts +0 -209
- package/src/api/messageProtocol/types.ts +0 -70
- package/src/config/protocol.ts +0 -97
- package/src/config/sdk.ts +0 -131
- package/src/core/SdkEventEmitter.ts +0 -91
- package/src/core/SdkPolling.ts +0 -134
- package/src/crypto/bip39.ts +0 -84
- package/src/crypto/encryption.ts +0 -77
- package/src/db.ts +0 -465
- package/src/gossipSdk.ts +0 -994
- package/src/index.ts +0 -211
- package/src/services/announcement.ts +0 -653
- package/src/services/auth.ts +0 -95
- package/src/services/discussion.ts +0 -380
- package/src/services/message.ts +0 -1055
- package/src/services/refresh.ts +0 -234
- package/src/types/events.ts +0 -108
- package/src/types.ts +0 -70
- package/src/utils/base64.ts +0 -39
- package/src/utils/contacts.ts +0 -161
- package/src/utils/discussions.ts +0 -55
- package/src/utils/logs.ts +0 -86
- package/src/utils/messageSerialization.ts +0 -257
- package/src/utils/queue.ts +0 -106
- package/src/utils/type.ts +0 -7
- package/src/utils/userId.ts +0 -114
- package/src/utils/validation.ts +0 -144
- package/src/wasm/loader.ts +0 -123
- package/src/wasm/session.ts +0 -276
- package/test/config/protocol.spec.ts +0 -31
- package/test/config/sdk.spec.ts +0 -163
- package/test/db/helpers.spec.ts +0 -142
- package/test/db/operations.spec.ts +0 -128
- package/test/db/states.spec.ts +0 -535
- package/test/integration/discussion-flow.spec.ts +0 -422
- package/test/integration/messaging-flow.spec.ts +0 -708
- package/test/integration/sdk-lifecycle.spec.ts +0 -325
- package/test/mocks/index.ts +0 -9
- package/test/mocks/mockMessageProtocol.ts +0 -100
- package/test/services/auth.spec.ts +0 -311
- package/test/services/discussion.spec.ts +0 -279
- package/test/services/message-deduplication.spec.ts +0 -299
- package/test/services/message-startup.spec.ts +0 -331
- package/test/services/message.spec.ts +0 -817
- package/test/services/refresh.spec.ts +0 -199
- package/test/services/session-status.spec.ts +0 -349
- package/test/session/wasm.spec.ts +0 -227
- package/test/setup.ts +0 -52
- package/test/utils/contacts.spec.ts +0 -156
- package/test/utils/discussions.spec.ts +0 -66
- package/test/utils/queue.spec.ts +0 -52
- package/test/utils/serialization.spec.ts +0 -120
- package/test/utils/userId.spec.ts +0 -120
- package/test/utils/validation.spec.ts +0 -223
- package/test/utils.ts +0 -212
- package/tsconfig.json +0 -26
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -28
|
@@ -1,817 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MessageService tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
6
|
-
import { DiscussionService } from '../../src/services/discussion';
|
|
7
|
-
import { MessageService } from '../../src/services/message';
|
|
8
|
-
import {
|
|
9
|
-
GossipDatabase,
|
|
10
|
-
db,
|
|
11
|
-
MessageStatus,
|
|
12
|
-
MessageDirection,
|
|
13
|
-
MessageType,
|
|
14
|
-
DiscussionStatus,
|
|
15
|
-
DiscussionDirection,
|
|
16
|
-
} from '../../src/db';
|
|
17
|
-
import type { IMessageProtocol } from '../../src/api/messageProtocol/types';
|
|
18
|
-
import type { SessionModule } from '../../src/wasm/session';
|
|
19
|
-
import { encodeUserId } from '../../src/utils/userId';
|
|
20
|
-
import { SessionStatus } from '../../src/assets/generated/wasm/gossip_wasm';
|
|
21
|
-
import { defaultSdkConfig } from '../../src/config/sdk';
|
|
22
|
-
|
|
23
|
-
const MESSAGE_OWNER_USER_ID = encodeUserId(new Uint8Array(32).fill(11));
|
|
24
|
-
const MESSAGE_CONTACT_USER_ID = encodeUserId(new Uint8Array(32).fill(12));
|
|
25
|
-
|
|
26
|
-
function createMessageProtocol(): IMessageProtocol {
|
|
27
|
-
return {
|
|
28
|
-
fetchMessages: vi.fn().mockResolvedValue([]),
|
|
29
|
-
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
30
|
-
sendAnnouncement: vi.fn().mockResolvedValue('1'),
|
|
31
|
-
fetchAnnouncements: vi.fn().mockResolvedValue([]),
|
|
32
|
-
fetchPublicKeyByUserId: vi.fn().mockResolvedValue(''),
|
|
33
|
-
postPublicKey: vi.fn().mockResolvedValue('hash'),
|
|
34
|
-
changeNode: vi.fn().mockResolvedValue({ success: true }),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function createMessageSession(
|
|
39
|
-
sessionStatus: SessionStatus = SessionStatus.Active
|
|
40
|
-
): SessionModule {
|
|
41
|
-
return {
|
|
42
|
-
peerSessionStatus: vi.fn().mockReturnValue(sessionStatus),
|
|
43
|
-
sendMessage: vi.fn(),
|
|
44
|
-
receiveMessage: vi.fn(),
|
|
45
|
-
refresh: vi.fn().mockReturnValue([]),
|
|
46
|
-
receiveAnnouncement: vi.fn(),
|
|
47
|
-
establishOutgoingSession: vi.fn(),
|
|
48
|
-
toEncryptedBlob: vi.fn(),
|
|
49
|
-
userIdEncoded: MESSAGE_OWNER_USER_ID,
|
|
50
|
-
userIdRaw: new Uint8Array(32).fill(11),
|
|
51
|
-
getMessageBoardReadKeys: vi.fn().mockReturnValue([]),
|
|
52
|
-
cleanup: vi.fn(),
|
|
53
|
-
} as unknown as SessionModule;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function createDiscussionServiceMock(
|
|
57
|
-
isStable: boolean = true
|
|
58
|
-
): DiscussionService {
|
|
59
|
-
return {
|
|
60
|
-
isStableState: vi.fn().mockResolvedValue(isStable),
|
|
61
|
-
} as unknown as DiscussionService;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const messageFakeSession = {} as SessionModule;
|
|
65
|
-
|
|
66
|
-
describe('MessageService', () => {
|
|
67
|
-
beforeEach(async () => {
|
|
68
|
-
if (!db.isOpen()) {
|
|
69
|
-
await db.open();
|
|
70
|
-
}
|
|
71
|
-
await Promise.all(db.tables.map(table => table.clear()));
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('finds message by seeker', async () => {
|
|
75
|
-
const seeker = new Uint8Array(32).fill(5);
|
|
76
|
-
await db.messages.add({
|
|
77
|
-
ownerUserId: MESSAGE_OWNER_USER_ID,
|
|
78
|
-
contactUserId: MESSAGE_CONTACT_USER_ID,
|
|
79
|
-
content: 'Hello',
|
|
80
|
-
type: MessageType.TEXT,
|
|
81
|
-
direction: MessageDirection.OUTGOING,
|
|
82
|
-
status: MessageStatus.SENT,
|
|
83
|
-
timestamp: new Date(),
|
|
84
|
-
seeker,
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const service = new MessageService(
|
|
88
|
-
db,
|
|
89
|
-
createMessageProtocol(),
|
|
90
|
-
messageFakeSession,
|
|
91
|
-
createDiscussionServiceMock()
|
|
92
|
-
);
|
|
93
|
-
const message = await service.findMessageBySeeker(
|
|
94
|
-
seeker,
|
|
95
|
-
MESSAGE_OWNER_USER_ID
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
expect(message).toBeDefined();
|
|
99
|
-
expect(message?.content).toBe('Hello');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('returns undefined for missing seeker', async () => {
|
|
103
|
-
const seeker = new Uint8Array(32).fill(9);
|
|
104
|
-
|
|
105
|
-
const service = new MessageService(
|
|
106
|
-
db,
|
|
107
|
-
createMessageProtocol(),
|
|
108
|
-
messageFakeSession,
|
|
109
|
-
createDiscussionServiceMock()
|
|
110
|
-
);
|
|
111
|
-
const message = await service.findMessageBySeeker(
|
|
112
|
-
seeker,
|
|
113
|
-
MESSAGE_OWNER_USER_ID
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
expect(message).toBeUndefined();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe('sendMessage with no active session (auto-renewal flow)', () => {
|
|
120
|
-
beforeEach(async () => {
|
|
121
|
-
await db.discussions.add({
|
|
122
|
-
ownerUserId: MESSAGE_OWNER_USER_ID,
|
|
123
|
-
contactUserId: MESSAGE_CONTACT_USER_ID,
|
|
124
|
-
direction: DiscussionDirection.INITIATED,
|
|
125
|
-
status: DiscussionStatus.ACTIVE,
|
|
126
|
-
unreadCount: 0,
|
|
127
|
-
createdAt: new Date(),
|
|
128
|
-
updatedAt: new Date(),
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should queue message as WAITING_SESSION when session is NoSession', async () => {
|
|
133
|
-
const mockSession = createMessageSession(SessionStatus.NoSession);
|
|
134
|
-
const service = new MessageService(
|
|
135
|
-
db,
|
|
136
|
-
createMessageProtocol(),
|
|
137
|
-
mockSession,
|
|
138
|
-
createDiscussionServiceMock()
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
const message = {
|
|
142
|
-
ownerUserId: MESSAGE_OWNER_USER_ID,
|
|
143
|
-
contactUserId: MESSAGE_CONTACT_USER_ID,
|
|
144
|
-
content: 'Test message',
|
|
145
|
-
type: MessageType.TEXT,
|
|
146
|
-
direction: MessageDirection.OUTGOING,
|
|
147
|
-
status: MessageStatus.SENDING,
|
|
148
|
-
timestamp: new Date(),
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const result = await service.sendMessage(message);
|
|
152
|
-
|
|
153
|
-
expect(result.success).toBe(true);
|
|
154
|
-
expect(result.message).toBeDefined();
|
|
155
|
-
expect(result.message?.status).toBe(MessageStatus.WAITING_SESSION);
|
|
156
|
-
expect(result.message?.id).toBeDefined();
|
|
157
|
-
|
|
158
|
-
const dbMessage = await db.messages.get(result.message!.id!);
|
|
159
|
-
expect(dbMessage).toBeDefined();
|
|
160
|
-
expect(dbMessage?.status).toBe(MessageStatus.WAITING_SESSION);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('should queue message as WAITING_SESSION when session is UnknownPeer', async () => {
|
|
164
|
-
const mockSession = createMessageSession(SessionStatus.UnknownPeer);
|
|
165
|
-
const service = new MessageService(
|
|
166
|
-
db,
|
|
167
|
-
createMessageProtocol(),
|
|
168
|
-
mockSession,
|
|
169
|
-
createDiscussionServiceMock()
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
const message = {
|
|
173
|
-
ownerUserId: MESSAGE_OWNER_USER_ID,
|
|
174
|
-
contactUserId: MESSAGE_CONTACT_USER_ID,
|
|
175
|
-
content: 'Test message',
|
|
176
|
-
type: MessageType.TEXT,
|
|
177
|
-
direction: MessageDirection.OUTGOING,
|
|
178
|
-
status: MessageStatus.SENDING,
|
|
179
|
-
timestamp: new Date(),
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const result = await service.sendMessage(message);
|
|
183
|
-
|
|
184
|
-
expect(result.success).toBe(true);
|
|
185
|
-
expect(result.message).toBeDefined();
|
|
186
|
-
|
|
187
|
-
const messages = await db.messages
|
|
188
|
-
.where('[ownerUserId+contactUserId]')
|
|
189
|
-
.equals([MESSAGE_OWNER_USER_ID, MESSAGE_CONTACT_USER_ID])
|
|
190
|
-
.toArray();
|
|
191
|
-
expect(messages.length).toBe(1);
|
|
192
|
-
expect(messages[0].status).toBe(MessageStatus.WAITING_SESSION);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('should queue message as WAITING_SESSION when session is Killed', async () => {
|
|
196
|
-
const mockSession = createMessageSession(SessionStatus.Killed);
|
|
197
|
-
const service = new MessageService(
|
|
198
|
-
db,
|
|
199
|
-
createMessageProtocol(),
|
|
200
|
-
mockSession,
|
|
201
|
-
createDiscussionServiceMock()
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
const message = {
|
|
205
|
-
ownerUserId: MESSAGE_OWNER_USER_ID,
|
|
206
|
-
contactUserId: MESSAGE_CONTACT_USER_ID,
|
|
207
|
-
content: 'Test message',
|
|
208
|
-
type: MessageType.TEXT,
|
|
209
|
-
direction: MessageDirection.OUTGOING,
|
|
210
|
-
status: MessageStatus.SENDING,
|
|
211
|
-
timestamp: new Date(),
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const result = await service.sendMessage(message);
|
|
215
|
-
|
|
216
|
-
expect(result.success).toBe(true);
|
|
217
|
-
expect(result.message).toBeDefined();
|
|
218
|
-
expect(result.message?.status).toBe(MessageStatus.WAITING_SESSION);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it('should NOT mark discussion as BROKEN when session is lost (auto-renewal)', async () => {
|
|
222
|
-
const mockSession = createMessageSession(SessionStatus.NoSession);
|
|
223
|
-
const service = new MessageService(
|
|
224
|
-
db,
|
|
225
|
-
createMessageProtocol(),
|
|
226
|
-
mockSession,
|
|
227
|
-
createDiscussionServiceMock()
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
const message = {
|
|
231
|
-
ownerUserId: MESSAGE_OWNER_USER_ID,
|
|
232
|
-
contactUserId: MESSAGE_CONTACT_USER_ID,
|
|
233
|
-
content: 'Test message',
|
|
234
|
-
type: MessageType.TEXT,
|
|
235
|
-
direction: MessageDirection.OUTGOING,
|
|
236
|
-
status: MessageStatus.SENDING,
|
|
237
|
-
timestamp: new Date(),
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
await service.sendMessage(message);
|
|
241
|
-
|
|
242
|
-
const discussion = await db.getDiscussionByOwnerAndContact(
|
|
243
|
-
MESSAGE_OWNER_USER_ID,
|
|
244
|
-
MESSAGE_CONTACT_USER_ID
|
|
245
|
-
);
|
|
246
|
-
expect(discussion?.status).toBe(DiscussionStatus.ACTIVE);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it('should emit onSessionRenewalNeeded event when session is lost', async () => {
|
|
250
|
-
const mockSession = createMessageSession(SessionStatus.NoSession);
|
|
251
|
-
const onSessionRenewalNeeded = vi.fn();
|
|
252
|
-
const service = new MessageService(
|
|
253
|
-
db,
|
|
254
|
-
createMessageProtocol(),
|
|
255
|
-
mockSession,
|
|
256
|
-
createDiscussionServiceMock(),
|
|
257
|
-
{ onSessionRenewalNeeded }
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
const message = {
|
|
261
|
-
ownerUserId: MESSAGE_OWNER_USER_ID,
|
|
262
|
-
contactUserId: MESSAGE_CONTACT_USER_ID,
|
|
263
|
-
content: 'Test message',
|
|
264
|
-
type: MessageType.TEXT,
|
|
265
|
-
direction: MessageDirection.OUTGOING,
|
|
266
|
-
status: MessageStatus.SENDING,
|
|
267
|
-
timestamp: new Date(),
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
await service.sendMessage(message);
|
|
271
|
-
|
|
272
|
-
expect(onSessionRenewalNeeded).toHaveBeenCalledTimes(1);
|
|
273
|
-
expect(onSessionRenewalNeeded).toHaveBeenCalledWith(
|
|
274
|
-
MESSAGE_CONTACT_USER_ID
|
|
275
|
-
);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it('should queue message as WAITING_SESSION and trigger accept when session is PeerRequested', async () => {
|
|
279
|
-
const mockSession = createMessageSession(SessionStatus.PeerRequested);
|
|
280
|
-
const onSessionAcceptNeeded = vi.fn();
|
|
281
|
-
const onSessionRenewalNeeded = vi.fn();
|
|
282
|
-
const service = new MessageService(
|
|
283
|
-
db,
|
|
284
|
-
createMessageProtocol(),
|
|
285
|
-
mockSession,
|
|
286
|
-
createDiscussionServiceMock(),
|
|
287
|
-
{ onSessionAcceptNeeded, onSessionRenewalNeeded }
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
const message = {
|
|
291
|
-
ownerUserId: MESSAGE_OWNER_USER_ID,
|
|
292
|
-
contactUserId: MESSAGE_CONTACT_USER_ID,
|
|
293
|
-
content: 'Test message',
|
|
294
|
-
type: MessageType.TEXT,
|
|
295
|
-
direction: MessageDirection.OUTGOING,
|
|
296
|
-
status: MessageStatus.SENDING,
|
|
297
|
-
timestamp: new Date(),
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
const result = await service.sendMessage(message);
|
|
301
|
-
|
|
302
|
-
expect(result.success).toBe(true);
|
|
303
|
-
expect(result.message).toBeDefined();
|
|
304
|
-
expect(result.message?.status).toBe(MessageStatus.WAITING_SESSION);
|
|
305
|
-
|
|
306
|
-
const dbMessage = await db.messages.get(result.message!.id!);
|
|
307
|
-
expect(dbMessage).toBeDefined();
|
|
308
|
-
expect(dbMessage?.status).toBe(MessageStatus.WAITING_SESSION);
|
|
309
|
-
|
|
310
|
-
expect(onSessionAcceptNeeded).toHaveBeenCalledWith(
|
|
311
|
-
MESSAGE_CONTACT_USER_ID
|
|
312
|
-
);
|
|
313
|
-
expect(onSessionRenewalNeeded).not.toHaveBeenCalled();
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
it('should queue message as WAITING_SESSION when session is SelfRequested', async () => {
|
|
317
|
-
const mockSession = createMessageSession(SessionStatus.SelfRequested);
|
|
318
|
-
const service = new MessageService(
|
|
319
|
-
db,
|
|
320
|
-
createMessageProtocol(),
|
|
321
|
-
mockSession,
|
|
322
|
-
createDiscussionServiceMock()
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
const message = {
|
|
326
|
-
ownerUserId: MESSAGE_OWNER_USER_ID,
|
|
327
|
-
contactUserId: MESSAGE_CONTACT_USER_ID,
|
|
328
|
-
content: 'Test message',
|
|
329
|
-
type: MessageType.TEXT,
|
|
330
|
-
direction: MessageDirection.OUTGOING,
|
|
331
|
-
status: MessageStatus.SENDING,
|
|
332
|
-
timestamp: new Date(),
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
const result = await service.sendMessage(message);
|
|
336
|
-
|
|
337
|
-
expect(result.success).toBe(true);
|
|
338
|
-
expect(result.message?.status).toBe(MessageStatus.WAITING_SESSION);
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
// ============================================================================
|
|
344
|
-
// Invalid contactUserId Validation
|
|
345
|
-
// ============================================================================
|
|
346
|
-
|
|
347
|
-
const EDGE_OWNER_USER_ID = encodeUserId(new Uint8Array(32).fill(1));
|
|
348
|
-
const EDGE_CONTACT_USER_ID = encodeUserId(new Uint8Array(32).fill(2));
|
|
349
|
-
const EDGE_SEEKER_SIZE = 34;
|
|
350
|
-
|
|
351
|
-
function createEdgeSession(
|
|
352
|
-
status: SessionStatus = SessionStatus.Active
|
|
353
|
-
): SessionModule {
|
|
354
|
-
return {
|
|
355
|
-
peerSessionStatus: vi.fn().mockReturnValue(status),
|
|
356
|
-
sendMessage: vi.fn().mockResolvedValue({
|
|
357
|
-
seeker: new Uint8Array(EDGE_SEEKER_SIZE).fill(1),
|
|
358
|
-
data: new Uint8Array([1, 2, 3, 4]),
|
|
359
|
-
}),
|
|
360
|
-
feedIncomingMessageBoardRead: vi.fn(),
|
|
361
|
-
refresh: vi.fn().mockResolvedValue([]),
|
|
362
|
-
feedIncomingAnnouncement: vi.fn(),
|
|
363
|
-
establishOutgoingSession: vi
|
|
364
|
-
.fn()
|
|
365
|
-
.mockResolvedValue(new Uint8Array([1, 2, 3])),
|
|
366
|
-
toEncryptedBlob: vi.fn(),
|
|
367
|
-
userIdEncoded: EDGE_OWNER_USER_ID,
|
|
368
|
-
userIdRaw: new Uint8Array(32).fill(1),
|
|
369
|
-
userId: new Uint8Array(32).fill(1),
|
|
370
|
-
getMessageBoardReadKeys: vi.fn().mockReturnValue([]),
|
|
371
|
-
cleanup: vi.fn(),
|
|
372
|
-
} as unknown as SessionModule;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function createEdgeMessageProtocol(): IMessageProtocol {
|
|
376
|
-
return {
|
|
377
|
-
fetchMessages: vi.fn().mockResolvedValue([]),
|
|
378
|
-
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
379
|
-
sendAnnouncement: vi.fn().mockResolvedValue('counter-123'),
|
|
380
|
-
fetchAnnouncements: vi.fn().mockResolvedValue([]),
|
|
381
|
-
fetchPublicKeyByUserId: vi.fn().mockResolvedValue(''),
|
|
382
|
-
postPublicKey: vi.fn().mockResolvedValue(''),
|
|
383
|
-
changeNode: vi.fn().mockResolvedValue({ success: true }),
|
|
384
|
-
} as IMessageProtocol;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function createEdgeDiscussionService(): DiscussionService {
|
|
388
|
-
return {
|
|
389
|
-
isStableState: vi.fn().mockResolvedValue(true),
|
|
390
|
-
initialize: vi.fn(),
|
|
391
|
-
accept: vi.fn(),
|
|
392
|
-
renew: vi.fn(),
|
|
393
|
-
} as unknown as DiscussionService;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
describe('Invalid contactUserId Validation', () => {
|
|
397
|
-
let testDb: GossipDatabase;
|
|
398
|
-
let mockSession: SessionModule;
|
|
399
|
-
let mockProtocol: IMessageProtocol;
|
|
400
|
-
let mockDiscussionService: DiscussionService;
|
|
401
|
-
let messageService: MessageService;
|
|
402
|
-
|
|
403
|
-
beforeEach(async () => {
|
|
404
|
-
testDb = new GossipDatabase();
|
|
405
|
-
await testDb.open();
|
|
406
|
-
await Promise.all(testDb.tables.map(table => table.clear()));
|
|
407
|
-
|
|
408
|
-
mockSession = createEdgeSession();
|
|
409
|
-
mockProtocol = createEdgeMessageProtocol();
|
|
410
|
-
mockDiscussionService = createEdgeDiscussionService();
|
|
411
|
-
|
|
412
|
-
messageService = new MessageService(
|
|
413
|
-
testDb,
|
|
414
|
-
mockProtocol,
|
|
415
|
-
mockSession,
|
|
416
|
-
mockDiscussionService,
|
|
417
|
-
{},
|
|
418
|
-
defaultSdkConfig
|
|
419
|
-
);
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
it('should fail when no contact or discussion exists', async () => {
|
|
423
|
-
const result = await messageService.sendMessage({
|
|
424
|
-
ownerUserId: EDGE_OWNER_USER_ID,
|
|
425
|
-
contactUserId: EDGE_CONTACT_USER_ID,
|
|
426
|
-
content: 'Test message',
|
|
427
|
-
type: MessageType.TEXT,
|
|
428
|
-
direction: MessageDirection.OUTGOING,
|
|
429
|
-
status: MessageStatus.SENDING,
|
|
430
|
-
timestamp: new Date(),
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
expect(result.success).toBe(false);
|
|
434
|
-
expect(result.error).toContain('not found');
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
it('should fail when discussion not found', async () => {
|
|
438
|
-
await testDb.contacts.add({
|
|
439
|
-
ownerUserId: EDGE_OWNER_USER_ID,
|
|
440
|
-
userId: EDGE_CONTACT_USER_ID,
|
|
441
|
-
name: 'Test Contact',
|
|
442
|
-
publicKeys: new Uint8Array(32),
|
|
443
|
-
isOnline: true,
|
|
444
|
-
lastSeen: new Date(),
|
|
445
|
-
createdAt: new Date(),
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
const result = await messageService.sendMessage({
|
|
449
|
-
ownerUserId: EDGE_OWNER_USER_ID,
|
|
450
|
-
contactUserId: EDGE_CONTACT_USER_ID,
|
|
451
|
-
content: 'Test message',
|
|
452
|
-
type: MessageType.TEXT,
|
|
453
|
-
direction: MessageDirection.OUTGOING,
|
|
454
|
-
status: MessageStatus.SENDING,
|
|
455
|
-
timestamp: new Date(),
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
expect(result.success).toBe(false);
|
|
459
|
-
expect(result.error).toContain('Discussion not found');
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
it('should succeed when both contact and discussion exist', async () => {
|
|
463
|
-
await testDb.contacts.add({
|
|
464
|
-
ownerUserId: EDGE_OWNER_USER_ID,
|
|
465
|
-
userId: EDGE_CONTACT_USER_ID,
|
|
466
|
-
name: 'Test Contact',
|
|
467
|
-
publicKeys: new Uint8Array(32),
|
|
468
|
-
isOnline: true,
|
|
469
|
-
lastSeen: new Date(),
|
|
470
|
-
createdAt: new Date(),
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
await testDb.discussions.add({
|
|
474
|
-
ownerUserId: EDGE_OWNER_USER_ID,
|
|
475
|
-
contactUserId: EDGE_CONTACT_USER_ID,
|
|
476
|
-
direction: DiscussionDirection.INITIATED,
|
|
477
|
-
status: DiscussionStatus.ACTIVE,
|
|
478
|
-
unreadCount: 0,
|
|
479
|
-
createdAt: new Date(),
|
|
480
|
-
updatedAt: new Date(),
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
const result = await messageService.sendMessage({
|
|
484
|
-
ownerUserId: EDGE_OWNER_USER_ID,
|
|
485
|
-
contactUserId: EDGE_CONTACT_USER_ID,
|
|
486
|
-
content: 'Test message',
|
|
487
|
-
type: MessageType.TEXT,
|
|
488
|
-
direction: MessageDirection.OUTGOING,
|
|
489
|
-
status: MessageStatus.SENDING,
|
|
490
|
-
timestamp: new Date(),
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
expect(result.success).toBe(true);
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
// ============================================================================
|
|
498
|
-
// Session Encryption Error and Network Error handling
|
|
499
|
-
// ============================================================================
|
|
500
|
-
|
|
501
|
-
const GAP_OWNER_USER_ID = encodeUserId(new Uint8Array(32).fill(1));
|
|
502
|
-
const GAP_CONTACT_USER_ID = encodeUserId(new Uint8Array(32).fill(2));
|
|
503
|
-
const GAP_SEEKER_SIZE = 34;
|
|
504
|
-
|
|
505
|
-
function createGapSession(
|
|
506
|
-
status: SessionStatus = SessionStatus.Active
|
|
507
|
-
): SessionModule {
|
|
508
|
-
return {
|
|
509
|
-
peerSessionStatus: vi.fn().mockReturnValue(status),
|
|
510
|
-
sendMessage: vi.fn().mockReturnValue({
|
|
511
|
-
seeker: new Uint8Array(GAP_SEEKER_SIZE).fill(1),
|
|
512
|
-
data: new Uint8Array([1, 2, 3, 4]),
|
|
513
|
-
}),
|
|
514
|
-
receiveMessage: vi.fn(),
|
|
515
|
-
refresh: vi.fn().mockResolvedValue([]),
|
|
516
|
-
receiveAnnouncement: vi.fn(),
|
|
517
|
-
establishOutgoingSession: vi
|
|
518
|
-
.fn()
|
|
519
|
-
.mockResolvedValue(new Uint8Array([1, 2, 3])),
|
|
520
|
-
toEncryptedBlob: vi.fn(),
|
|
521
|
-
userIdEncoded: GAP_OWNER_USER_ID,
|
|
522
|
-
userIdRaw: new Uint8Array(32).fill(1),
|
|
523
|
-
userId: new Uint8Array(32).fill(1),
|
|
524
|
-
getMessageBoardReadKeys: vi.fn().mockReturnValue([]),
|
|
525
|
-
cleanup: vi.fn(),
|
|
526
|
-
} as unknown as SessionModule;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function createGapMessageProtocol(): IMessageProtocol {
|
|
530
|
-
return {
|
|
531
|
-
fetchMessages: vi.fn().mockResolvedValue([]),
|
|
532
|
-
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
533
|
-
sendAnnouncement: vi.fn().mockResolvedValue('counter-123'),
|
|
534
|
-
fetchAnnouncements: vi.fn().mockResolvedValue([]),
|
|
535
|
-
fetchPublicKeyByUserId: vi.fn().mockResolvedValue(''),
|
|
536
|
-
postPublicKey: vi.fn().mockResolvedValue(''),
|
|
537
|
-
changeNode: vi.fn().mockResolvedValue({ success: true }),
|
|
538
|
-
} as IMessageProtocol;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function createGapDiscussionService(): DiscussionService {
|
|
542
|
-
return {
|
|
543
|
-
isStableState: vi.fn().mockResolvedValue(true),
|
|
544
|
-
initialize: vi.fn(),
|
|
545
|
-
accept: vi.fn(),
|
|
546
|
-
renew: vi.fn(),
|
|
547
|
-
} as unknown as DiscussionService;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
describe('Session Encryption Error marks Discussion BROKEN', () => {
|
|
551
|
-
let testDb: GossipDatabase;
|
|
552
|
-
let mockSession: SessionModule;
|
|
553
|
-
let mockProtocol: IMessageProtocol;
|
|
554
|
-
let mockDiscussionService: DiscussionService;
|
|
555
|
-
let messageService: MessageService;
|
|
556
|
-
|
|
557
|
-
beforeEach(async () => {
|
|
558
|
-
testDb = new GossipDatabase();
|
|
559
|
-
await testDb.open();
|
|
560
|
-
await Promise.all(testDb.tables.map(table => table.clear()));
|
|
561
|
-
|
|
562
|
-
mockSession = createGapSession();
|
|
563
|
-
mockProtocol = createGapMessageProtocol();
|
|
564
|
-
mockDiscussionService = createGapDiscussionService();
|
|
565
|
-
|
|
566
|
-
await testDb.contacts.add({
|
|
567
|
-
ownerUserId: GAP_OWNER_USER_ID,
|
|
568
|
-
userId: GAP_CONTACT_USER_ID,
|
|
569
|
-
name: 'Test Contact',
|
|
570
|
-
publicKeys: new Uint8Array(32),
|
|
571
|
-
isOnline: true,
|
|
572
|
-
lastSeen: new Date(),
|
|
573
|
-
createdAt: new Date(),
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
await testDb.discussions.add({
|
|
577
|
-
ownerUserId: GAP_OWNER_USER_ID,
|
|
578
|
-
contactUserId: GAP_CONTACT_USER_ID,
|
|
579
|
-
direction: DiscussionDirection.INITIATED,
|
|
580
|
-
status: DiscussionStatus.ACTIVE,
|
|
581
|
-
unreadCount: 0,
|
|
582
|
-
createdAt: new Date(),
|
|
583
|
-
updatedAt: new Date(),
|
|
584
|
-
});
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
it('should mark discussion as BROKEN when encryption fails', async () => {
|
|
588
|
-
(mockSession.sendMessage as ReturnType<typeof vi.fn>).mockImplementation(
|
|
589
|
-
() => {
|
|
590
|
-
throw new Error('Encryption failed: invalid session state');
|
|
591
|
-
}
|
|
592
|
-
);
|
|
593
|
-
|
|
594
|
-
const onMessageFailed = vi.fn();
|
|
595
|
-
|
|
596
|
-
messageService = new MessageService(
|
|
597
|
-
testDb,
|
|
598
|
-
mockProtocol,
|
|
599
|
-
mockSession,
|
|
600
|
-
mockDiscussionService,
|
|
601
|
-
{ onMessageFailed },
|
|
602
|
-
defaultSdkConfig
|
|
603
|
-
);
|
|
604
|
-
|
|
605
|
-
const result = await messageService.sendMessage({
|
|
606
|
-
ownerUserId: GAP_OWNER_USER_ID,
|
|
607
|
-
contactUserId: GAP_CONTACT_USER_ID,
|
|
608
|
-
content: 'Test message',
|
|
609
|
-
type: MessageType.TEXT,
|
|
610
|
-
direction: MessageDirection.OUTGOING,
|
|
611
|
-
status: MessageStatus.SENDING,
|
|
612
|
-
timestamp: new Date(),
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
expect(result.success).toBe(false);
|
|
616
|
-
expect(result.error).toBe('Session error');
|
|
617
|
-
|
|
618
|
-
const discussion = await testDb.getDiscussionByOwnerAndContact(
|
|
619
|
-
GAP_OWNER_USER_ID,
|
|
620
|
-
GAP_CONTACT_USER_ID
|
|
621
|
-
);
|
|
622
|
-
expect(discussion?.status).toBe(DiscussionStatus.BROKEN);
|
|
623
|
-
|
|
624
|
-
expect(onMessageFailed).toHaveBeenCalled();
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
it('should mark message as FAILED when encryption fails', async () => {
|
|
628
|
-
(mockSession.sendMessage as ReturnType<typeof vi.fn>).mockImplementation(
|
|
629
|
-
() => {
|
|
630
|
-
throw new Error('Encryption error');
|
|
631
|
-
}
|
|
632
|
-
);
|
|
633
|
-
|
|
634
|
-
messageService = new MessageService(
|
|
635
|
-
testDb,
|
|
636
|
-
mockProtocol,
|
|
637
|
-
mockSession,
|
|
638
|
-
mockDiscussionService,
|
|
639
|
-
{},
|
|
640
|
-
defaultSdkConfig
|
|
641
|
-
);
|
|
642
|
-
|
|
643
|
-
const result = await messageService.sendMessage({
|
|
644
|
-
ownerUserId: GAP_OWNER_USER_ID,
|
|
645
|
-
contactUserId: GAP_CONTACT_USER_ID,
|
|
646
|
-
content: 'Test message',
|
|
647
|
-
type: MessageType.TEXT,
|
|
648
|
-
direction: MessageDirection.OUTGOING,
|
|
649
|
-
status: MessageStatus.SENDING,
|
|
650
|
-
timestamp: new Date(),
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
expect(result.success).toBe(false);
|
|
654
|
-
expect(result.message?.status).toBe(MessageStatus.FAILED);
|
|
655
|
-
});
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
describe('Network Error Preserves Encrypted Message', () => {
|
|
659
|
-
let testDb: GossipDatabase;
|
|
660
|
-
let mockSession: SessionModule;
|
|
661
|
-
let mockProtocol: IMessageProtocol;
|
|
662
|
-
let mockDiscussionService: DiscussionService;
|
|
663
|
-
let messageService: MessageService;
|
|
664
|
-
|
|
665
|
-
const mockSeeker = new Uint8Array(GAP_SEEKER_SIZE).fill(123);
|
|
666
|
-
const mockEncryptedData = new Uint8Array([10, 20, 30, 40, 50]);
|
|
667
|
-
|
|
668
|
-
beforeEach(async () => {
|
|
669
|
-
testDb = new GossipDatabase();
|
|
670
|
-
await testDb.open();
|
|
671
|
-
await Promise.all(testDb.tables.map(table => table.clear()));
|
|
672
|
-
|
|
673
|
-
mockSession = createGapSession();
|
|
674
|
-
mockProtocol = createGapMessageProtocol();
|
|
675
|
-
mockDiscussionService = createGapDiscussionService();
|
|
676
|
-
|
|
677
|
-
(mockSession.sendMessage as ReturnType<typeof vi.fn>).mockReturnValue({
|
|
678
|
-
seeker: mockSeeker,
|
|
679
|
-
data: mockEncryptedData,
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
await testDb.contacts.add({
|
|
683
|
-
ownerUserId: GAP_OWNER_USER_ID,
|
|
684
|
-
userId: GAP_CONTACT_USER_ID,
|
|
685
|
-
name: 'Test Contact',
|
|
686
|
-
publicKeys: new Uint8Array(32),
|
|
687
|
-
isOnline: true,
|
|
688
|
-
lastSeen: new Date(),
|
|
689
|
-
createdAt: new Date(),
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
await testDb.discussions.add({
|
|
693
|
-
ownerUserId: GAP_OWNER_USER_ID,
|
|
694
|
-
contactUserId: GAP_CONTACT_USER_ID,
|
|
695
|
-
direction: DiscussionDirection.INITIATED,
|
|
696
|
-
status: DiscussionStatus.ACTIVE,
|
|
697
|
-
unreadCount: 0,
|
|
698
|
-
createdAt: new Date(),
|
|
699
|
-
updatedAt: new Date(),
|
|
700
|
-
});
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
it('should preserve encryptedMessage when network send fails', async () => {
|
|
704
|
-
(mockProtocol.sendMessage as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
705
|
-
new Error('Network error: connection refused')
|
|
706
|
-
);
|
|
707
|
-
|
|
708
|
-
messageService = new MessageService(
|
|
709
|
-
testDb,
|
|
710
|
-
mockProtocol,
|
|
711
|
-
mockSession,
|
|
712
|
-
mockDiscussionService,
|
|
713
|
-
{},
|
|
714
|
-
defaultSdkConfig
|
|
715
|
-
);
|
|
716
|
-
|
|
717
|
-
const result = await messageService.sendMessage({
|
|
718
|
-
ownerUserId: GAP_OWNER_USER_ID,
|
|
719
|
-
contactUserId: GAP_CONTACT_USER_ID,
|
|
720
|
-
content: 'Test message',
|
|
721
|
-
type: MessageType.TEXT,
|
|
722
|
-
direction: MessageDirection.OUTGOING,
|
|
723
|
-
status: MessageStatus.SENDING,
|
|
724
|
-
timestamp: new Date(),
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
expect(result.success).toBe(false);
|
|
728
|
-
|
|
729
|
-
const messages = await testDb.messages
|
|
730
|
-
.where('[ownerUserId+contactUserId]')
|
|
731
|
-
.equals([GAP_OWNER_USER_ID, GAP_CONTACT_USER_ID])
|
|
732
|
-
.toArray();
|
|
733
|
-
|
|
734
|
-
expect(messages.length).toBe(1);
|
|
735
|
-
const message = messages[0];
|
|
736
|
-
|
|
737
|
-
expect(message.encryptedMessage).toEqual(mockEncryptedData);
|
|
738
|
-
expect(message.seeker).toEqual(mockSeeker);
|
|
739
|
-
expect(message.status).toBe(MessageStatus.FAILED);
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
it('should NOT mark discussion as BROKEN on network error', async () => {
|
|
743
|
-
(mockProtocol.sendMessage as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
744
|
-
new Error('Network timeout')
|
|
745
|
-
);
|
|
746
|
-
|
|
747
|
-
messageService = new MessageService(
|
|
748
|
-
testDb,
|
|
749
|
-
mockProtocol,
|
|
750
|
-
mockSession,
|
|
751
|
-
mockDiscussionService,
|
|
752
|
-
{},
|
|
753
|
-
defaultSdkConfig
|
|
754
|
-
);
|
|
755
|
-
|
|
756
|
-
await messageService.sendMessage({
|
|
757
|
-
ownerUserId: GAP_OWNER_USER_ID,
|
|
758
|
-
contactUserId: GAP_CONTACT_USER_ID,
|
|
759
|
-
content: 'Test message',
|
|
760
|
-
type: MessageType.TEXT,
|
|
761
|
-
direction: MessageDirection.OUTGOING,
|
|
762
|
-
status: MessageStatus.SENDING,
|
|
763
|
-
timestamp: new Date(),
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
const discussion = await testDb.getDiscussionByOwnerAndContact(
|
|
767
|
-
GAP_OWNER_USER_ID,
|
|
768
|
-
GAP_CONTACT_USER_ID
|
|
769
|
-
);
|
|
770
|
-
expect(discussion?.status).toBe(DiscussionStatus.ACTIVE);
|
|
771
|
-
});
|
|
772
|
-
|
|
773
|
-
it('should allow resend without re-encryption when encrypted data preserved', async () => {
|
|
774
|
-
(
|
|
775
|
-
mockProtocol.sendMessage as ReturnType<typeof vi.fn>
|
|
776
|
-
).mockRejectedValueOnce(new Error('Network error'));
|
|
777
|
-
|
|
778
|
-
messageService = new MessageService(
|
|
779
|
-
testDb,
|
|
780
|
-
mockProtocol,
|
|
781
|
-
mockSession,
|
|
782
|
-
mockDiscussionService,
|
|
783
|
-
{},
|
|
784
|
-
defaultSdkConfig
|
|
785
|
-
);
|
|
786
|
-
|
|
787
|
-
await messageService.sendMessage({
|
|
788
|
-
ownerUserId: GAP_OWNER_USER_ID,
|
|
789
|
-
contactUserId: GAP_CONTACT_USER_ID,
|
|
790
|
-
content: 'Test message',
|
|
791
|
-
type: MessageType.TEXT,
|
|
792
|
-
direction: MessageDirection.OUTGOING,
|
|
793
|
-
status: MessageStatus.SENDING,
|
|
794
|
-
timestamp: new Date(),
|
|
795
|
-
});
|
|
796
|
-
|
|
797
|
-
const messages = await testDb.messages.toArray();
|
|
798
|
-
expect(messages.length).toBe(1);
|
|
799
|
-
const failedMessage = messages[0];
|
|
800
|
-
|
|
801
|
-
expect(failedMessage.encryptedMessage).toBeDefined();
|
|
802
|
-
expect(failedMessage.seeker).toBeDefined();
|
|
803
|
-
|
|
804
|
-
(
|
|
805
|
-
mockProtocol.sendMessage as ReturnType<typeof vi.fn>
|
|
806
|
-
).mockResolvedValueOnce(undefined);
|
|
807
|
-
|
|
808
|
-
const messageMap = new Map([[GAP_CONTACT_USER_ID, [failedMessage]]]);
|
|
809
|
-
await messageService.resendMessages(messageMap);
|
|
810
|
-
|
|
811
|
-
expect(mockProtocol.sendMessage).toHaveBeenLastCalledWith({
|
|
812
|
-
seeker: failedMessage.seeker,
|
|
813
|
-
ciphertext: failedMessage.encryptedMessage,
|
|
814
|
-
});
|
|
815
|
-
expect(mockSession.sendMessage).toHaveBeenCalledTimes(1);
|
|
816
|
-
});
|
|
817
|
-
});
|