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"); }); 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); }); }); });