import { expect } from 'chai'; import 'mocha'; import flip from '../../lib/flip'; import { Client, Server, Mapper, PacketType, CreateSuccessPacket, FailurePacket } from '../../src'; import * as net from 'net'; const LOCALHOST: Server = { ip: '0.0.0.0', name: 'localhost', usage: 0, location: { lat: 0, long: 0 } }; Mapper.mapIds({ 16: PacketType.HELLO, 8: PacketType.CREATE_SUCCESS, 10: PacketType.FAILURE }); describe('Client', () => { const mockAccountInfo = { guid: 'example@email.com', password: '12345', info: {} as any }; const mockGameState = { buildVersion: 'X00.0.0', characterId: 1, gameId: -2, key: [] as number[], keyTime: 0 }; it('should throw a TypeError for invalid constructor parameters.', () => { expect(() => new Client(432 as any)).to.throw(TypeError); expect(() => new Client(null)).to.throw(TypeError); expect(() => new Client('Hello world' as any)).to.throw(TypeError); }); describe('#connected', () => { const server = net.createServer(); beforeEach((done) => { server.listen(2050, '0.0.0.0', () => { done(); }); }); afterEach((done) => { server.close(() => { done(); }); }); it('should be true if the client is currently connected.', (done) => { const client = new Client(mockAccountInfo); client.attach(new net.Socket()); client.io.socket.once('connect', () => { expect(client.connected).to.equal(true, 'Incorrect value for connected getter.'); client.io.socket.destroy(); done(); }); client.io.socket.connect(2050, '0.0.0.0'); }); it('should be false if the client is not connected.', (done) => { const client = new Client(mockAccountInfo); client.attach(new net.Socket()); expect(client.connected).to.equal(false, 'Incorrect initial value for connected getter.'); client.io.socket.once('close', () => { expect(client.connected).to.equal(false, 'Incorrect value for connected getter.'); done(); }); client.io.socket.connect(2050, '0.0.0.0', () => { client.io.socket.destroy(); }); }); }); describe('#connect()', () => { const server = net.createServer(); beforeEach((done) => { server.listen(2050, '0.0.0.0', () => { done(); }); }); afterEach((done) => { server.close(() => { done(); }); }); it('should return a promise.', () => { const client = new Client(mockAccountInfo); client.attach(new net.Socket()); const promise = client.connect(mockGameState, LOCALHOST); expect(promise).instanceof(Promise, 'Connect did not return a Promise.'); client.io.socket.destroy(); }); it('should be rejected with a TypeError for invalid inputs.', () => { const client = new Client(mockAccountInfo); client.attach(new net.Socket()); return Promise.all([ flip(client.connect(432 as any, null)), flip(client.connect(null, null)), flip(client.connect(['String', 'array'] as any, null)), flip(client.connect(mockGameState, 123 as any)), flip(client.connect(mockGameState, null)), flip(client.connect(mockGameState, 'test string' as any)), flip(client.connect(mockGameState, {} as any)), ]); }); it('should be rejected with an Error if there is no socket attached.', () => { const client = new Client(mockAccountInfo); return flip(client.connect(mockGameState)); }); it('should connect and initiate the Hello handshake.', (done) => { const client = new Client(mockAccountInfo); client.attach(new net.Socket()); server.once('connection', (socket) => { socket.once('data', (data) => { socket.destroy(); expect(data.readInt32BE(0)).to.equal(408, 'Incorrect packet length.'); expect(data.readInt8(4)).to.equal(16, 'Incorrect packet id.'); done(); }); }); client.connect(mockGameState, LOCALHOST); }); it('should just initiate the Hello handshake if no server is provided.', (done) => { const client = new Client(mockAccountInfo); const sock = new net.Socket(); client.attach(sock); server.once('connection', (socket) => { socket.once('data', (data) => { socket.destroy(); expect(data.readInt32BE(0)).to.equal(408, 'Incorrect packet length.'); expect(data.readInt8(4)).to.equal(16, 'Incorrect packet id.'); done(); }); }); sock.connect(2050, LOCALHOST.ip, () => { client.connect(mockGameState); }); }); it('should be rejected if no server is provided and the socket is closed.', () => { const client = new Client(mockAccountInfo); client.attach(new net.Socket()); return flip(client.connect(mockGameState)); }); it('should resolve when the Hello handshake is complete.', () => { const client = new Client(mockAccountInfo); client.attach(new net.Socket()); server.once('connection', (socket) => { socket.once('data', () => { const reply = Buffer.alloc(13); reply.writeInt32BE(13, 0); reply.writeInt8(Mapper.reverseMap.get(PacketType.CREATE_SUCCESS), 4); socket.write(reply); }); }); return client.connect(mockGameState, LOCALHOST).then((packet) => { expect(client.connected).to.equal(true, 'Client not connected.'); expect(packet).instanceof(CreateSuccessPacket, 'Resolved with wrong value.'); client.io.socket.destroy(); }); }); it('should reject if the Hello handshake fails.', (done) => { const client = new Client(mockAccountInfo); client.attach(new net.Socket()); server.once('connection', (socket) => { socket.once('data', () => { const reply = Buffer.alloc(11); reply.writeInt32BE(11, 0); reply.writeInt8(Mapper.reverseMap.get(PacketType.FAILURE), 4); socket.write(reply); }); }); client.connect(mockGameState, LOCALHOST).catch((packet) => { client.io.socket.destroy(); expect(packet).instanceof(FailurePacket, 'Rejected with wrong value.'); done(); }); }); }); describe('#disconnect()', () => { const server = net.createServer(); beforeEach((done) => { server.listen(2050, '0.0.0.0', () => { done(); }); }); afterEach((done) => { server.close(() => { done(); }); }); it('should disconnect the client if it is currently connected.', (done) => { const client = new Client(mockAccountInfo); client.attach(new net.Socket()); server.once('connection', (socket) => { socket.once('close', () => { expect(client.connected).to.equal(false, 'Client not disconnected.'); done(); }); }); client.io.socket.connect(2050, '0.0.0.0', () => { client.disconnect(); }); }); it('should return a promise which resolves when the client has disconnected.', (done) => { const client = new Client(mockAccountInfo); client.attach(new net.Socket()); client.io.socket.connect(2050, '0.0.0.0', () => { client.disconnect().then(() => { expect(client.connected).to.equal(false, 'Client not disconnected.'); done(); }); }); }); }); describe('#attach()', () => { it('should throw a TypeError for invalid inputs.', () => { const client = new Client(mockAccountInfo); expect(() => client.attach(123 as any)).to.throw(TypeError); expect(() => client.attach('Hello, World!' as any)).to.throw(TypeError); expect(() => client.attach(null)).to.throw(TypeError); }); it('should attach event listeners to the socket.', () => { const socket = new net.Socket(); const client = new Client(mockAccountInfo); client.attach(socket); // there are 2 listeners here because the Packet IO is also attached. expect(socket.listenerCount('connect')).to.equal(2, 'Incorrect listener count for connect event.'); expect(socket.listenerCount('close')).to.equal(1, 'Incorrect listener count for close event.'); }); it('#should detach first if there is a socket already attached.', () => { const socketA = new net.Socket(); const socketB = new net.Socket(); const client = new Client(mockAccountInfo); client.attach(socketA); client.attach(socketB); expect(socketA.listenerCount('connect')).to.equal(0, 'Incorrect listener count for connect event.'); expect(socketA.listenerCount('close')).to.equal(0, 'Incorrect listener count for close event.'); }); }); describe('#detach()', () => { it('should remove the event listeners from the socket.', () => { const socket = new net.Socket(); socket.on('connect', () => null); socket.on('close', () => null); const client = new Client(mockAccountInfo); client.attach(socket); client.detach(); expect(socket.listenerCount('connect')).to.equal(1, 'Incorrect listener count for connect event.'); expect(socket.listenerCount('close')).to.equal(1, 'Incorrect listener count for close event.'); }); }); });