ammocpr/test/ammoprices/scraping/runner_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

52 lines
1.6 KiB
Elixir

defmodule Ammoprices.Scraping.RunnerTest do
use Ammoprices.DataCase, async: true
import Ammoprices.Fixtures
alias Ammoprices.Catalog
alias Ammoprices.Scraping.Retailers.LuckyGunner
alias Ammoprices.Scraping.Runner
@fixture_html File.read!("test/fixtures/lucky_gunner/9mm.html")
setup do
retailer = retailer_fixture(%{name: "Lucky Gunner", slug: "lucky-gunner", base_url: "https://www.luckygunner.com"})
caliber = caliber_fixture(%{name: "9mm Luger", slug: "9mm-luger", category: "handgun", aliases: ["9mm", "9x19"]})
Req.Test.stub(Ammoprices.Scraping.HttpClient, fn conn ->
Req.Test.html(conn, @fixture_html)
end)
%{retailer: retailer, caliber: caliber}
end
describe "run/2" do
test "creates products and snapshots from scrape", %{caliber: caliber} do
scraper = LuckyGunner
assert {:ok, stats} = Runner.run(scraper, caliber)
assert stats.products_count == 3
assert stats.snapshots_count == 3
products = Catalog.list_products_for_caliber(caliber.id)
assert length(products) == 3
end
test "updates retailer last_scraped_at", %{caliber: caliber} do
scraper = LuckyGunner
{:ok, _stats} = Runner.run(scraper, caliber)
retailer = Catalog.get_retailer_by_slug!("lucky-gunner")
assert retailer.last_scraped_at
end
test "handles scraper returning nil category_url", %{caliber: _caliber} do
caliber = caliber_fixture(%{name: "Unknown", slug: "unknown", category: "handgun"})
scraper = LuckyGunner
assert {:ok, stats} = Runner.run(scraper, caliber)
assert stats.products_count == 0
end
end
end