Skip to content

Commit

Permalink
Merge pull request #10 from Tarnadas/dev
Browse files Browse the repository at this point in the history
2.1
  • Loading branch information
Mario Reder committed Jul 27, 2018
2 parents db4615e + 1f3d626 commit 43fec4f
Show file tree
Hide file tree
Showing 14 changed files with 1,270 additions and 33 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The prebuilt version is bundled with the [Net64+ Client](https://github.com/Tarn
"enableWebHook": false, // set this to true, if you want your server to be listed
"gamemode": 1, // the initial gamemode
"enableGamemodeVote": true, // whether gamemode voting should be enabled
"passwordRequired": false, // whether password is required to join this server
"name": "A Net64+ Server", // display name for public server list
"domain": "", // domain of your server for public server list. keep it empty, if you don't have a domain
"description": "The **best** Net64+ server ever\n\n:unicorn_face:", // description for public server list
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "net64plus-server",
"version": "2.0.6",
"compatVersion": "1.0.0.",
"version": "2.1.0",
"compatVersion": "1.0.0",
"description": "Net64+ Dedicated Server",
"main": "dist/index.js",
"scripts": {
Expand Down
2 changes: 2 additions & 0 deletions settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"enableWebHook": false,
"gamemode": 1,
"enableGamemodeVote": true,
"passwordRequired": false,
"password": "",
"name": "A Net64+ Server",
"domain": "",
"description": "The **best** Net64+ server ever\n\n:unicorn_face:",
Expand Down
260 changes: 255 additions & 5 deletions src/Client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import * as zlib from 'zlib'

import { webSocketServer } from '.'
import { Client, CONNECTION_TIMEOUT, DECOMPRESSION_ERROR, AFK_TIMEOUT, AFK_TIMEOUT_COUNT, MAX_LENGTH_CHAT_MESSAGE } from './Client'
import { Client, CONNECTION_TIMEOUT, DECOMPRESSION_ERROR, AFK_TIMEOUT, AFK_TIMEOUT_COUNT, MAX_LENGTH_CHAT_MESSAGE, NO_PASSWORD_REQUIRED } from './Client'
import { Player } from './Player'
import { IClientServerMessage, Compression, ClientServer, ClientServerMessage, Chat } from './proto/ClientServerMessage'
import { IServerClientMessage, ServerClient, ServerClientMessage, ServerMessage, Error as ErrorProto } from './proto/ServerClientMessage'
import { IServerClientMessage, ServerClient, ServerClientMessage, ServerMessage, Error as ErrorProto, Authentication } from './proto/ServerClientMessage'
import {
MESSAGES_PER_HALF_MINUTE_THRESHOLD, MESSAGES_PER_HALF_MINUTE_DOS_THRESHOLD, MESSAGE_CHARACTERS_PER_HALF_MINUTE_THRESHOLD, SPAM_NOTIFICATION_MESSAGE, warningLevelMuteMapping, Identity
Identity,
PASSWORD_THROTTLE_INCREASE,
MESSAGE_CHARACTERS_PER_HALF_MINUTE_THRESHOLD,
MESSAGES_PER_HALF_MINUTE_DOS_THRESHOLD,
MESSAGES_PER_HALF_MINUTE_THRESHOLD,
SPAM_NOTIFICATION_MESSAGE,
warningLevelMuteMapping
} from './Identity'

const addClient = (client: Client) => {
Expand All @@ -28,7 +35,9 @@ describe('Client', () => {
webSocketServer = {
clients: [],
players: [],
onGlobalChatMessage: jest.fn()
onGlobalChatMessage: jest.fn(),
sendHandshake: jest.fn(),
addPlayer: jest.fn()
} as any
wsMock = {
on: (type: string, callback: () => Promise<void>) => {
Expand Down Expand Up @@ -127,6 +136,247 @@ describe('Client', () => {
})
})

describe('#onAuthentication', () => {
let username: string
let characterId: number

beforeEach(() => {
username = 'username'
characterId = 4
process.env.MAJOR = '0'
process.env.MINOR = '0'
})

beforeEach(() => {
expect(client.player).toBeUndefined()
})

describe('if password is required', () => {
beforeEach(() => {
// @ts-ignore
webSocketServer.passwordRequired = true
const message: IClientServerMessage = {
compression: Compression.NONE,
data: {
messageType: ClientServer.MessageType.HANDSHAKE,
handshake: {
characterId,
major: 0,
minor: 0,
username
}
}
}
const encodedMessage = ClientServerMessage.encode(ClientServerMessage.fromObject(message)).finish()

return fnMocks.message(encodedMessage)
})

describe('on correct password', () => {
beforeEach(async () => {
const password = 'server-password'
// @ts-ignore
webSocketServer.password = password
const message: IClientServerMessage = {
compression: Compression.NONE,
data: {
messageType: ClientServer.MessageType.AUTHENTICATE,
authenticate: {
password
}
}
}
const encodedMessage = ClientServerMessage.encode(ClientServerMessage.fromObject(message)).finish()

await fnMocks.message(encodedMessage)
})

it('should create player object', () => {
expect(client.player).toBeDefined()
expect(client.player!.username).toEqual(username)
expect(client.player!.characterId).toEqual(characterId)
})

it('should add player to server', () => {
expect(webSocketServer.addPlayer).toHaveBeenCalledWith(new Player(client, username, characterId))
})

it('should send password accepted message to client', () => {
const success: IServerClientMessage = {
compression: Compression.NONE,
data: {
messageType: ServerClient.MessageType.SERVER_MESSAGE,
serverMessage: {
messageType: ServerMessage.MessageType.AUTHENTICATION,
authentication: {
status: Authentication.Status.ACCEPTED
}
}
}
}
const successMessage = ServerClientMessage.encode(ServerClientMessage.fromObject(success)).finish()

expect(wsMock.send).toHaveBeenLastCalledWith(successMessage, {
binary: true
})
})
})

describe('on incorrect password', () => {
let wrongPasswordMessage: Uint8Array
beforeEach(async () => {
const password = 'server-password'
// @ts-ignore
webSocketServer.password = password
const message: IClientServerMessage = {
compression: Compression.NONE,
data: {
messageType: ClientServer.MessageType.AUTHENTICATE,
authenticate: {
password: 'incorrect-password'
}
}
}
wrongPasswordMessage = ClientServerMessage.encode(ClientServerMessage.fromObject(message)).finish()

await fnMocks.message(wrongPasswordMessage)
expect(wsMock.send).toHaveBeenCalledTimes(1)
})

it('should send message that password was incorrect', () => {
const message: IServerClientMessage = {
compression: Compression.NONE,
data: {
messageType: ServerClient.MessageType.SERVER_MESSAGE,
serverMessage: {
messageType: ServerMessage.MessageType.AUTHENTICATION,
authentication: {
status: Authentication.Status.DENIED,
throttle: PASSWORD_THROTTLE_INCREASE
}
}
}
}
const wrongPasswordMessage = ServerClientMessage.encode(ServerClientMessage.fromObject(message)).finish()

expect(wsMock.send).toHaveBeenLastCalledWith(wrongPasswordMessage, {
binary: true
})
})
})

describe('during throttling phase', () => {
let wrongPasswordMessage: Uint8Array
beforeEach(async () => {
const password = 'server-password'
// @ts-ignore
webSocketServer.password = password
const message: IClientServerMessage = {
compression: Compression.NONE,
data: {
messageType: ClientServer.MessageType.AUTHENTICATE,
authenticate: {
password: 'incorrect-password'
}
}
}
wrongPasswordMessage = ClientServerMessage.encode(ClientServerMessage.fromObject(message)).finish()

await fnMocks.message(wrongPasswordMessage)
expect(wsMock.send).toHaveBeenCalledTimes(1)
})

it('should send remaining throttling time', async () => {
const expectedRemainingTime = 3;
(client as any).identity.getPasswordThrottle = jest.fn(() => expectedRemainingTime)
const message: IServerClientMessage = {
compression: Compression.NONE,
data: {
messageType: ServerClient.MessageType.SERVER_MESSAGE,
serverMessage: {
messageType: ServerMessage.MessageType.AUTHENTICATION,
authentication: {
status: Authentication.Status.DENIED,
throttle: expectedRemainingTime
}
}
}
}
const expectedMessage = ServerClientMessage.encode(ServerClientMessage.fromObject(message)).finish()

await fnMocks.message(wrongPasswordMessage)

expect(wsMock.send).toHaveBeenLastCalledWith(expectedMessage, {
binary: true
})
})

it('should reaccept message after throttling phase', async () => {
jest.advanceTimersByTime(PASSWORD_THROTTLE_INCREASE * 1000)
await fnMocks.message(wrongPasswordMessage)

expect(wsMock.send).toHaveBeenCalledTimes(2)
})
})
})

describe('if password is not required', () => {
beforeEach(async () => {
// @ts-ignore
webSocketServer.passwordRequired = false
let message: IClientServerMessage = {
compression: Compression.NONE,
data: {
messageType: ClientServer.MessageType.HANDSHAKE,
handshake: {
characterId,
major: 0,
minor: 0,
username
}
}
}
let encodedMessage = ClientServerMessage.encode(ClientServerMessage.fromObject(message)).finish()

await fnMocks.message(encodedMessage)

message = {
compression: Compression.NONE,
data: {
messageType: ClientServer.MessageType.AUTHENTICATE,
authenticate: {
password: 'password'
}
}
}
encodedMessage = ClientServerMessage.encode(ClientServerMessage.fromObject(message)).finish()

await fnMocks.message(encodedMessage)
})

it('should send message that password is not required', () => {
const message: IServerClientMessage = {
compression: Compression.NONE,
data: {
messageType: ServerClient.MessageType.SERVER_MESSAGE,
serverMessage: {
messageType: ServerMessage.MessageType.ERROR,
error: {
errorType: ErrorProto.ErrorType.BAD_REQUEST,
message: NO_PASSWORD_REQUIRED
}
}
}
}
const noPassRequiredMessage = ServerClientMessage.encode(ServerClientMessage.fromObject(message)).finish()

expect(wsMock.send).toHaveBeenLastCalledWith(noPassRequiredMessage, {
binary: true
})
})
})
})

describe('#onChatMessage', () => {
it('should send global chat message', async () => {
const message: IClientServerMessage = {
Expand Down Expand Up @@ -251,7 +501,7 @@ describe('Client', () => {
}
await Promise.all(messages)

const muteMessage: IServerClientMessage = {
const muteMessage: IClientServerMessage = {
compression: Compression.NONE,
data: {
messageType: ClientServer.MessageType.CHAT,
Expand Down
Loading

0 comments on commit 43fec4f

Please sign in to comment.