defmodule Berrypod.ProductsUpsertTest do use Berrypod.DataCase, async: false alias Berrypod.Products import Berrypod.ProductsFixtures describe "upsert_product/2" do test "creates a new product when it doesn't exist" do conn = provider_connection_fixture() attrs = %{ provider_product_id: "new-product-123", title: "New Product", description: "A new product", provider_data: %{"blueprint_id" => 145} } assert {:ok, product, :created} = Products.upsert_product(conn, attrs) assert product.title == "New Product" assert product.provider_product_id == "new-product-123" end test "returns unchanged when checksum matches" do conn = provider_connection_fixture() provider_data = %{"blueprint_id" => 145, "data" => "same"} {:ok, original, :created} = Products.upsert_product(conn, %{ provider_product_id: "product-123", title: "Product", provider_data: provider_data }) # Same provider_data = same checksum {:ok, product, :unchanged} = Products.upsert_product(conn, %{ provider_product_id: "product-123", title: "Product Updated", provider_data: provider_data }) assert product.id == original.id # Title should NOT be updated since checksum matched assert product.title == "Product" end test "updates product when checksum differs" do conn = provider_connection_fixture() {:ok, original, :created} = Products.upsert_product(conn, %{ provider_product_id: "product-123", title: "Product", provider_data: %{"version" => 1} }) # Different provider_data = different checksum {:ok, product, :updated} = Products.upsert_product(conn, %{ provider_product_id: "product-123", title: "Product Updated", provider_data: %{"version" => 2} }) assert product.id == original.id assert product.title == "Product Updated" end test "handles race condition when two processes try to insert same product" do conn = provider_connection_fixture() provider_product_id = "race-condition-test-#{System.unique_integer()}" attrs = %{ provider_product_id: provider_product_id, title: "Race Condition Product", provider_data: %{"test" => true} } # Simulate race condition by running concurrent inserts tasks = for _ <- 1..5 do Task.async(fn -> Products.upsert_product(conn, attrs) end) end results = Task.await_many(tasks, 5000) # All should succeed (no crashes) assert Enum.all?(results, fn {:ok, _product, status} when status in [:created, :unchanged] -> true _ -> false end) # Only one product should exist assert Products.count_products_for_connection(conn.id) >= 1 # Verify we can fetch the product product = Products.get_product_by_provider(conn.id, provider_product_id) assert product.title == "Race Condition Product" end test "matches by slug when provider_product_id changes" do conn = provider_connection_fixture() # Create first product {:ok, product1, :created} = Products.upsert_product(conn, %{ provider_product_id: "old-product-id", title: "Same Title Product", provider_data: %{"id" => 1} }) assert product1.provider_product_id == "old-product-id" # Same title but different provider_product_id - matches by slug and updates {:ok, product2, :updated} = Products.upsert_product(conn, %{ provider_product_id: "new-product-id", title: "Same Title Product", provider_data: %{"id" => 2} }) # Should be the same product, with updated provider_product_id assert product2.id == product1.id assert product2.provider_product_id == "new-product-id" assert product2.slug == "same-title-product" end test "different titles create different slugs successfully" do conn = provider_connection_fixture() {:ok, product1, :created} = Products.upsert_product(conn, %{ provider_product_id: "product-1", title: "First Product", provider_data: %{"id" => 1} }) {:ok, product2, :created} = Products.upsert_product(conn, %{ provider_product_id: "product-2", title: "Second Product", provider_data: %{"id" => 2} }) assert product1.slug == "first-product" assert product2.slug == "second-product" end end end