- Add subsonic boolean field to products with composite filter indexes - Add TextDetector utility for detecting subsonic/casing from product titles - Wire TextDetector into SgAmmo (casing + subsonic) and LuckyGunner (subsonic + fallback casing) - Extend latest_prices_for_caliber with casing, grain_weight, and subsonic filter options - Add distinct_grain_weights_for_caliber and distinct_casings_for_caliber queries - Add filter UI with toggleable casing, grain weight, and subsonic buttons - Add grain weight column and subsonic badge to product table - Refresh filter options on PubSub price updates for real-time UI - Implement Target Sports USA scraper with 18 caliber mappings - Display all prices in dollar format ($0.38 instead of 38¢)
118 lines
3.9 KiB
Elixir
118 lines
3.9 KiB
Elixir
defmodule Ammoprices.Scraping.Retailers.TargetSportsUsaTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias Ammoprices.Scraping.Retailers.TargetSportsUsa
|
|
|
|
@fixture_path "test/fixtures/target_sports_usa/9mm.html"
|
|
|
|
describe "retailer_slug/0" do
|
|
test "returns target-sports-usa" do
|
|
assert TargetSportsUsa.retailer_slug() == "target-sports-usa"
|
|
end
|
|
end
|
|
|
|
describe "category_url/1" do
|
|
test "maps 9mm-luger caliber to correct URL" do
|
|
caliber = %{slug: "9mm-luger", category: "handgun"}
|
|
assert TargetSportsUsa.category_url(caliber) == "/9mm-luger-ammo-c-51.aspx"
|
|
end
|
|
|
|
test "maps 300-blackout caliber to correct URL" do
|
|
caliber = %{slug: "300-blackout", category: "rifle"}
|
|
assert TargetSportsUsa.category_url(caliber) == "/300-aac-blackout-ammo-c-969.aspx"
|
|
end
|
|
|
|
test "returns nil for unmapped caliber" do
|
|
caliber = %{slug: "unknown", category: "handgun"}
|
|
assert TargetSportsUsa.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 = TargetSportsUsa.parse_products(html)
|
|
assert length(products) == 4
|
|
end
|
|
|
|
test "extracts product title", %{html: html} do
|
|
[first | _] = TargetSportsUsa.parse_products(html)
|
|
assert first.title =~ "Federal Champion 9mm Ammo 115 Grain Full Metal Jacket"
|
|
end
|
|
|
|
test "extracts product URL", %{html: html} do
|
|
[first | _] = TargetSportsUsa.parse_products(html)
|
|
assert first.url == "/federal-champion-9mm-ammo-115-grain-fmj-wm5199-p-58432.aspx"
|
|
end
|
|
|
|
test "extracts price in cents", %{html: html} do
|
|
[first | _] = TargetSportsUsa.parse_products(html)
|
|
assert first.price_cents == 1275
|
|
end
|
|
|
|
test "extracts price per round in cents", %{html: html} do
|
|
[first | _] = TargetSportsUsa.parse_products(html)
|
|
assert first.price_per_round_cents == 26
|
|
end
|
|
|
|
test "extracts brand from h2 strong", %{html: html} do
|
|
[first | _] = TargetSportsUsa.parse_products(html)
|
|
assert first.brand == "Federal"
|
|
end
|
|
|
|
test "strips trailing Ammunition/Ammo from brand", %{html: html} do
|
|
products = TargetSportsUsa.parse_products(html)
|
|
# "Winchester Ammo" -> "Winchester"
|
|
assert Enum.at(products, 1).brand == "Winchester"
|
|
# "Tula Ammo" -> "Tula"
|
|
assert Enum.at(products, 3).brand == "Tula"
|
|
end
|
|
|
|
test "extracts grain weight from title", %{html: html} do
|
|
[first | _] = TargetSportsUsa.parse_products(html)
|
|
assert first.grain_weight == 115
|
|
end
|
|
|
|
test "extracts round count from title", %{html: html} do
|
|
products = TargetSportsUsa.parse_products(html)
|
|
# Second product: "50 Round Box"
|
|
assert Enum.at(products, 1).round_count == 50
|
|
end
|
|
|
|
test "marks in-stock products", %{html: html} do
|
|
[first | _] = TargetSportsUsa.parse_products(html)
|
|
assert first.in_stock == true
|
|
end
|
|
|
|
test "marks out-of-stock products", %{html: html} do
|
|
products = TargetSportsUsa.parse_products(html)
|
|
assert Enum.at(products, 3).in_stock == false
|
|
end
|
|
|
|
test "detects subsonic from title", %{html: html} do
|
|
products = TargetSportsUsa.parse_products(html)
|
|
# 3rd product has "Subsonic" in title
|
|
assert Enum.at(products, 2).subsonic == true
|
|
end
|
|
|
|
test "non-subsonic products return false", %{html: html} do
|
|
[first | _] = TargetSportsUsa.parse_products(html)
|
|
assert first.subsonic == false
|
|
end
|
|
|
|
test "detects casing from title", %{html: html} do
|
|
products = TargetSportsUsa.parse_products(html)
|
|
# 4th product has "Steel Case" in title
|
|
assert Enum.at(products, 3).casing == "steel"
|
|
end
|
|
|
|
test "returns nil casing when not detected", %{html: html} do
|
|
[first | _] = TargetSportsUsa.parse_products(html)
|
|
assert first.casing == nil
|
|
end
|
|
end
|
|
end
|