Files
discord.js/packages/voice/__tests__/VoiceConnection.test.ts
2022-01-07 17:18:25 +01:00

770 lines
28 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/dot-notation */
import {
createVoiceConnection,
VoiceConnection,
VoiceConnectionConnectingState,
VoiceConnectionDisconnectReason,
VoiceConnectionReadyState,
VoiceConnectionSignallingState,
VoiceConnectionStatus,
} from '../src/VoiceConnection';
import * as _DataStore from '../src/DataStore';
import * as _Networking from '../src/networking/Networking';
import * as _AudioPlayer from '../src/audio/AudioPlayer';
import { PlayerSubscription as _PlayerSubscription } from '../src/audio/PlayerSubscription';
import type { DiscordGatewayAdapterLibraryMethods } from '../src/util/adapter';
import EventEmitter from 'node:events';
jest.mock('../audio/AudioPlayer');
jest.mock('../audio/PlayerSubscription');
jest.mock('../DataStore');
jest.mock('../networking/Networking');
const DataStore = _DataStore as unknown as jest.Mocked<typeof _DataStore>;
const Networking = _Networking as unknown as jest.Mocked<typeof _Networking>;
const AudioPlayer = _AudioPlayer as unknown as jest.Mocked<typeof _AudioPlayer>;
const PlayerSubscription = _PlayerSubscription as unknown as jest.Mock<_PlayerSubscription>;
Networking.Networking.mockImplementation(function mockedConstructor() {
this.state = {};
return this;
});
function createFakeAdapter() {
const sendPayload = jest.fn();
sendPayload.mockReturnValue(true);
const destroy = jest.fn();
const libMethods: Partial<DiscordGatewayAdapterLibraryMethods> = {};
return {
sendPayload,
destroy,
libMethods,
creator: jest.fn((methods) => {
Object.assign(libMethods, methods);
return {
sendPayload,
destroy,
};
}),
};
}
function createJoinConfig() {
return {
channelId: '1',
guildId: '2',
selfDeaf: true,
selfMute: false,
group: 'default',
};
}
function createFakeVoiceConnection() {
const adapter = createFakeAdapter();
const joinConfig = createJoinConfig();
const voiceConnection = new VoiceConnection(joinConfig, {
debug: false,
adapterCreator: adapter.creator,
});
return { adapter, joinConfig, voiceConnection };
}
beforeEach(() => {
DataStore.createJoinVoiceChannelPayload.mockReset();
DataStore.getVoiceConnection.mockReset();
DataStore.trackVoiceConnection.mockReset();
DataStore.untrackVoiceConnection.mockReset();
});
describe('createVoiceConnection', () => {
test('New voice connection', () => {
const mockPayload = Symbol('mock') as any;
DataStore.createJoinVoiceChannelPayload.mockImplementation(() => mockPayload);
const adapter = createFakeAdapter();
const joinConfig = createJoinConfig();
const voiceConnection = createVoiceConnection(joinConfig, {
debug: false,
adapterCreator: adapter.creator,
});
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Signalling);
expect(DataStore.getVoiceConnection).toHaveBeenCalledTimes(1);
expect(DataStore.trackVoiceConnection).toHaveBeenCalledWith(voiceConnection);
expect(DataStore.untrackVoiceConnection).not.toHaveBeenCalled();
expect(adapter.sendPayload).toHaveBeenCalledWith(mockPayload);
});
test('New voice connection with adapter failure', () => {
const mockPayload = Symbol('mock') as any;
DataStore.createJoinVoiceChannelPayload.mockImplementation(() => mockPayload);
const adapter = createFakeAdapter();
adapter.sendPayload.mockReturnValue(false);
const joinConfig = createJoinConfig();
const voiceConnection = createVoiceConnection(joinConfig, {
debug: false,
adapterCreator: adapter.creator,
});
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Disconnected);
expect(DataStore.getVoiceConnection).toHaveBeenCalledTimes(1);
expect(DataStore.trackVoiceConnection).toHaveBeenCalledWith(voiceConnection);
expect(DataStore.untrackVoiceConnection).not.toHaveBeenCalled();
expect(adapter.sendPayload).toHaveBeenCalledWith(mockPayload);
});
test('Reconfiguring existing connection', () => {
const mockPayload = Symbol('mock') as any;
DataStore.createJoinVoiceChannelPayload.mockImplementation(() => mockPayload);
const existingAdapter = createFakeAdapter();
const existingJoinConfig = createJoinConfig();
const existingVoiceConnection = new VoiceConnection(existingJoinConfig, {
debug: false,
adapterCreator: existingAdapter.creator,
});
const stateSetter = jest.spyOn(existingVoiceConnection, 'state', 'set');
DataStore.getVoiceConnection.mockImplementation((guildId) =>
guildId === existingJoinConfig.guildId ? existingVoiceConnection : null,
);
const newAdapter = createFakeAdapter();
const newJoinConfig = createJoinConfig();
const newVoiceConnection = createVoiceConnection(newJoinConfig, {
debug: false,
adapterCreator: newAdapter.creator,
});
expect(DataStore.getVoiceConnection).toHaveBeenCalledWith(newJoinConfig.guildId);
expect(DataStore.trackVoiceConnection).not.toHaveBeenCalled();
expect(DataStore.untrackVoiceConnection).not.toHaveBeenCalled();
expect(newAdapter.creator).not.toHaveBeenCalled();
expect(existingAdapter.sendPayload).toHaveBeenCalledWith(mockPayload);
expect(newVoiceConnection).toBe(existingVoiceConnection);
expect(stateSetter).not.toHaveBeenCalled();
});
test('Calls rejoin() on existing disconnected connection', () => {
const mockPayload = Symbol('mock') as any;
DataStore.createJoinVoiceChannelPayload.mockImplementation(() => mockPayload);
const existingAdapter = createFakeAdapter();
const existingJoinConfig = createJoinConfig();
const existingVoiceConnection = new VoiceConnection(existingJoinConfig, {
debug: false,
adapterCreator: existingAdapter.creator,
});
existingVoiceConnection.state = {
status: VoiceConnectionStatus.Disconnected,
adapter: existingAdapter,
reason: VoiceConnectionDisconnectReason.EndpointRemoved,
};
const rejoinSpy = jest.spyOn(existingVoiceConnection, 'rejoin');
DataStore.getVoiceConnection.mockImplementation((guildId) =>
guildId === existingJoinConfig.guildId ? existingVoiceConnection : null,
);
const newAdapter = createFakeAdapter();
const newJoinConfig = createJoinConfig();
const { guildId, group, ...rejoinConfig } = newJoinConfig;
const newVoiceConnection = createVoiceConnection(newJoinConfig, {
debug: false,
adapterCreator: newAdapter.creator,
});
expect(DataStore.getVoiceConnection).toHaveBeenCalledWith(newJoinConfig.guildId);
expect(DataStore.trackVoiceConnection).not.toHaveBeenCalled();
expect(DataStore.untrackVoiceConnection).not.toHaveBeenCalled();
expect(newAdapter.creator).not.toHaveBeenCalled();
expect(rejoinSpy).toHaveBeenCalledWith(rejoinConfig);
expect(newVoiceConnection).toBe(existingVoiceConnection);
});
test('Reconfiguring existing connection with adapter failure', () => {
const mockPayload = Symbol('mock') as any;
DataStore.createJoinVoiceChannelPayload.mockImplementation(() => mockPayload);
const existingAdapter = createFakeAdapter();
const existingJoinConfig = createJoinConfig();
const existingVoiceConnection = new VoiceConnection(existingJoinConfig, {
debug: false,
adapterCreator: existingAdapter.creator,
});
DataStore.getVoiceConnection.mockImplementation((guildId) =>
guildId === existingJoinConfig.guildId ? existingVoiceConnection : null,
);
const newAdapter = createFakeAdapter();
const newJoinConfig = createJoinConfig();
existingAdapter.sendPayload.mockReturnValue(false);
const newVoiceConnection = createVoiceConnection(newJoinConfig, {
debug: false,
adapterCreator: newAdapter.creator,
});
expect(DataStore.getVoiceConnection).toHaveBeenCalledWith(newJoinConfig.guildId);
expect(DataStore.trackVoiceConnection).not.toHaveBeenCalled();
expect(DataStore.untrackVoiceConnection).not.toHaveBeenCalled();
expect(newAdapter.creator).not.toHaveBeenCalled();
expect(existingAdapter.sendPayload).toHaveBeenCalledWith(mockPayload);
expect(newVoiceConnection).toBe(existingVoiceConnection);
expect(newVoiceConnection.state.status).toBe(VoiceConnectionStatus.Disconnected);
});
});
describe('VoiceConnection#addServerPacket', () => {
test('Stores the packet and attempts to configure networking', () => {
const { voiceConnection } = createFakeVoiceConnection();
voiceConnection.configureNetworking = jest.fn();
const dummy = {
endpoint: 'discord.com',
guild_id: 123,
token: 'abc',
} as any;
voiceConnection['addServerPacket'](dummy);
expect(voiceConnection['packets'].server).toBe(dummy);
expect(voiceConnection.configureNetworking).toHaveBeenCalled();
});
test('Overwrites existing packet', () => {
const { voiceConnection } = createFakeVoiceConnection();
voiceConnection['packets'].server = Symbol('old') as any;
voiceConnection.configureNetworking = jest.fn();
const dummy = {
endpoint: 'discord.com',
guild_id: 123,
token: 'abc',
} as any;
voiceConnection['addServerPacket'](dummy);
expect(voiceConnection['packets'].server).toBe(dummy);
expect(voiceConnection.configureNetworking).toHaveBeenCalled();
});
test('Disconnects when given a null endpoint', () => {
const { voiceConnection } = createFakeVoiceConnection();
voiceConnection['packets'].server = Symbol('old') as any;
voiceConnection.configureNetworking = jest.fn();
const dummy = {
endpoint: null,
guild_id: 123,
token: 'abc',
} as any;
voiceConnection['addServerPacket'](dummy);
expect(voiceConnection['packets'].server).toBe(dummy);
expect(voiceConnection.configureNetworking).not.toHaveBeenCalled();
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Disconnected);
});
});
describe('VoiceConnection#addStatePacket', () => {
test('State is assigned to joinConfig', () => {
const { voiceConnection } = createFakeVoiceConnection();
voiceConnection['addStatePacket']({
self_deaf: true,
self_mute: true,
channel_id: '123',
} as any);
expect(voiceConnection.joinConfig).toMatchObject({
selfDeaf: true,
selfMute: true,
channelId: '123',
});
voiceConnection['addStatePacket']({
self_mute: false,
} as any);
expect(voiceConnection.joinConfig).toMatchObject({
selfDeaf: true,
selfMute: false,
channelId: '123',
});
});
});
describe('VoiceConnection#configureNetworking', () => {
test('Only creates Networking instance when both packets are present and not destroyed', () => {
const { voiceConnection } = createFakeVoiceConnection();
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Signalling);
voiceConnection.configureNetworking();
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Signalling);
const adapter = (voiceConnection.state as VoiceConnectionSignallingState).adapter;
const state = {
session_id: 'abc',
user_id: '123',
} as any;
const server = {
endpoint: 'def',
guild_id: '123',
token: 'xyz',
} as any;
Object.assign(voiceConnection['packets'], { state, server: undefined });
voiceConnection.configureNetworking();
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Signalling);
expect(Networking.Networking).toHaveBeenCalledTimes(0);
Object.assign(voiceConnection['packets'], { state: undefined, server });
voiceConnection.configureNetworking();
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Signalling);
expect(Networking.Networking).toHaveBeenCalledTimes(0);
Object.assign(voiceConnection['packets'], { state, server });
voiceConnection.state = { status: VoiceConnectionStatus.Destroyed };
voiceConnection.configureNetworking();
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Destroyed);
expect(Networking.Networking).toHaveBeenCalledTimes(0);
voiceConnection.state = { status: VoiceConnectionStatus.Signalling, adapter };
voiceConnection.configureNetworking();
expect(Networking.Networking).toHaveBeenCalledTimes(1);
expect(Networking.Networking).toHaveBeenCalledWith(
{
endpoint: server.endpoint,
serverId: server.guild_id,
token: server.token,
sessionId: state.session_id,
userId: state.user_id,
},
false,
);
expect(voiceConnection.state).toMatchObject({
status: VoiceConnectionStatus.Connecting,
adapter,
});
expect((voiceConnection.state as unknown as VoiceConnectionConnectingState).networking).toBeInstanceOf(
Networking.Networking,
);
});
});
describe('VoiceConnection#onNetworkingClose', () => {
test('Does nothing in destroyed state', () => {
const { voiceConnection, adapter } = createFakeVoiceConnection();
voiceConnection.state = {
status: VoiceConnectionStatus.Destroyed,
};
voiceConnection['onNetworkingClose'](1000);
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Destroyed);
expect(adapter.sendPayload).not.toHaveBeenCalled();
});
test('Disconnects for code 4014', () => {
const { voiceConnection, adapter } = createFakeVoiceConnection();
voiceConnection['onNetworkingClose'](4014);
expect(voiceConnection.state).toMatchObject({
status: VoiceConnectionStatus.Disconnected,
closeCode: 4014,
});
expect(adapter.sendPayload).not.toHaveBeenCalled();
});
test('Attempts rejoin for codes != 4014', () => {
const dummyPayload = Symbol('dummy') as any;
const { voiceConnection, adapter, joinConfig } = createFakeVoiceConnection();
DataStore.createJoinVoiceChannelPayload.mockImplementation((config) =>
config === joinConfig ? dummyPayload : undefined,
);
voiceConnection['onNetworkingClose'](1234);
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Signalling);
expect(adapter.sendPayload).toHaveBeenCalledWith(dummyPayload);
expect(voiceConnection.rejoinAttempts).toBe(1);
});
test('Attempts rejoin for codes != 4014 (with adapter failure)', () => {
const dummyPayload = Symbol('dummy') as any;
const { voiceConnection, adapter, joinConfig } = createFakeVoiceConnection();
DataStore.createJoinVoiceChannelPayload.mockImplementation((config) =>
config === joinConfig ? dummyPayload : undefined,
);
adapter.sendPayload.mockReturnValue(false);
voiceConnection['onNetworkingClose'](1234);
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Disconnected);
expect(adapter.sendPayload).toHaveBeenCalledWith(dummyPayload);
expect(voiceConnection.rejoinAttempts).toBe(1);
});
});
describe('VoiceConnection#onNetworkingStateChange', () => {
test('Does nothing when status code identical', () => {
const { voiceConnection } = createFakeVoiceConnection();
const stateSetter = jest.spyOn(voiceConnection, 'state', 'set');
voiceConnection['onNetworkingStateChange'](
{ code: _Networking.NetworkingStatusCode.Ready } as any,
{ code: _Networking.NetworkingStatusCode.Ready } as any,
);
voiceConnection['onNetworkingStateChange'](
{ code: _Networking.NetworkingStatusCode.Closed } as any,
{ code: _Networking.NetworkingStatusCode.Closed } as any,
);
expect(stateSetter).not.toHaveBeenCalled();
});
test('Does nothing when not in Ready or Connecting states', () => {
const { voiceConnection } = createFakeVoiceConnection();
const stateSetter = jest.spyOn(voiceConnection, 'state', 'set');
const call = [
{ code: _Networking.NetworkingStatusCode.Ready } as any,
{ code: _Networking.NetworkingStatusCode.Closed } as any,
];
voiceConnection['_state'] = { status: VoiceConnectionStatus.Signalling } as any;
voiceConnection['onNetworkingStateChange'](call[0], call[1]);
voiceConnection['_state'] = { status: VoiceConnectionStatus.Disconnected } as any;
voiceConnection['onNetworkingStateChange'](call[0], call[1]);
voiceConnection['_state'] = { status: VoiceConnectionStatus.Destroyed } as any;
voiceConnection['onNetworkingStateChange'](call[0], call[1]);
expect(stateSetter).not.toHaveBeenCalled();
});
test('Transitions to Ready', () => {
const { voiceConnection } = createFakeVoiceConnection();
const stateSetter = jest.spyOn(voiceConnection, 'state', 'set');
voiceConnection['_state'] = {
...(voiceConnection.state as VoiceConnectionSignallingState),
status: VoiceConnectionStatus.Connecting,
networking: new Networking.Networking({} as any, false),
};
voiceConnection['onNetworkingStateChange'](
{ code: _Networking.NetworkingStatusCode.Closed } as any,
{ code: _Networking.NetworkingStatusCode.Ready } as any,
);
expect(stateSetter).toHaveBeenCalledTimes(1);
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Ready);
});
test('Transitions to Connecting', () => {
const { voiceConnection } = createFakeVoiceConnection();
const stateSetter = jest.spyOn(voiceConnection, 'state', 'set');
voiceConnection['_state'] = {
...(voiceConnection.state as VoiceConnectionSignallingState),
status: VoiceConnectionStatus.Connecting,
networking: new Networking.Networking({} as any, false),
};
voiceConnection['onNetworkingStateChange'](
{ code: _Networking.NetworkingStatusCode.Ready } as any,
{ code: _Networking.NetworkingStatusCode.Identifying } as any,
);
expect(stateSetter).toHaveBeenCalledTimes(1);
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Connecting);
});
});
describe('VoiceConnection#destroy', () => {
test('Throws when in Destroyed state', () => {
const { voiceConnection } = createFakeVoiceConnection();
voiceConnection.state = { status: VoiceConnectionStatus.Destroyed };
expect(() => voiceConnection.destroy()).toThrow();
});
test('Cleans up in a valid, destroyable state', () => {
const { voiceConnection, joinConfig, adapter } = createFakeVoiceConnection();
DataStore.getVoiceConnection.mockImplementation((guildId) =>
joinConfig.guildId === guildId ? voiceConnection : undefined,
);
const dummy = Symbol('dummy');
DataStore.createJoinVoiceChannelPayload.mockImplementation(() => dummy as any);
voiceConnection.destroy();
expect(DataStore.getVoiceConnection).toHaveReturnedWith(voiceConnection);
expect(DataStore.untrackVoiceConnection).toHaveBeenCalledWith(voiceConnection);
expect(DataStore.createJoinVoiceChannelPayload.mock.calls[0][0]).toMatchObject({
channelId: null,
guildId: joinConfig.guildId,
});
expect(adapter.sendPayload).toHaveBeenCalledWith(dummy);
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Destroyed);
});
});
describe('VoiceConnection#disconnect', () => {
test('Fails in Destroyed and Signalling states', () => {
const { voiceConnection, adapter } = createFakeVoiceConnection();
voiceConnection.state = { status: VoiceConnectionStatus.Destroyed };
expect(voiceConnection.disconnect()).toBe(false);
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Destroyed);
voiceConnection.state = { status: VoiceConnectionStatus.Signalling, adapter };
expect(voiceConnection.disconnect()).toBe(false);
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Signalling);
});
test('Disconnects - available adapter', () => {
const { voiceConnection, adapter } = createFakeVoiceConnection();
voiceConnection.state = {
status: VoiceConnectionStatus.Ready,
adapter,
networking: new Networking.Networking({} as any, false),
};
const leavePayload = Symbol('dummy');
DataStore.createJoinVoiceChannelPayload.mockImplementation(() => leavePayload as any);
expect(voiceConnection.disconnect()).toBe(true);
expect(voiceConnection.joinConfig).toMatchObject({
channelId: null,
guildId: '2',
selfDeaf: true,
selfMute: false,
});
expect(DataStore.createJoinVoiceChannelPayload).toHaveBeenCalledWith(voiceConnection.joinConfig);
expect(adapter.sendPayload).toHaveBeenCalledWith(leavePayload);
expect(voiceConnection.state).toMatchObject({
status: VoiceConnectionStatus.Disconnected,
reason: VoiceConnectionDisconnectReason.Manual,
});
});
test('Disconnects - unavailable adapter', () => {
const { voiceConnection, adapter } = createFakeVoiceConnection();
voiceConnection.state = {
status: VoiceConnectionStatus.Ready,
adapter,
networking: new Networking.Networking({} as any, false),
};
adapter.sendPayload.mockImplementation(() => false);
expect(voiceConnection.disconnect()).toBe(false);
expect(voiceConnection.state).toMatchObject({
status: VoiceConnectionStatus.Disconnected,
reason: VoiceConnectionDisconnectReason.AdapterUnavailable,
});
});
});
describe('VoiceConnection#rejoin', () => {
test('Rejoins in a disconnected state', () => {
const dummy = Symbol('dummy') as any;
DataStore.createJoinVoiceChannelPayload.mockImplementation(() => dummy);
const { voiceConnection, adapter } = createFakeVoiceConnection();
voiceConnection.state = {
...(voiceConnection.state as VoiceConnectionSignallingState),
status: VoiceConnectionStatus.Disconnected,
reason: VoiceConnectionDisconnectReason.WebSocketClose,
closeCode: 1000,
};
expect(voiceConnection.rejoin()).toBe(true);
expect(voiceConnection.rejoinAttempts).toBe(1);
expect(adapter.sendPayload).toHaveBeenCalledWith(dummy);
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Signalling);
});
test('Rejoins in a ready state', () => {
const dummy = Symbol('dummy') as any;
DataStore.createJoinVoiceChannelPayload.mockImplementation(() => dummy);
const { voiceConnection, adapter } = createFakeVoiceConnection();
voiceConnection.state = {
...(voiceConnection.state as VoiceConnectionReadyState),
status: VoiceConnectionStatus.Ready,
};
expect(voiceConnection.rejoin()).toBe(true);
expect(voiceConnection.rejoinAttempts).toBe(0);
expect(adapter.sendPayload).toHaveBeenCalledWith(dummy);
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Ready);
});
test('Stays in the disconnected state when the adapter fails', () => {
const dummy = Symbol('dummy') as any;
DataStore.createJoinVoiceChannelPayload.mockImplementation(() => dummy);
const { voiceConnection, adapter } = createFakeVoiceConnection();
voiceConnection.state = {
...(voiceConnection.state as VoiceConnectionSignallingState),
status: VoiceConnectionStatus.Disconnected,
reason: VoiceConnectionDisconnectReason.WebSocketClose,
closeCode: 1000,
};
adapter.sendPayload.mockReturnValue(false);
expect(voiceConnection.rejoin()).toBe(false);
expect(voiceConnection.rejoinAttempts).toBe(1);
expect(adapter.sendPayload).toHaveBeenCalledWith(dummy);
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Disconnected);
});
});
describe('VoiceConnection#subscribe', () => {
test('Does nothing in Destroyed state', () => {
const { voiceConnection } = createFakeVoiceConnection();
const player = new AudioPlayer.AudioPlayer();
player['subscribe'] = jest.fn();
voiceConnection.state = { status: VoiceConnectionStatus.Destroyed };
expect(voiceConnection.subscribe(player)).toBeUndefined();
expect(player['subscribe']).not.toHaveBeenCalled();
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Destroyed);
});
test('Subscribes in a live state', () => {
const { voiceConnection } = createFakeVoiceConnection();
const adapter = (voiceConnection.state as VoiceConnectionSignallingState).adapter;
const player = new AudioPlayer.AudioPlayer();
const dummy = Symbol('dummy');
player['subscribe'] = jest.fn().mockImplementation(() => dummy);
expect(voiceConnection.subscribe(player)).toBe(dummy);
expect(player['subscribe']).toHaveBeenCalledWith(voiceConnection);
expect(voiceConnection.state).toMatchObject({
status: VoiceConnectionStatus.Signalling,
adapter,
});
});
});
describe('VoiceConnection#onSubscriptionRemoved', () => {
test('Does nothing in Destroyed state', () => {
const { voiceConnection } = createFakeVoiceConnection();
const subscription = new PlayerSubscription(voiceConnection, new AudioPlayer.AudioPlayer());
subscription.unsubscribe = jest.fn();
voiceConnection.state = { status: VoiceConnectionStatus.Destroyed };
voiceConnection['onSubscriptionRemoved'](subscription);
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Destroyed);
expect(subscription.unsubscribe).not.toHaveBeenCalled();
});
test('Does nothing when subscription is not the same as the stored one', () => {
const { voiceConnection } = createFakeVoiceConnection();
const subscription = new PlayerSubscription(voiceConnection, new AudioPlayer.AudioPlayer());
subscription.unsubscribe = jest.fn();
voiceConnection.state = { ...(voiceConnection.state as VoiceConnectionSignallingState), subscription };
voiceConnection['onSubscriptionRemoved'](Symbol('new subscription') as any);
expect(voiceConnection.state).toMatchObject({
status: VoiceConnectionStatus.Signalling,
subscription,
});
expect(subscription.unsubscribe).not.toHaveBeenCalled();
});
test('Unsubscribes in a live state with matching subscription', () => {
const { voiceConnection } = createFakeVoiceConnection();
const subscription = new PlayerSubscription(voiceConnection, new AudioPlayer.AudioPlayer());
subscription.unsubscribe = jest.fn();
voiceConnection.state = { ...(voiceConnection.state as VoiceConnectionSignallingState), subscription };
voiceConnection['onSubscriptionRemoved'](subscription);
expect(voiceConnection.state).toEqual({
...voiceConnection.state,
subscription: undefined,
});
expect(subscription.unsubscribe).toHaveBeenCalledTimes(1);
});
describe('updateReceiveBindings', () => {
test('Applies and removes udp listeners', () => {
// Arrange
const ws = new EventEmitter() as any;
const oldNetworking = new Networking.Networking({} as any, false);
oldNetworking.state = {
code: _Networking.NetworkingStatusCode.Ready,
connectionData: {} as any,
connectionOptions: {} as any,
udp: new EventEmitter() as any,
ws,
};
const newNetworking = new Networking.Networking({} as any, false);
newNetworking.state = {
...oldNetworking.state,
udp: new EventEmitter() as any,
};
const { voiceConnection } = createFakeVoiceConnection();
// Act
voiceConnection['updateReceiveBindings'](newNetworking.state, oldNetworking.state);
// Assert
expect(oldNetworking.state.udp.listenerCount('message')).toBe(0);
expect(newNetworking.state.udp.listenerCount('message')).toBe(1);
expect(voiceConnection.receiver.connectionData).toBe(newNetworking.state.connectionData);
});
test('Applies and removes ws listeners', () => {
// Arrange
const udp = new EventEmitter() as any;
const oldNetworking = new Networking.Networking({} as any, false);
oldNetworking.state = {
code: _Networking.NetworkingStatusCode.Ready,
connectionData: {} as any,
connectionOptions: {} as any,
udp,
ws: new EventEmitter() as any,
};
const newNetworking = new Networking.Networking({} as any, false);
newNetworking.state = {
...oldNetworking.state,
ws: new EventEmitter() as any,
};
const { voiceConnection } = createFakeVoiceConnection();
// Act
voiceConnection['updateReceiveBindings'](newNetworking.state, oldNetworking.state);
// Assert
expect(oldNetworking.state.ws.listenerCount('packet')).toBe(0);
expect(newNetworking.state.ws.listenerCount('packet')).toBe(1);
expect(voiceConnection.receiver.connectionData).toBe(newNetworking.state.connectionData);
});
test('Applies initial listeners', () => {
// Arrange
const newNetworking = new Networking.Networking({} as any, false);
newNetworking.state = {
code: _Networking.NetworkingStatusCode.Ready,
connectionData: {} as any,
connectionOptions: {} as any,
udp: new EventEmitter() as any,
ws: new EventEmitter() as any,
};
const { voiceConnection } = createFakeVoiceConnection();
// Act
voiceConnection['updateReceiveBindings'](newNetworking.state, undefined);
// Assert
expect(newNetworking.state.ws.listenerCount('packet')).toBe(1);
expect(newNetworking.state.udp.listenerCount('message')).toBe(1);
expect(voiceConnection.receiver.connectionData).toBe(newNetworking.state.connectionData);
});
});
});
describe('Adapter', () => {
test('onVoiceServerUpdate', () => {
const { adapter, voiceConnection } = createFakeVoiceConnection();
voiceConnection['addServerPacket'] = jest.fn();
const dummy = Symbol('dummy') as any;
adapter.libMethods.onVoiceServerUpdate(dummy);
expect(voiceConnection['addServerPacket']).toHaveBeenCalledWith(dummy);
});
test('onVoiceStateUpdate', () => {
const { adapter, voiceConnection } = createFakeVoiceConnection();
voiceConnection['addStatePacket'] = jest.fn();
const dummy = Symbol('dummy') as any;
adapter.libMethods.onVoiceStateUpdate(dummy);
expect(voiceConnection['addStatePacket']).toHaveBeenCalledWith(dummy);
});
test('destroy', () => {
const { adapter, voiceConnection } = createFakeVoiceConnection();
adapter.libMethods.destroy();
expect(voiceConnection.state.status).toBe(VoiceConnectionStatus.Destroyed);
expect(adapter.sendPayload).not.toHaveBeenCalled();
});
});