Files
Toju/toju-app/src/app/domains/direct-message/application/services/direct-message.service.spec.ts
Myx baa350e90a fix: Bug - Users doesn't receive dm messages
Match direct messages against every local identity alias (home id and provisioned signal-server actor ids) so recipients accept traffic addressed to their per-server presence id instead of silently dropping it.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 20:59:57 +02:00

198 lines
7.2 KiB
TypeScript

import {
advanceDirectMessageStatus,
createDirectConversation,
createGroupConversation,
directMessageEventIncludesUser,
directMessageSyncIncludesUser,
directMessageConversationIncludesUser,
createDirectCallStartedMessage,
getDirectConversationId,
isGroupDirectConversation,
updateMessageStatusInConversation,
upsertDirectMessage
} from '../../domain/logic/direct-message.logic';
import type { DirectMessage, DirectMessageParticipant } from '../../domain/models/direct-message.model';
const alice: DirectMessageParticipant = {
userId: 'alice',
username: 'alice',
displayName: 'Alice'
};
const bob: DirectMessageParticipant = {
userId: 'bob',
username: 'bob',
displayName: 'Bob'
};
const charlie: DirectMessageParticipant = {
userId: 'charlie',
username: 'charlie',
displayName: 'Charlie'
};
describe('DirectMessageService domain flow', () => {
it('should create conversation', () => {
const conversation = createDirectConversation(alice, bob, 10);
expect(conversation.id).toBe(getDirectConversationId('alice', 'bob'));
expect(conversation.participants).toEqual(['alice', 'bob']);
expect(conversation.unreadCount).toBe(0);
});
it('should send message', () => {
const conversation = createDirectConversation(alice, bob, 10);
const queuedMessage = createMessage('message-1', 'QUEUED');
const withQueuedMessage = upsertDirectMessage(conversation, queuedMessage, false);
const withSentMessage = updateMessageStatusInConversation(withQueuedMessage, queuedMessage.id, 'SENT');
expect(withSentMessage.messages[0].status).toBe('SENT');
});
it('should queue message when offline', () => {
const conversation = createDirectConversation(alice, bob, 10);
const queuedMessage = createMessage('message-1', 'QUEUED');
const updatedConversation = upsertDirectMessage(conversation, queuedMessage, false);
expect(updatedConversation.messages[0].status).toBe('QUEUED');
});
it('should create empty group conversation without direct-message history', () => {
const directConversation = upsertDirectMessage(createDirectConversation(alice, bob, 10), createMessage('message-1', 'SENT'), false);
const groupConversation = createGroupConversation('dm-group-test', [
alice,
bob,
charlie
], 30, 'Alice, Bob, Charlie');
expect(isGroupDirectConversation(groupConversation)).toBe(true);
expect(groupConversation.id).toBe('dm-group-test');
expect(groupConversation.title).toBe('Alice, Bob, Charlie');
expect(groupConversation.participants).toEqual([
'alice',
'bob',
'charlie'
]);
expect(groupConversation.messages).toEqual([]);
expect(directConversation.messages).toHaveLength(1);
});
it('should preserve group message recipient metadata', () => {
const conversation = createGroupConversation('dm-group-test', [
alice,
bob,
charlie
], 10);
const recipientIds = ['bob', 'charlie'];
const message = createMessage('message-1', 'QUEUED', conversation.id, recipientIds);
const updatedConversation = upsertDirectMessage(conversation, message, false);
expect(updatedConversation.messages[0].recipientId).toBe('bob');
expect(updatedConversation.messages[0].recipientIds).toEqual(recipientIds);
});
it('does not increment unread when an existing message is upserted again', () => {
const conversation = createDirectConversation(alice, bob, 10);
const message = createMessage('message-1', 'SENT');
const withUnreadMessage = upsertDirectMessage(conversation, message, true);
const withDuplicateMessage = upsertDirectMessage(withUnreadMessage, { ...message, status: 'DELIVERED' }, true);
expect(withDuplicateMessage.messages).toHaveLength(1);
expect(withDuplicateMessage.unreadCount).toBe(1);
expect(withDuplicateMessage.messages[0].status).toBe('DELIVERED');
});
it('does not increment unread for call-started system messages', () => {
const conversation = createDirectConversation(alice, bob, 0);
const message = createDirectCallStartedMessage(conversation.id, alice, ['bob'], 123);
const withSystemMessage = upsertDirectMessage(conversation, message, true);
expect(withSystemMessage.unreadCount).toBe(0);
expect(withSystemMessage.messages).toHaveLength(1);
});
it('creates call-started system messages that do not read like normal text', () => {
const message = createDirectCallStartedMessage(getDirectConversationId('alice', 'bob'), alice, ['bob'], 123);
expect(message.id).toBe(`dm-call-started-${getDirectConversationId('alice', 'bob')}-123`);
expect(message.kind).toBe('system');
expect(message.systemEvent).toBe('call-started');
expect(message.content).toBe('Alice started a call');
expect(message.status).toBe('DELIVERED');
});
it('should update status correctly', () => {
expect(advanceDirectMessageStatus('QUEUED', 'SENT')).toBe('SENT');
expect(advanceDirectMessageStatus('SENT', 'DELIVERED')).toBe('DELIVERED');
expect(advanceDirectMessageStatus('DELIVERED', 'SENT')).toBe('DELIVERED');
expect(advanceDirectMessageStatus('DELIVERED', 'ACKNOWLEDGED')).toBe('ACKNOWLEDGED');
});
it('recognises only declared direct-message recipients and participants', () => {
const payload = {
message: createMessage('message-1', 'SENT', 'dm-group-test', ['bob']),
participants: [alice, bob],
sender: alice
};
expect(directMessageEventIncludesUser(payload, 'bob')).toBe(true);
expect(directMessageEventIncludesUser(payload, 'charlie')).toBe(false);
});
it('recognises direct-message recipients across identity aliases', () => {
const payload = {
message: createMessage('message-1', 'SENT', getDirectConversationId('alice', 'bob-foreign'), ['bob-foreign']),
participants: [alice, { ...bob, userId: 'bob-foreign' }],
sender: alice
};
const bobIds = new Set(['bob', 'bob-foreign']);
expect(directMessageEventIncludesUser(payload, bobIds)).toBe(true);
expect(directMessageEventIncludesUser(payload, 'bob')).toBe(false);
});
it('recognises conversation participants across identity aliases', () => {
const conversation = {
...createDirectConversation(alice, bob, 10),
participants: ['alice', 'bob-foreign'],
participantProfiles: {
alice,
'bob-foreign': { ...bob, userId: 'bob-foreign' }
}
};
expect(directMessageConversationIncludesUser(conversation, new Set(['bob', 'bob-foreign']))).toBe(true);
expect(directMessageConversationIncludesUser(conversation, 'bob')).toBe(false);
});
it('recognises only declared sync participants', () => {
const payload = {
conversationId: 'dm-group-test',
messages: [],
participants: [alice, bob],
sender: alice,
syncedAt: 30
};
expect(directMessageSyncIncludesUser(payload, 'alice')).toBe(true);
expect(directMessageSyncIncludesUser(payload, 'charlie')).toBe(false);
});
});
function createMessage(
id: string,
status: DirectMessage['status'],
conversationId = getDirectConversationId('alice', 'bob'),
recipientIds = ['bob']
): DirectMessage {
return {
id,
conversationId,
senderId: 'alice',
recipientId: recipientIds[0],
recipientIds,
content: 'Hello',
timestamp: 20,
status
};
}