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