const { calculateNewPrice, isValidPrice, formatPrice, calculatePercentageChange, isValidPercentage, preparePriceUpdate, validateRollbackEligibility, prepareRollbackUpdate, } = require("../../src/utils/price"); describe("Price Utilities", () => { describe("calculateNewPrice", () => { test("should calculate price increase correctly", () => { expect(calculateNewPrice(100, 10)).toBe(110); expect(calculateNewPrice(50, 20)).toBe(60); expect(calculateNewPrice(29.99, 5.5)).toBe(31.64); }); test("should calculate price decrease correctly", () => { expect(calculateNewPrice(100, -10)).toBe(90); expect(calculateNewPrice(50, -20)).toBe(40); expect(calculateNewPrice(29.99, -5.5)).toBe(28.34); }); test("should handle zero percentage change", () => { expect(calculateNewPrice(100, 0)).toBe(100); expect(calculateNewPrice(29.99, 0)).toBe(29.99); }); test("should handle zero price", () => { expect(calculateNewPrice(0, 10)).toBe(0); expect(calculateNewPrice(0, -10)).toBe(0); expect(calculateNewPrice(0, 0)).toBe(0); }); test("should round to 2 decimal places", () => { expect(calculateNewPrice(10.005, 10)).toBe(11.01); expect(calculateNewPrice(10.004, 10)).toBe(11.0); expect(calculateNewPrice(33.333, 10)).toBe(36.67); }); test("should handle decimal percentages", () => { expect(calculateNewPrice(100, 5.5)).toBe(105.5); expect(calculateNewPrice(100, -2.25)).toBe(97.75); }); test("should throw error for invalid original price", () => { expect(() => calculateNewPrice("invalid", 10)).toThrow( "Original price must be a valid number" ); expect(() => calculateNewPrice(NaN, 10)).toThrow( "Original price must be a valid number" ); expect(() => calculateNewPrice(null, 10)).toThrow( "Original price must be a valid number" ); expect(() => calculateNewPrice(undefined, 10)).toThrow( "Original price must be a valid number" ); }); test("should throw error for invalid percentage", () => { expect(() => calculateNewPrice(100, "invalid")).toThrow( "Percentage must be a valid number" ); expect(() => calculateNewPrice(100, NaN)).toThrow( "Percentage must be a valid number" ); expect(() => calculateNewPrice(100, null)).toThrow( "Percentage must be a valid number" ); expect(() => calculateNewPrice(100, undefined)).toThrow( "Percentage must be a valid number" ); }); test("should throw error for negative original price", () => { expect(() => calculateNewPrice(-10, 10)).toThrow( "Original price cannot be negative" ); expect(() => calculateNewPrice(-0.01, 5)).toThrow( "Original price cannot be negative" ); }); test("should throw error when result would be negative", () => { expect(() => calculateNewPrice(10, -150)).toThrow( "Price adjustment would result in negative price" ); expect(() => calculateNewPrice(50, -200)).toThrow( "Price adjustment would result in negative price" ); }); test("should handle edge case of 100% decrease", () => { expect(calculateNewPrice(100, -100)).toBe(0); expect(calculateNewPrice(50, -100)).toBe(0); }); }); describe("isValidPrice", () => { test("should return true for valid prices", () => { expect(isValidPrice(0)).toBe(true); expect(isValidPrice(10)).toBe(true); expect(isValidPrice(99.99)).toBe(true); expect(isValidPrice(1000000)).toBe(true); expect(isValidPrice(0.01)).toBe(true); }); test("should return false for invalid prices", () => { expect(isValidPrice(-1)).toBe(false); expect(isValidPrice(-0.01)).toBe(false); expect(isValidPrice("10")).toBe(false); expect(isValidPrice("invalid")).toBe(false); expect(isValidPrice(NaN)).toBe(false); expect(isValidPrice(null)).toBe(false); expect(isValidPrice(undefined)).toBe(false); expect(isValidPrice(Infinity)).toBe(false); expect(isValidPrice(-Infinity)).toBe(false); }); }); describe("formatPrice", () => { test("should format valid prices correctly", () => { expect(formatPrice(10)).toBe("10.00"); expect(formatPrice(99.99)).toBe("99.99"); expect(formatPrice(0)).toBe("0.00"); expect(formatPrice(1000)).toBe("1000.00"); expect(formatPrice(0.5)).toBe("0.50"); }); test("should handle prices with more than 2 decimal places", () => { expect(formatPrice(10.005)).toBe("10.01"); expect(formatPrice(10.004)).toBe("10.00"); expect(formatPrice(99.999)).toBe("100.00"); }); test("should return 'Invalid Price' for invalid inputs", () => { expect(formatPrice(-1)).toBe("Invalid Price"); expect(formatPrice("invalid")).toBe("Invalid Price"); expect(formatPrice(NaN)).toBe("Invalid Price"); expect(formatPrice(null)).toBe("Invalid Price"); expect(formatPrice(undefined)).toBe("Invalid Price"); expect(formatPrice(Infinity)).toBe("Invalid Price"); }); }); describe("calculatePercentageChange", () => { test("should calculate percentage increase correctly", () => { expect(calculatePercentageChange(100, 110)).toBe(10); expect(calculatePercentageChange(50, 60)).toBe(20); expect(calculatePercentageChange(100, 150)).toBe(50); }); test("should calculate percentage decrease correctly", () => { expect(calculatePercentageChange(100, 90)).toBe(-10); expect(calculatePercentageChange(50, 40)).toBe(-20); expect(calculatePercentageChange(100, 50)).toBe(-50); }); test("should handle no change", () => { expect(calculatePercentageChange(100, 100)).toBe(0); expect(calculatePercentageChange(50, 50)).toBe(0); }); test("should handle zero old price", () => { expect(calculatePercentageChange(0, 0)).toBe(0); expect(calculatePercentageChange(0, 10)).toBe(Infinity); }); test("should round to 2 decimal places", () => { expect(calculatePercentageChange(29.99, 31.64)).toBe(5.5); expect(calculatePercentageChange(33.33, 36.66)).toBe(9.99); }); test("should throw error for invalid prices", () => { expect(() => calculatePercentageChange("invalid", 100)).toThrow( "Both prices must be valid numbers" ); expect(() => calculatePercentageChange(100, "invalid")).toThrow( "Both prices must be valid numbers" ); expect(() => calculatePercentageChange(-10, 100)).toThrow( "Both prices must be valid numbers" ); expect(() => calculatePercentageChange(100, -10)).toThrow( "Both prices must be valid numbers" ); }); }); describe("isValidPercentage", () => { test("should return true for valid percentages", () => { expect(isValidPercentage(0)).toBe(true); expect(isValidPercentage(10)).toBe(true); expect(isValidPercentage(-10)).toBe(true); expect(isValidPercentage(100)).toBe(true); expect(isValidPercentage(-100)).toBe(true); expect(isValidPercentage(5.5)).toBe(true); expect(isValidPercentage(-2.25)).toBe(true); expect(isValidPercentage(1000)).toBe(true); }); test("should return false for invalid percentages", () => { expect(isValidPercentage("10")).toBe(false); expect(isValidPercentage("invalid")).toBe(false); expect(isValidPercentage(NaN)).toBe(false); expect(isValidPercentage(null)).toBe(false); expect(isValidPercentage(undefined)).toBe(false); expect(isValidPercentage(Infinity)).toBe(false); expect(isValidPercentage(-Infinity)).toBe(false); }); }); describe("preparePriceUpdate", () => { test("should prepare price update with increase", () => { const result = preparePriceUpdate(100, 10); expect(result.newPrice).toBe(110); expect(result.compareAtPrice).toBe(100); }); test("should prepare price update with decrease", () => { const result = preparePriceUpdate(100, -20); expect(result.newPrice).toBe(80); expect(result.compareAtPrice).toBe(100); }); test("should prepare price update with zero change", () => { const result = preparePriceUpdate(50, 0); expect(result.newPrice).toBe(50); expect(result.compareAtPrice).toBe(50); }); test("should handle decimal prices and percentages", () => { const result = preparePriceUpdate(29.99, 5.5); expect(result.newPrice).toBe(31.64); expect(result.compareAtPrice).toBe(29.99); }); test("should throw error for invalid original price", () => { expect(() => preparePriceUpdate("invalid", 10)).toThrow( "Original price must be a valid number" ); expect(() => preparePriceUpdate(-10, 10)).toThrow( "Original price must be a valid number" ); }); test("should throw error for invalid percentage", () => { expect(() => preparePriceUpdate(100, "invalid")).toThrow( "Percentage must be a valid number" ); expect(() => preparePriceUpdate(100, NaN)).toThrow( "Percentage must be a valid number" ); }); test("should throw error when result would be negative", () => { expect(() => preparePriceUpdate(10, -150)).toThrow( "Price adjustment would result in negative price" ); }); }); describe("validateRollbackEligibility", () => { test("should return eligible for valid variant with different prices", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", compareAtPrice: "75.00", }; const result = validateRollbackEligibility(variant); expect(result.isEligible).toBe(true); expect(result.variant.id).toBe("gid://shopify/ProductVariant/123"); expect(result.variant.currentPrice).toBe(50.0); expect(result.variant.compareAtPrice).toBe(75.0); expect(result.reason).toBeUndefined(); }); test("should return not eligible when variant is null or undefined", () => { expect(validateRollbackEligibility(null).isEligible).toBe(false); expect(validateRollbackEligibility(null).reason).toBe( "Invalid variant object" ); expect(validateRollbackEligibility(undefined).isEligible).toBe(false); expect(validateRollbackEligibility(undefined).reason).toBe( "Invalid variant object" ); }); test("should return not eligible when variant is not an object", () => { expect(validateRollbackEligibility("invalid").isEligible).toBe(false); expect(validateRollbackEligibility("invalid").reason).toBe( "Invalid variant object" ); expect(validateRollbackEligibility(123).isEligible).toBe(false); expect(validateRollbackEligibility(123).reason).toBe( "Invalid variant object" ); }); test("should return not eligible when current price is invalid", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "invalid", compareAtPrice: "75.00", }; const result = validateRollbackEligibility(variant); expect(result.isEligible).toBe(false); expect(result.reason).toBe("Invalid current price"); expect(result.variant.currentPrice).toBeNaN(); }); test("should return not eligible when current price is negative", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "-10.00", compareAtPrice: "75.00", }; const result = validateRollbackEligibility(variant); expect(result.isEligible).toBe(false); expect(result.reason).toBe("Invalid current price"); expect(result.variant.currentPrice).toBe(-10.0); }); test("should return not eligible when compare-at price is null", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", compareAtPrice: null, }; const result = validateRollbackEligibility(variant); expect(result.isEligible).toBe(false); expect(result.reason).toBe("No compare-at price available"); expect(result.variant.compareAtPrice).toBe(null); }); test("should return not eligible when compare-at price is undefined", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", // compareAtPrice is undefined }; const result = validateRollbackEligibility(variant); expect(result.isEligible).toBe(false); expect(result.reason).toBe("No compare-at price available"); expect(result.variant.compareAtPrice).toBe(null); }); test("should return not eligible when compare-at price is invalid", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", compareAtPrice: "invalid", }; const result = validateRollbackEligibility(variant); expect(result.isEligible).toBe(false); expect(result.reason).toBe("Invalid compare-at price"); expect(result.variant.compareAtPrice).toBeNaN(); }); test("should return not eligible when compare-at price is zero", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", compareAtPrice: "0.00", }; const result = validateRollbackEligibility(variant); expect(result.isEligible).toBe(false); expect(result.reason).toBe("Compare-at price must be greater than zero"); expect(result.variant.compareAtPrice).toBe(0.0); }); test("should return not eligible when compare-at price is negative", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", compareAtPrice: "-10.00", }; const result = validateRollbackEligibility(variant); expect(result.isEligible).toBe(false); expect(result.reason).toBe("Compare-at price must be greater than zero"); expect(result.variant.compareAtPrice).toBe(-10.0); }); test("should return not eligible when prices are the same", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", compareAtPrice: "50.00", }; const result = validateRollbackEligibility(variant); expect(result.isEligible).toBe(false); expect(result.reason).toBe( "Compare-at price is the same as current price" ); expect(result.variant.currentPrice).toBe(50.0); expect(result.variant.compareAtPrice).toBe(50.0); }); test("should return not eligible when prices are nearly the same (within epsilon)", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", compareAtPrice: "50.005", // Within 0.01 epsilon }; const result = validateRollbackEligibility(variant); expect(result.isEligible).toBe(false); expect(result.reason).toBe( "Compare-at price is the same as current price" ); }); test("should handle numeric price values", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: 50.0, compareAtPrice: 75.0, }; const result = validateRollbackEligibility(variant); expect(result.isEligible).toBe(true); expect(result.variant.currentPrice).toBe(50.0); expect(result.variant.compareAtPrice).toBe(75.0); }); test("should handle decimal prices correctly", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "29.99", compareAtPrice: "39.99", }; const result = validateRollbackEligibility(variant); expect(result.isEligible).toBe(true); expect(result.variant.currentPrice).toBe(29.99); expect(result.variant.compareAtPrice).toBe(39.99); }); }); describe("prepareRollbackUpdate", () => { test("should prepare rollback update for eligible variant", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", compareAtPrice: "75.00", }; const result = prepareRollbackUpdate(variant); expect(result.newPrice).toBe(75.0); expect(result.compareAtPrice).toBe(null); }); test("should prepare rollback update with decimal prices", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "29.99", compareAtPrice: "39.99", }; const result = prepareRollbackUpdate(variant); expect(result.newPrice).toBe(39.99); expect(result.compareAtPrice).toBe(null); }); test("should handle numeric price values", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: 25.5, compareAtPrice: 35.75, }; const result = prepareRollbackUpdate(variant); expect(result.newPrice).toBe(35.75); expect(result.compareAtPrice).toBe(null); }); test("should throw error for variant without compare-at price", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", compareAtPrice: null, }; expect(() => prepareRollbackUpdate(variant)).toThrow( "Cannot prepare rollback update: No compare-at price available" ); }); test("should throw error for variant with invalid compare-at price", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", compareAtPrice: "invalid", }; expect(() => prepareRollbackUpdate(variant)).toThrow( "Cannot prepare rollback update: Invalid compare-at price" ); }); test("should throw error for variant with zero compare-at price", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", compareAtPrice: "0.00", }; expect(() => prepareRollbackUpdate(variant)).toThrow( "Cannot prepare rollback update: Compare-at price must be greater than zero" ); }); test("should throw error for variant with same prices", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "50.00", compareAtPrice: "50.00", }; expect(() => prepareRollbackUpdate(variant)).toThrow( "Cannot prepare rollback update: Compare-at price is the same as current price" ); }); test("should throw error for invalid variant object", () => { expect(() => prepareRollbackUpdate(null)).toThrow( "Cannot prepare rollback update: Invalid variant object" ); expect(() => prepareRollbackUpdate("invalid")).toThrow( "Cannot prepare rollback update: Invalid variant object" ); }); test("should throw error for variant with invalid current price", () => { const variant = { id: "gid://shopify/ProductVariant/123", price: "invalid", compareAtPrice: "75.00", }; expect(() => prepareRollbackUpdate(variant)).toThrow( "Cannot prepare rollback update: Invalid current price" ); }); }); });