577 lines
18 KiB
JavaScript
577 lines
18 KiB
JavaScript
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"
|
|
);
|
|
});
|
|
});
|
|
});
|