Initial commit: Complete Shopify Price Updater implementation

- Full Node.js application with Shopify GraphQL API integration
- Compare At price support for promotional pricing
- Comprehensive error handling and retry logic
- Progress tracking with markdown logging
- Complete test suite with unit and integration tests
- Production-ready with proper exit codes and signal handling
This commit is contained in:
2025-08-05 10:05:05 -05:00
commit 1e6881ba86
29 changed files with 10663 additions and 0 deletions

263
tests/utils/price.test.js Normal file
View File

@@ -0,0 +1,263 @@
const {
calculateNewPrice,
isValidPrice,
formatPrice,
calculatePercentageChange,
isValidPercentage,
preparePriceUpdate,
} = 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"
);
});
});
});