All modules, configs, paths, and references updated. 836 tests pass, zero warnings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
755 lines
24 KiB
Elixir
755 lines
24 KiB
Elixir
defmodule Berrypod.ProductsTest do
|
|
use Berrypod.DataCase, async: false
|
|
|
|
alias Berrypod.Products
|
|
alias Berrypod.Products.{ProviderConnection, Product, ProductImage, ProductVariant}
|
|
|
|
import Berrypod.ProductsFixtures
|
|
|
|
# =============================================================================
|
|
# Provider Connections
|
|
# =============================================================================
|
|
|
|
describe "list_provider_connections/0" do
|
|
test "returns empty list when no connections exist" do
|
|
assert Products.list_provider_connections() == []
|
|
end
|
|
|
|
test "returns all provider connections" do
|
|
conn1 = provider_connection_fixture(%{provider_type: "printify"})
|
|
conn2 = provider_connection_fixture(%{provider_type: "gelato"})
|
|
|
|
connections = Products.list_provider_connections()
|
|
assert length(connections) == 2
|
|
assert Enum.any?(connections, &(&1.id == conn1.id))
|
|
assert Enum.any?(connections, &(&1.id == conn2.id))
|
|
end
|
|
end
|
|
|
|
describe "get_provider_connection/1" do
|
|
test "returns the connection with given id" do
|
|
conn = provider_connection_fixture()
|
|
assert Products.get_provider_connection(conn.id).id == conn.id
|
|
end
|
|
|
|
test "returns nil for non-existent id" do
|
|
assert Products.get_provider_connection(Ecto.UUID.generate()) == nil
|
|
end
|
|
end
|
|
|
|
describe "get_provider_connection_by_type/1" do
|
|
test "returns the connection with given provider_type" do
|
|
conn = provider_connection_fixture(%{provider_type: "printify"})
|
|
assert Products.get_provider_connection_by_type("printify").id == conn.id
|
|
end
|
|
|
|
test "returns nil for non-existent type" do
|
|
assert Products.get_provider_connection_by_type("nonexistent") == nil
|
|
end
|
|
end
|
|
|
|
describe "create_provider_connection/1" do
|
|
test "creates a provider connection with valid attrs" do
|
|
attrs = valid_provider_connection_attrs()
|
|
assert {:ok, %ProviderConnection{} = conn} = Products.create_provider_connection(attrs)
|
|
|
|
assert conn.provider_type == attrs.provider_type
|
|
assert conn.name == attrs.name
|
|
assert conn.enabled == true
|
|
end
|
|
|
|
test "returns error changeset with invalid attrs" do
|
|
assert {:error, %Ecto.Changeset{}} = Products.create_provider_connection(%{})
|
|
end
|
|
end
|
|
|
|
describe "update_provider_connection/2" do
|
|
test "updates the connection with valid attrs" do
|
|
conn = provider_connection_fixture()
|
|
assert {:ok, updated} = Products.update_provider_connection(conn, %{name: "Updated Name"})
|
|
assert updated.name == "Updated Name"
|
|
end
|
|
|
|
test "returns error changeset with invalid attrs" do
|
|
conn = provider_connection_fixture()
|
|
|
|
assert {:error, %Ecto.Changeset{}} =
|
|
Products.update_provider_connection(conn, %{provider_type: "invalid"})
|
|
end
|
|
end
|
|
|
|
describe "delete_provider_connection/1" do
|
|
test "deletes the connection" do
|
|
conn = provider_connection_fixture()
|
|
assert {:ok, %ProviderConnection{}} = Products.delete_provider_connection(conn)
|
|
assert Products.get_provider_connection(conn.id) == nil
|
|
end
|
|
end
|
|
|
|
describe "update_sync_status/3" do
|
|
test "updates sync status" do
|
|
conn = provider_connection_fixture()
|
|
assert {:ok, updated} = Products.update_sync_status(conn, "syncing")
|
|
assert updated.sync_status == "syncing"
|
|
end
|
|
|
|
test "updates sync status with timestamp" do
|
|
conn = provider_connection_fixture()
|
|
now = DateTime.utc_now() |> DateTime.truncate(:second)
|
|
assert {:ok, updated} = Products.update_sync_status(conn, "completed", now)
|
|
|
|
assert updated.sync_status == "completed"
|
|
assert updated.last_synced_at == now
|
|
end
|
|
end
|
|
|
|
# =============================================================================
|
|
# Products
|
|
# =============================================================================
|
|
|
|
describe "list_products/1" do
|
|
test "returns empty list when no products exist" do
|
|
assert Products.list_products() == []
|
|
end
|
|
|
|
test "returns all products" do
|
|
product1 = product_fixture()
|
|
product2 = product_fixture()
|
|
|
|
products = Products.list_products()
|
|
assert length(products) == 2
|
|
ids = Enum.map(products, & &1.id)
|
|
assert product1.id in ids
|
|
assert product2.id in ids
|
|
end
|
|
|
|
test "filters by visible" do
|
|
_visible = product_fixture(%{visible: true})
|
|
_hidden = product_fixture(%{visible: false})
|
|
|
|
visible_products = Products.list_products(visible: true)
|
|
assert length(visible_products) == 1
|
|
assert hd(visible_products).visible == true
|
|
end
|
|
|
|
test "filters by status" do
|
|
_active = product_fixture(%{status: "active"})
|
|
_draft = product_fixture(%{status: "draft"})
|
|
|
|
active_products = Products.list_products(status: "active")
|
|
assert length(active_products) == 1
|
|
assert hd(active_products).status == "active"
|
|
end
|
|
|
|
test "filters by category" do
|
|
_apparel = product_fixture(%{category: "Apparel"})
|
|
_homewares = product_fixture(%{category: "Homewares"})
|
|
|
|
apparel_products = Products.list_products(category: "Apparel")
|
|
assert length(apparel_products) == 1
|
|
assert hd(apparel_products).category == "Apparel"
|
|
end
|
|
|
|
test "filters by provider_connection_id" do
|
|
conn1 = provider_connection_fixture(%{provider_type: "printify"})
|
|
conn2 = provider_connection_fixture(%{provider_type: "gelato"})
|
|
|
|
product1 = product_fixture(%{provider_connection: conn1})
|
|
_product2 = product_fixture(%{provider_connection: conn2})
|
|
|
|
products = Products.list_products(provider_connection_id: conn1.id)
|
|
assert length(products) == 1
|
|
assert hd(products).id == product1.id
|
|
end
|
|
|
|
test "preloads associations" do
|
|
product = product_fixture()
|
|
_image = product_image_fixture(%{product: product})
|
|
_variant = product_variant_fixture(%{product: product})
|
|
|
|
[loaded] = Products.list_products(preload: [:images, :variants])
|
|
assert length(loaded.images) == 1
|
|
assert length(loaded.variants) == 1
|
|
end
|
|
end
|
|
|
|
describe "get_product/2" do
|
|
test "returns the product with given id" do
|
|
product = product_fixture()
|
|
assert Products.get_product(product.id).id == product.id
|
|
end
|
|
|
|
test "returns nil for non-existent id" do
|
|
assert Products.get_product(Ecto.UUID.generate()) == nil
|
|
end
|
|
|
|
test "preloads associations when requested" do
|
|
product = product_fixture()
|
|
_image = product_image_fixture(%{product: product})
|
|
|
|
loaded = Products.get_product(product.id, preload: [:images])
|
|
assert length(loaded.images) == 1
|
|
end
|
|
end
|
|
|
|
describe "get_product_by_slug/2" do
|
|
test "returns the product with given slug" do
|
|
product = product_fixture(%{slug: "my-product"})
|
|
assert Products.get_product_by_slug("my-product").id == product.id
|
|
end
|
|
|
|
test "returns nil for non-existent slug" do
|
|
assert Products.get_product_by_slug("nonexistent") == nil
|
|
end
|
|
end
|
|
|
|
describe "get_product_by_provider/2" do
|
|
test "returns the product by provider connection and product id" do
|
|
conn = provider_connection_fixture()
|
|
product = product_fixture(%{provider_connection: conn, provider_product_id: "ext_123"})
|
|
|
|
assert Products.get_product_by_provider(conn.id, "ext_123").id == product.id
|
|
end
|
|
|
|
test "returns nil when not found" do
|
|
conn = provider_connection_fixture()
|
|
assert Products.get_product_by_provider(conn.id, "nonexistent") == nil
|
|
end
|
|
end
|
|
|
|
describe "create_product/1" do
|
|
test "creates a product with valid attrs" do
|
|
conn = provider_connection_fixture()
|
|
attrs = valid_product_attrs(%{provider_connection_id: conn.id})
|
|
|
|
assert {:ok, %Product{} = product} = Products.create_product(attrs)
|
|
assert product.title == attrs.title
|
|
assert product.provider_product_id == attrs.provider_product_id
|
|
end
|
|
|
|
test "returns error changeset with invalid attrs" do
|
|
assert {:error, %Ecto.Changeset{}} = Products.create_product(%{})
|
|
end
|
|
end
|
|
|
|
describe "update_product/2" do
|
|
test "updates the product with valid attrs" do
|
|
product = product_fixture()
|
|
assert {:ok, updated} = Products.update_product(product, %{title: "Updated Title"})
|
|
assert updated.title == "Updated Title"
|
|
end
|
|
end
|
|
|
|
describe "delete_product/1" do
|
|
test "deletes the product" do
|
|
product = product_fixture()
|
|
assert {:ok, %Product{}} = Products.delete_product(product)
|
|
assert Products.get_product(product.id) == nil
|
|
end
|
|
end
|
|
|
|
describe "upsert_product/2" do
|
|
test "creates new product when not exists" do
|
|
conn = provider_connection_fixture()
|
|
|
|
attrs = %{
|
|
provider_product_id: "new_ext_123",
|
|
title: "New Product",
|
|
slug: "new-product",
|
|
provider_data: %{"key" => "value"}
|
|
}
|
|
|
|
assert {:ok, product, :created} = Products.upsert_product(conn, attrs)
|
|
assert product.title == "New Product"
|
|
assert product.provider_connection_id == conn.id
|
|
end
|
|
|
|
test "updates existing product when checksum differs" do
|
|
conn = provider_connection_fixture()
|
|
existing = product_fixture(%{provider_connection: conn, provider_product_id: "ext_123"})
|
|
|
|
attrs = %{
|
|
provider_product_id: "ext_123",
|
|
title: "Updated Title",
|
|
slug: existing.slug,
|
|
provider_data: %{"different" => "data"}
|
|
}
|
|
|
|
assert {:ok, product, :updated} = Products.upsert_product(conn, attrs)
|
|
assert product.id == existing.id
|
|
assert product.title == "Updated Title"
|
|
end
|
|
|
|
test "returns unchanged when checksum matches" do
|
|
conn = provider_connection_fixture()
|
|
provider_data = %{"key" => "value"}
|
|
|
|
existing =
|
|
product_fixture(%{
|
|
provider_connection: conn,
|
|
provider_product_id: "ext_123",
|
|
provider_data: provider_data,
|
|
checksum: Product.compute_checksum(provider_data)
|
|
})
|
|
|
|
attrs = %{
|
|
provider_product_id: "ext_123",
|
|
title: "Different Title",
|
|
slug: existing.slug,
|
|
provider_data: provider_data
|
|
}
|
|
|
|
assert {:ok, product, :unchanged} = Products.upsert_product(conn, attrs)
|
|
assert product.id == existing.id
|
|
# Title should NOT be updated since checksum matched
|
|
assert product.title == existing.title
|
|
end
|
|
end
|
|
|
|
# =============================================================================
|
|
# Product Images
|
|
# =============================================================================
|
|
|
|
describe "create_product_image/1" do
|
|
test "creates a product image" do
|
|
product = product_fixture()
|
|
attrs = valid_product_image_attrs(%{product_id: product.id})
|
|
|
|
assert {:ok, %ProductImage{} = image} = Products.create_product_image(attrs)
|
|
assert image.product_id == product.id
|
|
end
|
|
end
|
|
|
|
describe "delete_product_images/1" do
|
|
test "deletes all images for a product" do
|
|
product = product_fixture()
|
|
_image1 = product_image_fixture(%{product: product})
|
|
_image2 = product_image_fixture(%{product: product})
|
|
|
|
assert {2, nil} = Products.delete_product_images(product)
|
|
|
|
loaded = Products.get_product(product.id, preload: [:images])
|
|
assert loaded.images == []
|
|
end
|
|
end
|
|
|
|
describe "sync_product_images/2" do
|
|
test "replaces all images" do
|
|
product = product_fixture()
|
|
_old_image = product_image_fixture(%{product: product})
|
|
|
|
new_images = [
|
|
%{src: "https://new.com/1.jpg"},
|
|
%{src: "https://new.com/2.jpg"}
|
|
]
|
|
|
|
results = Products.sync_product_images(product, new_images)
|
|
assert length(results) == 2
|
|
assert Enum.all?(results, &match?({:ok, _}, &1))
|
|
|
|
loaded = Products.get_product(product.id, preload: [:images])
|
|
assert length(loaded.images) == 2
|
|
end
|
|
|
|
test "assigns positions based on list order" do
|
|
product = product_fixture()
|
|
|
|
images = [
|
|
%{src: "https://new.com/first.jpg"},
|
|
%{src: "https://new.com/second.jpg"}
|
|
]
|
|
|
|
Products.sync_product_images(product, images)
|
|
|
|
loaded = Products.get_product(product.id, preload: [:images])
|
|
sorted = Enum.sort_by(loaded.images, & &1.position)
|
|
|
|
assert Enum.at(sorted, 0).position == 0
|
|
assert Enum.at(sorted, 1).position == 1
|
|
end
|
|
end
|
|
|
|
# =============================================================================
|
|
# Product Variants
|
|
# =============================================================================
|
|
|
|
describe "create_product_variant/1" do
|
|
test "creates a product variant" do
|
|
product = product_fixture()
|
|
attrs = valid_product_variant_attrs(%{product_id: product.id})
|
|
|
|
assert {:ok, %ProductVariant{} = variant} = Products.create_product_variant(attrs)
|
|
assert variant.product_id == product.id
|
|
end
|
|
end
|
|
|
|
describe "update_product_variant/2" do
|
|
test "updates the variant" do
|
|
variant = product_variant_fixture()
|
|
assert {:ok, updated} = Products.update_product_variant(variant, %{price: 3000})
|
|
assert updated.price == 3000
|
|
end
|
|
end
|
|
|
|
describe "delete_product_variants/1" do
|
|
test "deletes all variants for a product" do
|
|
product = product_fixture()
|
|
_variant1 = product_variant_fixture(%{product: product})
|
|
_variant2 = product_variant_fixture(%{product: product})
|
|
|
|
assert {2, nil} = Products.delete_product_variants(product)
|
|
|
|
loaded = Products.get_product(product.id, preload: [:variants])
|
|
assert loaded.variants == []
|
|
end
|
|
end
|
|
|
|
describe "get_variant_by_provider/2" do
|
|
test "returns variant by product and provider variant id" do
|
|
product = product_fixture()
|
|
variant = product_variant_fixture(%{product: product, provider_variant_id: "var_123"})
|
|
|
|
assert Products.get_variant_by_provider(product.id, "var_123").id == variant.id
|
|
end
|
|
|
|
test "returns nil when not found" do
|
|
product = product_fixture()
|
|
assert Products.get_variant_by_provider(product.id, "nonexistent") == nil
|
|
end
|
|
end
|
|
|
|
# =============================================================================
|
|
# Storefront queries
|
|
# =============================================================================
|
|
|
|
describe "recompute_cached_fields/1" do
|
|
test "computes cheapest price from available variants" do
|
|
product = product_fixture()
|
|
|
|
product_variant_fixture(%{
|
|
product: product,
|
|
price: 3000,
|
|
is_enabled: true,
|
|
is_available: true
|
|
})
|
|
|
|
product_variant_fixture(%{
|
|
product: product,
|
|
price: 2000,
|
|
is_enabled: true,
|
|
is_available: true
|
|
})
|
|
|
|
product_variant_fixture(%{
|
|
product: product,
|
|
price: 1500,
|
|
is_enabled: false,
|
|
is_available: true
|
|
})
|
|
|
|
assert {:ok, updated} = Products.recompute_cached_fields(product)
|
|
assert updated.cheapest_price == 2000
|
|
end
|
|
|
|
test "sets cheapest_price to 0 when no available variants" do
|
|
product = product_fixture()
|
|
|
|
product_variant_fixture(%{
|
|
product: product,
|
|
price: 2000,
|
|
is_enabled: true,
|
|
is_available: false
|
|
})
|
|
|
|
assert {:ok, updated} = Products.recompute_cached_fields(product)
|
|
assert updated.cheapest_price == 0
|
|
end
|
|
|
|
test "sets in_stock based on available variants" do
|
|
product = product_fixture()
|
|
product_variant_fixture(%{product: product, is_enabled: true, is_available: true})
|
|
|
|
assert {:ok, updated} = Products.recompute_cached_fields(product)
|
|
assert updated.in_stock == true
|
|
end
|
|
|
|
test "sets in_stock false when no available variants" do
|
|
product = product_fixture()
|
|
product_variant_fixture(%{product: product, is_enabled: true, is_available: false})
|
|
|
|
assert {:ok, updated} = Products.recompute_cached_fields(product)
|
|
assert updated.in_stock == false
|
|
end
|
|
|
|
test "sets on_sale when any variant has compare_at_price > price" do
|
|
product = product_fixture()
|
|
product_variant_fixture(%{product: product, price: 2000, compare_at_price: 3000})
|
|
|
|
assert {:ok, updated} = Products.recompute_cached_fields(product)
|
|
assert updated.on_sale == true
|
|
end
|
|
|
|
test "sets on_sale false when no sale variants" do
|
|
product = product_fixture()
|
|
product_variant_fixture(%{product: product, price: 2000, compare_at_price: nil})
|
|
|
|
assert {:ok, updated} = Products.recompute_cached_fields(product)
|
|
assert updated.on_sale == false
|
|
end
|
|
|
|
test "stores compare_at_price from cheapest available variant" do
|
|
product = product_fixture()
|
|
|
|
product_variant_fixture(%{
|
|
product: product,
|
|
price: 2000,
|
|
compare_at_price: 3000,
|
|
is_enabled: true,
|
|
is_available: true
|
|
})
|
|
|
|
product_variant_fixture(%{
|
|
product: product,
|
|
price: 1500,
|
|
compare_at_price: 2500,
|
|
is_enabled: true,
|
|
is_available: true
|
|
})
|
|
|
|
assert {:ok, updated} = Products.recompute_cached_fields(product)
|
|
assert updated.cheapest_price == 1500
|
|
assert updated.compare_at_price == 2500
|
|
end
|
|
end
|
|
|
|
describe "get_visible_product/1" do
|
|
test "returns visible active product by slug" do
|
|
product = product_fixture(%{slug: "test-product", visible: true, status: "active"})
|
|
found = Products.get_visible_product("test-product")
|
|
assert found.id == product.id
|
|
end
|
|
|
|
test "returns nil for hidden product" do
|
|
_product = product_fixture(%{slug: "hidden", visible: false, status: "active"})
|
|
assert Products.get_visible_product("hidden") == nil
|
|
end
|
|
|
|
test "returns nil for draft product" do
|
|
_product = product_fixture(%{slug: "draft", visible: true, status: "draft"})
|
|
assert Products.get_visible_product("draft") == nil
|
|
end
|
|
|
|
test "preloads images and variants" do
|
|
product = product_fixture(%{slug: "preloaded"})
|
|
product_image_fixture(%{product: product})
|
|
product_variant_fixture(%{product: product})
|
|
|
|
found = Products.get_visible_product("preloaded")
|
|
assert length(found.images) == 1
|
|
assert length(found.variants) == 1
|
|
end
|
|
end
|
|
|
|
describe "list_visible_products/1" do
|
|
test "returns only visible active products" do
|
|
_visible = product_fixture(%{visible: true, status: "active"})
|
|
_hidden = product_fixture(%{visible: false, status: "active"})
|
|
_draft = product_fixture(%{visible: true, status: "draft"})
|
|
|
|
products = Products.list_visible_products()
|
|
assert length(products) == 1
|
|
end
|
|
|
|
test "filters by category" do
|
|
_apparel = product_fixture(%{category: "Apparel"})
|
|
_home = product_fixture(%{category: "Homewares"})
|
|
|
|
products = Products.list_visible_products(category: "Apparel")
|
|
assert length(products) == 1
|
|
assert hd(products).category == "Apparel"
|
|
end
|
|
|
|
test "filters by on_sale" do
|
|
sale = product_fixture()
|
|
_regular = product_fixture()
|
|
product_variant_fixture(%{product: sale, price: 2000, compare_at_price: 3000})
|
|
Products.recompute_cached_fields(sale)
|
|
|
|
products = Products.list_visible_products(on_sale: true)
|
|
assert length(products) == 1
|
|
assert hd(products).id == sale.id
|
|
end
|
|
|
|
test "filters by in_stock" do
|
|
in_stock = product_fixture()
|
|
out_of_stock = product_fixture()
|
|
product_variant_fixture(%{product: in_stock, is_enabled: true, is_available: true})
|
|
product_variant_fixture(%{product: out_of_stock, is_enabled: true, is_available: false})
|
|
Products.recompute_cached_fields(in_stock)
|
|
Products.recompute_cached_fields(out_of_stock)
|
|
|
|
products = Products.list_visible_products(in_stock: true)
|
|
assert length(products) == 1
|
|
assert hd(products).id == in_stock.id
|
|
end
|
|
|
|
test "sorts by price ascending" do
|
|
cheap = product_fixture()
|
|
expensive = product_fixture()
|
|
|
|
product_variant_fixture(%{
|
|
product: cheap,
|
|
price: 1000,
|
|
is_enabled: true,
|
|
is_available: true
|
|
})
|
|
|
|
product_variant_fixture(%{
|
|
product: expensive,
|
|
price: 5000,
|
|
is_enabled: true,
|
|
is_available: true
|
|
})
|
|
|
|
Products.recompute_cached_fields(cheap)
|
|
Products.recompute_cached_fields(expensive)
|
|
|
|
products = Products.list_visible_products(sort: "price_asc")
|
|
assert Enum.map(products, & &1.id) == [cheap.id, expensive.id]
|
|
end
|
|
|
|
test "sorts by price descending" do
|
|
cheap = product_fixture()
|
|
expensive = product_fixture()
|
|
|
|
product_variant_fixture(%{
|
|
product: cheap,
|
|
price: 1000,
|
|
is_enabled: true,
|
|
is_available: true
|
|
})
|
|
|
|
product_variant_fixture(%{
|
|
product: expensive,
|
|
price: 5000,
|
|
is_enabled: true,
|
|
is_available: true
|
|
})
|
|
|
|
Products.recompute_cached_fields(cheap)
|
|
Products.recompute_cached_fields(expensive)
|
|
|
|
products = Products.list_visible_products(sort: "price_desc")
|
|
assert Enum.map(products, & &1.id) == [expensive.id, cheap.id]
|
|
end
|
|
|
|
test "sorts by name ascending" do
|
|
b = product_fixture(%{title: "Banana"})
|
|
a = product_fixture(%{title: "Apple"})
|
|
|
|
products = Products.list_visible_products(sort: "name_asc")
|
|
assert Enum.map(products, & &1.id) == [a.id, b.id]
|
|
end
|
|
|
|
test "limits results" do
|
|
for _ <- 1..5, do: product_fixture()
|
|
|
|
products = Products.list_visible_products(limit: 3)
|
|
assert length(products) == 3
|
|
end
|
|
|
|
test "excludes a product by ID" do
|
|
p1 = product_fixture()
|
|
p2 = product_fixture()
|
|
|
|
products = Products.list_visible_products(exclude: p1.id)
|
|
assert length(products) == 1
|
|
assert hd(products).id == p2.id
|
|
end
|
|
|
|
test "preloads images but not variants" do
|
|
product = product_fixture()
|
|
product_image_fixture(%{product: product})
|
|
product_variant_fixture(%{product: product})
|
|
|
|
[loaded] = Products.list_visible_products()
|
|
assert length(loaded.images) == 1
|
|
assert %Ecto.Association.NotLoaded{} = loaded.variants
|
|
end
|
|
end
|
|
|
|
describe "list_categories/0" do
|
|
test "returns distinct categories from visible products" do
|
|
product_fixture(%{category: "Apparel"})
|
|
product_fixture(%{category: "Homewares"})
|
|
product_fixture(%{category: "Apparel"})
|
|
product_fixture(%{category: nil})
|
|
|
|
categories = Products.list_categories()
|
|
assert length(categories) == 2
|
|
assert Enum.map(categories, & &1.name) == ["Apparel", "Homewares"]
|
|
assert Enum.map(categories, & &1.slug) == ["apparel", "homewares"]
|
|
end
|
|
|
|
test "excludes categories from hidden products" do
|
|
product_fixture(%{category: "Visible", visible: true})
|
|
product_fixture(%{category: "Hidden", visible: false})
|
|
|
|
categories = Products.list_categories()
|
|
assert length(categories) == 1
|
|
assert hd(categories).name == "Visible"
|
|
end
|
|
end
|
|
|
|
describe "sync_product_variants/2" do
|
|
test "creates new variants" do
|
|
product = product_fixture()
|
|
|
|
variants = [
|
|
%{provider_variant_id: "v1", title: "Small", price: 2000},
|
|
%{provider_variant_id: "v2", title: "Large", price: 2500}
|
|
]
|
|
|
|
results = Products.sync_product_variants(product, variants)
|
|
assert length(results) == 2
|
|
assert Enum.all?(results, &match?({:ok, _}, &1))
|
|
|
|
loaded = Products.get_product(product.id, preload: [:variants])
|
|
assert length(loaded.variants) == 2
|
|
end
|
|
|
|
test "updates existing variants" do
|
|
product = product_fixture()
|
|
|
|
existing =
|
|
product_variant_fixture(%{product: product, provider_variant_id: "v1", price: 2000})
|
|
|
|
variants = [
|
|
%{provider_variant_id: "v1", title: "Small Updated", price: 2200}
|
|
]
|
|
|
|
Products.sync_product_variants(product, variants)
|
|
|
|
updated = Repo.get!(ProductVariant, existing.id)
|
|
assert updated.title == "Small Updated"
|
|
assert updated.price == 2200
|
|
end
|
|
|
|
test "removes variants not in incoming list" do
|
|
product = product_fixture()
|
|
_keep = product_variant_fixture(%{product: product, provider_variant_id: "keep"})
|
|
_remove = product_variant_fixture(%{product: product, provider_variant_id: "remove"})
|
|
|
|
variants = [
|
|
%{provider_variant_id: "keep", title: "Keep", price: 2000}
|
|
]
|
|
|
|
Products.sync_product_variants(product, variants)
|
|
|
|
loaded = Products.get_product(product.id, preload: [:variants])
|
|
assert length(loaded.variants) == 1
|
|
assert hd(loaded.variants).provider_variant_id == "keep"
|
|
end
|
|
end
|
|
end
|