0
0
mirror of https://github.com/sp-tarkov/server.git synced 2025-02-13 09:50:43 -05:00
server/project/tests/services/PaymentService.test.ts

211 lines
9.0 KiB
TypeScript
Raw Normal View History

import "reflect-metadata";
BunJS Runtime, Updated Build System, and... (#992) This is a big one. 🔥 **Changes:** - Targets next major release (v4.0.0) - Switch runtimes from NodeJS to BunJS (bun.sh) - Typescript library support moved to `ESNext` - Typescript compile option `verbatimModuleSyntax` enabled - Updated all interfaces to be imported explicitly as types - Strict mode enabled - Reduces the number of tsconfig files - Pins all dep packages to specific patch versions - Includes Bun lock file in repo (doesn't cause issues like the package-lock did) - Replaces Gulp with a new Typescript based build system - Adds `core-js` as a workaround for Bun not playing nice with `reflect-metadata` - Removes `pkg` and `swc` (Yay Bun!) - Updated package scripts and entry point system to be more intuitive - Updated VSCode workspace configurations - Updated `.gitignore` to align with updated project structure - Updated Biome configuration to align with updated project structure - `Program.ts` - Removes call to set encoding on the process - `global.d.ts` - Added underscores to build globals to match other global names - `JsonUtil.ts` - Replaced old `fixJson` package with newer `jsonrepair` package - `HashUtil.ts` - Replaced old `buffer-crc32` package with built-in `node:zlib` package - `DatabaseImporter.ts` - Updates database validation object to be flat, where the keys are the relative path to the file - `BunTimer.ts` - Adds an easy to use timer class that's compatible with nanoseconds. **TODO:** - Look into mod loading. I think we use a TS transpiler for mods and I believe that can be removed now. - Bun includes a number of APIs that can be used in place of Node's packages (built-in or otherwise); HTTP server, WebSocket server, File IO, Hashing, File Globing, Testing... Each of these should be utilized where ever possible. - Update in-repo documentation to reference BunJS instead of NodeJS.
2024-12-21 17:46:39 -05:00
import type { IPmcData } from "@spt/models/eft/common/IPmcData";
import type { IItem } from "@spt/models/eft/common/tables/IItem";
import type { ITraderBase } from "@spt/models/eft/common/tables/ITrader";
import type { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
import type { IProcessBuyTradeRequestData } from "@spt/models/eft/trade/IProcessBuyTradeRequestData";
import { PaymentService } from "@spt/services/PaymentService";
import { HashUtil } from "@spt/utils/HashUtil";
import { container } from "tsyringe";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
describe("PaymentService", () => {
let paymentService: any; // Using "any" to access private/protected methods without type errors.
beforeEach(() => {
paymentService = container.resolve<PaymentService>("PaymentService");
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("payMoney", () => {
it("should output a currency change when a single non-barter item is purchased from a trader", () => {
const hashUtil = container.resolve<HashUtil>("HashUtil");
const traderId = "54cb57776803fa99248b456e"; // Therapist
const purchaseItemId = hashUtil.generate(); // Inconsequential ID
const purchaseQuantity = 1; // The amount of the items that the player is purchasing.
const costItemId = hashUtil.generate(); // Inconsequential ID
const costItemTpl = "5449016a4bdc2d6f028b456f"; // Roubles
const costAmount = 17896; // The amount of roubles that the item costs.
// Object representing a money item.
const moneyItem = {
_id: costItemId,
_tpl: costItemTpl,
upd: {
2023-11-10 17:21:20 -05:00
StackObjectsCount: costAmount * 4, // More than enough.
},
} as IItem;
// Object representing the player's PMC inventory.
const pmcData = {
TradersInfo: { [traderId]: { salesSum: 0, unlocked: true, disabled: false } },
Inventory: { items: [moneyItem] },
} as unknown as IPmcData;
// Buy a factory map from Therapist... although it doesn't really matter what the item is as there's no
// template ID provided in the request data, just the ID.
const processBuyTradeRequestData = {
Action: "TradingConfirm",
type: "buy_from_trader",
tid: traderId,
item_id: purchaseItemId,
count: purchaseQuantity,
scheme_id: 0,
scheme_items: [{ id: costItemId, count: costAmount }],
} as IProcessBuyTradeRequestData;
// Inconsequential profile ID
const sessionID = hashUtil.generate();
const itemEventRouterResponse = {
warnings: [],
profileChanges: { [sessionID]: { _id: sessionID, items: { new: [], change: [], del: [] } } },
} as unknown as IItemEventRouterResponse;
// Mock the logger debug method to return void.
vi.spyOn((paymentService as any).logger, "debug").mockResolvedValue(undefined);
// Mock the trader helper to return a trader with the currency of Roubles.
const getTraderSpy = vi
.spyOn((paymentService as any).traderHelper, "getTrader")
.mockReturnValue({ tid: traderId, currency: "RUB" } as unknown as ITraderBase);
// Mock the addPaymentToOutput method to subtract the item cost from the money stack.
const addPaymentToOutputSpy = vi
.spyOn(paymentService as any, "addPaymentToOutput")
.mockImplementation(
(
pmcData: IPmcData,
currencyTpl: string,
amountToPay: number,
sessionIdentifier: string,
output: IItemEventRouterResponse,
) => {
moneyItem.upd.StackObjectsCount -= costAmount;
output.profileChanges[sessionIdentifier].items.change.push(moneyItem);
},
);
// Mock the traderHelper lvlUp method to return void.
const lvlUpSpy = vi.spyOn((paymentService as any).traderHelper, "lvlUp").mockResolvedValue(undefined);
2023-11-10 17:21:20 -05:00
paymentService.payMoney(pmcData, processBuyTradeRequestData, sessionID, itemEventRouterResponse);
// Check for absence of output warnings.
expect(itemEventRouterResponse.warnings).toHaveLength(0);
// Check that the currency change was correctly handled.
expect(itemEventRouterResponse.profileChanges[sessionID].items.change).toHaveLength(1);
expect(itemEventRouterResponse.profileChanges[sessionID].items.change[0]._id).toBe(costItemId);
expect(itemEventRouterResponse.profileChanges[sessionID].items.change[0]._tpl).toBe(costItemTpl);
expect(itemEventRouterResponse.profileChanges[sessionID].items.change[0].upd.StackObjectsCount).toBe(
costAmount * 3,
);
// Check if mocked methods were called as expected.
expect(getTraderSpy).toBeCalledTimes(1);
2023-11-10 17:21:20 -05:00
expect(addPaymentToOutputSpy).toBeCalledWith(
expect.anything(),
costItemTpl,
costAmount,
sessionID,
expect.anything(),
);
expect(lvlUpSpy).toBeCalledTimes(1);
});
});
describe("isInStash", () => {
it("should return true when item is direct parent of stash", () => {
const hashUtil = container.resolve<HashUtil>("HashUtil");
const stashItem: IItem = {
_id: "stashid",
2023-11-10 17:21:20 -05:00
_tpl: "55d7217a4bdc2d86028b456d", // standard stash id
};
const inventoryItemToFind: IItem = {
_id: hashUtil.generate(),
_tpl: "544fb6cc4bdc2d34748b456e", // Slickers chocolate bar
parentId: stashItem._id,
2023-11-10 17:21:20 -05:00
slotId: "hideout",
};
const playerInventory = [stashItem, inventoryItemToFind];
const result = paymentService.isInStash(inventoryItemToFind._id, playerInventory, stashItem._id);
expect(result).toBe(true);
});
it("should return true when item is indirect parent of inventory", () => {
const hashUtil = container.resolve<HashUtil>("HashUtil");
const stashItem: IItem = {
_id: "stashId",
2023-11-10 17:21:20 -05:00
_tpl: "55d7217a4bdc2d86028b456d", // standard stash id
};
const foodBagToHoldItemToFind: IItem = {
_id: hashUtil.generate(),
_tpl: "5c093db286f7740a1b2617e3",
parentId: stashItem._id,
2023-11-10 17:21:20 -05:00
slotId: "hideout",
};
const inventoryItemToFind: IItem = {
_id: hashUtil.generate(),
_tpl: "544fb6cc4bdc2d34748b456e", // Slickers chocolate bar
2023-11-10 17:21:20 -05:00
parentId: foodBagToHoldItemToFind._id,
};
const playerInventory = [stashItem, foodBagToHoldItemToFind, inventoryItemToFind];
const result = paymentService.isInStash(inventoryItemToFind._id, playerInventory, stashItem._id);
expect(result).toBe(true);
});
it("should return false when desired item is not in inventory", () => {
const hashUtil = container.resolve<HashUtil>("HashUtil");
const stashItem: IItem = {
_id: "stashId",
2023-11-10 17:21:20 -05:00
_tpl: "55d7217a4bdc2d86028b456d", // standard stash id
};
const inventoryItemToFind: IItem = {
_id: hashUtil.generate(),
_tpl: "544fb6cc4bdc2d34748b456e", // Slickers chocolate bar
parentId: stashItem._id,
2023-11-10 17:21:20 -05:00
slotId: "hideout",
};
const playerInventory = [stashItem, inventoryItemToFind];
const result = paymentService.isInStash("notCorrectId", playerInventory, stashItem._id);
expect(result).toBe(false);
});
it("should return false when player inventory array has no inventory item", () => {
const hashUtil = container.resolve<HashUtil>("HashUtil");
const stashItem: IItem = {
_id: "stashId",
2023-11-10 17:21:20 -05:00
_tpl: "55d7217a4bdc2d86028b456d", // standard stash id
};
const inventoryItemToFind: IItem = {
_id: hashUtil.generate(),
_tpl: "544fb6cc4bdc2d34748b456e", // Slickers chocolate bar
parentId: stashItem._id,
2023-11-10 17:21:20 -05:00
slotId: "hideout",
};
2023-11-10 17:21:20 -05:00
const playerInventory = [inventoryItemToFind];
const result = paymentService.isInStash("notCorrectId", playerInventory, stashItem._id);
expect(result).toBe(false);
});
});
});