From ab39acb0f24011a70ecd7c963e6351e56a3a5009 Mon Sep 17 00:00:00 2001 From: Pyronewbic Date: Tue, 12 May 2026 23:51:58 +0530 Subject: [PATCH] fix: eBay live search 500 error, CI Playwright caching, portfolio smoke tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed stale-while-revalidate callbacks in searchActive and searchSold that referenced bare deliveryCountries/languages instead of the destructured deliveryCountriesOpt/languagesOpt — caused 500 on all live (non-demo) searches. CI: cache Playwright browsers via actions/cache, skip download on cache hit (install-deps only). Should cut smoke time from ~3.5min to ~45s. Added 16 smoke tests: portfolio UI section, portfolio API endpoints (demo, history, CSV export, grading opportunities). 254 tests (118 unit + 80 API + 56 smoke). --- .github/workflows/test.yml | 16 +++++++++++++++- lib/sources/ebay.js | 4 ++-- test/smoke-test.js | 39 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f555e43..af180c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,6 +24,20 @@ jobs: with: node-version: 24 - run: npm install - - run: npx playwright install chromium --with-deps + - name: Get Playwright version + id: pw-version + run: echo "version=$(npx playwright --version | awk '{print $2}')" >> $GITHUB_OUTPUT + - name: Cache Playwright browsers + uses: actions/cache@v4 + id: pw-cache + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }} + - name: Install Playwright browsers + if: steps.pw-cache.outputs.cache-hit != 'true' + run: npx playwright install chromium --with-deps + - name: Install Playwright deps only + if: steps.pw-cache.outputs.cache-hit == 'true' + run: npx playwright install-deps chromium - run: node test/smoke-test.js diff --git a/lib/sources/ebay.js b/lib/sources/ebay.js index 6a5ee8f..ddbb398 100644 --- a/lib/sources/ebay.js +++ b/lib/sources/ebay.js @@ -1234,7 +1234,7 @@ export async function searchActive( const { value: ent, stale } = await cacheGetStale(ACTIVE_CACHE_COL, dk); if (ent?.itemsByCountry) { if (stale && !cp) { - searchActive({ query, relevanceQuery, deliveryCountries, languages, config, refresh: true, noEbay: false, getToken, on401 }).catch(() => {}); + searchActive({ query, relevanceQuery, deliveryCountries: deliveryCountriesOpt, languages: languagesOpt, config, refresh: true, noEbay: false, getToken, on401 }).catch(() => {}); } return ent; } @@ -1614,7 +1614,7 @@ export async function searchSold( const { value: cached, stale } = await cacheGetStale(SOLD_CACHE_COL, soldDk); if (cached?.items) { if (stale && !cp) { - searchSold({ query, relevanceQuery, languages, config, refresh: true, noEbay: false, getToken, on401, soldBrowser }).catch(() => {}); + searchSold({ query, relevanceQuery, languages: languagesOpt, config, refresh: true, noEbay: false, getToken, on401, soldBrowser }).catch(() => {}); } return { items: cached.items, diff --git a/test/smoke-test.js b/test/smoke-test.js index 467275d..1efecc0 100644 --- a/test/smoke-test.js +++ b/test/smoke-test.js @@ -188,6 +188,45 @@ async function run() { await mobile.close(); await page.close(); + // --- Portfolio section --- + console.log("\n[Portfolio section]"); + const portfolioPage = await browser.newPage(); + await portfolioPage.goto(BASE); + assert(await portfolioPage.locator("#portfolio-section").isVisible(), "portfolio section visible on landing"); + assert(await portfolioPage.locator("#portfolio-load").isVisible(), "portfolio load button visible"); + await portfolioPage.close(); + + // --- Portfolio API endpoints --- + console.log("\n[Portfolio API]"); + const apiPage = await browser.newPage(); + + const portfolioRes = await apiPage.goto(`${BASE}/api/portfolio?demo=true`); + assert(portfolioRes.status() === 200, "portfolio demo returns 200"); + const portfolioBody = await portfolioRes.json(); + assert(portfolioBody.cards?.length === 3, "demo portfolio has 3 cards"); + assert(typeof portfolioBody.roiPercent === "number", "portfolio has roiPercent"); + + const historyRes = await apiPage.goto(`${BASE}/api/portfolio/history?demo=true&days=7`); + assert(historyRes.status() === 200, "portfolio history returns 200"); + const historyBody = await historyRes.json(); + assert(historyBody.history?.length === 7, "portfolio history returns 7 entries"); + + const csvCheck = await apiPage.evaluate(async (base) => { + const r = await fetch(`${base}/api/portfolio/export?format=csv&demo=true`); + return { status: r.status, ct: r.headers.get("content-type"), body: await r.text() }; + }, BASE); + assert(csvCheck.status === 200, "portfolio CSV export returns 200"); + assert(csvCheck.ct?.includes("text/csv"), "CSV export has text/csv content-type"); + assert(csvCheck.body.includes("Card ID"), "CSV has header row"); + + const gradingRes = await apiPage.goto(`${BASE}/api/portfolio/grading-opportunities?demo=true`); + assert(gradingRes.status() === 200, "grading opportunities returns 200"); + const gradingBody = await gradingRes.json(); + assert(gradingBody.opportunities?.length === 2, "2 grading opportunities (Umbreon + Greninja)"); + assert(gradingBody.skipped?.length === 1, "1 skipped (Pikachu PSA 10)"); + + await apiPage.close(); + // --- Static assets --- console.log("\n[Static assets]"); const page2 = await browser.newPage();