berrypod/test/berrypod/products_upsert_test.exs

153 lines
4.6 KiB
Elixir
Raw Normal View History

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