Server/project/tests/services/RagfairPriceService.test.ts
Refringe ed8dbbd195 Adds Biome - Removes ESLint & Prettier (!383)
Boogidy, boogidy, boogidy. Let's go racing! 🏎️

Removes the over-complicated and super-slow setup we had with ESLint & Prettier in favour of Biome. The largest change with the formatting is moving from Allman braces to 1TBS braces. Other than that, it's *pretty much* the same. Ah, and that Biome runs formatting and linting on the entire project about x10 faster than the old system ran formatting on one file. Seriously, the guy who came up with that last solution should be fired. :runs:

I've kept all of the formatting and linting commands the same as before, with the main mamma-jamma being: `npm run format`, which applies formatting and linting changes to the entire project.

Formatting-on-save works (quickly!) by (1) ensuring that you're working within the VSC workspace (as you should be), and (2) have the recommended Biome VSC extension installed. The link to the Biome extension is in the README.

This limits our options on code formatting going forward; Biome, like prettier, is very opinionated with very few formatting options available. But I see this as a good thing. I'd rather spend my time arguing about which gun in Tarkov is the best, rather than coding brace styles...

...It's the TOZ, and it always will be. Don't DM me.

Co-authored-by: chomp <chomp@noreply.dev.sp-tarkov.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT/Server/pulls/383
Co-authored-by: Refringe <me@refringe.com>
Co-committed-by: Refringe <me@refringe.com>
2024-07-22 21:15:57 +00:00

498 lines
23 KiB
TypeScript

import "reflect-metadata";
import { container } from "tsyringe";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { Money } from "@spt/models/enums/Money";
import { RagfairPriceService } from "@spt/services/RagfairPriceService";
describe("RagfairPriceService", () =>
{
let ragfairPriceService: any; // Using "any" to access private/protected methods without type errors.
beforeEach(() =>
{
ragfairPriceService = container.resolve<RagfairPriceService>("RagfairPriceService");
});
afterEach(() =>
{
vi.restoreAllMocks();
});
describe("getDynamicOfferPriceForOffer", () =>
{
it("should return zero when empty offerItems array is passed", () =>
{
const offerItems = [];
const desiredCurrency = Money.ROUBLES;
const isPackOffer = false;
const price = ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, desiredCurrency, isPackOffer);
expect(price).toEqual(0);
});
it("should return non-zero number when valid item is passed", () =>
{
const offerItems = [{
_id: "d445ea263cdfc5f278334264",
_tpl: "57e3dba62459770f0c32322b",
parentId: "631abbff398cc0170cbd3089",
slotId: "mod_pistol_grip",
}];
const desiredCurrency = Money.ROUBLES;
const isPackOffer = false;
const expectedPrice = 42069;
// Mock the getDynamicItemPrice method to return a static price.
vi.spyOn(ragfairPriceService, "getDynamicItemPrice").mockReturnValue(expectedPrice);
const price = ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, desiredCurrency, isPackOffer);
expect(price).toBe(expectedPrice);
});
it("should always return a whole number", () =>
{
const offerItems = [{
_id: "d445ea263cdfc5f278334264",
_tpl: "57e3dba62459770f0c32322b",
parentId: "631abbff398cc0170cbd3089",
slotId: "mod_pistol_grip",
}];
const desiredCurrency = Money.ROUBLES;
const isPackOffer = false;
const originalPrice = 42069.999999999;
// Mock the getDynamicItemPrice method to return a static price.
vi.spyOn(ragfairPriceService, "getDynamicItemPrice").mockReturnValue(originalPrice);
const price = ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, desiredCurrency, isPackOffer);
expect(price).toBeGreaterThan(originalPrice);
expect(price).toBe(Math.round(originalPrice));
});
it("should skip prices for soft armour inserts", () =>
{
const offerItems = [{
_id: "d445ea263cdfc5f278334264",
_tpl: "657080a212755ae0d907ad04",
parentId: "631abbff398cc0170cbd3089",
slotId: "Soft_armor_front",
}];
const desiredCurrency = Money.ROUBLES;
const isPackOffer = false;
// Mock the getDynamicItemPrice method.
const getDynamicItemPriceSpy = vi.spyOn(ragfairPriceService, "getDynamicItemPrice");
const price = ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, desiredCurrency, isPackOffer);
expect(price).toBe(0);
expect(getDynamicItemPriceSpy).not.toHaveBeenCalled();
});
it("should not add value of mods to weapon preset", () =>
{
const offerItems = [{
_id: "344d02bbf2102ce4e145bf35",
_tpl: "579204f224597773d619e051",
upd: {
StackObjectsCount: 1,
UnlimitedCount: true,
sptPresetId: "5841499024597759f825ff3e",
Repairable: { Durability: 90, MaxDurability: 90 },
},
}, {
_id: "59c6897a59ed48f1ca02f659",
_tpl: "5448c12b4bdc2d02308b456f",
parentId: "344d02bbf2102ce4e145bf35",
slotId: "mod_magazine",
}, {
_id: "7e8062d4bc57b56927c2d117",
_tpl: "6374a822e629013b9c0645c8",
parentId: "344d02bbf2102ce4e145bf35",
slotId: "mod_reciever",
}, {
_id: "3b09149e8b7833dc5fdd32a4",
_tpl: "63c6adcfb4ba094317063742",
parentId: "7e8062d4bc57b56927c2d117",
slotId: "mod_sight_rear",
}, {
_id: "e833a5c26af29870df9cdd2e",
_tpl: "6374a7e7417239a7bf00f042",
parentId: "344d02bbf2102ce4e145bf35",
slotId: "mod_pistolgrip",
}];
const desiredCurrency = Money.ROUBLES;
const isPackOffer = false;
const expectedPrice = 10000;
// Mock the getDynamicItemPrice method to return a static price.
const getDynamicItemPriceSpy = vi.spyOn(ragfairPriceService, "getDynamicItemPrice").mockReturnValue(
expectedPrice,
);
const price = ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, desiredCurrency, isPackOffer);
expect(price).toBe(expectedPrice);
expect(getDynamicItemPriceSpy).toHaveBeenCalledTimes(1);
});
it("should sum value of all offer items", () =>
{
const offerItems = [{
_id: "59c6897a59ed48f1ca02f659",
_tpl: "5448c12b4bdc2d02308b456f",
parentId: "344d02bbf2102ce4e145bf35",
slotId: "mod_magazine",
}, {
_id: "7e8062d4bc57b56927c2d117",
_tpl: "6374a822e629013b9c0645c8",
parentId: "344d02bbf2102ce4e145bf35",
slotId: "mod_reciever",
}, {
_id: "3b09149e8b7833dc5fdd32a4",
_tpl: "63c6adcfb4ba094317063742",
parentId: "7e8062d4bc57b56927c2d117",
slotId: "mod_sight_rear",
}, {
_id: "e833a5c26af29870df9cdd2e",
_tpl: "6374a7e7417239a7bf00f042",
parentId: "344d02bbf2102ce4e145bf35",
slotId: "mod_pistolgrip",
}];
const desiredCurrency = Money.ROUBLES;
const isPackOffer = false;
const expectedPrice = 10000;
// Mock the getDynamicItemPrice method to return a static price.
const getDynamicItemPriceSpy = vi.spyOn(ragfairPriceService, "getDynamicItemPrice").mockReturnValue(
expectedPrice,
);
const price = ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, desiredCurrency, isPackOffer);
expect(price).toBe(expectedPrice * offerItems.length);
expect(getDynamicItemPriceSpy).toHaveBeenCalledTimes(offerItems.length);
});
});
describe("getDynamicItemPrice", () =>
{
it("should not return zero for a valid template ID", () =>
{
const itemTemplateId = "5e54f6af86f7742199090bf3";
const desiredCurrency = Money.ROUBLES;
const price = ragfairPriceService.getDynamicItemPrice(itemTemplateId, desiredCurrency);
expect(price).not.toBe(0);
});
it("should use trader price if it is higher than flea price and configuration allows it", () =>
{
const itemTemplateId = "5e54f6af86f7742199090bf3";
const desiredCurrency = Money.ROUBLES;
const mockTraderPrice = 20000;
const mockFleaPrice = 15000;
const getOfferTypeRangeValues = { max: 1, min: 1 };
// Mock the configs to allow using trader price if higher. Disable other adjustments for isolation.
ragfairPriceService.ragfairConfig.dynamic.offerAdjustment.adjustPriceWhenBelowHandbookPrice = false;
ragfairPriceService.ragfairConfig.dynamic.useTraderPriceForOffersIfHigher = true;
ragfairPriceService.ragfairConfig.dynamic.itemPriceMultiplier[itemTemplateId] = null;
// Mock the getFleaPriceForItem method to return a static price.
vi.spyOn(ragfairPriceService, "getFleaPriceForItem").mockReturnValue(mockFleaPrice);
// Mock the getHighestSellToTraderPrice method to return a higher static price.
vi.spyOn((ragfairPriceService as any).traderHelper, "getHighestSellToTraderPrice").mockReturnValue(
mockTraderPrice,
);
// Mock the getOfferTypeRangeValues method to return a static minMax.
vi.spyOn(ragfairPriceService, "getOfferTypeRangeValues").mockReturnValue(getOfferTypeRangeValues);
// Call the method.
const price = ragfairPriceService.getDynamicItemPrice(itemTemplateId, desiredCurrency);
expect(price).toBe(mockTraderPrice);
});
it("should adjust flea price when below handbook price and configuration allows it", () =>
{
const itemTemplateId = "5e54f6af86f7742199090bf3";
const desiredCurrency = Money.ROUBLES;
const mockFleaPrice = 1;
const handbookPrice = 10000;
const adjustedPrice = 9000;
const getOfferTypeRangeValues = { max: 1, min: 1 };
// Enable adjustment for prices below handbook price. Disable other adjustments for isolation.
ragfairPriceService.ragfairConfig.dynamic.offerAdjustment.adjustPriceWhenBelowHandbookPrice = true;
ragfairPriceService.ragfairConfig.dynamic.useTraderPriceForOffersIfHigher = false;
ragfairPriceService.ragfairConfig.dynamic.itemPriceMultiplier[itemTemplateId] = null;
// Mock the getFleaPriceForItem method to return a static price below the handbook price.
vi.spyOn(ragfairPriceService, "getFleaPriceForItem").mockReturnValue(mockFleaPrice);
// Mock the adjustPriceIfBelowHandbook method to simulate price adjustment.
vi.spyOn(ragfairPriceService, "adjustPriceIfBelowHandbook").mockImplementation(
(price: number, templateId) =>
{
return price < handbookPrice ? adjustedPrice : price;
},
);
// Mock the getOfferTypeRangeValues method to return a static minMax.
vi.spyOn(ragfairPriceService, "getOfferTypeRangeValues").mockReturnValue(getOfferTypeRangeValues);
// Call the method.
const price = ragfairPriceService.getDynamicItemPrice(itemTemplateId, desiredCurrency);
// Verify the price is adjusted correctly according to the mocked handbook price adjustment logic.
expect(price).toBe(adjustedPrice);
});
it("should handle weapon preset prices correctly", () =>
{
const itemTemplateId = "579204f224597773d619e051";
const desiredCurrency = Money.ROUBLES;
const mockPresetPrice = 25000;
const getOfferTypeRangeValues = { max: 1, min: 1 };
const offerItems = [{
_id: "344d02bbf2102ce4e145bf35",
_tpl: "579204f224597773d619e051",
upd: {
StackObjectsCount: 1,
UnlimitedCount: true,
sptPresetId: "5841499024597759f825ff3e",
Repairable: { Durability: 90, MaxDurability: 90 },
},
}, {
_id: "7e8062d4bc57b56927c2d117",
_tpl: "6374a822e629013b9c0645c8",
parentId: "344d02bbf2102ce4e145bf35",
slotId: "mod_reciever",
}];
const item = offerItems[0];
// Disable other adjustments for isolation.
ragfairPriceService.ragfairConfig.dynamic.offerAdjustment.adjustPriceWhenBelowHandbookPrice = false;
ragfairPriceService.ragfairConfig.dynamic.useTraderPriceForOffersIfHigher = false;
ragfairPriceService.ragfairConfig.dynamic.itemPriceMultiplier[itemTemplateId] = null;
// Mock getFleaPriceForItem to bypass initial flea price fetch
vi.spyOn(ragfairPriceService, "getFleaPriceForItem").mockReturnValue(0);
// Mock the isPresetBaseClass method to return true for the item
vi.spyOn((ragfairPriceService as any).presetHelper, "isPresetBaseClass").mockReturnValue(true);
// Mock the getWeaponPresetPrice method to return a specific preset price
const getWeaponPresetPriceSpy = vi.spyOn(ragfairPriceService, "getWeaponPresetPrice").mockReturnValue(
mockPresetPrice,
);
// Mock the getOfferTypeRangeValues method to return a static minMax.
vi.spyOn(ragfairPriceService, "getOfferTypeRangeValues").mockReturnValue(getOfferTypeRangeValues);
// Mock the getItemQualityModifier method to return 1 (no change)
vi.spyOn((ragfairPriceService as any).itemHelper, "getItemQualityModifier").mockReturnValue(1);
// Call the method with the mock item and offer items
const price = ragfairPriceService.getDynamicItemPrice(itemTemplateId, desiredCurrency, item, offerItems);
// Call the method.
expect(price).toBe(mockPresetPrice);
// Additionally, you can verify that getWeaponPresetPrice was called with the correct parameters
expect(getWeaponPresetPriceSpy).toHaveBeenCalledWith(item, offerItems, expect.any(Number));
});
it("should update price based on the ragfair config item price multiplier values", () =>
{
const itemTemplateId = "5e54f6af86f7742199090bf3";
const desiredCurrency = Money.ROUBLES;
const mockFleaPrice = 20000;
const itemPriceMultiplier = 2;
const getOfferTypeRangeValues = { max: 1, min: 1 };
// Mock the ragfair config to have a price multiplier of 2. Disable other adjustments for isolation.
ragfairPriceService.ragfairConfig.dynamic.itemPriceMultiplier[itemTemplateId] = itemPriceMultiplier;
ragfairPriceService.ragfairConfig.dynamic.offerAdjustment.adjustPriceWhenBelowHandbookPrice = false;
ragfairPriceService.ragfairConfig.dynamic.useTraderPriceForOffersIfHigher = false;
// Mock the getFleaPriceForItem method to return a static price.
vi.spyOn(ragfairPriceService, "getFleaPriceForItem").mockReturnValue(mockFleaPrice);
// Mock the getOfferTypeRangeValues method to return a static minMax.
vi.spyOn(ragfairPriceService, "getOfferTypeRangeValues").mockReturnValue(getOfferTypeRangeValues);
// Call the method.
const price = ragfairPriceService.getDynamicItemPrice(itemTemplateId, desiredCurrency);
expect(price).toBe(mockFleaPrice * itemPriceMultiplier);
});
it("should adjust price when durability is not perfect", () =>
{
const itemTemplateId = "579204f224597773d619e051";
const desiredCurrency = Money.ROUBLES;
const mockPrice = 25000;
const mockDurabilityMulti = 0.5;
const getOfferTypeRangeValues = { max: 1, min: 1 };
const offerItems = [{
_id: "344d02bbf2102ce4e145bf35",
_tpl: "579204f224597773d619e051",
upd: {
StackObjectsCount: 1,
UnlimitedCount: true,
sptPresetId: "5841499024597759f825ff3e",
Repairable: { Durability: 40, MaxDurability: 90 },
},
}, {
_id: "7e8062d4bc57b56927c2d117",
_tpl: "6374a822e629013b9c0645c8",
parentId: "344d02bbf2102ce4e145bf35",
slotId: "mod_reciever",
}];
const item = offerItems[0];
// Disable other adjustments for isolation.
ragfairPriceService.ragfairConfig.dynamic.offerAdjustment.adjustPriceWhenBelowHandbookPrice = false;
ragfairPriceService.ragfairConfig.dynamic.useTraderPriceForOffersIfHigher = false;
ragfairPriceService.ragfairConfig.dynamic.itemPriceMultiplier[itemTemplateId] = null;
// Mock getFleaPriceForItem to bypass initial flea price fetch
vi.spyOn(ragfairPriceService, "getFleaPriceForItem").mockReturnValue(0);
// Mock the isPresetBaseClass method to return true for the item
vi.spyOn((ragfairPriceService as any).presetHelper, "isPresetBaseClass").mockReturnValue(true);
// Mock the getWeaponPresetPrice method to return a specific preset price
vi.spyOn(ragfairPriceService, "getWeaponPresetPrice").mockReturnValue(mockPrice);
// Mock the getOfferTypeRangeValues method to return a static minMax.
vi.spyOn(ragfairPriceService, "getOfferTypeRangeValues").mockReturnValue(getOfferTypeRangeValues);
// Mock the getItemQualityModifier method to return 1 (no change)
const getItemQualityModifierSpy = vi.spyOn(
(ragfairPriceService as any).itemHelper,
"getItemQualityModifier",
).mockReturnValue(mockDurabilityMulti);
// Call the method.
const price = ragfairPriceService.getDynamicItemPrice(itemTemplateId, desiredCurrency, item, offerItems);
expect(getItemQualityModifierSpy).toHaveBeenCalled();
expect(price).toBe(mockPrice * mockDurabilityMulti);
});
it("should adjust unreasonable prices based on ragfair config unreasonable price values", () =>
{
const itemTemplateId = "5c052f6886f7746b1e3db148";
const desiredCurrency = Money.ROUBLES;
const mockFleaPrice = 9999999;
const getOfferTypeRangeValues = { max: 1, min: 1 };
const mockBaseClassTemplateId = "57864a66245977548f04a81f";
const mockUnreasonableModPrices = {
itemType: "Electronics",
enabled: true,
handbookPriceOverMultiplier: 11,
newPriceHandbookMultiplier: 11,
};
// Mock the Disable unreasonableModPrices config. Disable other adjustments for isolation.
ragfairPriceService.ragfairConfig.dynamic.unreasonableModPrices[mockBaseClassTemplateId]
= mockUnreasonableModPrices;
ragfairPriceService.ragfairConfig.dynamic.offerAdjustment.adjustPriceWhenBelowHandbookPrice = false;
ragfairPriceService.ragfairConfig.dynamic.useTraderPriceForOffersIfHigher = false;
ragfairPriceService.ragfairConfig.dynamic.itemPriceMultiplier[itemTemplateId] = null;
// Mock getFleaPriceForItem to bypass initial flea price fetch
vi.spyOn(ragfairPriceService, "getFleaPriceForItem").mockReturnValue(mockFleaPrice);
// Mock isOfBaseclass to ensure that the item is always of the base class
const isOfBaseclassSpy = vi.spyOn((ragfairPriceService as any).itemHelper, "isOfBaseclass").mockReturnValue(
true,
);
// Mock the adjustUnreasonablePrice method to ensure it was called
const adjustUnreasonablePriceSpy = vi.spyOn(ragfairPriceService, "adjustUnreasonablePrice");
// Mock the getOfferTypeRangeValues method to return a static minMax
vi.spyOn(ragfairPriceService, "getOfferTypeRangeValues").mockReturnValue(getOfferTypeRangeValues);
// Call the method.
const price = ragfairPriceService.getDynamicItemPrice(itemTemplateId, desiredCurrency);
expect(isOfBaseclassSpy).toHaveBeenCalled();
expect(adjustUnreasonablePriceSpy).toHaveBeenCalled();
expect(price).toBeLessThan(mockFleaPrice);
});
it("should vary the price within a random range", () =>
{
const itemTemplateId = "5e54f6af86f7742199090bf3";
const desiredCurrency = Money.ROUBLES;
const mockFleaPrice = 10000;
const mockRandomiseOfferPrice = 9500;
// Mock the configs to allow using trader price if higher. Disable other adjustments for isolation.
ragfairPriceService.ragfairConfig.dynamic.offerAdjustment.adjustPriceWhenBelowHandbookPrice = false;
ragfairPriceService.ragfairConfig.dynamic.useTraderPriceForOffersIfHigher = false;
ragfairPriceService.ragfairConfig.dynamic.itemPriceMultiplier[itemTemplateId] = null;
// Mock the getFleaPriceForItem method to return a static price
vi.spyOn(ragfairPriceService, "getFleaPriceForItem").mockReturnValue(mockFleaPrice);
// Mock the isPresetBaseClass method to return false
vi.spyOn((ragfairPriceService as any).presetHelper, "isPresetBaseClass").mockReturnValue(false);
// Mock the randomiseOfferPrice method to have a simplified implementation
const randomiseOfferPriceSpy = vi.spyOn(ragfairPriceService, "randomiseOfferPrice").mockReturnValue(
mockRandomiseOfferPrice,
);
// Call the method.
const price = ragfairPriceService.getDynamicItemPrice(itemTemplateId, desiredCurrency);
expect(randomiseOfferPriceSpy).toHaveBeenCalled();
expect(price).toBe(mockRandomiseOfferPrice);
});
it("should convert currency", () =>
{
const itemTemplateId = "5e54f6af86f7742199090bf3";
const desiredCurrency = Money.DOLLARS;
const mockRoublePrice = 10000;
const mockDollarPrice = 500;
const getOfferTypeRangeValues = { max: 1, min: 1 };
// Mock the configs to allow using trader price if higher. Disable other adjustments for isolation.
ragfairPriceService.ragfairConfig.dynamic.offerAdjustment.adjustPriceWhenBelowHandbookPrice = false;
ragfairPriceService.ragfairConfig.dynamic.useTraderPriceForOffersIfHigher = false;
ragfairPriceService.ragfairConfig.dynamic.itemPriceMultiplier[itemTemplateId] = null;
// Mock the getFleaPriceForItem method to return a static price.
vi.spyOn(ragfairPriceService, "getFleaPriceForItem").mockReturnValue(mockRoublePrice);
// Mock the getOfferTypeRangeValues method to return a static minMax
vi.spyOn(ragfairPriceService, "getOfferTypeRangeValues").mockReturnValue(getOfferTypeRangeValues);
// Mock the fromRUB method to convert the price to a different currency
const fromRUBSpy = vi.spyOn((ragfairPriceService as any).handbookHelper, "fromRUB").mockReturnValue(
mockDollarPrice,
);
// Call the method.
const price = ragfairPriceService.getDynamicItemPrice(itemTemplateId, desiredCurrency);
expect(fromRUBSpy).toHaveBeenCalledWith(mockRoublePrice, desiredCurrency);
expect(price).not.toBe(mockRoublePrice);
expect(price).toBe(mockDollarPrice);
});
});
});