Files
Toju/toju-app/src/app/domains/direct-message/application/services/peer-delivery.service.spec.ts
Myx a49e18b9f0
All checks were successful
Queue Release Build / prepare (push) Successful in 18s
Deploy Web Apps / deploy (push) Successful in 6m32s
Queue Release Build / build-windows (push) Successful in 26m8s
Queue Release Build / build-linux (push) Successful in 40m18s
Queue Release Build / finalize (push) Successful in 42s
fix: recurriing network issue
2026-04-30 04:04:34 +02:00

132 lines
3.9 KiB
TypeScript

import {
Injector,
runInInjectionContext,
signal
} from '@angular/core';
import { Store } from '@ngrx/store';
import { Subject } from 'rxjs';
import { RealtimeSessionFacade } from '../../../../core/realtime';
import { selectAllUsers } from '../../../../store/users/users.selectors';
import type { ChatEvent, User } from '../../../../shared-kernel';
import { PeerDeliveryService } from './peer-delivery.service';
describe('PeerDeliveryService', () => {
it('relays direct messages through signaling when no data channel is connected', () => {
const context = createServiceContext({ connectedPeers: [], routedPeers: ['bob'] });
const event: ChatEvent = {
type: 'direct-message',
directMessage: {
message: {
id: 'message-1',
conversationId: 'dm-alice-bob',
senderId: 'alice',
recipientId: 'bob',
content: 'hello',
timestamp: 1,
status: 'QUEUED'
},
sender: {
userId: 'alice',
username: 'alice',
displayName: 'Alice'
}
}
};
expect(context.service.sendViaWebRTC('bob', event)).toBe(true);
expect(context.realtime.sendToPeer).not.toHaveBeenCalled();
expect(context.realtime.sendRawMessage).toHaveBeenCalledWith({
...event,
targetUserId: 'bob'
});
});
it('keeps messages queued when neither P2P nor signaling can reach the recipient', () => {
const context = createServiceContext({ connectedPeers: [], routedPeers: [] });
expect(context.service.sendViaWebRTC('bob', { type: 'direct-message' })).toBe(false);
expect(context.realtime.sendRawMessage).not.toHaveBeenCalled();
});
it('emits direct messages received over signaling', () => {
const context = createServiceContext({ connectedPeers: [] });
const received: ChatEvent[] = [];
context.service.directMessageEvents$.subscribe((event) => received.push(event));
context.signalingMessages.next({ type: 'direct-message' } as ChatEvent);
expect(received).toEqual([{ type: 'direct-message' }]);
});
});
interface ServiceContextOptions {
connectedPeers: string[];
routedPeers?: string[];
}
interface ServiceContext {
service: PeerDeliveryService;
signalingMessages: Subject<ChatEvent>;
realtime: {
getConnectedPeers: ReturnType<typeof vi.fn>;
hasSignalingRouteForPeer: ReturnType<typeof vi.fn>;
sendRawMessage: ReturnType<typeof vi.fn>;
sendToPeer: ReturnType<typeof vi.fn>;
};
}
function createServiceContext(options: ServiceContextOptions): ServiceContext {
const users = signal<User[]>([createUser('alice', 'Alice'), createUser('bob', 'Bob')]);
const incomingMessages = new Subject<ChatEvent>();
const signalingMessages = new Subject<ChatEvent>();
const peerConnected = new Subject<string>();
const realtime = {
onMessageReceived: incomingMessages.asObservable(),
onSignalingMessage: signalingMessages.asObservable(),
onPeerConnected: peerConnected.asObservable(),
getConnectedPeers: vi.fn(() => options.connectedPeers),
hasSignalingRouteForPeer: vi.fn((peerId: string) => (options.routedPeers ?? []).includes(peerId)),
sendRawMessage: vi.fn(),
sendToPeer: vi.fn()
};
const store = {
selectSignal: vi.fn((selector: unknown) => {
if (selector === selectAllUsers) {
return users;
}
throw new Error('Unexpected selector requested by PeerDeliveryService test.');
})
};
const injector = Injector.create({
providers: [
{
provide: RealtimeSessionFacade,
useValue: realtime
},
{
provide: Store,
useValue: store
}
]
});
return {
service: runInInjectionContext(injector, () => new PeerDeliveryService()),
signalingMessages,
realtime
};
}
function createUser(id: string, displayName: string): User {
return {
id,
oderId: id,
username: displayName.toLowerCase(),
displayName,
status: 'online',
role: 'member',
joinedAt: 1
};
}