ammocpr/test/ammoprices/scraping/retailers/palmetto_state_armory_test.exs
Graham McIntire 9eb85faebd
Add 4 new retailer scrapers for broader price coverage
Add True Shot Ammo (Shopify JSON), Palmetto State Armory (Magento HTML),
Bulk Ammo (Magento HTML), and Natchez Shooters Supply (GraphQL API).

Extends scraper infrastructure with HttpClient.post_json/3 and an
optional fetch/2 callback on the Scraper behaviour for scrapers that
need custom HTTP flows (e.g. GraphQL POST requests).
2026-03-12 10:00:59 -05:00

106 lines
3.5 KiB
Elixir

defmodule Ammoprices.Scraping.Retailers.PalmettoStateArmoryTest do
use ExUnit.Case, async: true
alias Ammoprices.Scraping.Retailers.PalmettoStateArmory
@fixture_path "test/fixtures/palmetto_state_armory/9mm.html"
describe "retailer_slug/0" do
test "returns palmetto-state-armory" do
assert PalmettoStateArmory.retailer_slug() == "palmetto-state-armory"
end
end
describe "category_url/1" do
test "maps 9mm-luger caliber to correct URL" do
caliber = %{slug: "9mm-luger"}
assert PalmettoStateArmory.category_url(caliber) == "/9mm-ammo.html"
end
test "maps 556-223 caliber to correct URL" do
caliber = %{slug: "556-223"}
assert PalmettoStateArmory.category_url(caliber) == "/223-remington-ammo.html"
end
test "returns nil for unmapped caliber" do
caliber = %{slug: "unknown-caliber"}
assert PalmettoStateArmory.category_url(caliber) == nil
end
end
describe "parse_products/1" do
setup do
html = File.read!(@fixture_path)
%{html: html}
end
test "extracts all products from HTML", %{html: html} do
products = PalmettoStateArmory.parse_products(html)
assert length(products) == 4
end
test "extracts product title", %{html: html} do
[first | _] = PalmettoStateArmory.parse_products(html)
assert first.title == "TROY 9MM 115GR FMJ Ammo, 50rds - YTR9M115FMJ"
end
test "extracts product URL", %{html: html} do
[first | _] = PalmettoStateArmory.parse_products(html)
assert first.url ==
"https://palmettostatearmory.com/troy-9mm-115gr-fmj-ammo-50rds-ytr9m115fmj.html"
end
test "extracts price in cents from data-price-amount", %{html: html} do
[first | _] = PalmettoStateArmory.parse_products(html)
assert first.price_cents == 1199
end
test "extracts price per round in cents from unit_price", %{html: html} do
[first | _] = PalmettoStateArmory.parse_products(html)
# 0.2398 * 100 = 23.98 → 24 cents
assert first.price_per_round_cents == 24
end
test "extracts grain weight from title", %{html: html} do
[first | _] = PalmettoStateArmory.parse_products(html)
assert first.grain_weight == 115
end
test "extracts round count from title", %{html: html} do
[first | _] = PalmettoStateArmory.parse_products(html)
assert first.round_count == 50
end
test "marks all listed products as in stock", %{html: html} do
products = PalmettoStateArmory.parse_products(html)
assert Enum.all?(products, & &1.in_stock)
end
test "detects casing from title", %{html: html} do
products = PalmettoStateArmory.parse_products(html)
# Third product: "Steel Case" in title
assert Enum.at(products, 2).casing == "steel"
# Second product: "Brass" in title
assert Enum.at(products, 1).casing == "brass"
end
test "detects subsonic from title", %{html: html} do
products = PalmettoStateArmory.parse_products(html)
# Fourth product: "Subsonic" in title
assert Enum.at(products, 3).subsonic == true
end
test "non-subsonic products return false", %{html: html} do
[first | _] = PalmettoStateArmory.parse_products(html)
assert first.subsonic == false
end
test "extracts brand from beginning of title", %{html: html} do
products = PalmettoStateArmory.parse_products(html)
assert hd(products).brand == "TROY"
assert Enum.at(products, 1).brand == "CCI"
assert Enum.at(products, 2).brand == "Tula"
end
end
end