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
132 lines
3.9 KiB
TypeScript
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
|
|
};
|
|
}
|