ammocpr/test/ammoprices/scraping/retailers/sg_ammo_test.exs
Graham McIntire bbe5bde145
Initial implementation of ammo price tracker
Phoenix 1.8 app that scrapes ammunition retailers (Lucky Gunner,
SGAmmo) for price data and displays historical price trends.

- Data model: retailers, calibers, products, price snapshots
- Scraper infrastructure with Req, Floki, realistic browser headers
- Oban-scheduled scrape jobs (every 4h with randomized delays)
- LiveView pages: homepage with category cards, caliber detail with
  price table, Chart.js price history, and price stats banner
- 18 seeded calibers across handgun/rifle/rimfire/shotgun categories
- 77 tests
2026-03-11 15:58:12 -05:00

77 lines
2.2 KiB
Elixir

defmodule Ammoprices.Scraping.Retailers.SgAmmoTest do
use ExUnit.Case, async: true
alias Ammoprices.Scraping.Retailers.SgAmmo
@fixture_path "test/fixtures/sg_ammo/9mm.html"
describe "retailer_slug/0" do
test "returns sgammo" do
assert SgAmmo.retailer_slug() == "sgammo"
end
end
describe "category_url/1" do
test "maps 9mm-luger caliber to correct URL" do
caliber = %{slug: "9mm-luger", category: "handgun"}
assert SgAmmo.category_url(caliber) == "/catalog/pistol-ammo-for-sale/9mm-luger-ammo"
end
test "returns nil for unmapped caliber" do
caliber = %{slug: "unknown", category: "handgun"}
assert SgAmmo.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 = SgAmmo.parse_products(html)
assert length(products) == 3
end
test "extracts product title", %{html: html} do
[first | _] = SgAmmo.parse_products(html)
assert first.title =~ "50 Round Box - 9mm Luger 115 Grain FMJ Ammo by Magtech"
end
test "extracts product URL", %{html: html} do
[first | _] = SgAmmo.parse_products(html)
assert first.url =~ "sgammo.com/product/9mm-luger-ammo/"
end
test "extracts price in cents", %{html: html} do
[first | _] = SgAmmo.parse_products(html)
assert first.price_cents == 1395
end
test "extracts price per round in cents", %{html: html} do
[first | _] = SgAmmo.parse_products(html)
assert first.price_per_round_cents == 28
end
test "extracts round count from title", %{html: html} do
[first | _] = SgAmmo.parse_products(html)
assert first.round_count == 50
end
test "extracts grain weight from title", %{html: html} do
[first | _] = SgAmmo.parse_products(html)
assert first.grain_weight == 115
end
test "extracts brand from title", %{html: html} do
[first | _] = SgAmmo.parse_products(html)
assert first.brand == "Magtech"
end
test "marks in-stock products", %{html: html} do
[first | _] = SgAmmo.parse_products(html)
assert first.in_stock == true
end
end
end