From 087b88f32ba9706404e40632c2d71b67a9006d5d Mon Sep 17 00:00:00 2001 From: Molly Crendraven Date: Thu, 5 Sep 2024 10:38:59 -0600 Subject: [PATCH] Seperate test uploads (#2107) Fixes #2106 ## Summary by CodeRabbit - **Bug Fixes** - Updated configuration settings to ensure coverage data is treated independently for multiple components, improving accuracy in reports. - **New Features** - Enhanced the GitHub Actions workflow with directory specifications for Codecov uploads, clarifying the source of coverage reports. - Added new steps for uploading coverage reports for various components, ensuring distinct handling of each component's coverage. - Updated testing scripts to enable actual test execution and coverage reporting. - Created a new `receiveLoginData` function to manage login requests with structured logging for better traceability. - Introduced a new `MockNPSMessage` for easier unit testing and integration. - **Refactor** - Improved coverage configuration in the test settings by integrating default exclusions, allowing for better management of test coverage. - Simplified the test setup in `clientConnect.test.ts` by removing local state management and utilizing an external database session management function. - Corrected import paths across various modules to enhance maintainability and consistency. - Removed unnecessary mock functions to streamline testing strategy. - Restructured module organization for better clarity and maintainability. --- .github/codecov.yml | 20 +- .github/workflows/node.yml | 114 +++++++++- packages/cli/ConsoleThread.ts | 118 +++++----- packages/connection/package.json | 3 +- packages/gateway/package.json | 2 +- packages/interfaces/index.d.ts | 208 ------------------ packages/lobby/package.json | 2 +- .../src/handlers/requestConnectGameServer.ts | 2 +- packages/login/index.ts | 2 +- packages/login/package.json | 2 +- packages/login/src/index.ts | 164 -------------- packages/login/src/internal.ts | 4 +- packages/login/src/receiveLoginData.ts | 58 +++++ packages/login/test/LoginServer.test.ts | 22 -- packages/login/test/index.test.ts | 7 + packages/persona/index.ts | 0 packages/persona/package.json | 29 +++ .../persona/src/getPersonasByPersonaId.ts | 4 +- packages/persona/src/internal.ts | 6 +- .../test/getPersonasByPersonaId.test.ts | 2 +- packages/persona/vite.config.ts | 23 ++ packages/shared/index.ts | 2 + packages/shared/src/interfaces.ts | 84 +++++++ packages/transactions/package.json | 2 +- .../transactions/test/clientConnect.test.ts | 16 +- packages/transactions/test/getLobbies.test.ts | 2 +- pnpm-lock.yaml | 16 ++ test/factoryMocks.ts | 7 - vite.config.ts | 30 +-- 29 files changed, 425 insertions(+), 526 deletions(-) delete mode 100644 packages/interfaces/index.d.ts delete mode 100644 packages/login/src/index.ts create mode 100644 packages/login/src/receiveLoginData.ts delete mode 100644 packages/login/test/LoginServer.test.ts create mode 100644 packages/login/test/index.test.ts create mode 100644 packages/persona/index.ts create mode 100644 packages/persona/package.json create mode 100644 packages/persona/vite.config.ts create mode 100644 packages/shared/src/interfaces.ts diff --git a/.github/codecov.yml b/.github/codecov.yml index 050bc1f04..55230c167 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -8,39 +8,39 @@ flags: database: paths: - packages/database/** - carryforward: true + carryforward: false gateway: paths: - packages/gateway/** - carryforward: true + carryforward: false lobby: paths: - packages/lobby/** - carryforward: true + carryforward: false login: paths: - packages/login/** - carryforward: true + carryforward: false patch: paths: - packages/patch/** - carryforward: true + carryforward: false persona: paths: - packages/persona/** - carryforward: true + carryforward: false shard: paths: - packages/shard/** - carryforward: true + carryforward: false shared: paths: - packages/shared/** - carryforward: true + carryforward: false transactions: paths: - packages/transactions/** - carryforward: true + carryforward: false interfaces: paths: - packages/interfaces/** @@ -48,7 +48,7 @@ flags: core: paths: - packages/core/** - carryforward: true + carryforward: false component_management: individual_components: diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index 91eceaf41..c23876462 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -25,33 +25,123 @@ jobs: with: node-version: 22.x - name: Setup pnpm - uses: pnpm/action-setup@v4.0.0 + uses: pnpm/action-setup@v4.0.0 - name: Install and test run: | pnpm install make test env: CODECOV_UPLOAD_BUNDLE_TOKEN: ${{ secrets.CODECOV_UPLOAD_BUNDLE_TOKEN }} - - name: Codecov + - name: Codecov install cli if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} run: | pip install --user pytest pip install --user codecov-cli + - name: Codecov create comit and report + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | codecovcli --verbose create-commit --fail-on-error codecovcli --verbose create-report --fail-on-error + - name: Codecov upload test results + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | codecovcli do-upload --report-type test_results --file mcos.junit.xml - codecovcli --verbose do-upload --fail-on-error --flag cli --name cli-${{ matrix.node-version }} - codecovcli --verbose do-upload --fail-on-error --flag connection --name connection-${{ matrix.node-version }} - codecovcli --verbose do-upload --fail-on-error --flag database --name database-${{ matrix.node-version }} - codecovcli --verbose do-upload --fail-on-error --flag gateway --name gateway-${{ matrix.node-version }} - codecovcli --verbose do-upload --fail-on-error --flag mcots --name mcots-${{ matrix.node-version }} - codecovcli --verbose do-upload --fail-on-error --flag nps --name nps-${{ matrix.node-version }} - codecovcli --verbose do-upload --fail-on-error --flag patch --name patch-${{ matrix.node-version }} - codecovcli --verbose do-upload --fail-on-error --flag shard --name shard-${{ matrix.node-version }} - codecovcli --verbose do-upload --fail-on-error --flag shared --name shared-${{ matrix.node-version }} - codecovcli --verbose do-upload --fail-on-error --flag shared-packets --name shared-packets-${{ matrix.node-version }} + - name: Codecov upload cli coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag cli --name cli --dir packages/cli + - name: Codecov upload connection coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag connection --name connection --dir packages/connection + - name: Codecov upload database coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag database --name database --dir packages/database + - name: Codecov upload gateway coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag gateway --name gateway --dir packages/gateway + - name: Codecov upload lobby coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag lobby --name lobby --dir packages/lobby + - name: Codecov upload login coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag login --name login --dir packages/login + - name: Codecov upload mcots coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag mcots --name mcots --dir packages/mcots + - name: Codecov upload nps coverageq + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag nps --name nps --dir packages/nps + - name: Codecov upload patch coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag patch --name patch --dir packages/patch + - name: Codecov upload persona coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag persona --name persona --dir packages/persona + - name: Codecov upload sessions coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag sessions --name sessions --dir packages/sessions + - name: Codecov upload shard coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag shard --name shard --dir packages/shard + - name: Codecov upload shared coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag shared --name shared --dir packages/shared + - name: Codecov upload shared-packets coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag shared-packets --name shared-packets --dir packages/shared-packets + - name: Codecov upload transactions coverage + if: ${{ always() }} # using always() to always run this step because i am uploading test results and coverage in one step + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + codecovcli --verbose do-upload --fail-on-error --flag transactions --name transactions --dir packages/transactions - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4 with: diff --git a/packages/cli/ConsoleThread.ts b/packages/cli/ConsoleThread.ts index df7983f01..c0ade40b7 100644 --- a/packages/cli/ConsoleThread.ts +++ b/packages/cli/ConsoleThread.ts @@ -12,71 +12,73 @@ import { ServerError } from "rusty-motors-shared"; * Console thread */ export class ConsoleThread extends SubThread { - parentThread: Gateway; - /** - * @param {object} options - * @param {Gateway} options.parentThread The parent thread - * @param {import("pino").Logger} options.log The logger - */ - constructor({ - parentThread, - log, - }: { - parentThread: Gateway; - log: import("pino").Logger; - }) { - super("ReadInput", log, 100); - if (parentThread === undefined) { - throw new ServerError( - "parentThread is undefined when creating ReadInput", - ); - } - this.parentThread = parentThread; - } + parentThread: Gateway; + /** + * @param {object} options + * @param {Gateway} options.parentThread The parent thread + * @param {import("pino").Logger} options.log The logger + */ + constructor({ + parentThread, + log, + }: { + parentThread: Gateway; + log: import("pino").Logger; + }) { + super("ReadInput", log, 100); + if (parentThread === undefined) { + throw new ServerError( + "parentThread is undefined when creating ReadInput", + ); + } + this.parentThread = parentThread; + } - /** @param {import("../interfaces/index.js").KeypressEvent} key */ - handleKeypressEvent(key: import("../interfaces/index.js").KeypressEvent) { - const keyString = key.sequence; + /** @param {import("../interfaces/index.js").KeypressEvent} key */ + handleKeypressEvent( + key: import("../shared/src/interfaces.js").KeypressEvent, + ) { + const keyString = key.sequence; - if (keyString === "x") { - this.emit("userExit"); - } + if (keyString === "x") { + this.emit("userExit"); + } - if (keyString === "r") { - this.emit("userRestart"); - } + if (keyString === "r") { + this.emit("userRestart"); + } - if (keyString === "?") { - this.emit("userHelp"); - } - } + if (keyString === "?") { + this.emit("userHelp"); + } + } - override init() { - super.init(); - emitKeypressEvents(process.stdin); - if (process.stdin.isTTY) { - process.stdin.setRawMode(true); - } + override init() { + super.init(); + emitKeypressEvents(process.stdin); + if (process.stdin.isTTY) { + process.stdin.setRawMode(true); + } - this.log.info("GatewayServer started"); - this.log.info("Press x to quit"); + this.log.info("GatewayServer started"); + this.log.info("Press x to quit"); - process.stdin.resume(); - process.stdin.on("keypress", (str, key) => { - if (key !== undefined) { - this.handleKeypressEvent(key); - } - }); - } + process.stdin.resume(); + process.stdin.on("keypress", (_str, key) => { + if (key !== undefined) { + this.handleKeypressEvent(key); + } + }); + } - override run() { - // Intentionally left blank - } + override run() { + // Intentionally left blank + } - stop() { - // Remove all listeners from stdin, preventing further input - process.stdin.removeAllListeners("keypress"); - process.stdin.pause(); - super.shutdown(); - } + stop() { + // Remove all listeners from stdin, preventing further input + process.stdin.removeAllListeners("keypress"); + process.stdin.pause(); + super.shutdown(); + } } diff --git a/packages/connection/package.json b/packages/connection/package.json index 55fbfeea8..91d8f16ad 100644 --- a/packages/connection/package.json +++ b/packages/connection/package.json @@ -13,8 +13,7 @@ "lint": "eslint .", "format": "npm prettier --write .", "test:db": "vitest run --coverage", - "test:diabled": "dotenvx run -f ../../.env -- pnpm run test:db", - "test": "echo 'No tests yet' && exit 0" + "test": "dotenvx run -f ../../.env -- pnpm run test:db" }, "keywords": [], "author": "", diff --git a/packages/gateway/package.json b/packages/gateway/package.json index 6733ee448..1ad029e8e 100644 --- a/packages/gateway/package.json +++ b/packages/gateway/package.json @@ -12,7 +12,7 @@ "check": "tsc", "lint": "eslint .", "format": "npx prettier --write .", - "test": "echo \"Error: no test specified\" && exit 0", + "test": "vitest run --coverage", "test:crypto": "pnpm node --openssl-legacy-provider node_modules/vitest/vitest.mjs run --coverage" }, "keywords": [], diff --git a/packages/interfaces/index.d.ts b/packages/interfaces/index.d.ts deleted file mode 100644 index aeb81bb56..000000000 --- a/packages/interfaces/index.d.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { Cipher, Decipher } from "node:crypto"; -import { IncomingMessage, ServerResponse } from "node:http"; -import { Socket } from "node:net"; -import pino from "pino"; -import { Configuration } from "rusty-motors-shared"; -import { ServerError } from "rusty-motors-shared"; -import { SerializedBufferOld } from "rusty-motors-shared"; - -/** - * @module interfaces - */ - -export as namespace interfaces; - -export namespace external { - export namespace pino { - export type Logger = pino.Logger; - } -} - -export const name = "interfaces"; - -/** - * @exports - * @interface - */ -interface SerializedObject { - serialize: () => Buffer; - serializeSize: () => number; -} -export const SerializedObject = { - serialize() { - throw new ServerError("Not implemented"); - }, - serializeSize() { - throw new ServerError("Not implemented"); - }, -}; - -interface EncryptionSession { - connectionId: string; - remoteAddress: string; - localPort: number; - sessionKey: string; - sKey: string; - gsCipher: Cipher; - gsDecipher: Decipher; - tsCipher: Cipher; - tsDecipher: Decipher; -} - -interface ClientConnection { - status: number; - appID: number; - id: string; - socket: Socket; - remoteAddress: string; - seq: number; - personaId: number; - lastMessageTimestamp: number; - inQueue: boolean; - encryptionSession: EncryptionSession; - useEncryption: boolean; - port: number; - ip: string; -} - -interface SocketWithConnectionInfo { - connectionId: string; - socket: Socket; - seq: number; - id: string; - remoteAddress: string; - localPort: number; - personaId: number; - lastMessageTimestamp: number; - inQueue: boolean; - encryptionSession: EncryptionSession; - useEncryption: boolean; -} - -export interface DatabaseManager { - updateSessionKey: ( - arg0: number, - arg1: string, - arg2: string, - arg3: string, - ) => Promise; - fetchSessionKeyByCustomerId: (arg0: number) => Promise; -} - -/** - * @exports - */ -export interface ConnectionRecord { - customerId: number; - connectionId: string; - sessionKey: string; - sKey: string; - contextId: string; -} - -interface SessionKeys { - sessionKey: string; - sKey: string; -} - -interface TConnection { - connectionId: string; - localPort: number; - remoteAddress: string; - socket: Socket; - encryptionSession: EncryptionSession; - useEncryption: boolean; - inQueue: boolean; -} - -interface JSONResponseOfGameMessage { - msgNo: number; - opCode: number | null; - msgLength: number; - msgVersion: number; - content: string; - contextId: string; - direction: "sent" | "received"; - sessionKey: string | null; - rawBuffer: string; -} - -interface GameMessage { - serialize: () => Buffer; - deserialize: (arg0: Buffer) => void; - toJSON: () => JSONResponseOfGameMessage; - dumpPacket: () => string; -} - -export interface GameMessageOpCode { - name: string; - value: number; - module: "Lobby" | "Login"; -} - -interface BuiltinError { - code: number; - message: string; -} - -export interface PersonaRecord { - customerId: number; - id: Buffer; - maxPersonas: Buffer; - name: Buffer; - personaCount: Buffer; - shardId: Buffer; -} - -export interface UserRecordMini { - contextId: string; - customerId: number; - userId: number; -} - -interface WebJSONResponse { - code: number; - headers: - | import("http").OutgoingHttpHeaders - | import("http").OutgoingHttpHeader[] - | undefined; - body: { connectionId: string; remoteAddress: string; inQueue: boolean }[]; -} - -type WebConnectionHandler = ( - req: IncomingMessage, - res: ServerResponse, - config: Configuration, - log: import("pino").Logger, -) => void; - -/** - * @exports - */ -export interface RaceLobbyRecord { - lobbyId: number; - raceTypeId: number; - turfId: number; - riffName: string; - eTurfName: string; -} - -export interface ServiceArgs { - connectionId: string; - message: SerializedBufferOld; - log: import("pino").Logger; -} - -type Service = ( - args: ServiceArgs, -) => Promise; - -export interface KeypressEvent { - sequence: string; - name: string; - ctrl: boolean; - meta: boolean; - shift: boolean; -} - -export as namespace interfaces; diff --git a/packages/lobby/package.json b/packages/lobby/package.json index 2921feae4..50a1fed87 100644 --- a/packages/lobby/package.json +++ b/packages/lobby/package.json @@ -12,7 +12,7 @@ "check": "tsc", "lint": "eslint .", "format": "npx prettier --write .", - "test": "echo \"Error: no test specified\" && exit 0", + "test": "vitest run --coverage", "test:crypto": "pnpm node --openssl-legacy-provider node_modules/vitest/vitest.mjs run --coverage" }, "keywords": [], diff --git a/packages/lobby/src/handlers/requestConnectGameServer.ts b/packages/lobby/src/handlers/requestConnectGameServer.ts index b9295a263..d0f29240d 100644 --- a/packages/lobby/src/handlers/requestConnectGameServer.ts +++ b/packages/lobby/src/handlers/requestConnectGameServer.ts @@ -49,7 +49,7 @@ export async function _npsRequestGameConnectServer({ log = getServerLogger({ module: "LoginServer", }), -}: import("../../../interfaces/index.js").ServiceArgs): Promise<{ +}: import("../../../shared/src/interfaces.js").ServiceArgs): Promise<{ connectionId: string; messages: SerializedBufferOld[]; }> { diff --git a/packages/login/index.ts b/packages/login/index.ts index 7674ca0ba..8cbf46997 100644 --- a/packages/login/index.ts +++ b/packages/login/index.ts @@ -1 +1 @@ -export { receiveLoginData } from "./src/index.js"; +export { receiveLoginData } from "./src/receiveLoginData.js"; diff --git a/packages/login/package.json b/packages/login/package.json index 0f099a9b0..25922da39 100644 --- a/packages/login/package.json +++ b/packages/login/package.json @@ -12,7 +12,7 @@ "check": "tsc", "lint": "eslint .", "format": "npx prettier --write .", - "test": "echo \"Error: no test specified\" && exit 0", + "test": "vitest run --coverage", "test:crypto": "pnpm node --openssl-legacy-provider node_modules/vitest/vitest.mjs run --coverage" }, "keywords": [], diff --git a/packages/login/src/index.ts b/packages/login/src/index.ts deleted file mode 100644 index 3cd3bf9ef..000000000 --- a/packages/login/src/index.ts +++ /dev/null @@ -1,164 +0,0 @@ -// mcos is a game server, written from scratch, for an old game -// Copyright (C) <2017> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -import { DatabaseManager } from "../../interfaces/index.js"; -import { ServerError } from "../../shared/src/ServerError.js"; -import { getServerLogger } from "rusty-motors-shared"; -import { NPSMessage } from "../../shared/NPSMessage.js"; -import { handleLoginData } from "./internal.js"; - -/** - * Please use {@link LoginServer.getInstance()} - */ -export class LoginServer { - _log: any; - static _instance: LoginServer | undefined; - /** - * Please use {@see LoginServer.getInstance} instead - * @param {object} options - * @param {import("../../interfaces/index.js").DatabaseManager} options.database - * @param {import("pino").Logger} [options.log=getServerLogger({ module: "LoginServer" })] - * @memberof LoginServer - */ - constructor({ - log = getServerLogger({ - module: "LoginServer", - }), - }: { - database: import("../../interfaces/index.js").DatabaseManager; - log?: import("pino").Logger; - }) { - this._log = log; - LoginServer._instance = this; - } - - /** - * Get the single instance of the login server - * - * @static - * @param {import("../../interfaces/index.js").DatabaseManager} database - * @param {import("pino").Logger} log - * @return {LoginServer} - */ - static getInstance( - database: import("../../interfaces/index.js").DatabaseManager, - log: import("pino").Logger, - ): LoginServer { - if (typeof LoginServer._instance === "undefined") { - LoginServer._instance = new LoginServer({ - database, - log, - }); - } - return LoginServer._instance; - } - - /** - * - * @param {string} contextId - * @return {import("../../interfaces/index.js").UserRecordMini} - */ - _npsGetCustomerIdByContextId( - contextId: string, - ): import("../../interfaces/index.js").UserRecordMini { - this._log.debug(">>> _npsGetCustomerIdByContextId"); - /** @type {import("../../interfaces/index.js").UserRecordMini[]} */ - const users: import("../../interfaces/index.js").UserRecordMini[] = [ - { - contextId: "5213dee3a6bcdb133373b2d4f3b9962758", - customerId: 0x0012808b, - userId: 0x00000002, - }, - { - contextId: "d316cd2dd6bf870893dfbaaf17f965884e", - customerId: 0x0054b46c, - userId: 0x00000001, - }, - ]; - if (contextId.toString() === "") { - const err = new ServerError( - `Unknown contextId: ${contextId.toString()}`, - ); - throw err; - } - - const userRecord = users.filter((user) => user.contextId === contextId); - if (typeof userRecord[0] === "undefined" || userRecord.length !== 1) { - this._log.debug( - `preparing to leave _npsGetCustomerIdByContextId after not finding record', - ${JSON.stringify({ - contextId, - })}`, - ); - const err = new ServerError( - `Unable to locate user record matching contextId ${contextId}`, - ); - throw err; - } - - this._log.debug( - `preparing to leave _npsGetCustomerIdByContextId after finding record', - ${JSON.stringify({ - contextId, - userRecord, - })}`, - ); - return userRecord[0]; - } -} - -/** @type {LoginServer | undefined} */ -LoginServer._instance = undefined; - -/** - * Entry and exit point of the Login service - * - * @export - * @param {object} args - * @param {string} args.connectionId - * @param {NPSMessage} args.message - * @param {import("pino").Logger} [args.log=getServerLogger({ module: "LoginServer" })] - * - * @return {Promise} - */ -export async function receiveLoginData({ - connectionId, - message, - log = getServerLogger({ - module: "LoginServer", - }), -}: { - connectionId: string; - message: NPSMessage; - log?: import("pino").Logger; -}): Promise { - try { - log.debug("Entering login module"); - const response = await handleLoginData({ - connectionId, - message, - log, - }); - log.debug(`There are ${response.messages.length} messages`); - log.debug("Exiting login module"); - return response; - } catch (error) { - const err = new ServerError( - `There was an error in the login service: ${String(error)}`, - ); - throw err; - } -} diff --git a/packages/login/src/internal.ts b/packages/login/src/internal.ts index 07cd944dd..48de4228f 100644 --- a/packages/login/src/internal.ts +++ b/packages/login/src/internal.ts @@ -21,10 +21,10 @@ import { SerializedBufferOld } from "../../shared/SerializedBufferOld.js"; import { NPSMessage } from "../../shared/NPSMessage.js"; import { NetworkMessage } from "../../shared/src/NetworkMessage.js"; import { NPSUserStatus } from "./NPSUserStatus.js"; -import { updateSessionKey } from "../../database/index.js"; +import { updateSessionKey } from "rusty-motors-database"; /** @type {import("../../interfaces/index.js").UserRecordMini[]} */ -const userRecords: import("../../interfaces/index.js").UserRecordMini[] = [ +const userRecords: import("../../shared/src/interfaces.js").UserRecordMini[] = [ { contextId: "5213dee3a6bcdb133373b2d4f3b9962758", customerId: 0x0012808b, diff --git a/packages/login/src/receiveLoginData.ts b/packages/login/src/receiveLoginData.ts new file mode 100644 index 000000000..03abba024 --- /dev/null +++ b/packages/login/src/receiveLoginData.ts @@ -0,0 +1,58 @@ +// mcos is a game server, written from scratch, for an old game +// Copyright (C) <2017> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import { getServerLogger, NPSMessage } from "rusty-motors-shared"; +import { handleLoginData } from "./internal.js"; + +/** + * Receives login data and handles the login process. + * + * @param {Object} options - The options for receiving login data. + * @param {string} options.connectionId - The connection ID. + * @param {NPSMessage} options.message - The login message. + * @param {import("pino").Logger} [options.log] - The logger instance. + * + * @returns {Promise} The response from the login process. + * + * @throws {Error} If there was an error in the login service. + */ +export async function receiveLoginData({ + connectionId, + message, + log = getServerLogger({ + module: "LoginServer", + }), +}: { + connectionId: string; + message: NPSMessage; + log?: import("pino").Logger; +}): Promise { + try { + log.debug("Entering login module"); + const response = await handleLoginData({ + connectionId, + message, + log, + }); + log.debug(`There are ${response.messages.length} messages`); + log.debug("Exiting login module"); + return response; + } catch (error) { + const err = new Error( + `There was an error in the login service: ${String(error)}`, + ); + throw err; + } +} diff --git a/packages/login/test/LoginServer.test.ts b/packages/login/test/LoginServer.test.ts deleted file mode 100644 index b5a88559d..000000000 --- a/packages/login/test/LoginServer.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - mockDatabaseManager, - mockPino, - unmockPino, -} from "../../../test/factoryMocks.js"; -import { getServerLogger } from "rusty-motors-shared"; -import { LoginServer } from "../src/index.js"; - -describe("LoginServer", () => { - describe("constructor", () => { - it("should create a new instance", () => { - mockPino(); - const loginServer = new LoginServer({ - database: mockDatabaseManager(), - log: getServerLogger({}), - }); - expect(loginServer).toBeDefined(); - unmockPino(); - }); - }); -}); diff --git a/packages/login/test/index.test.ts b/packages/login/test/index.test.ts new file mode 100644 index 000000000..240a0e966 --- /dev/null +++ b/packages/login/test/index.test.ts @@ -0,0 +1,7 @@ +import type { NPSMessage } from "rusty-motors-shared"; +import { handleLoginData } from "../src/internal.js"; +import { describe, expect, it } from "vitest"; + +it("needs tests", () => { + expect(true).toBe(true); +}); \ No newline at end of file diff --git a/packages/persona/index.ts b/packages/persona/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/persona/package.json b/packages/persona/package.json new file mode 100644 index 000000000..78a4e5e55 --- /dev/null +++ b/packages/persona/package.json @@ -0,0 +1,29 @@ +{ + "name": "rusty-motors-persona", + "version": "1.0.0", + "exports": { + ".": { + "import": "./index.js", + "require": "./index.js" + } + }, + "type": "module", + "scripts": { + "check": "tsc", + "lint": "eslint .", + "format": "npm prettier --write .", + "test": "vitest run --coverage" + }, + "keywords": [], + "author": "", + "license": "AGPL-3.0", + "dependencies": { + "@sentry/node": "^8.0.0", + "short-unique-id": "^5.0.3" + }, + "description": "", + "devDependencies": { + "@vitest/coverage-v8": "2", + "vitest": "^2.0.5" + } +} diff --git a/packages/persona/src/getPersonasByPersonaId.ts b/packages/persona/src/getPersonasByPersonaId.ts index 0bdf8d1eb..86eac4388 100644 --- a/packages/persona/src/getPersonasByPersonaId.ts +++ b/packages/persona/src/getPersonasByPersonaId.ts @@ -1,4 +1,4 @@ -import { PersonaRecord } from "../../interfaces/index.js"; +import { PersonaRecord } from "../../shared/src/interfaces.js"; import { ServerError } from "../../shared/src/ServerError.js"; import { personaRecords } from "./internal.js"; @@ -14,7 +14,7 @@ export async function getPersonasByPersonaId({ }: { personas?: PersonaRecord[]; id: number; -}): Promise { +}): Promise { const results = personaRecords.filter((persona) => { const match = id === persona.id.readInt32BE(0); return match; diff --git a/packages/persona/src/internal.ts b/packages/persona/src/internal.ts index 03fea1833..b305f04cd 100644 --- a/packages/persona/src/internal.ts +++ b/packages/persona/src/internal.ts @@ -101,7 +101,7 @@ export function generateNameBuffer(name: string, size: number): Buffer { * NOTE: Currently we only support one persona per customer * @type {import("../../interfaces/index.js").PersonaRecord[]} */ -export const personaRecords: import("../../interfaces/index.js").PersonaRecord[] = +export const personaRecords: import("../../shared/src/interfaces.js").PersonaRecord[] = [ { customerId: 2868969472, @@ -128,7 +128,7 @@ export const personaRecords: import("../../interfaces/index.js").PersonaRecord[] */ async function getPersonasByCustomerId( customerId: number, -): Promise { +): Promise { const results = personaRecords.filter( (persona) => persona.customerId === customerId, ); @@ -145,7 +145,7 @@ async function getPersonasByCustomerId( */ async function getPersonaMapsByCustomerId( customerId: number, -): Promise { +): Promise { switch (customerId) { case 5551212: return getPersonasByCustomerId(customerId); diff --git a/packages/persona/test/getPersonasByPersonaId.test.ts b/packages/persona/test/getPersonasByPersonaId.test.ts index 547ae9c07..b14ca4955 100644 --- a/packages/persona/test/getPersonasByPersonaId.test.ts +++ b/packages/persona/test/getPersonasByPersonaId.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { PersonaRecord } from "../../interfaces/index.js"; +import { PersonaRecord } from "rusty-motors-shared"; import { getPersonasByPersonaId } from "../src/getPersonasByPersonaId.js"; describe("getPersonasByPersonaId", () => { diff --git a/packages/persona/vite.config.ts b/packages/persona/vite.config.ts new file mode 100644 index 000000000..efe0ba0db --- /dev/null +++ b/packages/persona/vite.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + coverage: { + enabled: true, + all: true, + exclude: [ + "src/**/*.spec.ts", + "src/**/*.test.ts", + "bin/**/*.ts", + "ecosystem.config.js", + "migrate.ts", + "packages/**/*.d.ts", + "vite.config.ts" + ], + reporter: ["lcov", "text", "cobertura"], + }, + reporters: ["junit", "default", "hanging-process"], + outputFile: "mcos.junit.xml", + pool: "forks", + }, +}); diff --git a/packages/shared/index.ts b/packages/shared/index.ts index 526708762..1c1789ee2 100644 --- a/packages/shared/index.ts +++ b/packages/shared/index.ts @@ -30,6 +30,8 @@ export { export type { State } from "./State.js"; export type { OnDataHandler, ServiceResponse } from "./State.js"; export { LegacyMessage } from "./LegacyMessage.js"; +export { NPSHeader } from "./NPSHeader.js"; +export * from "./src/interfaces.js"; export interface KeypressEvent { sequence: string; diff --git a/packages/shared/src/interfaces.ts b/packages/shared/src/interfaces.ts new file mode 100644 index 000000000..217feb5f4 --- /dev/null +++ b/packages/shared/src/interfaces.ts @@ -0,0 +1,84 @@ +import { SerializedBufferOld } from "rusty-motors-shared"; + +/** + * @module interfaces + */ + +export const name = "interfaces"; + +/** + * @exports + * @interface + */ + +export interface DatabaseManager { + updateSessionKey: ( + arg0: number, + arg1: string, + arg2: string, + arg3: string, + ) => Promise; + fetchSessionKeyByCustomerId: (arg0: number) => Promise; +} + +/** + * @exports + */ +export interface ConnectionRecord { + customerId: number; + connectionId: string; + sessionKey: string; + sKey: string; + contextId: string; +} + +interface SessionKeys { + sessionKey: string; + sKey: string; +} + +export interface GameMessageOpCode { + name: string; + value: number; + module: "Lobby" | "Login"; +} + +export interface PersonaRecord { + customerId: number; + id: Buffer; + maxPersonas: Buffer; + name: Buffer; + personaCount: Buffer; + shardId: Buffer; +} + +export interface UserRecordMini { + contextId: string; + customerId: number; + userId: number; +} + +/** + * @exports + */ +export interface RaceLobbyRecord { + lobbyId: number; + raceTypeId: number; + turfId: number; + riffName: string; + eTurfName: string; +} + +export interface ServiceArgs { + connectionId: string; + message: SerializedBufferOld; + log: import("pino").Logger; +} + +export interface KeypressEvent { + sequence: string; + name: string; + ctrl: boolean; + meta: boolean; + shift: boolean; +} diff --git a/packages/transactions/package.json b/packages/transactions/package.json index 0756208b1..abb29232a 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -12,7 +12,7 @@ "check": "tsc", "lint": "eslint .", "format": "npx prettier --write .", - "test": "echo \"Error: no test specified\" && exit 0", + "test": "pnpm node --openssl-legacy-provider node_modules/vitest/vitest.mjs run --coverage", "test:crypto": "pnpm node --openssl-legacy-provider node_modules/vitest/vitest.mjs run --coverage" }, "keywords": [], diff --git a/packages/transactions/test/clientConnect.test.ts b/packages/transactions/test/clientConnect.test.ts index 638358a11..dd5ac0db4 100644 --- a/packages/transactions/test/clientConnect.test.ts +++ b/packages/transactions/test/clientConnect.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from "vitest"; -import { getDatabaseServer } from "../../database/src/DatabaseManager.js"; +import { updateSessionKey } from "rusty-motors-database"; import { State } from "../../shared/State.js"; -import { ServerError } from "../../shared/errors/ServerError.js"; import { getServerLogger } from "rusty-motors-shared"; import { TClientConnectMessage } from "../src/TClientConnectMessage.js"; import { clientConnect } from "../src/clientConnect.js"; @@ -18,16 +17,7 @@ describe("clientConnect", () => { incomingMessage._customerId = customerId; const log = getServerLogger({}); - const state: State = { - encryptions: {}, - sessions: {}, - filePaths: {}, - sockets: {}, - queuedConnections: {}, - onDataHandlers: {}, - save() {}, - }; - getDatabaseServer().updateSessionKey( + updateSessionKey( customerId, sessionKey, contextId, @@ -44,7 +34,7 @@ describe("clientConnect", () => { } catch (error) { // assert expect(error).toEqual( - new ServerError(`Encryption not found for connection ${connectionId}`), + new Error(`Encryption not found for connection ${connectionId}`), ); } }); diff --git a/packages/transactions/test/getLobbies.test.ts b/packages/transactions/test/getLobbies.test.ts index dfac4f1b9..39f490ce7 100644 --- a/packages/transactions/test/getLobbies.test.ts +++ b/packages/transactions/test/getLobbies.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { mockPino } from "../../../test/factoryMocks.js"; -import { getServerLogger } from "rusty-motors-shareds"; +import { getServerLogger } from "rusty-motors-shared"; import { OldServerMessage } from "rusty-motors-shared"; import { getLobbies } from "../src/getLobbies.js"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 63b372e27..e801b5c46 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -323,6 +323,22 @@ importers: specifier: ^2.0.5 version: 2.0.5(@types/node@22.5.0) + packages/persona: + dependencies: + '@sentry/node': + specifier: ^8.0.0 + version: 8.26.0 + short-unique-id: + specifier: ^5.0.3 + version: 5.2.0 + devDependencies: + '@vitest/coverage-v8': + specifier: '2' + version: 2.0.5(vitest@2.0.5(@types/node@22.5.0)) + vitest: + specifier: ^2.0.5 + version: 2.0.5(@types/node@22.5.0) + packages/sessions: dependencies: '@sentry/node': diff --git a/test/factoryMocks.ts b/test/factoryMocks.ts index 7c1e14b5a..e5c7c7b58 100644 --- a/test/factoryMocks.ts +++ b/test/factoryMocks.ts @@ -1,13 +1,6 @@ import { expect, it, vi } from "vitest"; import { verifyLegacyCipherSupport } from "../packages/gateway/src/encryption.js"; -import { DatabaseManager } from "../packages/interfaces/index.js"; -export function mockDatabaseManager(): DatabaseManager { - return { - updateSessionKey: vi.fn(), - fetchSessionKeyByCustomerId: vi.fn(), - }; -} export function mockPino() { vi.mock("pino", () => { diff --git a/vite.config.ts b/vite.config.ts index 0bb055768..62eace73b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,19 +1,19 @@ import { defineConfig, coverageConfigDefaults } from "vitest/config"; export default defineConfig({ - test: { - coverage: { - enabled: true, - all: true, - exclude: [ - "src/**/*.spec.ts", - "src/**/*.test.ts", - "bin/**/*.ts", - "interfaces", - "vite.config.ts", - ...coverageConfigDefaults.exclude - ], - reporter: ["lcov", "text", "cobertura"], - }, - }, + test: { + coverage: { + enabled: true, + all: true, + exclude: [ + "src/**/*.spec.ts", + "src/**/*.test.ts", + "bin/**/*.ts", + "interfaces", + "vite.config.ts", + ...coverageConfigDefaults.exclude, + ], + reporter: ["lcov", "text", "cobertura"], + }, + }, });