add analytics-powered 404 monitoring with FTS5 auto-resolve
All checks were successful
deploy / deploy (push) Successful in 9m48s
All checks were successful
deploy / deploy (push) Successful in 9m48s
BrokenUrlTracker now queries real analytics pageview counts instead of hardcoding 0, so broken URLs with prior traffic are distinguished from bot noise. For /products/ 404s with a single FTS5 search match, auto- creates a redirect and marks the broken URL resolved. 1232 tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -366,6 +366,23 @@ defmodule Berrypod.AnalyticsTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "count_pageviews_for_path/1" do
|
||||
test "counts all pageviews for a specific path" do
|
||||
v1 = :crypto.strong_rand_bytes(8)
|
||||
v2 = :crypto.strong_rand_bytes(8)
|
||||
|
||||
insert_event(%{visitor_hash: v1, pathname: "/products/classic-tee"})
|
||||
insert_event(%{visitor_hash: v2, pathname: "/products/classic-tee"})
|
||||
insert_event(%{visitor_hash: v1, pathname: "/products/other"})
|
||||
|
||||
assert Analytics.count_pageviews_for_path("/products/classic-tee") == 2
|
||||
end
|
||||
|
||||
test "returns 0 for paths with no history" do
|
||||
assert Analytics.count_pageviews_for_path("/products/never-visited") == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete_events_before/1" do
|
||||
test "deletes old events" do
|
||||
old = DateTime.add(DateTime.utc_now(), -400, :day) |> DateTime.truncate(:second)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
defmodule Berrypod.RedirectsIntegrationTest do
|
||||
use Berrypod.DataCase, async: true
|
||||
# async: false because FTS5 search index is shared state
|
||||
use Berrypod.DataCase, async: false
|
||||
|
||||
alias Berrypod.{Products, Redirects}
|
||||
alias Berrypod.{Products, Redirects, Search}
|
||||
import Berrypod.ProductsFixtures
|
||||
|
||||
setup do
|
||||
@@ -88,4 +89,45 @@ defmodule Berrypod.RedirectsIntegrationTest do
|
||||
Redirects.lookup("/products/#{product.slug}")
|
||||
end
|
||||
end
|
||||
|
||||
describe "attempt_auto_resolve/1" do
|
||||
test "creates redirect when FTS5 finds a single match" do
|
||||
# Product was renamed: "Classic Tee" -> "Classic Tee V2"
|
||||
# Old slug was "classic-tee", new slug is "classic-tee-v2"
|
||||
product =
|
||||
product_fixture(%{
|
||||
title: "Classic Tee V2",
|
||||
slug: "classic-tee-v2",
|
||||
category: "T-Shirts",
|
||||
visible: true,
|
||||
status: "active"
|
||||
})
|
||||
|
||||
product_variant_fixture(%{product: product, title: "Medium / Black", price: 2500})
|
||||
Search.rebuild_index()
|
||||
|
||||
# Record a broken URL for the old slug
|
||||
{:ok, _} = Redirects.record_broken_url("/products/classic-tee", 10)
|
||||
|
||||
assert :resolved = Redirects.attempt_auto_resolve("/products/classic-tee")
|
||||
|
||||
# Redirect should point to the matched product
|
||||
assert {:ok, %{to_path: "/products/classic-tee-v2"}} =
|
||||
Redirects.lookup("/products/classic-tee")
|
||||
|
||||
# Broken URL should be marked resolved
|
||||
assert Redirects.list_broken_urls("pending") == []
|
||||
end
|
||||
|
||||
test "returns :no_match when no search results" do
|
||||
Search.rebuild_index()
|
||||
|
||||
assert :no_match = Redirects.attempt_auto_resolve("/products/totally-unique-nonexistent")
|
||||
end
|
||||
|
||||
test "returns :no_match for non-product paths" do
|
||||
assert :no_match = Redirects.attempt_auto_resolve("/about")
|
||||
assert :no_match = Redirects.attempt_auto_resolve("/collections/old-category")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user