feat: enforce SKU validity, use SKU as handle
This commit enforces proper SKU validation, uses the SKU as the Shopify handle, and implements ID-based product updates to allow renaming. It also extracts the IShop interface for TDD.
This commit is contained in:
106
src/ProductLogic.test.ts
Normal file
106
src/ProductLogic.test.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { Product } from "./Product";
|
||||
import { MockSpreadsheetService } from "./services/MockSpreadsheetService";
|
||||
import { MockShop } from "./test/MockShop";
|
||||
|
||||
describe("Product Logic (TDD)", () => {
|
||||
let mockService: MockSpreadsheetService;
|
||||
let mockShop: MockShop;
|
||||
|
||||
beforeEach(() => {
|
||||
mockService = new MockSpreadsheetService({
|
||||
product_inventory: [
|
||||
["sku", "title", "price", "shopify_id", "product_type"], // Headers
|
||||
["TEST-SKU-1", "Test Product", 10.99, "", "Type A"] // Data
|
||||
],
|
||||
values: [
|
||||
["product_type", "shopify_category", "ebay_category_id"],
|
||||
["Type A", "Category A", "123"]
|
||||
]
|
||||
});
|
||||
mockShop = new MockShop();
|
||||
});
|
||||
|
||||
test("UpdateShopifyProduct should abort if SKU is invalid", () => {
|
||||
// Setup invalid SKU
|
||||
mockService.setSheetData("product_inventory", [
|
||||
["sku", "title"],
|
||||
["?", "Invalid Product"]
|
||||
]);
|
||||
// Allow empty sku to be passed to constructor logic check, but Product constructor throws if sku not found.
|
||||
// So we pass a valid sku that exists in sheet, but looks invalid?
|
||||
// The requirement is "based on the product's title... If I have a placeholder value... ensure products are not created until they have a valid SKU".
|
||||
// In `Product.ts`, constructor takes `sku`.
|
||||
// If I pass `?` and it's in the sheet, it constructs.
|
||||
|
||||
const product = new Product("?", mockService);
|
||||
|
||||
// Attempt update
|
||||
product.UpdateShopifyProduct(mockShop);
|
||||
|
||||
// Verify no calls to creating product
|
||||
// We expect MatchToShopifyProduct might be called (read only), but NOT productSet (writ)
|
||||
// Actually our plan said "abort operation" at start of UpdateShopifyProduct.
|
||||
expect(mockShop.productSetCalledWith).toBeNull();
|
||||
});
|
||||
|
||||
test("ToShopifyProductSet should set handle to SKU", () => {
|
||||
const product = new Product("TEST-SKU-1", mockService);
|
||||
const sps = product.ToShopifyProductSet();
|
||||
|
||||
// We expect sps to have a 'handle' property equal to the sku
|
||||
// This will fail compilation initially as ShopifyProductSetInput doesn't have handle
|
||||
expect((sps as any).handle).toBe("TEST-SKU-1");
|
||||
});
|
||||
|
||||
test("MatchToShopifyProduct should verify ID if present", () => {
|
||||
// Setup data with shopify_id
|
||||
mockService.setSheetData("product_inventory", [
|
||||
["sku", "shopify_id"],
|
||||
["TEST-SKU-OLD", "123456789"]
|
||||
]);
|
||||
const product = new Product("TEST-SKU-OLD", mockService);
|
||||
|
||||
// Mock the response for GetProductById
|
||||
mockShop.mockProductById = {
|
||||
id: "123456789",
|
||||
title: "Old Title",
|
||||
variants: { nodes: [{ id: "gid://shopify/ProductVariant/123456789", sku: "TEST-SKU-OLD" }] },
|
||||
options: [{ id: "opt1", optionValues: [{ id: "optval1" }] }]
|
||||
};
|
||||
|
||||
// We need to call Match, but it's called inside Update usually.
|
||||
// We can call it directly for testing.
|
||||
product.MatchToShopifyProduct(mockShop);
|
||||
|
||||
// Expect GetProductById to have been called
|
||||
expect(mockShop.getProductByIdCalledWith).toBe("123456789");
|
||||
expect(product.shopify_id).toBe("123456789");
|
||||
});
|
||||
|
||||
test("MatchToShopifyProduct should fall back to SKU if ID lookup fails", () => {
|
||||
// Setup data with shopify_id that is invalid
|
||||
mockService.setSheetData("product_inventory", [
|
||||
["sku", "shopify_id"],
|
||||
["TEST-SKU-FAIL", "999999999"]
|
||||
]);
|
||||
const product = new Product("TEST-SKU-FAIL", mockService);
|
||||
|
||||
// Mock ID lookup failure (returns null/undefined)
|
||||
mockShop.mockProductById = null;
|
||||
|
||||
// Mock SKU lookup success
|
||||
mockShop.mockProductBySku = {
|
||||
id: "555555555",
|
||||
title: "Found By SKU",
|
||||
variants: { nodes: [{ id: "gid://shopify/ProductVariant/555555555", sku: "TEST-SKU-FAIL" }] },
|
||||
options: [{ id: "opt2", optionValues: [{ id: "optval2" }] }]
|
||||
};
|
||||
|
||||
product.MatchToShopifyProduct(mockShop);
|
||||
|
||||
expect(mockShop.getProductByIdCalledWith).toBe("999999999");
|
||||
// Should fall back to SKU
|
||||
expect(mockShop.getProductBySkuCalledWith).toBe("TEST-SKU-FAIL");
|
||||
expect(product.shopify_id).toBe("555555555");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user