defmodule Ammoprices.CatalogTest do use Ammoprices.DataCase, async: true import Ammoprices.Fixtures alias Ammoprices.Catalog describe "retailers" do test "list_retailers/0 returns all retailers" do retailer = retailer_fixture() assert Catalog.list_retailers() == [retailer] end test "get_retailer!/1 returns the retailer with given id" do retailer = retailer_fixture() assert Catalog.get_retailer!(retailer.id) == retailer end test "get_retailer_by_slug!/1 returns the retailer with given slug" do retailer = retailer_fixture(%{slug: "lucky-gunner"}) assert Catalog.get_retailer_by_slug!("lucky-gunner") == retailer end test "create_retailer/1 with valid data creates a retailer" do attrs = %{name: "Lucky Gunner", slug: "lucky-gunner", base_url: "https://luckygunner.com"} assert {:ok, retailer} = Catalog.create_retailer(attrs) assert retailer.name == "Lucky Gunner" assert retailer.slug == "lucky-gunner" assert retailer.base_url == "https://luckygunner.com" assert retailer.enabled == true end test "create_retailer/1 with duplicate slug returns error" do retailer_fixture(%{slug: "duplicate"}) attrs = %{name: "Another", slug: "duplicate", base_url: "https://example.com"} assert {:error, changeset} = Catalog.create_retailer(attrs) assert %{slug: ["has already been taken"]} = errors_on(changeset) end test "create_retailer/1 with missing required fields returns error" do assert {:error, changeset} = Catalog.create_retailer(%{}) assert %{name: ["can't be blank"]} = errors_on(changeset) end test "update_retailer/2 updates the retailer" do retailer = retailer_fixture() assert {:ok, updated} = Catalog.update_retailer(retailer, %{name: "Updated Name"}) assert updated.name == "Updated Name" end end describe "calibers" do test "list_calibers/0 returns all calibers" do caliber = caliber_fixture() assert Catalog.list_calibers() == [caliber] end test "list_calibers_by_category/1 filters by category" do handgun = caliber_fixture(%{name: "9mm", slug: "9mm", category: "handgun"}) _rifle = caliber_fixture(%{name: "5.56", slug: "556", category: "rifle"}) result = Catalog.list_calibers_by_category("handgun") assert result == [handgun] end test "get_caliber!/1 returns the caliber with given id" do caliber = caliber_fixture() assert Catalog.get_caliber!(caliber.id) == caliber end test "get_caliber_by_slug!/1 returns the caliber with given slug" do caliber = caliber_fixture(%{slug: "9mm-luger"}) assert Catalog.get_caliber_by_slug!("9mm-luger") == caliber end test "create_caliber/1 with valid data creates a caliber" do attrs = %{ name: ".45 ACP", slug: "45-acp", category: "handgun", aliases: [".45", "45 auto"] } assert {:ok, caliber} = Catalog.create_caliber(attrs) assert caliber.name == ".45 ACP" assert caliber.aliases == [".45", "45 auto"] end test "create_caliber/1 with invalid category returns error" do attrs = %{name: "Test", slug: "test", category: "invalid"} assert {:error, changeset} = Catalog.create_caliber(attrs) assert %{category: ["is invalid"]} = errors_on(changeset) end end describe "products" do test "upsert_product/3 creates a new product" do retailer = retailer_fixture() caliber = caliber_fixture() attrs = %{ title: "Federal 9mm 115gr FMJ", url: "/products/federal-9mm", brand: "Federal", grain_weight: 115, round_count: 50, casing: "brass", condition: "new", in_stock: true, last_seen_at: DateTime.truncate(DateTime.utc_now(), :second) } assert {:ok, product} = Catalog.upsert_product(retailer.id, caliber.id, attrs) assert product.title == "Federal 9mm 115gr FMJ" assert product.retailer_id == retailer.id assert product.caliber_id == caliber.id end test "upsert_product/3 updates existing product on duplicate retailer_id + url" do retailer = retailer_fixture() caliber = caliber_fixture() attrs = %{ title: "Federal 9mm 115gr FMJ", url: "/products/federal-9mm", brand: "Federal", grain_weight: 115, round_count: 50, casing: "brass", in_stock: true, last_seen_at: DateTime.truncate(DateTime.utc_now(), :second) } assert {:ok, product1} = Catalog.upsert_product(retailer.id, caliber.id, attrs) assert {:ok, product2} = Catalog.upsert_product(retailer.id, caliber.id, %{attrs | title: "Updated Title", in_stock: false}) assert product1.id == product2.id assert product2.title == "Updated Title" assert product2.in_stock == false end test "upsert_product/3 stores subsonic flag" do retailer = retailer_fixture() caliber = caliber_fixture() attrs = %{ title: "Federal 9mm 147gr Subsonic HP", url: "/products/federal-subsonic", brand: "Federal", grain_weight: 147, round_count: 50, casing: "brass", condition: "new", in_stock: true, subsonic: true, last_seen_at: DateTime.truncate(DateTime.utc_now(), :second) } assert {:ok, product} = Catalog.upsert_product(retailer.id, caliber.id, attrs) assert product.subsonic == true end test "upsert_product/3 updates subsonic on conflict" do retailer = retailer_fixture() caliber = caliber_fixture() attrs = %{ title: "Federal 9mm 147gr HP", url: "/products/federal-hp", brand: "Federal", grain_weight: 147, round_count: 50, casing: "brass", in_stock: true, subsonic: false, last_seen_at: DateTime.truncate(DateTime.utc_now(), :second) } assert {:ok, _} = Catalog.upsert_product(retailer.id, caliber.id, attrs) assert {:ok, updated} = Catalog.upsert_product(retailer.id, caliber.id, %{attrs | subsonic: true}) assert updated.subsonic == true end test "list_products_for_caliber/2 returns products for a caliber" do caliber = caliber_fixture() retailer = retailer_fixture() product = product_fixture(%{caliber: caliber, retailer: retailer}) _other = product_fixture() results = Catalog.list_products_for_caliber(caliber.id) assert length(results) == 1 assert hd(results).id == product.id end end describe "distinct_grain_weights_for_caliber/1" do test "returns sorted unique grain weights" do caliber = caliber_fixture() retailer = retailer_fixture() product_fixture(%{caliber: caliber, retailer: retailer, grain_weight: 147}) product_fixture(%{caliber: caliber, retailer: retailer, grain_weight: 115}) product_fixture(%{caliber: caliber, retailer: retailer, grain_weight: 115}) product_fixture(%{caliber: caliber, retailer: retailer, grain_weight: 124}) result = Catalog.distinct_grain_weights_for_caliber(caliber.id) assert result == [115, 124, 147] end test "excludes nil grain weights" do caliber = caliber_fixture() retailer = retailer_fixture() product_fixture(%{caliber: caliber, retailer: retailer, grain_weight: 115}) product_fixture(%{caliber: caliber, retailer: retailer, grain_weight: nil}) result = Catalog.distinct_grain_weights_for_caliber(caliber.id) assert result == [115] end end describe "distinct_casings_for_caliber/1" do test "returns sorted unique casings" do caliber = caliber_fixture() retailer = retailer_fixture() product_fixture(%{caliber: caliber, retailer: retailer, casing: "brass"}) product_fixture(%{caliber: caliber, retailer: retailer, casing: "steel"}) product_fixture(%{caliber: caliber, retailer: retailer, casing: "brass"}) product_fixture(%{caliber: caliber, retailer: retailer, casing: "aluminum"}) result = Catalog.distinct_casings_for_caliber(caliber.id) assert result == ["aluminum", "brass", "steel"] end test "excludes nil casings" do caliber = caliber_fixture() retailer = retailer_fixture() product_fixture(%{caliber: caliber, retailer: retailer, casing: "brass"}) product_fixture(%{caliber: caliber, retailer: retailer, casing: nil}) result = Catalog.distinct_casings_for_caliber(caliber.id) assert result == ["brass"] end end end