fix(amazon-shopping): verify prime and delivery filters
This commit is contained in:
@@ -43,6 +43,13 @@ describe("amazon-shopping CLI", () => {
|
||||
"200",
|
||||
"--max-unit-price",
|
||||
"4",
|
||||
"--min-width",
|
||||
"77",
|
||||
"--require-prime",
|
||||
"--delivery-by",
|
||||
"tomorrow",
|
||||
"--sort-by",
|
||||
"price",
|
||||
"--max-search-pages",
|
||||
"3",
|
||||
"--skip-details",
|
||||
@@ -53,6 +60,10 @@ describe("amazon-shopping CLI", () => {
|
||||
assert.equal(request.filters.minRating, 4.5);
|
||||
assert.equal(request.filters.minReviews, 200);
|
||||
assert.equal(request.filters.maxUnitPrice, 4);
|
||||
assert.equal(request.filters.minWidthInches, 77);
|
||||
assert.equal(request.filters.requirePrime, true);
|
||||
assert.equal(request.filters.deliveryBy, "tomorrow");
|
||||
assert.equal(request.filters.sortBy, "price");
|
||||
assert.equal(request.maxSearchPages, 3);
|
||||
assert.equal(request.skipDetails, true);
|
||||
assert.equal(request.dryRun, true);
|
||||
@@ -105,6 +116,13 @@ describe("amazon-shopping CLI", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects unsupported sort modes", () => {
|
||||
assert.throws(
|
||||
() => parseCliRequest(["usb c cable", "--sort-by", "rating"]),
|
||||
/sort-by must be either price or relevance/
|
||||
);
|
||||
});
|
||||
|
||||
it("builds the Amazon search URL without live network access", () => {
|
||||
assert.equal(
|
||||
buildSearchUrl("100w led bulbs"),
|
||||
|
||||
@@ -74,4 +74,45 @@ describe("extractDetailPage", () => {
|
||||
assert.equal(details.availability, "In Stock");
|
||||
assert.deepEqual(details.specs, [{ name: "Wattage", value: "15 watts" }]);
|
||||
});
|
||||
|
||||
it("preserves a search-card Prime signal when detail delivery text omits Prime", () => {
|
||||
const details = extractDetailPage(`
|
||||
<h1 id="productTitle">Prime Sofa Bed</h1>
|
||||
<div id="mir-layout-DELIVERY_BLOCK-slot-PRIMARY_DELIVERY_MESSAGE_LARGE">FREE delivery Tomorrow. Details</div>
|
||||
<table>
|
||||
<tr><td>Product Dimensions</td><td>35"D x 83"W x 31"H</td></tr>
|
||||
</table>
|
||||
`, {
|
||||
asin: "B0PRIME123",
|
||||
title: "Prime Sofa Bed",
|
||||
url: "https://www.amazon.com/dp/B0PRIME123",
|
||||
delivery: { display: "FREE delivery Tomorrow", free: true, prime: true },
|
||||
specs: [],
|
||||
bullets: [],
|
||||
matchedFilters: [],
|
||||
missingFields: [],
|
||||
extractionNotes: []
|
||||
});
|
||||
|
||||
assert.equal(details.delivery?.prime, true);
|
||||
assert.equal(details.delivery?.free, true);
|
||||
});
|
||||
|
||||
it("does not treat Prime in a detail title as Prime delivery", () => {
|
||||
const details = extractDetailPage(`
|
||||
<h1 id="productTitle">Prime Sofa Bed</h1>
|
||||
<div id="mir-layout-DELIVERY_BLOCK-slot-PRIMARY_DELIVERY_MESSAGE_LARGE">FREE delivery Tomorrow. Details</div>
|
||||
`, {
|
||||
asin: "B0TITLE123",
|
||||
title: "Prime Sofa Bed",
|
||||
url: "https://www.amazon.com/dp/B0TITLE123",
|
||||
specs: [],
|
||||
bullets: [],
|
||||
matchedFilters: [],
|
||||
missingFields: [],
|
||||
extractionNotes: []
|
||||
});
|
||||
|
||||
assert.equal(details.delivery?.prime, false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,4 +72,55 @@ describe("applyFiltersAndLimit", () => {
|
||||
|
||||
assert.deepEqual(result.results.map((item) => item.asin), ["B0DUP0001", "B0UNIQUE1"]);
|
||||
});
|
||||
|
||||
it("applies width, Prime, and delivery-by filters", () => {
|
||||
const result = applyFiltersAndLimit([
|
||||
product({
|
||||
asin: "B0MATCH001",
|
||||
rating: 4.3,
|
||||
reviewCount: 250,
|
||||
price: { amount: 399, currency: "USD", display: "$399.00" },
|
||||
delivery: { display: "FREE delivery Tomorrow", free: true, prime: true },
|
||||
specs: [{ name: "Product Dimensions", value: "35\"D x 83\"W x 31\"H" }]
|
||||
}),
|
||||
product({
|
||||
asin: "B0NOPRIME1",
|
||||
rating: 4.5,
|
||||
reviewCount: 300,
|
||||
delivery: { display: "FREE delivery Tomorrow", free: true, prime: false },
|
||||
specs: [{ name: "Product Dimensions", value: "35\"D x 84\"W x 31\"H" }]
|
||||
}),
|
||||
product({
|
||||
asin: "B0NARROW01",
|
||||
rating: 4.6,
|
||||
reviewCount: 300,
|
||||
delivery: { display: "FREE delivery Tomorrow", free: true, prime: true },
|
||||
specs: [{ name: "Product Dimensions", value: "35\"D x 65\"W x 31\"H" }]
|
||||
})
|
||||
], {
|
||||
includeKeywords: [],
|
||||
excludeKeywords: [],
|
||||
minRating: 4,
|
||||
minReviews: 200,
|
||||
minWidthInches: 77,
|
||||
requirePrime: true,
|
||||
deliveryBy: "tomorrow"
|
||||
}, 10);
|
||||
|
||||
assert.deepEqual(result.results.map((item) => item.asin), ["B0MATCH001"]);
|
||||
assert.match(result.filteredOutReasons["B0NOPRIME1"]?.join(" ") ?? "", /Prime delivery not verified/);
|
||||
assert.match(result.filteredOutReasons["B0NARROW01"]?.join(" ") ?? "", /width 65/);
|
||||
assert.ok(result.results[0]?.matchedFilters.includes("width >= 77 inches"));
|
||||
assert.ok(result.results[0]?.matchedFilters.includes("Prime delivery"));
|
||||
assert.ok(result.results[0]?.matchedFilters.includes("delivery by tomorrow"));
|
||||
});
|
||||
|
||||
it("sorts by price when requested", () => {
|
||||
const result = applyFiltersAndLimit([
|
||||
product({ asin: "B0EXPENSIV", rating: 4.9, reviewCount: 1000, price: { amount: 500, currency: "USD", display: "$500.00" } }),
|
||||
product({ asin: "B0CHEAPER1", rating: 4.1, reviewCount: 300, price: { amount: 200, currency: "USD", display: "$200.00" } })
|
||||
], { includeKeywords: [], excludeKeywords: [], sortBy: "price" }, 10);
|
||||
|
||||
assert.deepEqual(result.results.map((item) => item.asin), ["B0CHEAPER1", "B0EXPENSIV"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
|
||||
import { extractWidthInches, formatWidthInches } from "../src/product-metrics.js";
|
||||
import type { ProductSearchResult } from "../src/types.js";
|
||||
|
||||
function product(overrides: Partial<ProductSearchResult>): ProductSearchResult {
|
||||
return {
|
||||
asin: "B0WIDTH001",
|
||||
title: "Base Product",
|
||||
url: "https://www.amazon.com/dp/B0WIDTH001",
|
||||
specs: [],
|
||||
bullets: [],
|
||||
matchedFilters: [],
|
||||
missingFields: [],
|
||||
extractionNotes: [],
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
describe("product metrics", () => {
|
||||
it("extracts explicit W dimensions from overall product specs", () => {
|
||||
const width = extractWidthInches(product({
|
||||
specs: [{ name: "Product Dimensions", value: "35\"D x 83.4\"W x 31\"H" }]
|
||||
}));
|
||||
|
||||
assert.equal(width, 83.4);
|
||||
});
|
||||
|
||||
it("uses dimension order labels when W is not repeated in the value", () => {
|
||||
const width = extractWidthInches(product({
|
||||
specs: [{ name: "Item Dimensions D x W x H", value: "35 x 108 x 31 inches" }]
|
||||
}));
|
||||
|
||||
assert.equal(width, 108);
|
||||
});
|
||||
|
||||
it("ignores non-overall width specs before falling back to title width", () => {
|
||||
const width = extractWidthInches(product({
|
||||
title: "83 Inch Sofa Bed",
|
||||
specs: [
|
||||
{ name: "Seat Interior Width", value: "65 Inches" },
|
||||
{ name: "Arm Width", value: "5 Inches" },
|
||||
{ name: "Minimum Required Door Width", value: "72 Inches" }
|
||||
]
|
||||
}));
|
||||
|
||||
assert.equal(width, 83);
|
||||
});
|
||||
|
||||
it("formats unknown and decimal widths", () => {
|
||||
assert.equal(formatWidthInches(undefined), "unknown");
|
||||
assert.equal(formatWidthInches(83.4), "83.4\"");
|
||||
assert.equal(formatWidthInches(108), "108\"");
|
||||
});
|
||||
});
|
||||
@@ -42,4 +42,27 @@ describe("parseNaturalLanguageRequest", () => {
|
||||
assert.equal(parsed.limit, 5);
|
||||
assert.equal(parsed.filters.maxPrice, 30);
|
||||
});
|
||||
|
||||
it("extracts sofa width, Prime, and delivery urgency filters", () => {
|
||||
const parsed = parseNaturalLanguageRequest(
|
||||
"sofa bed of 77inches or wider in width, review score of 4 stars and higher, 200+ reviews and shipped with prime, color beige if possible, delivery by tomorrow"
|
||||
);
|
||||
|
||||
assert.equal(parsed.query, "sofa bed color beige if possible");
|
||||
assert.equal(parsed.filters.minWidthInches, 77);
|
||||
assert.equal(parsed.filters.minRating, 4);
|
||||
assert.equal(parsed.filters.ratingComparison, "gte");
|
||||
assert.equal(parsed.filters.minReviews, 200);
|
||||
assert.equal(parsed.filters.reviewCountComparison, "gte");
|
||||
assert.equal(parsed.filters.requirePrime, true);
|
||||
assert.equal(parsed.filters.deliveryBy, "tomorrow");
|
||||
});
|
||||
|
||||
it("extracts overnight delivery requests", () => {
|
||||
const parsed = parseNaturalLanguageRequest("queen sleeper sofa with overnight shipping and Prime");
|
||||
|
||||
assert.equal(parsed.query, "queen sleeper sofa");
|
||||
assert.equal(parsed.filters.requirePrime, true);
|
||||
assert.equal(parsed.filters.deliveryBy, "overnight");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,4 +52,46 @@ describe("report", () => {
|
||||
assert.match(markdown, /price missing/);
|
||||
assert.match(markdown, /https:\/\/www\.amazon\.com\/dp\/B0TEST0001/);
|
||||
});
|
||||
|
||||
it("creates a table template with constraint status markers", () => {
|
||||
const markdown = createMarkdownReport(createResponse({
|
||||
query: "sofa bed beige",
|
||||
filters: {
|
||||
includeKeywords: [],
|
||||
excludeKeywords: [],
|
||||
minRating: 4,
|
||||
minReviews: 200,
|
||||
minWidthInches: 77,
|
||||
requirePrime: true,
|
||||
deliveryBy: "tomorrow"
|
||||
},
|
||||
limit: 10,
|
||||
maxSearchPages: 2,
|
||||
filteredOutCount: 3,
|
||||
warnings: [],
|
||||
now: () => new Date("2026-04-15T00:00:00.000Z"),
|
||||
results: [{
|
||||
asin: "B0SOFABED1",
|
||||
title: "HONBAY Modular Sectional Sleeper",
|
||||
url: "https://www.amazon.com/dp/B0SOFABED1",
|
||||
price: { amount: 539.99, currency: "USD", display: "$539.99" },
|
||||
rating: 4.1,
|
||||
reviewCount: 242,
|
||||
delivery: { display: "FREE delivery Tomorrow", free: true, prime: true },
|
||||
specs: [{ name: "Item Dimensions D x W x H", value: "83.4\"D x 83.4\"W x 35\"H" }],
|
||||
bullets: [],
|
||||
matchedFilters: [],
|
||||
missingFields: ["starBreakdown"],
|
||||
extractionNotes: []
|
||||
}]
|
||||
}));
|
||||
|
||||
assert.match(markdown, /## Best Matches/);
|
||||
assert.match(markdown, /\| # \| Product \| Price \| Rating \| Reviews \| Width \| Prime \| Delivery \| Link \|/);
|
||||
assert.match(markdown, /HONBAY Modular Sectional Sleeper/);
|
||||
assert.match(markdown, /83\.4" OK/);
|
||||
assert.match(markdown, /Prime OK/);
|
||||
assert.match(markdown, /Tomorrow OK/);
|
||||
assert.match(markdown, /\[Amazon\]\(https:\/\/www\.amazon\.com\/dp\/B0SOFABED1\)/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,4 +62,34 @@ describe("extractSearchPage", () => {
|
||||
assert.equal(extracted.products.length, 1);
|
||||
assert.equal(extracted.products[0]?.price, undefined);
|
||||
});
|
||||
|
||||
it("detects Prime badges even when visible delivery text omits the word Prime", () => {
|
||||
const extracted = extractSearchPage(`
|
||||
<div data-asin="B0PRIME123">
|
||||
<h2><a href="/dp/B0PRIME123">Prime Sofa Bed</a></h2>
|
||||
<span class="a-price"><span class="a-offscreen">$299.99</span></span>
|
||||
<span aria-label="4.4 out of 5 stars"></span>
|
||||
<span aria-label="246 ratings"></span>
|
||||
<i class="a-icon a-icon-prime" aria-label="Amazon Prime"></i>
|
||||
<span>FREE delivery Tomorrow</span>
|
||||
</div>
|
||||
`, "https://www.amazon.com/s?k=sofa+bed");
|
||||
|
||||
assert.equal(extracted.products.length, 1);
|
||||
assert.equal(extracted.products[0]?.delivery?.prime, true);
|
||||
assert.equal(extracted.products[0]?.delivery?.free, true);
|
||||
assert.match(extracted.products[0]?.delivery?.display ?? "", /Tomorrow/);
|
||||
});
|
||||
|
||||
it("does not treat Prime in a product title as Prime delivery", () => {
|
||||
const extracted = extractSearchPage(`
|
||||
<div data-asin="B0TITLE123">
|
||||
<h2><a href="/dp/B0TITLE123">Prime Sofa Bed</a></h2>
|
||||
<span>FREE delivery Tomorrow</span>
|
||||
</div>
|
||||
`, "https://www.amazon.com/s?k=sofa+bed");
|
||||
|
||||
assert.equal(extracted.products.length, 1);
|
||||
assert.equal(extracted.products[0]?.delivery?.prime, false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user